Skip to main content
Join the official 2019 Python Developers SurveyStart the survey!

Argument parsing for the lazy

Project description

argz '''

Argument parsing for the lazy.

Core Concepts

  • Simplicity over robustness
  • Your script requires arguments
  • "Consumers" of scripts using this are devlopers themselves

Usage

# $ cat example.py
import json

def func(jsondict, dbg=False):
    """
    put descriptive docstring here
    """
    pass  # use jsondict

if __name__ == '__main__':
    import argz
    f = argz.route(func)
    f.jsondict.adapter = [open, json.load]    # will be chained
    argz.go()                                 # will use sys.argv as input

Running:

$ example.py

Usage:
example.py jsondict [-dbg]

for detailed help, use any of (-h, /h, -?, /?, /help, --help)
$ example.py -h
|> jsondict [-dbg]
|
|    jsondict
|        adapter         |  [<built-in function open>, <function load at 0x0336B1B0>]
|    dbg
|        default         |  False
|        adapter         |  <type 'bool'>
|
|- - doc - -
|
|    put descriptive docstring here
|_ _ _ _ _ _

Installation

Download the file or use pip:

pip install argz

Passing Arguments: Named vs Positional

You can pass any argument by name or by position. An argument will be treated as named if it starts with a double dash --, otherwise* as positional.

You can pass a mishmash of positional and named arguments, because argz will keep track of which arguments are left to parse according to the order in the function's definition.

* except for switches

Arguments From File

To pass arguments from a file to your script you can prefix its path with @. This must be the first argument passed to the script. If there are multiple routes the route name must be included in the file as the first argument.

Currently argz does not allow a mixture of file input and command line input.

Adapter

A callable object that accepts the string argument and returns the 'adapted' value, which will passed later to the routed function.

If the adapter is instead a sequence, each will be chained so that the return value of the former is passed to the latter, using the last returned value as input to the routed function.

To abort the parsing process, you may raise any exception. The message property will be printed to the user.

Validator

Used to validate the input before any adapting is done. Can be one of the following types:

  • regex string: will be used to match against the input
  • callable object: will be called with the input string. Any exception or non-truthy value will abort the parsing process and display a message to the user.
  • Any object that implements __contains__
def func(alphanum, filepath, key, novalidation):
    """
    put descriptive docstring here
    """
    pass

# ...
f = argz.route(func)

f.alphanum.validator = '[a-zA-Z0-9]{2,}'
f.filepath.validator = os.path.isfile
f.key.validator = {'option1': 1, 'option2': 2}

argz.go()

Min / Max

You can set a minimum and/or maximum value for an argument:

def func(count):
    """
    put descriptive docstring here
    """
    pass

# ...
f = argz.route(func)
f.count.min = 1  # same for max
argz.go()

These constraints will be checked after validation and adapters have run. Both values are inclusive. For example, unsigned char range would be: min = 0; max = 0xFF

Fallback vs Default

Setting either one will deem an argument optional, however, they have one major difference:

Default is any value that will be passed to the called route without any parsing or validation.

Fallback is a string value that will pass all validations and parsing, as if it was specified via the commandline.

If the argument was not provieded via the commandline, argz will use either fallback or default. The fallback value takes precedence.

If a default value is specified in the function definition, argz will use it as the argument's default and infer a default adapter in some cases (see SUPPORTED_INFERRED_ADAPTERS).

Switches

Function arguments that have a default boolean value will be inferred as a switch. This means this argument can also be passed using a single dash without a value following it (e.g. -dbg). Doing so will 'switch' the default value (False to True, and vice versa)

The dbg argument from the example.py code above demonstrates this.

Using Split

Setting the split member of an argument changes a few things:

  • the input will be split using that string
  • if set, min \ max value(s) will be checked against the length of the list
  • the validator and parser(s) will be called for each item in the list separately

Varargs and Kwargs:

If the function accepts them, any additional positional and named arguments will be passed in varargs/kwargs respectively.

Adapter(s) and validator will be called with the entire list/dictionary, and they must return a value of the same type.

Accessing function arguments

Setting arguments properties can be done in two ways:

  • by name:
f = argz.route(func)
f.myvar.min = 1
  • by index:
f = argz.route(func)
f[0].min = 1

Note: argument names are case sensitive

Parsing Flow

The following graph illustrates the flow for parsing an argument (varargs\kwargs do not support splitting the input):

 +------------+  +-----------+          +-----------+
 |  fallback  |  |   input   |          |  default  |
 +-----+------+  +-----+-----+          +-----+-----+
       |               |                      |
       +-------+-------+                      |
               |                              |
               v                              |
          +----+-----+                        |
          |  split?  |                        |
          +---+-+----+                        |
              | |                             |
              | | Yes                         |
   +----------+ v                             |
   |   +--------+--------+                    |
No |   |  min / max len  |                    |
   |   +--------+--------+                    |
   +-------+    |                             |
           |    |    +-----------+            |
           V    v    v           |            |
       +--------+----+---+       |            |
       |                 |       |            |
       |    validator    |       |            |
       |        +        |       |            |
       |        v        |       |            |
       |  adapter chain  |       |            |
       |                 |       |            |
       +--------+----+---+       |            |
                |    |           |            |
                |    | split?    |            |
                |    |           |            |
                |    +-----------+            |
       +--------+---------+                   |
       |  min / max check | (if not split)    |
       +--------+---------+                   |
                |                             |
                v                             |
        +-------+--------+                    |
        |  return value  | <------------------+
        +----------------+

Using Multiple Routes

You can 'export' several different routes using argz. This means that the user must choose which one they want to run:

# $ cat example_routes.py
from os.path import isfile

def entry1(filepath, dbg=False):
    pass

def entry2(count):
    pass

# ...

if __name__ == '__main__':
    import argz
    argz.route(entry1).validator = isfile
    argz.route(entry2).count.min = 1
    argz.go()

Running:

$ example_routes.py

Available routes:
> 'entry1' filepath [-dbg]
> 'entry2' count

for detailed help, use any of (-h, /h, -?, /?, /help, --help)
you can specify the route name (e.g. -h MY_ROUTE)
$ example_routes.py -h
|> 'entry1' filepath [-dbg]
|
|    filepath
|    dbg
|        default         |  False
|        adapter         |  <type 'bool'>
|_ _ _ _ _ _

|> 'entry2' count
|
|    count
|        1 <= X
|_ _ _ _ _ _

Note: routes are allowed to run without arguments, so long as there is more than one route available.

Overriding Defaults

You can specify custom doc string to print when verbose help is shown by passing doc argument to route. To completely suppress it pass an empty string.

If you use a single route that accepts an argument whose name is in HELP_OPTIONS, you can replace those by specifying a custom_help_options list when calling go.

Tested on

  • Python 2.7.15, windows 10
  • Python 3.7.2, windows 10

Troubleshooting

To enable logging:

set ARGZ_LOG=<LOG_LEVEL>

LOG_LEVEL will be passed to Logger.setLevel

Testing

added some unit-tests, run runtests.bat

License

MIT

TODO

  • Allow mixture of file input and command-line input
  • Normalize/strip underscores ( _ ) in argument names
  • Handle name collisions with Route object properties
  • Infer from type annotaitons/hints (py3 signature?)

Take a look at the code:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for argz, version 0.1.4
Filename, size File type Python version Upload date Hashes
Filename, size argz-0.1.4.tar.gz (22.4 kB) File type Source Python version None Upload date Hashes View hashes

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN SignalFx SignalFx Supporter DigiCert DigiCert EV certificate StatusPage StatusPage Status page