Declare program arguments declaratively and type-safely
Project description
arcparse
Declare program arguments type-safely.
This project builds on top of argparse
by adding type-safety and allowing a more expressive argument parser definition.
Disclaimer: This library is young and relatively unstable. Issues are open and pull requests are welcome!
Example usage
from arcparse import arcparser, positional
@arcparser
class Args:
name: str = positional()
age: int
happy: bool
args = Args.parse("--age 25 --happy Thomas".split())
print(f"Hi, my name is {args.name}!")
For a complete overview of features see Features.
Installation
# Using pip
$ pip install arcparse
Features
Required and optional arguments
Arguments without explicitly assigned argument class are implicitly options (prefixed with --
). A non-optional typehint results in required=True
for options. Defaults can be set by directly assigning them. You can use option()
to further customize the argument.
@arcparser
class Args:
required: str
optional: str | None
default: str = "foo"
default_with_help: str = option(default="bar", help="help message")
Positional arguments
Positional arguments use positional()
. Optional type-hints use nargs="?"
in the background.
@arcparser
class Args:
required: str = positional()
optional: str | None = positional()
Flags
All arguments type-hinted as bool
are flags, they use action="store_true"
in the background. Flags (as well as options) can also define short forms for each argument. They can also disable the long form with short_only=True
.
Use no_flag()
to easily create a --no-...
flag with action="store_false"
.
Use tri_flag()
(or type-hint argument as bool | None
) to create a "true" flag and a "false" flag (e.g. --clone
and --no-clone
). Passing --clone
will store True
, passing --no-clone
will store False
and not passing anything will store None
. Passing both is an error ensured by an implicit mutually exclusive group.
@arcparser
class Args:
sync: bool
recurse: bool = no_flag(help="Do not recurse")
clone: bool | None
debug: bool = flag("-d") # both -d and --debug
verbose: bool = flag("-v", short_only=True) # only -v
Multiple values per argument
By type-hinting the argument as list[...]
, the argument will use nargs="*"
in the background. Passing at_least_one=True
uses nargs="+"
instead. Passing append=True
to option()
uses action="append"
instead (this is available only for option()
and incompatible with at_least_one
).
@arcparser
class Args:
option_nargs: list[str]
positional_nargs: list[str] = positional()
append_option: list[str] = option(append=True)
nargs_plus_option: list[str] = option(at_least_one=True)
nargs_plus_positional: list[str] = positional(at_least_one=True)
Note that option(at_least_one=True)
will cause the option to be required. If this is not intended, provide a default value.
Name overriding
Passing name_override=...
will cause the provided string to be used instead of the variable name for the argument name. The string will undergo a replacement of _
with -
and will contain a --
prefix if used in option()
.
This is useful in combination with accepting multiple values with append=True
, because the user will use --value foo --value bar
, while the code will use args.values
.
@arcparser
class Args:
values: list[str] = option(name_override="value", append=True)
Type conversions
Automatic type conversions are supported. The type-hint type is used to convert the string argument to the desired type. This is NOT done using argparse's type=...
because it was causing issues for dict_option()
and dict_positional()
. Using a StrEnum
subclass as a type-hint automatically populates choices
, using Literal
also populates choices but does not set converter unlike StrEnum
. Using a re.Pattern
typehint automatically uses re.compile
as a converter. A custom type-converter can be used by passing converter=...
to either option()
or positional()
. Come common utility converters are defined in converters.py.
Custom converters may be used in combination with multiple values per argument. These converters are called itemwise
and need to be wrapped in itemwise()
. This wrapper is used automatically if an argument is typed as list[...]
and no converter is set.
from arcparse.converters import sv, csv, sv_dict, itemwise
from enum import StrEnum
from re import Pattern
from typing import Literal
@arcparser
class Args:
class Result(StrEnum):
PASS = "pass"
FAIL = "fail"
@classmethod
def from_int(cls, arg: str) -> "Result":
number = int(arg)
return cls.PASS if number == 1 else cls.FAIL
number: int
result: Result
literal: Literal["yes", "no"]
pattern: Pattern
custom: Result = option(converter=Result.from_int)
ints: list[int] = option(converter=csv(int))
ip_parts: list[int] = option(converter=sv(".", int), name_override="ip")
int_overrides: dict[str, int] = option(converter=sv_dict(",", "=", value_type=int)) # accepts x=1,y=2
results: list[Result] = option(converter=itemwise(Result.from_int))
dict helpers
Sometimes creating an argument able to choose a value from a dict by its key is desired. dict_option()
and dict_positional()
do exactly that. In the following example passing --foo yes
will result in .foo
being True
.
from arcparse import dict_option
values = {
"yes": True,
"no": False,
}
@arcparser
class Args:
foo: bool = dict_option(values)
Mutually exclusive groups
Use mx_group
to group multiple arguments together in a mutually exclusive group. Each argument has to have a default defined either implicitly through the type (being bool
or a union with None
) or explicitly with default
.
@arcparser
class Args:
group = mx_group() # alternatively use `mx_group=(group := mx_group())` on the next line
flag: bool = flag(mx_group=group)
option: str | None = option(mx_group=group)
Subparsers
Type-hinting an argument as a union of classes creates subparsers from them in the background. Assigning from subparsers()
gives them names as they will be entered from the command-line. Subparsers are required by default. Adding None
to the union makes the subparsers optional.
class FooArgs:
arg1: str
class BarArgs:
arg2: int = positional()
@arcparser
class Args:
action: FooArgs | BarArgs = subparsers("foo", "bar")
@arcparser
class OptionalSubparsersArgs:
action: FooArgs | BarArgs | None = subparsers("foo", "bar")
Once the arguments are parsed, the different subparsers can be triggered and distinguished like so:
python3 script.py foo --arg1 baz
python3 script.py bar --arg2 123
args = Args.parse("foo --arg1 baz".split())
if isinstance(foo := args.action, FooArgs):
print(f"foo {foo.arg1}")
elif isinstance(bar := args.action, BarArgs):
print(f"bar {bar.arg2}")
Credits
This project was inspired by swansonk14/typed-argument-parser.
Known issues
Annotations
from __future__ import annotations
makes all annotations strings at runtime. This library relies on class variable annotations's types being actual types. inspect.get_annotations(obj, eval_str=True)
is used to evaluate string annotations to types in order to assign converters. If an argument is annotated with a non-builtin type which is defined outside of the argument-defining class body the type can't be found which results in NameError
s. This is avoidable either by only using custom types which have been defined in the argument-defining class body (which is restrictive), or alternatively by not using the annotations
import which should not be necessary from python 3.13 forward thanks to PEP 649.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file arcparse-0.6.9.tar.gz
.
File metadata
- Download URL: arcparse-0.6.9.tar.gz
- Upload date:
- Size: 13.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.10.12 Linux/5.15.133.1-microsoft-standard-WSL2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 72a82bf681bd21f349729a623097252f167e958d5ed04402617209175d2c272d |
|
MD5 | b29b4d5c70d0ec08932b8b304989991e |
|
BLAKE2b-256 | 917445dd9d08496f4a819ba1621e0dc2208355e206937898685a46913782129c |
File details
Details for the file arcparse-0.6.9-py3-none-any.whl
.
File metadata
- Download URL: arcparse-0.6.9-py3-none-any.whl
- Upload date:
- Size: 13.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.7.1 CPython/3.10.12 Linux/5.15.133.1-microsoft-standard-WSL2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6a2c43f844f3e93f663ea7324c0b27c2d626be152a104e13e1af3f6d0a1a6a21 |
|
MD5 | 699988e995a4fc5d5343d9fe235d9a8e |
|
BLAKE2b-256 | d12ff040f36f26c06bd9a35d1510fd841f5970364ed207e9044dc068ed14581c |