entrypoint2

easy to use command-line interface for python modules


Keywords
argparse, decorator, optparse, signature, command-line
License
BSD-3-Clause
Install
pip install entrypoint2==1.1

Documentation

entrypoint2 is an easy to use argparse based command-line interface for python modules. It translates function signature and documentation to argparse configuration.

Links:

workflow

Goals:

  • simplicity: only one decorator to add to existing code

Features:

  • good for protoyping or simple CLI
  • generate CLI parameters from function signature
  • generate CLI documentation from python documentation
  • the decorated function has the same behavior as without the entrypoint2 decorator
  • boolean parameters are toggle flags (e.g. --verbose)
  • function signature is preserved so it can be called both from command-line and external module
  • function name, doc and module are preserved so it can be used with sphinx autodoc
  • sphinx autodoc documentation style is supported: :param x: this is x
  • automatic --version flag, which prints version variable from the current module (__version__, VERSION, ..)
  • automatic --debug flag, which turns on logging
  • short flags are generated from long flags automatically (e.g. --parameter -> -p)
  • supported python versions: 3.6, 3.7, 3.8, 3.9, 3.10, 3.11
  • support for repeating arguments

installation:

$ python3 -m pip install entrypoint2

Hello world

# entrypoint2/examples/hello.py

from entrypoint2 import entrypoint


@entrypoint
def hello(message):
    # type of 'message' is not defined, default is str
    print(message)

Generated help:

$ python3 -m entrypoint2.examples.hello --help
usage: hello.py [-h] [--debug] message

positional arguments:
  message

options:
  -h, --help  show this help message and exit
  --debug     set logging level to DEBUG

Running:

$ python3 -m entrypoint2.examples.hello hi
hi

Basic usage

Example:

# entrypoint2/examples/add.py

import logging

from entrypoint2 import entrypoint

__version__ = "3.2"


@entrypoint
def add(one: int, two=4, three=False):
    """This function adds two numbers.

    :param one: first number to add
    :param two: second number to add
    :param three: print hello if True
    :rtype: int
    """

    # 'one' and 'two' are converted to int
    s = one + two

    logging.debug(s)
    print(s)
    if three:
        print("hello")
    return s

Generated help:

$ python3 -m entrypoint2.examples.add --help
usage: add.py [-h] [-t TWO] [--three] [--debug] [--version] one

This function adds two numbers.

positional arguments:
  one                first number to add

options:
  -h, --help         show this help message and exit
  -t TWO, --two TWO  second number to add
  --three            print hello if True
  --debug            set logging level to DEBUG
  --version          show program's version number and exit

Positional parameter:

$ python3 -m entrypoint2.examples.add 1
5

Optional parameter:

$ python3 -m entrypoint2.examples.add 1 --two 1
2

Short flag: First parameter with first letter 't' is used ('two'). Next parameters with same first letter ('three') has no short flag.

$ python3 -m entrypoint2.examples.add 1 -t 1
2

Boolean parameter:

$ python3 -m entrypoint2.examples.add 1 --three
5
hello

Logging:

--debug is a special flag, it sets logging level to DEBUG with this call:

logging.basicConfig(level=logging.DEBUG, format='%(asctime)-6s: %(name)s - %(levelname)s - %(message)s')

Logging example:

$ python3 -m entrypoint2.examples.add 1 --debug
2021-04-05 13:30:15,590: root - DEBUG - 5
5

Missing positional parameter:

$ python3 -m entrypoint2.examples.add
usage: add.py [-h] [-t TWO] [--three] [--debug] [--version] one
add.py: error: the following arguments are required: one

Printing version:

--version is a special flag, it prints the program's version number and exit. The version can be set with one of this line:

__version__ = "1.0"
VERSION = "1.0" 
version = "1.0"
$ python3 -m entrypoint2.examples.add --version
3.2

Repeating arguments

Example:

# entrypoint2/examples/repeating.py

from entrypoint2 import entrypoint


@entrypoint
def main(files=[]):
    """This function has repeating arguments.
    :param files: test input
    """
    print(files)

Only string list is supported

Printing help:

$ python3 -m entrypoint2.examples.repeating --help
usage: repeating.py [-h] [-f FILES] [--debug]

This function has repeating arguments.

options:
  -h, --help            show this help message and exit
  -f FILES, --files FILES
                        test input
  --debug               set logging level to DEBUG

Repeating flag:

$ python3 -m entrypoint2.examples.repeating -f input1.txt -f input2.txt
['input1.txt', 'input2.txt']

type hints

The parameter conversion is based on the type hint. If the hint is 'int' then the command line parameter is converted from string to int.

# entrypoint2/examples/typehints.py

from entrypoint2 import entrypoint


@entrypoint
def func(
    strpar: str,
    bytespar: bytes,
    intpar: int,
    floatpar: float,
    boolpar: bool,
    listpar: list[int],
):
    print(f"strpar={repr(strpar)}")
    print(f"bytespar={repr(bytespar)}")
    print(f"intpar={repr(intpar)}")
    print(f"floatpar={repr(floatpar)}")
    print(f"boolpar={repr(boolpar)}")
    print(f"listpar={repr(listpar)}")
$ python3 -m entrypoint2.examples.typehints -h
usage: typehints.py [-h] [--debug]
                    strpar bytespar intpar floatpar boolpar [listpar ...]

positional arguments:
  strpar
  bytespar
  intpar
  floatpar
  boolpar
  listpar

options:
  -h, --help  show this help message and exit
  --debug     set logging level to DEBUG
$ python3 -m entrypoint2.examples.typehints 1 2 3 4 5 6 7
strpar='1'
bytespar=b'2'
intpar=3
floatpar=4.0
boolpar=True
listpar=[6, 7]

default value

The parameter conversion is based on the default value. If the default value is an int value like '21' then the command line parameter is converted from string to int.

# entrypoint2/examples/defaultvalues.py

from entrypoint2 import entrypoint


@entrypoint
def add(
    strpar="string",
    bytespar=b"bytes",
    intpar=21,
    floatpar=3.14,
    boolpar=False,
):
    print(f"strpar={repr(strpar)}")
    print(f"bytespar={repr(bytespar)}")
    print(f"intpar={repr(intpar)}")
    print(f"floatpar={repr(floatpar)}")
    print(f"boolpar={repr(boolpar)}")
$ python3 -m entrypoint2.examples.defaultvalues -h
usage: defaultvalues.py [-h] [-s STRPAR] [-b BYTESPAR] [-i INTPAR]
                        [-f FLOATPAR] [--boolpar] [--debug]

options:
  -h, --help            show this help message and exit
  -s STRPAR, --strpar STRPAR
  -b BYTESPAR, --bytespar BYTESPAR
  -i INTPAR, --intpar INTPAR
  -f FLOATPAR, --floatpar FLOATPAR
  --boolpar
  --debug               set logging level to DEBUG
$ python3 -m entrypoint2.examples.defaultvalues -s 1 -b 1 -i 1 -f 1 --boolpar
strpar='1'
bytespar=b'1'
intpar=1
floatpar=1.0
boolpar=True
$ python3 -m entrypoint2.examples.defaultvalues -s hello -b hello -i 3 -f 3.141
strpar='hello'
bytespar=b'hello'
intpar=3
floatpar=3.141
boolpar=False

Variable-length arguments (varargs)

# entrypoint2/examples/varargs.py

from entrypoint2 import entrypoint


@entrypoint
def func(*args):
    print(args)
$ python3 -m entrypoint2.examples.varargs -h
usage: varargs.py [-h] [--debug] [args ...]

positional arguments:
  args

options:
  -h, --help  show this help message and exit
  --debug     set logging level to DEBUG
$ python3 -m entrypoint2.examples.varargs a b c
('a', 'b', 'c')