A simple way to create python CLI app | by Maksym Balatsko | Towards Data Science

When we create any type of application, we have to provide an interface using which other people will have the opportunity to use it. I want to show you an easy example of wrapping ML application to the CLI interface.
I think it is safe to say that we want the best results from the least effort. In this case, fire library will be the best choice. fire is a library, which is developed and widely used by Google. Let’s explore its functionality on concrete examples:
main.pyimport firedef add(a: int, b: int):    """    Returns sum of a and b    :param a: first argument    :param b: second argument    :return: sum of a and b    """    return a+bif __name__ == "__main__":    fire.Fire({        "sum": add    })
Here fire creates CLI command sum from function add. From python doc, it creates a command description.
Output of python main.py sum --help :
NAME    main.py sum - Returns sum of a and bSYNOPSIS    main.py sum A BDESCRIPTION    Returns sum of a and bPOSITIONAL ARGUMENTS    A        first argument    B        second argumentNOTES    You can also use flags syntax for POSITIONAL ARGUMENTS
So you now can use this command in several ways:
python main.py 1 2
python main.py --a 1 --b 2
Also, you can define optional command arguments, by defining default value in a function definition:
def add(a: int, b: int = 2):    """    Returns sum of a and b    :param a: first argument    :param b: second argument (default: 2)    :return: sum of a and b    """    return a+b
Now the result of python main.py sum --help is:
NAME    main.py sum - Returns sum of a and bSYNOPSIS    main.py sum A <flags>DESCRIPTION    Returns sum of a and bPOSITIONAL ARGUMENTS    A        first argumentFLAGS    --b=B        second argument (default: 2)NOTES    You can also use flags syntax for POSITIONAL ARGUMENTS
So you now can use this command in several ways:
python main.py 1
python main.py 1 2
python main.py 1 --b 2
python main.py --a 1 --b 2
No more words needed, you can see how easy and quick CLI application configuration could be.
Now let’s create a program skeleton for a common use case for ML application. Let’s assume that you need to create a CLI application with two functions train and predict . The train function inputs are train data and some model parameters, the outputs is a trained model file. Predict takes on input data for predictions, the trained model, and saves predictions to the output file.
Except for functions mapping, fire can create a CLI interface from python class instance, which in this case could be a better option. In our case the code could be:
import fireimport pickleimport loggingimport pandas as pdfrom sklearn.neighbors import KNeighborsClassifierlogging.basicConfig(level=logging.INFO)logger = logging.getLogger(__name__)def load_model(model_path: str):    """Loads model from `model_path`"""    with open(model_path, 'rb') as file:        saved_model = pickle.load(file)        return saved_modeldef save_model(model, model_path: str):    """Saves `model` to `model_path`"""    with open(model_path, 'wb') as file:        pickle.dump(model, file)class Classifier:    """    Some classifier, that makes some random classifications.    """    def train(self, train_data_path: str, model_path: str, k: int = 5):        """        Trains model on `train_data_path` data and saves trained model to `model_path`.        Additionaly you can set KNN classifier `k` parameter.        :param train_data_path: path to train data in csv format        :param model_path: path to save model to.        :param k: k-neighbors parameter of model.        """        logger.info(f"Loading train data from {train_data_path} ...")        df = pd.read_csv(train_data_path)        X = df.drop(columns=['y'])        y = df['y']        logger.info("Running model training...")        model = KNeighborsClassifier(n_neighbors=k)        model.fit(X, y)        logger.info(f"Saving model to {model_path} ...")        save_model(model, model_path)        logger.info("Successfully trained model.")    def predict(self, predict_data_path: str, model_path: str, output_path: str):        """        Predicts `predict_data_path` data using `model_path` model and saves predictions to `output_path`        :param predict_data_path: path to data for predictions        :param model_path: path to trained model        :param output_path: path to save predictions        """        logger.info(f"Loading data for predictions from {predict_data_path} ...")        X = pd.read_csv(predict_data_path)        logger.info(f"Loading model from {model_path} ...")        model = load_model(model_path)        logger.info("Running model predictions...")        y_pred = model.predict(X)        logger.info(f"Saving predictions to {output_path} ...")        pd.DataFrame(y_pred).to_csv(output_path)        logger.info("Successfully predicted.")if __name__ == "__main__":    fire.Fire(Classifier)
The output of python main.py is:
NAME    main.py - Some classifier, that makes some random classifications.SYNOPSIS    main.py COMMANDDESCRIPTION    Some classifier, that makes some random classifications.COMMANDS    COMMAND is one of the following:predict       Predicts `predict_data_path` data using `model_path` model and saves predictions to `output_path`train       Trains model on `train_data_path` data and saves trained model to `model_path`. Additionaly you can set KNN classifier `k` parameter.
And the corresponding command documentation:
python main.py train --help :
NAME    main.py train - Trains model on `train_data_path` data and saves trained model to `model_path`. Additionaly you can set KNN classifier `k` parameter.SYNOPSIS    main.py train TRAIN_DATA_PATH MODEL_PATH <flags>DESCRIPTION    Trains model on `train_data_path` data and saves trained model to `model_path`. Additionaly you can set KNN classifier `k` parameter.POSITIONAL ARGUMENTS    TRAIN_DATA_PATH        path to train data in csv format    MODEL_PATH        path to save model to.FLAGS    --k=K        k-neighbors parameter of model.NOTES    You can also use flags syntax for POSITIONAL ARGUMENTS
python main.py predict --help :
NAME    main.py predict - Predicts `predict_data_path` data using `model_path` model and saves predictions to `output_path`SYNOPSIS    main.py predict PREDICT_DATA_PATH MODEL_PATH OUTPUT_PATHDESCRIPTION    Predicts `predict_data_path` data using `model_path` model and saves predictions to `output_path`POSITIONAL ARGUMENTS    PREDICT_DATA_PATH        path to data for predictions    MODEL_PATH        path to trained model    OUTPUT_PATH        path to save predictionsNOTES    You can also use flags syntax for POSITIONAL ARGUMENTS
Usage example:
python main.py train train.csv knn.sav --k 7python main.py predict test.csv knn.sav out.csv
Now you can use this snippet for the creation of your own CLI application. Some tips for further improvement:
Use logging instead of printingCreate arguments validationCreate README template with a brief application usage description for the end-user.Explore more cool features on https://github.com/google/python-fire
Use logging instead of printing
Create arguments validation
Create README template with a brief application usage description for the end-user.
Explore more cool features on https://github.com/google/python-fire
I hope this article will be useful and informative. I’m looking forward to your feedback!