Declarative ArgumentParser
definition with dataclass
notation.
- No
ArgumentParser
boilerplate code - IDE autocompletion and type hints
pip install classparse==0.1.4
This is a simple example of the most basic usage of this library.
# examples/simple.py
from dataclasses import dataclass
from classparse import classparser
@classparser
@dataclass
class SimpleArgs:
"""My simple program's arguments"""
retries: int = 5 # number of retries
eps: float = 1e-3 # epsilon
if __name__ == "__main__":
print(SimpleArgs.parse_args())
$ python examples/simple.py --help
usage: simple.py [-h] [--retries RETRIES] [--eps EPS]
My simple program's arguments
options:
-h, --help show this help message and exit
--retries RETRIES number of retries
--eps EPS epsilon
$ python examples/simple.py --retries 10 --eps 1e-6
SimpleArgs(retries=10, eps=1e-06)
This example demonstrates all the usage scenarios of this library.
# examples/usage.py
import dataclasses
from dataclasses import dataclass
from enum import Enum, auto
from pathlib import Path
from typing import List, Literal, Optional, Tuple, Union, Any
from classparse import arg, classparser, no_arg, pos_arg
from classparse.analyze import to_arg_name, to_var_name
class Action(Enum):
Initialize = "init"
Execute = "exec"
class Animal(Enum):
Cat = auto()
Dog = auto()
@dataclass(frozen=True)
class SubChild:
"""Double nested class"""
str_arg: str = "sub-child-test"
@dataclass(frozen=True)
class Child:
"""Many nesting levels are supported"""
str_arg: str = "child-test" # (default=%(default)s)
child_arg: SubChild = None # We can override the nested class description
@classparser(
# Keyword arguments are passed to the parser init.
prog="my_program.py",
# `default_argument_args` sets default arguments for each call of add_argument().
default_argument_args=dict(help="(type: %(type)s)"),
)
@dataclass(frozen=True)
class AllOptions:
"""
Class doc string ==> parser description.
The fields' inline/above comment ==> argument's help.
"""
pos_arg_1: str # Field with no explicit default ==> positional arguments (default=%(default)s)
pos_arg_2: int = pos_arg(
5,
nargs="?",
help=(
"pos_arg() is a wrapper around dataclasses.field()."
"The first argument (optional) is the argument default (default=%(default)s)."
"The following keyword arguments can be any argparse.add_argument() parameter."
),
) # When the help field is specified explicitly, the inline comment is ignored
int_arg: int = 1 # Field's type and default are applied to the parser (type=%(type)s, default=%(default)s)
str_enum_choice_arg: Action = Action.Initialize # StrEnum ==> choice argument (type=%(type)s, default=%(default)s)
int_enum_choice_arg: Animal = Animal.Cat # IntEnum ==> choice argument (type=%(type)s, default=%(default)s)
literal_arg: Literal["a", "b", "c"] = None # Literal ==> choice argument (type=%(type)s, default=%(default)s)
literal_int_arg: Literal[1, 2, 3] = None # Literal's type is automatically inferred (type=%(type)s)
mixed_literal: Literal[1, 2, "3", "4", True, Animal.Cat] = None # We can mix multiple literal types (type=%(type)s)
optional_arg: Optional[int] = None # Optional can be used for type hinting (type=%(type)s)
just_optional_arg: Optional = None # Bare optional also works, although it is uninformative (type=%(type)s)
any_optional_arg: Optional[Any] = None # This is also uninformative (type=%(type)s)
optional_choice_arg: Optional[Action] = None # Nested types are supported (type=%(type)s)
union_arg: Union[int, float, bool] = None # Tries to convert to type in order until first success (type=%(type)s)
path_arg: Path = None
flag_arg: int = arg(
"-f",
help=(
"arg() is a wrapper around dataclasses.field()."
"The first argument (optional) is the short argument name."
"The following keyword arguments can be any argparse.add_argument() parameter."
),
default=1,
)
required_arg: float = arg("-r", required=True) # E.g., required=%(required)s
metavar_arg: str = arg(metavar="M") # E.g., metavar=%(metavar)s
int_list: List[int] = (1,) # List[T] ==> nargs="+" (type=%(type)s)
int_2_list: Tuple[int, int] = (1, 2) # Tuple[T1, T2, Tn] ==> nargs=<tuple length> (nargs=%(nargs)s, type=%(type)s)
int_tuple: Tuple[int, ...] = (1, 2, 3) # Tuple[T, ...] ==> nargs="+" (nargs=%(nargs)s, type=%(type)s)
multi_type_tuple: Tuple[int, float, str] = (1, 1e-3, "a") # We can use multiple types (type=%(type)s)
actions: List[Action] = () # List[Enum] ==> choices with nargs="+" (nargs=%(nargs)s, type=%(type)s)
animals: List[Animal] = () # List[Enum] ==> choices with nargs="+" (nargs=%(nargs)s, type=%(type)s)
literal_list: List[Literal["aa", "bb", 11, 22, Animal.Cat]] = ("aa",) # List[Literal] ==> choices with nargs="+"
union_list: List[Union[int, float, str, bool]] = ()
union_with_literal: List[Union[Literal["a", "b", 1, 2], float, bool]] = ()
typeless_list: list = () # If list type is unspecified, then it uses argparse default (type=%(type)s)
typeless_typing_list: List = () # typing.List or list are supported
none_bool_arg: bool = None # bool ==> argparse.BooleanOptionalAction (type=%(type)s)
true_bool_arg: bool = True # We can set any default value
false_bool_arg: bool = False
complex_arg: complex = complex(1, -1)
group_arg: Child = None
default_child_arg: Child = Child(str_arg="override") # We can override the nested class default values
default_factory: List[int] = arg(default_factory=lambda: [1, 2, 3]) # Default factory=%(default)s
# no_arg() is used to not include this argument in the parser.
# The first argument (optional) sets the default value.
# The following keyword arguments is forwarded to the dataclasses.field() method.
non_parsed_arg: int = no_arg(0)
non_parsed_with_meta: str = no_arg("", metadata={"key": "third-party-data"})
# We used this argument for the README example.
# Note that comments above the arg are also included in the help of the argument.
# This is a convenient way to include long help messages.
show: List[str] = arg("-s", default=())
def __repr__(self):
"""Print only the specified fields"""
fields = self.show or list(dataclasses.asdict(self))
return "\n".join([f"{to_arg_name(k)}: {getattr(self, to_var_name(k))}" for k in fields])
if __name__ == "__main__":
print(AllOptions.parse_args())
$ python examples/usage.py --help
usage: my_program.py [-h] [--int-arg INT_ARG]
[--str-enum-choice-arg {Initialize/init,Execute/exec}]
[--int-enum-choice-arg {Cat/1,Dog/2}]
[--literal-arg {a,b,c}] [--literal-int-arg {1,2,3}]
[--mixed-literal {1,2,3,4,True,Cat/1}]
[--optional-arg OPTIONAL_ARG]
[--just-optional-arg JUST_OPTIONAL_ARG]
[--any-optional-arg ANY_OPTIONAL_ARG]
[--optional-choice-arg {Initialize/init,Execute/exec}]
[--union-arg UNION_ARG] [--path-arg PATH_ARG]
[--flag-arg FLAG_ARG] --required-arg REQUIRED_ARG
[--metavar-arg M] [--int-list INT_LIST [INT_LIST ...]]
[--int-2-list INT_2_LIST INT_2_LIST]
[--int-tuple INT_TUPLE [INT_TUPLE ...]]
[--multi-type-tuple MULTI_TYPE_TUPLE MULTI_TYPE_TUPLE MULTI_TYPE_TUPLE]
[--actions {Initialize/init,Execute/exec} [{Initialize/init,Execute/exec} ...]]
[--animals {Cat/1,Dog/2} [{Cat/1,Dog/2} ...]]
[--literal-list {aa,bb,11,22,Cat/1} [{aa,bb,11,22,Cat/1} ...]]
[--union-list UNION_LIST [UNION_LIST ...]]
[--union-with-literal UNION_WITH_LITERAL [UNION_WITH_LITERAL ...]]
[--typeless-list TYPELESS_LIST [TYPELESS_LIST ...]]
[--typeless-typing-list TYPELESS_TYPING_LIST [TYPELESS_TYPING_LIST ...]]
[--none-bool-arg | --no-none-bool-arg]
[--true-bool-arg | --no-true-bool-arg]
[--false-bool-arg | --no-false-bool-arg]
[--complex-arg COMPLEX_ARG]
[--group-arg.str-arg GROUP_ARG.STR_ARG]
[--group-arg.child-arg.str-arg GROUP_ARG.CHILD_ARG.STR_ARG]
[--default-child-arg.str-arg DEFAULT_CHILD_ARG.STR_ARG]
[--default-child-arg.child-arg.str-arg DEFAULT_CHILD_ARG.CHILD_ARG.STR_ARG]
[--default-factory DEFAULT_FACTORY [DEFAULT_FACTORY ...]]
[--show SHOW [SHOW ...]]
pos-arg-1 [pos-arg-2]
Class doc string ==> parser description. The fields' inline/above comment ==>
argument's help.
positional arguments:
pos-arg-1 Field with no explicit default ==> positional
arguments (default=None)
pos-arg-2 pos_arg() is a wrapper around dataclasses.field().The
first argument (optional) is the argument default
(default=5).The following keyword arguments can be any
argparse.add_argument() parameter.
options:
-h, --help show this help message and exit
--int-arg INT_ARG Field's type and default are applied to the parser
(type=int, default=1)
--str-enum-choice-arg {Initialize/init,Execute/exec}
StrEnum ==> choice argument (type=Action,
default=Initialize)
--int-enum-choice-arg {Cat/1,Dog/2}
IntEnum ==> choice argument (type=Animal, default=Cat)
--literal-arg {a,b,c}
Literal ==> choice argument (type=str, default=None)
--literal-int-arg {1,2,3}
Literal's type is automatically inferred (type=int)
--mixed-literal {1,2,3,4,True,Cat/1}
We can mix multiple literal types
(type=typing.Literal[1, 2, '3', '4', True,
<Animal.Cat: 1>])
--optional-arg OPTIONAL_ARG
Optional can be used for type hinting (type=int)
--just-optional-arg JUST_OPTIONAL_ARG
Bare optional also works, although it is uninformative
(type=None)
--any-optional-arg ANY_OPTIONAL_ARG
This is also uninformative (type=None)
--optional-choice-arg {Initialize/init,Execute/exec}
Nested types are supported (type=Action)
--union-arg UNION_ARG
Tries to convert to type in order until first success
(type=typing.Union[int, float, bool])
--path-arg PATH_ARG (type: Path)
--flag-arg FLAG_ARG, -f FLAG_ARG
arg() is a wrapper around dataclasses.field().The
first argument (optional) is the short argument
name.The following keyword arguments can be any
argparse.add_argument() parameter.
--required-arg REQUIRED_ARG, -r REQUIRED_ARG
E.g., required=True
--metavar-arg M E.g., metavar=M
--int-list INT_LIST [INT_LIST ...]
List[T] ==> nargs="+" (type=int)
--int-2-list INT_2_LIST INT_2_LIST
Tuple[T1, T2, Tn] ==> nargs=<tuple length> (nargs=2,
type=int)
--int-tuple INT_TUPLE [INT_TUPLE ...]
Tuple[T, ...] ==> nargs="+" (nargs=+, type=int)
--multi-type-tuple MULTI_TYPE_TUPLE MULTI_TYPE_TUPLE MULTI_TYPE_TUPLE
We can use multiple types (type=typing.Union[int,
float, str])
--actions {Initialize/init,Execute/exec} [{Initialize/init,Execute/exec} ...]
List[Enum] ==> choices with nargs="+" (nargs=+,
type=Action)
--animals {Cat/1,Dog/2} [{Cat/1,Dog/2} ...]
List[Enum] ==> choices with nargs="+" (nargs=+,
type=Animal)
--literal-list {aa,bb,11,22,Cat/1} [{aa,bb,11,22,Cat/1} ...]
List[Literal] ==> choices with nargs="+"
--union-list UNION_LIST [UNION_LIST ...]
(type: typing.Union[int, float, str, bool])
--union-with-literal UNION_WITH_LITERAL [UNION_WITH_LITERAL ...]
(type: typing.Union[typing.Literal['a', 'b', 1, 2],
float, bool])
--typeless-list TYPELESS_LIST [TYPELESS_LIST ...]
If list type is unspecified, then it uses argparse
default (type=None)
--typeless-typing-list TYPELESS_TYPING_LIST [TYPELESS_TYPING_LIST ...]
typing.List or list are supported
--none-bool-arg, --no-none-bool-arg
bool ==> argparse.BooleanOptionalAction (type=bool)
--true-bool-arg, --no-true-bool-arg
We can set any default value (default: True)
--false-bool-arg, --no-false-bool-arg
(type: bool) (default: False)
--complex-arg COMPLEX_ARG
(type: complex)
--default-factory DEFAULT_FACTORY [DEFAULT_FACTORY ...]
Default factory=[1, 2, 3]
--show SHOW [SHOW ...], -s SHOW [SHOW ...]
We used this argument for the README example. Note
that comments above the arg are also included in the
help of the argument. This is a convenient way to
include long help messages.
group-arg:
Many nesting levels are supported
--group-arg.str-arg GROUP_ARG.STR_ARG
(default=child-test)
child-arg:
We can override the nested class description
--group-arg.child-arg.str-arg GROUP_ARG.CHILD_ARG.STR_ARG
(type: str)
default-child-arg:
We can override the nested class default values
--default-child-arg.str-arg DEFAULT_CHILD_ARG.STR_ARG
(default=override)
child-arg:
We can override the nested class description
--default-child-arg.child-arg.str-arg DEFAULT_CHILD_ARG.CHILD_ARG.STR_ARG
(type: str)
Note that for Enums, we can use either the enum name or its value.
$ python examples/usage.py str-choices --actions Initialize init Execute exec -r1 -s actions
actions: [<Action.Initialize: 'init'>, <Action.Initialize: 'init'>, <Action.Execute: 'exec'>, <Action.Execute: 'exec'>]
$ python examples/usage.py int-choices --animals Cat 1 Dog 2 -r1 -s animals
animals: [<Animal.Cat: 1>, <Animal.Cat: 1>, <Animal.Dog: 2>, <Animal.Dog: 2>]
classparse
supports loading default values from a YAML file.
# examples/load_defaults.py
from dataclasses import dataclass
from classparse import classparser
@classparser(load_defaults_from_file=True)
@dataclass
class SimpleLoadDefaults:
"""A simple dataclass that loads defaults from file"""
retries: int = 5 # retries-default: %(default)s
eps: float = 1e-3 # eps-default: %(default)s
if __name__ == "__main__":
print(SimpleLoadDefaults.parse_args())
$ python examples/load_defaults.py --help
usage: load_defaults.py [-h] [--load-defaults PATH] [--retries RETRIES]
[--eps EPS]
A simple dataclass that loads defaults from file
options:
-h, --help show this help message and exit
--load-defaults PATH A YAML file path that overrides the default values.
--retries RETRIES retries-default: 5
--eps EPS eps-default: 0.001
$ printf "retries: 10\neps: 1e-10" | python examples/load_defaults.py --load-defaults - --eps 1e-6
SimpleLoadDefaults(retries=10, eps=1e-06)
- Passing
-
to--load-defaults
will load the YAML form stdin.
The help message will show the defaults after loading them from the YAML file.
$ printf "retries: 10\neps: 1e-10" | python examples/load_defaults.py --load-defaults - --help
usage: load_defaults.py [-h] [--load-defaults PATH] [--retries RETRIES]
[--eps EPS]
A simple dataclass that loads defaults from file
options:
-h, --help show this help message and exit
--load-defaults PATH A YAML file path that overrides the default values.
--retries RETRIES retries-default: 10
--eps EPS eps-default: 1e-10
- Allow transforming dataclass to
ArgumentParser
. - Missing features:
Enum
supportarg/pos_arg/no_arg
functionality- Implicit positional argument
nargs
support
- Allow adding dataclass to
ArgumentParser
by usingparser.add_arguments()
- Requires boilerplate code to create the parser
- Positional arguments
nargs
support
- Creating argument parser from classes and functions
- Rich functionality
- Post-processing of arguments
- Save/load arguments
- Load from dict