Skip to main content

Elegant command line parsing

Project description

corgy

Elegant command line parsing for Python.

Corgy allows you to create a command line interface in Python, without worrying about boilerplate code. This results in cleaner, more modular code.

from corgy import Corgy, corgyparser

class ArgGroup(Corgy):
    arg1: Annotated[Optional[int], "optional number"]
    arg2: Annotated[bool, "a boolean"]

class MyArgs(Corgy):
    arg1: Annotated[int, "a number"] = 1
    arg2: Annotated[Sequence[float], "at least one float"]
    grp1: Annotated[ArgGroup, "group 1"]

args = MyArgs.parse_from_cmdline()

Compare this to the equivalent code which uses argparse:

from argparse import ArgumentParser, BooleanOptionalAction

parser = ArgumentParser()
parser.add_argument("--arg1", type=int, help="a number", default=1)
parser.add_argument("--arg2", type=float, nargs="+", help="at least one float", required=True)

grp_parser = parser.add_argument_group("group 1")
grp_parser.add_argument("--arg3:arg1", type=int, help="optional number")
grp_parser.add_argument("--arg3:arg2", help="a boolean", action=BooleanOptionalAction)

args = parser.parse_args()

Install

corgy requires Python 3.9+. It is available on PyPI, and can be installed with pip:

pip install corgy

Usage

To create a command line interface, subclass Corgy, and declare your arguments using type annotations.

class A(Corgy):
    x: int
    y: float

At runtime, class A will have x, and y as properties, so that the class can be used similar to Python dataclasses.

a = A()
a.x = 1
a.y = a.x + 1.1

For command line parsing, x and y are added to an ArgumentParser object with the approriate arguments passed to ArgumentParser.add_argument.

parser = ArgumentParser()
parser.add_argument("--x", type=int, required=True)
parser.add_argument("--y", type=float, required=True)

Corgy does not support positional arguments. All arguments are converted to optional arguments, and prefixed with --.

Special annotations

Corgy recognizes a number of special annotations, which are used to control how the argument is parsed.

Annotated

typing.Annotated can be used to add a help message.

x: Annotated[int, "help for x"]

Annotated can accept multiple arguments, but only the first two are used by Corgy. The first argument is the type, and the second is the help message. Annotated should always be the outermost annotation; other special annotations should be part of the type.

Optional

typing.Optional can be used to mark an argument as optional.

x: Optional[int]

Another way to mark an argument as optional is to provide a default value.

x: int = 0

Default values can be used in conjunction with Optional.

x: Optional[int] = 0

Note that the last two examples are not equivalent, since the type of x is Optional[int] in the last example, so it is allowed to be None.

When parsing from the command line, arguments which are not marked as optional (because they are not marked with Optional, and don't have a default value) will be required.

Default values are not type checked, and can be arbitrary objects.

Sequence

collections.abc.Sequence can be used to specify that an argument accepts multiple space-separated values. typing.Sequence can also be used, but is not recommended as it is deprecated since Python 3.9.

There are a few different ways to use Sequence, each resulting in different conditions for the parser. The simplest case is a plain sequence.

x: Sequence[int]

This represents a (possibly empty) sequence, and corresponds to the following call to ArgumentParser.add_argument.

parser.add_argument("--x", type=int, nargs="*", required=True)

Note that since the argument is required, parsing an empty list will still require --x in the command line. After parsing, x will be a list. To denote an optional sequence, use Optional[Sequence[...]].

To specify that a sequence must be non-empty, use:

x: Sequence[int, ...]

This will result in nargs being set to + in the call to ArgumentParser.add_argument. Using this syntax requires collections.abc.Sequence, since typing.Sequence does not accept ... as an argument.

Finally, you can specify a fixed length sequence.

x: Sequence[int, int, int]

This amounts to nargs=3. All types in the sequence must be the same. So, Sequence[int, str, int] will result in a TypeError.

Literal

typing.Literal can be used to specify that an argument takes one of a fixed set of values.

x: Literal[0, 1, 2]

The provided values are passed to the choices argument of ArgumentParser.add_argument. All values must be of the same type, which will be inferred from the type of the first value.

Literal itself can be used as a type, for instance inside a Sequence.

x: Sequence[Literal[0, 1, 2], Literal[0, 1, 2]]

This is a sequence of length 2, where each element is either 0, 1, or 2.

Bool

bool types (when not in a sequence) are converted to argparse.BooleanOptionalAction.

class A(Corgy):
    arg: bool

parser = ArgumentParser()
A.add_to_parser(parser)
parser.print_help()
usage: -c [-h] --arg | --no-arg

optional arguments:
  -h, --help       show this help message and exit
  --arg, --no-arg

Argument Groups

Corgy classes can themselves be used as a type, to represent a group of arguments.

class A(Corgy):
    x: int
    y: float

class B(Corgy):
    x: int
    grp: Annotated[A, "a group"]

Group arguments are added to the command line parser with the group argument name prefixed. In the above example, parsing using B would result in the arguments --x, --grp:x, and --grp:y. grp:x and grp:y will be converted to an instance of A, and set as the grp property of B.

Custom Parsers

To use a custom function for parsing an argument, use the corgy.corgyparser decorator.

class A(Corgy):
    time: tuple[int, int, int]

    @corgyparser("time")
    def parse_time(s):
        return tuple(map(int, s.split(":")))

The decorated function should accept a single string, and return the parsed value.

Corgy Methods

Corgy subclasses have the following public methods.

classmethod Corgy.add_args_to_parser(parser: argparse.ArgumentParser, name_prefix: str = "")

Add arguments for the class to the given parser. Options:

  • parser: The argparse.ArgumentParser to add arguments to.

  • name_prefix: A prefix to add to the argument names. Arguments will be named --<name-prefix>:<var-name>. This is useful for grouping arguments.

classmethod Corgy.parse_from_cmdline(parser: Optional[argparse.ArgumentParser], **parser_args)

Parse an object of the class from command line arguments. Options:

  • parser: The argparse.ArgumentParser to use. If not provided or None, a new parser will be created.

  • parser_args: Arguments to be passed to argparse.ArgumentParser(). Ignored if parser is not None.

This method will return an instance of the Corgy subclass, with properties set to their parsed values.

Project details


Download files

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

Source Distribution

corgy-1.0.0.tar.gz (14.2 kB view hashes)

Uploaded Source

Built Distribution

corgy-1.0.0-py3-none-any.whl (8.5 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page