Template for creating command line applications in Python

How to make a command line application in Python

When creating a command line application (such as httpie made by python) in Python Writing ** entry_point ** in setup.py is the mainstream, isn't it?

setup.py


from setuptools import setup

setup(
    # ...Omission...
    entry_points={
        "console_scripts": [
            "my-console-app=my_console_app.__main__:main"
        ]
    },
    # ...Omission...
)

In the above case, the main function written in __main__.py of the my_console_app package is defined as an application called my-console-app.

And the important thing is how to write this ** main function **. In this article, I will introduce the template of the main function that has become established while I was making console applications.

What to write in the main function

All you need for the main function

I think.

Getting command line arguments using argparse

Command line applications typically take arguments. For example, in the case of httpie given as an example,

$ http PUT http://httpbin.org/put hello=world

Take an argument like this. (PUT, http://httpbin.org/put, hello = world are all arguments) There is a standard library called ** argparse ** to define and get command line arguments in Python.

from argparse import ArgumentParser

parser = ArgumentParser(
    prog="my-console-app",
    description="This is my console application.")

parser.add_argument(
    "message",
    action="store",
    help="The message string.")

Define command line arguments like this. I will not write about argparse in detail because the article will be unnecessarily long. Please check by yourself.

The main function uses the parser created by this argpase to get command line arguments.

__main__.py



from .cli import parser #Cli inside the package.Define parser in py file

def main(argv=sys.argv):
    
    args = parser.parse_args()

Application internal implementation

Next, you need to implement the application itself. This application uses the command line arguments obtained above to change the output content, so

__main__.py



from .cli import parser
from .core import program #Core inside the package.Implement the application in py

def main(argv=sys.argv):

    args = parser.parse_args()
    program(args) #Since command line arguments are required to execute the application, take args as an argument

It is good to take the command line argument after parsing as the argument of the program like this.

Exit status definition

What is the exit status? The exit status (English: exit status) or return code (English: return code) of a process in computer programming is a specific procedure or task to which a child process (or called party) is delegated. Is a small number to pass to the parent process (or caller) when it completes execution. [Exit Status-Wikipedia](https://ja.wikipedia.org/wiki/%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC% E3% 82% BF% E3% 82% B9)

Find out more about exit status yourself. Anyway, when an application quits, it's an integer ** to determine if the application quits correctly or if it quits abnormally with an error. Let's embed this in the main function.

Regarding this exit status, it is better to implement it so that the program inside the application returns the exit status rather than defining it in the main function. What does that mean?

__main__.py



import sys
from .cli import parser
from .core import program

def main(argv=sys.argv):

    args = parser.parse_args()
    exit_status = program(args) #It is good to implement so that the program inside the application returns the exit status.
    
    return sys.exit(exit_status)

This is what it is.

Error output

Since it is an application that runs on the console, it is more user-friendly to output errors properly.

$ my-console-app -m 1111
Traceback (most recent call last):                                                                                                                            
  File "/home/vagrant/.anyenv/envs/pyenv/versions/2.7.5/lib/python2.7/runpy.py", line 162, in _run_module_as_main                                             
    "__main__", fname, loader, pkg_name)                                                                        
  File "/home/vagrant/.anyenv/envs/pyenv/versions/2.7.5/lib/python2.7/runpy.py", line 72, in _run_code                                                        
    exec code in run_globals                                                                                                                                  
  File "/home/vagrant/lab/Python/my-console-app/my_console_app/__main__.py", line 63, in <module>                                                              
    main()                                                                                                                                                    
  File "/home/vagrant/lab/Python/my-console-app/my_console_app/__main__.py", line 52, in main                                                                   
    exit_code = program(args)                                                                                                                                 
  File "/home/vagrant/lab/Python/my-console-app/my_console_app/__main__.py", line 34, in program                                                               
    print prettyprint(result)                                                                                                                                 
  File "my_console_app/utils.py", line 50, in prettyprint                                                                                                          
    raise TypeError("Message must be string not integer")
TypeError: Message must be string not integer                          

** The error I want to convey is only the last sentence **, but when so much information comes out, users who do nothing will be scared. Therefore, this error format should be displayed only for the last sentence.

__main__.py



import sys
from .cli import parser
from .core import program

def main(argv=sys.argv):

    args = parser.parse_args()

    try:
        exit_status = program(args) #It is good to implement so that the program inside the application returns the exit status.
        return sys.exit(exit_status)

    except Exception as e:
        error_type = type(e).__name__ # 29.6.2 Addendum: You can get the error name with this
        sys.stderr.write("{0}: {1}\n".format(error_type, e.message))
        sys.exit(1) #If the exit status is an integer other than 0, it indicates an abnormal termination.

If you write code like this, the error will be output as follows.

$ my-console-app -m 1111
TYpeError: Message must be string not integer    

It's easier to see! !!

But if you think that you can't see the details of the error inside the application. There is a workaround.

  1. Add ** --stack-trace ** argument to parser created by argparse
  2. Output a stack trace using the ** traceback ** library if there is a --stack-trace argument when an error occurs

cli.py


parser.add_argument(
    "--stack-trace",
    dest="stacktrace",
    action="store_true",
    help="Display the stack trace when error occured.")

__main__.py



import sys
import traceback
from .cli import parser
from .core import program

def main(argv=sys.argv):

    if len(argv) == 1:
        # 2017.04.29 Correction
        # len(argv) ==It was 1 instead of 0. Excuse me
        parser.parse_args(["-h"])

    args = parser.parse_args(argv)

    try:
        exit_code = program(args)
        sys.exit(exit_code)

    except Exception as e:
        error_type = type(e).__name__ # 29.6.2 Addendum: You can get the error name with this
        stack_trace = traceback.format_exc() #Save the stack trace when an error occurs

        if args.stacktrace: # --stack-Output stack trace if there is a trace argument
            print "{:=^30}".format(" STACK TRACE ")
            print stack_trace.strip()

        else:
            sys.stderr.write(
                "{0}: {1}\n".format(e_type, e.message))
            sys.exit(1)

Template for creating command line applications in Python

In other words, the template of the main function is as follows.

__main__.py



import sys
import traceback
from .cli import parser
from .core import program

def main(argv=sys.argv):

    if len(argv) == 1:
        # 2017.04.29 Correction
        # len(argv) ==It was 1 instead of 0. Excuse me

        parser.parse_args(["-h"]) #Output help message if there are no command line arguments
        sys.exit(0)

    args = parser.parse_args(argv)

    try:
        exit_code = program(args)
        sys.exit(exit_code)

    except Exception as e:
         error_type = type(e).__name__ # 29.6.2 Addendum: You can get the error name with this
         stack_trace = traceback.format_exc()

        if args.stacktrace:
            print "{:=^30}".format(" STACK TRACE ")
            print stack_trace.strip()

        else:
            sys.stderr.write(
                "{0}: {1}\n".format(e_type, e.message))
            sys.exit(1)

2017.04.29 Correction

Of the above code


if len(sys.argv) == 0:
    parser.parse_args(["-h"])

There was an error in this part. Correctly


if len(sys.argv) == 1: # 0 -> 1
    parser.parse_args(["-h"])

The purpose of this code (to get help message from parser) is that in the case of argparse, if you start the application without setting any command line arguments,

usage: setup-script [-h] [-l] [--stack-trace] formula project
setup-script: error: too few arguments

You will get an error message like this (meaning there are no arguments), so if sys.argv is 1 with the intention of not giving this error,parser.parse_args (["-h"]) The help message is output as.

2017.6.2 Addendum

I learned that you can use type (e) .__ name__ to get the error name from the error object.

Recommended Posts

Template for creating command line applications in Python
Template for writing batch scripts in python
Decompose command arguments in one line in Python
How to receive command line arguments in Python
My thoughts on python2.6 command line app template
Personal best practices for putting command line launch Python projects in Docker containers
[For beginners] How to use say command in python!
Specify a subcommand as a command line argument in Python
Command line collection for using virtual environment in Anaconda
Fizzbuzz in Python (in one line)
Execute external command in python
Search for strings in Python
Techniques for sorting in Python
Try LINE Notify in Python
Python template for Codeforces-manual test-
External command execution in Python
About "for _ in range ():" in python
Allow brew install of command line tools made in Python
Google search for the last line of the file in Python
Check for memory leaks in Python
Command line argument processing (Python docopt)
Make python segfault in one line
Check for external commands in python
Hit a command in Python (Windows)
Command line tool for Chainer ChainerCMD
Run shell command / python in R
In the python command python points to python3.8
Recommended container image for Python applications
Preprocessing template for data analysis (Python)
Run unittests in Python (for beginners)
I tried Line notification in Python
[Introduction] Insert line breaks in Python 3
Implemented in 1 minute! LINE Notify in Python
Command for the current directory Python
python Note: Determine if command line arguments are in the list
How to get a string from a command line argument in python
CGI server (1) python edition in one line
[LLDB] Create your own command in Python
Notes on nfc.ContactlessFrontend () for nfcpy in python
Inject is recommended for DDD in Python
Line graphs and scale lines in python
Learn the design pattern "Command" in Python
Tips for dealing with binaries in Python
Creating amateur python environment settings (for MAC)
Summary of various for statements in Python
Type annotations for Python2 in stub files!
Read the file line by line in Python
Process multiple lists with for in Python
Read the file line by line in Python
Get options in Python from both JSON files and command line arguments
MongoDB for the first time in Python
Get a token for conoha in python
Sample for handling eml files in Python
Tips for building large applications in Flask
AtCoder cheat sheet in python (for myself)
I searched for prime numbers in python
Notes for using python (pydev) in eclipse
Tips for making small tools in python
[Python] Invert bool value in one line
Use pathlib in Maya (Python 2.7) for upcoming Python 3.7
A note I looked up to make a command line tool in Python