corgy
corgy.Corgy #
Base class for collections of attributes.
To use, subclass Corgy
, and declare attributes using type
annotations.
Examples:
>>> from corgy import Corgy
>>> 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.
Examples:
>>> a = A()
>>> a.x = 1
>>> a.x
1
>>> a.y
Traceback (most recent call last):
...
AttributeError: no value available for attribute `y`
>>> a.y = a.x + 1.1
>>> a.y
2.1
>>> del a.x # unset x
>>> a.x
Traceback (most recent call last):
...
AttributeError: no value available for attribute `x`
Note
The class's __init__
method only accepts keyword arguments,
and ignores arguments without a corresponding attribute. The
following are all valid.
Examples:
>>> A(x=1, y=2.1)
A(x=1, y=2.1)
>>> A(x=1, z=3) # y is not set, and z is ignored
A(x=1)
>>> A(**{"x": 1, "y": 2.1, "z": 3})
A(x=1, y=2.1)
Attribute values are type-checked, and ValueError
is raised on
type mismatch.
Examples:
>>> a = A(x="1")
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'int'>': '1'
>>> a = A()
>>> a.x = "1"
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'int'>': '1'
>>> class A(Corgy):
... x: int = "1"
Traceback (most recent call last):
...
ValueError: default value type mismatch for 'x'
Any type which supports type checking with isinstance
can be used
as an attribute type (along with some special type annotations that
are discussed below). This includes other corgy classes.
Examples:
>>> class A(Corgy):
... x: int
... y: float
>>> class B(Corgy):
... x: int
... a: A
>>> b = B(x=1)
>>> b.a = A()
>>> b.a.x = 10
>>> b
B(x=1, a=A(x=10))
Corgy
classes have their __slots__
set to the annotated
attributes. So, if you want to use additional attributes not tracked
by Corgy
, define them (and only them) in __slots__
.
Examples:
>>> class A(Corgy):
... __slots__ = ("x",)
... y: int
>>> a = A()
>>> a.y = 1 # `Corgy` attribute
>>> a.x = 2 # custom attribute
>>> a
A(y=1)
To allow arbitrary instance attributes, add __dict__
to
__slots__
. Names added through custom __slots__
are not
processed by Corgy
. Alternatively, to disable setting __slots__
completely, set corgy_make_slots
to False
in the class
definition.
Examples:
>>> class A(Corgy, corgy_make_slots=False):
... y: int
>>> a = A()
>>> a.y = 1 # `Corgy` attribute
>>> a.x = 2 # custom attribute
>>> a
A(y=1)
Names marked with the ClassVar
type will be added as class
variables, and will not be available as Corgy
attributes.
Examples:
>>> from typing import ClassVar
>>> class A(Corgy):
... x: ClassVar[int] = 3
>>> A.x
3
>>> A.x = 4
>>> A.x
4
>>> a = A()
>>> a.x
4
>>> a.x = 5
Traceback (most recent call last):
...
AttributeError: 'A' object attribute 'x' is read-only
Note
Class variables need to be assigned to a value during
definition, and this value will not be type checked by Corgy
.
Inheritance works as expected, whether base classes are themselves
Corgy
classes or not, with sub-classes inheriting the attributes
of the base class, and overriding any redefined attributes.
Examples:
>>> class A:
... x: int
>>> class B(Corgy, A):
... y: float = 1.0
... z: str
>>> class C(B):
... y: float = 2.0
... z: str
... w: float
>>> c = C()
>>> print(c)
C(x=<unset>, y=2.0, z=<unset>, w=<unset>)
Tracking of base class attributes can be disabled by setting
corgy_track_bases
to False
in the class definition. Properties
will still be inherited following standard inheritance rules, but
Corgy
will ignore them.
Examples:
>>> class A:
... x: int
>>> class B(Corgy, A, corgy_track_bases=False):
... y: float = 1.0
... z: str
>>> b = B()
>>> print(b)
B(y=1.0, z=<unset>)
Corgy
instances can be frozen (preventing any further changes)
using the freeze
method. This method can be called automatically
after __init__
by by setting corgy_freeze_after_init
to True
in the class definition.
Examples:
>>> class A(Corgy, corgy_freeze_after_init=True):
... x: int
>>> a = A(x=1)
>>> a.x = 2
Traceback (most recent call last):
...
TypeError: cannot set `x`: object is frozen
Corgy
recognizes a number of special annotations, which are used
to control how attribute values are processed.
Note
If any of the following annotations are unavailable in the
Python version being used, you can import them from
typing_extensions
(which is available on PyPI).
Annotations
typing.Annotated
can be used to add additional metadata to
attributes, akin to doc strings. It is primarily used to control how
attributes are added to ArgumentParser
instances.
typing.Annotated
is stripped on class creation, leaving only the
base type.
Examples:
>>> import sys
>>> if sys.version_info >= (3, 9):
... from typing import Annotated, Literal
... else:
... from typing_extensions import Annotated, Literal
>>> class A(Corgy):
... x: Annotated[int, "this is x"]
>>> A.attrs()
{'x': <class 'int'>}
Annotated
should always be the outermost type annotation for an
attribute. Refer to the docs for Corgy.add_args_to_parser
for
details on usage.
Required/NotRequired
By default, Corgy
attributes are not required, and can be unset.
This can be changed by setting corgy_required_by_default
to True
in the class definition.
Examples:
>>> class A(Corgy, corgy_required_by_default=True):
... x: int
>>> A()
Traceback (most recent call last):
...
ValueError: missing required attribute: `x`
>>> a = A(x=1)
>>> del a.x
Traceback (most recent call last):
...
TypeError: attribute `x` cannot be unset
Attributes can also explicitly be marked as required/not-required
using corgy.Required
and corgy.NotRequired
annotations.
Examples:
>>> from corgy import Required, NotRequired
>>> class A(Corgy):
... x: Required[int]
... y: NotRequired[int]
... z: int # not required by default
>>> a = A(x=1)
>>> print(a)
A(x=1, y=<unset>, z=<unset>)
>>> class B(Corgy, corgy_required_by_default=True):
... x: Required[int]
... y: NotRequired[int]
... z: int
>>> b = B(x=1, z=2)
>>> print(b)
B(x=1, y=<unset>, z=2)
Optional
Annotating an attribute with typing.Optional
allows it to be
None
.
Examples:
>>> from typing import Optional
>>> class A(Corgy):
... x: Optional[int]
>>> a = A()
>>> a.x = None
In Python >= 3.10, instead of using typing.Annotated
, | None
can
be used, i.e., x: int | None
for example.
Note
Optional
is not the same as NotRequired
. Optional
allows
an attribute to be None
, while NotRequired
allows an
attribute to be unset. A Required
Optional
attribute will
need a value (which can be None
).
Examples:
>>> class A(Corgy):
... x: Required[Optional[int]]
>>> A()
Traceback (most recent call last):
...
ValueError: missing required attribute: `x`
>>> a = A(x=None)
>>> print(a)
A(x=None)
Collections Several collection types can be used to annotate attributes, which will restrict the type of accepted values. Values in the collection will be checked to ensure that they match the annotated collection types. The following collection types are supported:
collections.abc.Sequence
(typing.Sequence
on Python < 3.9)tuple
(typing.Tuple
on Python < 3.9)list
(typing.List
on Python < 3.9)set
(typing.Set
on Python < 3.9)
There are a few different ways to use these types, each resulting in different validation conditions. The simplest case is a plain (possibly empty) collection of a single type.
Examples:
>>> from typing import List, Sequence, Set, Tuple
>>> class A(Corgy):
... x: Sequence[int]
... y: Tuple[str]
... z: Set[float]
... w: List[int]
>>> a = A()
>>> a.x = [1, 2]
>>> a.y = ("1", "2")
>>> a.z = {1.0, 2.0}
>>> a.w = [1, 2]
>>> a
A(x=[1, 2], y=('1', '2'), z={1.0, 2.0}, w=[1, 2])
>>> a.x = [1, "2"]
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'int'>': '2'
>>> a.x = (1, 2) # `Sequence` accepts any sequence type
>>> # `Tuple` only accepts tuples
>>> a.y = ["1", "2"]
Traceback (most recent call last):
...
ValueError: error setting `y`: invalid value for type
'typing.Tuple[str]': ['1', '2']
The collection length can be controlled by the arguments of the type annotation.
Note
typing.Sequence/typing.List/typing.Set
do not accept multiple
arguments, and so, cannot be used if collection length has to be
specified. On Python < 3.9, only typing.Tuple
can be used to
control collection lengths.
To specify that a collection must be non-empty, use ellipsis (...
)
as the second argument of the type.
Examples:
>>> class A(Corgy):
... x: Tuple[int, ...]
>>> a = A()
>>> a.x = tuple()
Traceback (most recent call last):
...
ValueError: error setting `x`: expected non-empty collection for
type 'typing.Tuple[int, ...]'
Collections can also be restricted to be of a fixed length.
Examples:
>>> class A(Corgy):
... x: Tuple[int, str]
... y: Tuple[int, int, int]
>>> a = A()
>>> a.x = (1, 1)
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'str'>': 1
>>> a.y = (1, 1)
Traceback (most recent call last):
...
ValueError: error setting `y`: invalid value for type
'typing.Tuple[int, int, int]': (1, 1): expected exactly '3'
elements
Literals
typing.Literal
can be used to specify that an attribute takes one
of a fixed set of values.
Examples:
>>> class A(Corgy):
... x: Literal[0, 1, "2"]
>>> a = A()
>>> a.x = 0
>>> a.x = "2"
>>> a.x = "1"
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'typing.Literal[0, 1, '2']': '1'
Type annotations can be nested; for instance,
Sequence[Literal[0, 1, 2], Literal[0, 1, 2]]
represents a sequence
of length 2, where each element is either 0, 1, or 2.
A fixed set of attribute values can also be specified by adding a
__choices__
attribute to the argument type, containing a
collection of choices.
Examples:
>>> class T(int):
... __choices__ = (1, 2)
>>> class A(Corgy):
... x: T
>>> a = A()
>>> a.x = 1
>>> a.x = 3
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'T'>': 3: expected one of: (1, 2)
Note
Choices specified in this way are not type-checked to ensure
that they match the argument type; in the above example,
__choices__
could be set to (1, "2")
.
Self
Corgy
classes can have attributes of their own type, annotated
using typing.Self
.
Examples:
>>> if sys.version_info >= (3, 11):
... from typing import Self
... else:
... from typing_extensions import Self
>>> class C(Corgy):
... x: int
... c: Self
>>> c = C(x=1)
>>> c.c = C(x=2)
>>> c
C(x=1, c=C(x=2))
>>> class D(C):
... ...
>>> c.c = D(x=3)
Traceback (most recent call last):
...
ValueError: error setting `c`: invalid value for type
'Self (bound to <class 'C'>)': D(x=3)
add_args_to_parser
classmethod
#
add_args_to_parser(parser, name_prefix='', flatten_subgrps=False, defaults=None)
Add the class' Corgy
attributes to the given parser.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
parser |
_ActionsContainer
|
Argument parser/group to which the attributes will be added. |
required |
name_prefix |
str
|
Prefix for argument names. Arguments will be
named |
''
|
flatten_subgrps |
bool
|
Whether to add sub-groups to the main
parser instead of creating argument groups. Note that
sub-sub-groups are always added with this argument set
to |
False
|
defaults |
Optional[Mapping[str, Any]]
|
Optional mapping with default values for
arguments. Any value specified here will override
default values specified in the class. Values for
groups can be specified either as |
None
|
Type annotations control how attributes are added to the parser.
A number of special annotations are parsed and stripped from
attribute types to determine the parameters for calling
ArgumentParser.add_argument
. These special annotations are
described below.
Note
add_args_to_parser
cannot be used if the type annotation
for any attribute of the class includes Self
, unless a
custom parser is defined for such attributes. See docs for
corgyparser
on how to define custom parsers.
Annotated
typing.Annotated
can be used to add a help message for the
argument.
Examples:
>>> import argparse
>>> from argparse import ArgumentParser
>>> from corgy import CorgyHelpFormatter
>>> class A(Corgy):
... x: Annotated[int, "help for x"]
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--x int help for x (optional)
This annotation can also be used to modify the parser flags for
the argument. By default, the attribute name is used, prefixed
with --
, and with _
replaced by -
. If the custom flag does
not have a leading -
, a positional argument will be created.
Examples:
>>> class A(Corgy):
... x: Annotated[int, "help for x", ["-x", "--ex"]]
... y: Annotated[int, "help for y", ["y"]]
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
positional arguments:
y int help for y
options:
-x/--ex int help for x (optional)
Annotated
can accept multiple arguments, but only the first
three are used by Corgy
. The first argument is the attribute
type, the second is the help message (which must be a string),
and the third is a sequence of flags.
Required/NotRequired
Every corgy attribute is either required or not required. The
default status depends on the class parameter
corgy_required_by_default
(False
by default). Attributes
can also be explicitly marked as required or not required, and
will control whether the argument will be added with
required=True
.
Examples:
>>> from corgy import Required, NotRequired
>>> class A(Corgy):
... x: Required[int]
... y: NotRequired[int]
... z: int
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--x int (required)
--y int (optional)
--z int (optional)
Attributes which are not required, and don't have a default
value are added with default=argparse.SUPPRESS
, and so will
not be in the parsed namespace.
Examples:
>>> parser.parse_args(["--x", "1", "--y", "2"])
Namespace(x=1, y=2)
Optional
Attributes marked with typing.Optional
are allowed to be
None
. The arguments for these attributes can be passed with no
values (i.e. --x
instead of --x=1
or --x 1
) to indicate
that the value should be None
.
Note
Attributes with default values are also "optional" in the
sense that they can be omitted from the command line.
However, they are not the same as attributes marked with
Optional
, since the former are not allowed to be None
.
Furthermore, Required
Optional
attributes without
default values will need to be passed on the command line
(possibly with no values).
Examples:
>>> class A(Corgy):
... x: Required[Optional[int]]
>>> parser = ArgumentParser()
>>> A.add_args_to_parser(parser)
>>> parser.parse_args(["--x"])
Namespace(x=None)
Boolean
bool
types (when not in a collection) are converted to a pair
of options.
Examples:
>>> class A(Corgy):
... arg: bool
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--arg/--no-arg
Collection
Collection types are added to the parser by setting nargs
. The
value for nargs
is determined by the collection type. Plain
collections, such as Sequence[int]
, will be added with
nargs=*
; Non-empty collections, such as Sequence[int, ...]
,
will be added with nargs=+
; Finally, fixed-length collections,
such as Sequence[int, int, int]
, will be added with nargs
set to the length of the collection.
In all cases, collection types can only be added to a parser if
they are single type. Heterogenous collections, such as
Sequence[int, str]
cannot be added, and will raise
ValueError
. Untyped collections (e.g., x: Sequence
), also
cannot be added.
Arguments for optional collections will also accept no values to
indicate None
. Due to this, it is not possible to parse an
empty collection for an optional collection argument.
Examples:
>>> class A(Corgy):
... x: Optional[Sequence[int]]
... y: Sequence[int]
>>> parser = ArgumentParser()
>>> A.add_args_to_parser(parser)
>>> parser.parse_args(["--x", "--y"])
Namespace(x=None, y=[])
Enum
Enum
types work as expected; the members of the enum are
passed to the choices
argument of
ArgumentParser.add_argument
.
Examples:
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
>>> class A(Corgy):
... x: Color
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--x Color ({RED/GREEN/BLUE} optional)
Literal
For Literal
types, 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. If the first value has a __bases__
attribute, the type will be inferred as the first base type, and
all other choices must be subclasses of that type.
Examples:
>>> class T: ...
>>> class T1(T): ...
>>> class T2(T): ...
>>> class A(Corgy):
... x: Literal[T1, T2]
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--x T ({T1/T2} optional)
For types which specify choices by defining __choices__
, the
values are passed to the choices
argument as with Literal
,
but no type inference is performed, and the base attribute type
will be used as the argument type.
Single-value Literals
A special case for Literal
types is when there is only one
choice. In this case, the argument is added as a store_const
action, with the value as the const
argument. A further
special case is when the choice is True/False
, in which case
the action is store_true
/store_false
respectively.
Examples:
>>> class A(Corgy):
... x: Literal[True]
... y: Literal[False]
... z: Literal[42]
>>> parser = ArgumentParser()
>>> A.add_args_to_parser(parser)
>>> parser.parse_args(["--x"]) # Note `y`, `z` are absent
Namespace(x=True)
>>> parser.parse_args(["--y"])
Namespace(y=False)
>>> parser.parse_args(["--z"])
Namespace(z=42)
Note
This special case only applies to Literal
types, and not
types which define __choices__
.
Corgy
Attributes which are themselves Corgy
types are treated as
argument groups. Group arguments are added to the command line
parser with the group attribute name prefixed.
Note
Groups will ignore any custom flags when computing the
prefix; elements within the group will use custom flags, but
because they are prefixed with --
, they will not be
positional.
Examples:
>>> class G(Corgy):
... x: int = 0
... y: float
>>> class A(Corgy):
... x: int
... g: G
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.print_help()
options:
--x int (optional)
g:
--g:x int (default: 0)
--g:y float (optional)
Custom parsers
Attributes for which a custom parser is defined using
@corgyparser
will be added with a custom action that will call
the parser. Refer to the documentation for corgyparser
for
details.
Metavar
This function will not explicitly pass a value for the metavar
argument of ArgumentParser.add_argument
, unless an attribute's
type defines __metavar__
, in which case, it will be passed as
is. To change the metavar for attributes with custom parsers,
set the metavar
argument of corgyparser
.
as_dict #
as_dict(recursive=True, flatten=False)
Return the object as a dictionary.
The returned dictionary maps attribute names to their values. Unset attributes are omitted, unless they have default values.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
recursive |
bool
|
whether to recursively call |
True
|
flatten |
bool
|
whether to flatten group arguments into |
False
|
Examples:
>>> class G(Corgy):
... x: int
>>> g = G(x=1)
>>> g.as_dict()
{'x': 1}
>>> class A(Corgy):
... x: str
... g: G
>>> a = A(x="one", g=g)
>>> a.as_dict(recursive=False)
{'x': 'one', 'g': G(x=1)}
>>> a.as_dict()
{'x': 'one', 'g': {'x': 1}}
>>> a.as_dict(flatten=True)
{'x': 'one', 'g:x': 1}
attrs
classmethod
#
attrs()
Return a dictionary mapping attributes to their types.
Examples:
>>> class A(Corgy):
... x: Annotated[int, "x"]
... y: Sequence[str]
>>> A.attrs()
{'x': <class 'int'>, 'y': typing.Sequence[str]}
freeze #
freeze()
Freeze the object, preventing any further changes.
Examples:
>>> class A(Corgy):
... x: int
... y: int
>>> a = A(x=1, y=2)
>>> a.x = 2
>>> a.freeze()
>>> a.x = 3
Traceback (most recent call last):
...
TypeError: cannot set `x`: object is frozen
>>> del a.y
Traceback (most recent call last):
...
TypeError: cannot delete `y`: object is frozen
from_dict
classmethod
#
from_dict(d, try_cast=False)
Return a new instance of the class using a dictionary.
This is roughly equivalent to cls(**d)
, with the main
exception being that groups can be specified as dictionaries
themselves, and will be processed recursively.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
d |
Mapping[str, Any]
|
Dictionary to create the instance from. |
required |
try_cast |
bool
|
Whether to try and cast values which don't match attribute types. |
False
|
Examples:
>>> class G(Corgy):
... x: int
>>> class A(Corgy):
... x: str
... g: G
>>> A.from_dict({"x": "one", "g": G(x=1)})
A(x='one', g=G(x=1))
>>> A.from_dict({"x": "one", "g": {"x": 1}})
A(x='one', g=G(x=1))
>>> A.from_dict({"x": "1", "g": {"x": "1"}}, try_cast=True)
A(x='1', g=G(x=1))
>>> G.from_dict({"x": "1"})
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'int'>': '1'
Group attributes can also be passed directly in the dictionary by prefixing their names with the group name and a colon.
Examples:
>>> A.from_dict({"x": "one", "g:x": 1})
A(x='one', g=G(x=1))
>>> class B(Corgy):
... x: float
... a: A
>>> B.from_dict({"x": 1.1, "a:x": "one", "a:g:x": 1})
B(x=1.1, a=A(x='one', g=G(x=1)))
is_attr_set #
is_attr_set(attr_name)
Check if a Corgy
attribute is set.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
attr_name |
str
|
Name of the attribute to check. |
required |
Raises:
Type | Description |
---|---|
AttributeError
|
if the attribute is not a |
load_dict #
load_dict(d, try_cast=False, strict=False)
Load a dictionary into an instance of the class.
Previous attributes are overwritten. Sub-dictionaries will be
parsed recursively if the corresponding attribute already
exists, else will be parsed using from_dict
. As with
from_dict
, items in the dictionary without corresponding
attributes are ignored.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
d |
Dict[str, Any]
|
Dictionary to load. |
required |
try_cast |
bool
|
Whether to try and cast values which don't match attribute types. |
False
|
strict |
bool
|
If |
False
|
Examples:
>>> class A(Corgy):
... x: int
... y: str
>>> a = A(x=1)
>>> _i = id(a)
>>> a.load_dict({"y": "two"})
>>> a
A(x=1, y='two')
>>> _i == id(a)
True
>>> a.load_dict({"y": "three"}, strict=True)
>>> a
A(y='three')
>>> _i == id(a)
True
>>> a = A()
>>> a.load_dict({"x": "1"})
Traceback (most recent call last):
...
ValueError: error setting `x`: invalid value for type
'<class 'int'>': '1'
>>> a.load_dict({"x": "1"}, try_cast=True)
>>> a
A(x=1)
parse_from_cmdline
classmethod
#
parse_from_cmdline(parser=None, defaults=None, **parser_args)
Return an instance of the class parsed from the command line.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
parser |
Optional[ArgumentParser]
|
An instance of |
None
|
defaults |
Optional[Mapping[str, Any]]
|
A dictionary of default values for the attributes,
passed to |
None
|
parser_args |
Arguments to be passed to
|
{}
|
Raises:
Type | Description |
---|---|
ArgumentTypeError
|
Error parsing command line arguments. |
parse_from_toml
classmethod
#
parse_from_toml(toml_file, defaults=None)
Parse an object of the class from a toml file.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
toml_file |
IO[bytes]
|
A file-like object containing the class attributes in toml. |
required |
defaults |
Optional[Mapping[str, Any]]
|
A dictionary of default values, overriding any values specified in the class. |
None
|
Raises:
Type | Description |
---|---|
TOMLDecodeError
|
Error parsing the toml file. |
Examples:
>>> from io import BytesIO
>>> class G(Corgy):
... x: int
... y: Sequence[int]
>>> class A(Corgy):
... x: str
... g: G
>>> f = BytesIO(b'''
... x = 'one'
... [g]
... x = 1
... y = [1, 2, 3]
... ''')
>>> A.parse_from_toml(f)
A(x='one', g=G(x=1, y=[1, 2, 3]))
corgy.corgyparser #
corgy.corgyparser(*var_names, metavar=None, nargs=None)
Decorate a function as a custom attribute parser.
To use a custom function for parsing a Corgy
attribute, use this
decorator. Parsing functions must be static, and should only accept
a single argument. Decorating the function with @staticmethod
is
optional, but prevents type errors. @corgyparser
must be the
final decorator in the decorator chain.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
var_names |
str
|
The attributes associated with the decorated parser. |
()
|
metavar |
Optional[str]
|
Keyword only argument to set the metavar when adding
the associated attribute(s) to an |
None
|
nargs |
Union[None, Literal['*', '+'], int]
|
Keyword only argument to set the number of arguments to
be used for the associated attribute(s). Must be |
None
|
Examples:
>>> import argparse
>>> from argparse import ArgumentParser
>>> from typing import Tuple
>>> from corgy import Corgy, CorgyHelpFormatter, corgyparser
>>> class A(Corgy):
... time: Tuple[int, int, int]
... @corgyparser("time", metavar="int:int:int")
... @staticmethod
... def parse_time(s):
... return tuple(map(int, s.split(":")))
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.parse_args(["--time", "1:2:3"])
Namespace(time=(1, 2, 3))
Multiple arguments can be passed to the decorator, and will all be associated with the same parser.
Examples:
>>> class A(Corgy):
... x: int
... y: int
... @corgyparser("x", "y")
... @staticmethod
... def parse_x_y(s):
... return int(s)
The @corgyparser
decorator can also be chained to use the same
parser for multiple arguments.
Examples:
>>> class A(Corgy):
... x: int
... y: int
... @corgyparser("x")
... @corgyparser("y")
... @staticmethod
... def parse_x_y(s):
... return int(s)
Note
When chaining, the outer-most non-None
value of metavar
will
be used.
Custom parsers can control the number of arguments they receive, independent of the argument type.
Examples:
>>> class A(Corgy):
... x: int
... @corgyparser("x", nargs=3)
... @staticmethod
... def parse_x(s):
... # `s` will be a list of 3 strings.
... return sum(map(int, s))
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> A.add_args_to_parser(parser)
>>> parser.parse_args(["--x", "1", "2", "3"])
Namespace(x=6)
When chaining, nargs
must be the same for all decorators,
otherwise TypeError
is raised.
corgy.corgychecker #
corgy.corgychecker(*var_names)
Decorate a function as a custom attribute checker.
To use a custom function for checking the value of a Corgy
attribute, use this decorator. Checking functions must be static,
and should only accept a single argument, the value to be checked.
They should raise ValueError
to indicate value mismatch.
Decorating the function with @staticmethod
is optional, but
prevents type errors. @corgychecker
must be the final decorator in
the decorator chain.
Custom checkers are called after type checking, so the values passed to them will be of type corresponding to one of the assigned attributes.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
var_names |
str
|
The attributes associated with the decorated checker. |
()
|
Examples:
>>> from corgy import Corgy, corgychecker
>>> class A(Corgy):
... x: int
... @corgychecker("x")
... @staticmethod
... def check_x(val):
... if val % 2:
... raise ValueError(f"'{val}' is not even")
>>> a = A()
>>> a.x = 2
>>> a.x = 3
Traceback (most recent call last):
...
ValueError: error setting `x`: '3' is not even
Multiple attributes can use the same checker, either by chaining
corgychecker
, or by passing all attribute names directly.
Examples:
>>> from typing import Sequence
>>> class A(Corgy):
... x: int
... y: float
... z: str
... w: Sequence[int]
... @corgychecker("x")
... @corgychecker("y")
... def check_num(val):
... if val < 0:
... raise ValueError("should be non-negative")
... @corgychecker("z", "w")
... def check_seq(val):
... if len(val) > 10:
... raise ValueError("too long")
corgy.CorgyHelpFormatter #
Bases: HelpFormatter
Formatter class for argparse
with cleaner layout and colors.
Corgy.parse_from_cmdline
uses this formatter by default, unless a
different formatter_class
argument is provided.
CorgyHelpFormatter
can also be used independently of Corgy
.
Simply pass it as the formatter_class
argument to
argparse.ArgumentParser()
.
Examples:
>>> import argparse
>>> from argparse import ArgumentParser
>>> from corgy import CorgyHelpFormatter
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... usage=argparse.SUPPRESS,
... )
>>> _ = parser.add_argument("--x", type=int, required=True)
>>> _ = parser.add_argument(
... "--y", type=str, nargs="*", required=True
... )
>>> parser.print_help()
options:
-h/--help show this help message and exit
--x int (required)
--y [str ...] (required)
To configure CorgyHelpFormatter
, you can set a number of
attributes on the class.
Note
You do not need to create an instance of the class; that is done by the parser itself. The following public attributes are available:
Color-related attributes:
-
enable_colors
: IfNone
(the default), colors are enabled if thecrayons
package is available, and the output is a tty. To explicitly enable or disable colors, set toTrue
orFalse
. -
color_<choices/keywords/metavars/defaults/options>
: These attributes control the colors used for various parts of the output (see below for reference). Available colors arered
,green
,yellow
,blue
,black
,magenta
,cyan
, andwhite
. Specifying the name in all caps will make the color bold. You can also use the special valueBOLD
to make the output bold without changing the color. The default value areblue
for choices,green
for keywords,RED
for metavars,YELLOW
for defaults, andBOLD
for options. Format::-a/--arg str help for arg ({'a'/'b'/'c'} default: 'a') # noqa | | | | | options metavars choices keywords defaults
Layout-related attributes:
-
output_width
: The number of columns used for the output. IfNone
(the default), the current terminal width is used. -
max_help_position
: How far to the right (from the start), the help string can start from. IfNone
, there is no limit. The default is to use half the current terminal width.
Marker-related attributes:
-
marker_extras_<begin/end>
: The strings used to enclose the extra help text (choices, default values etc.). The defaults are(
and)
. -
marker_choices_<begin/end>
: The strings used to enclose the list of choices for an argument. The defaults are{
and}
. -
marker_choices_sep
: The string used to separate individual choices in the choice list. The default is/
.
Misc. attributes:
show_full_help
: Whether to show the full help, including choices, indicators for required arguments, and the usage string. The default isTrue
.
Formatting of individual arguments can be customized with magic attributes defined on the argument type. The following attributes are recognized:
__metavar__
: This can be set to a string on the argument type to override the default metavar.
Examples:
>>> class T:
... __metavar__ = "METAVAR"
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> _ = parser.add_argument("--arg", type=T)
>>> parser.print_help()
options:
--arg METAVAR (default: None)
FullHelpAction #
Bases: Action
argparse.Action
that displays the full help, and exits.
ShortHelpAction #
Bases: Action
argparse.Action
that displays the short help, and exits.
add_short_full_helps
classmethod
#
add_short_full_helps(parser, short_help_flags=('-h', '--help'), full_help_flags=('--helpfull'), short_help_msg='show help message and exit', full_help_msg='show full help message and exit')
Add arguments for displaying the short or full help.
The parser must be created with add_help=False
to prevent a
clash with the added arguments.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
parser |
ArgumentParser
|
|
required |
short_help_flags |
Sequence[str]
|
Sequence of argument strings for the short
help option. Default is |
('-h', '--help')
|
full_help_flags |
Sequence[str]
|
Sequence of argument strings for the full
help option. Default is |
('--helpfull')
|
short_help_msg |
str
|
String to describe the short help option.
Default is |
'show help message and exit'
|
full_help_msg |
str
|
String to describe the full help option.
Default is |
'show full help message and exit'
|
Examples:
>>> parser = ArgumentParser(
... formatter_class=CorgyHelpFormatter,
... add_help=False,
... usage=argparse.SUPPRESS,
... )
>>> CorgyHelpFormatter.add_short_full_helps(parser)
>>> parser.print_help()
options:
-h/--help show help message and exit
--helpfull show full help message and exit