cmdargparse 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cmdargparse/__init__.py +10 -0
- cmdargparse/argument.py +99 -0
- cmdargparse/command.py +200 -0
- cmdargparse/custom.py +120 -0
- cmdargparse/field.py +778 -0
- cmdargparse/namespace.py +37 -0
- cmdargparse/parser.py +40 -0
- cmdargparse/unset.py +79 -0
- cmdargparse-0.1.0.dist-info/METADATA +209 -0
- cmdargparse-0.1.0.dist-info/RECORD +13 -0
- cmdargparse-0.1.0.dist-info/WHEEL +5 -0
- cmdargparse-0.1.0.dist-info/licenses/LICENSE +21 -0
- cmdargparse-0.1.0.dist-info/top_level.txt +1 -0
cmdargparse/__init__.py
ADDED
cmdargparse/argument.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
import functools
|
|
3
|
+
from typing import (
|
|
4
|
+
TYPE_CHECKING,
|
|
5
|
+
Callable,
|
|
6
|
+
ClassVar,
|
|
7
|
+
Type,
|
|
8
|
+
TypeVar,
|
|
9
|
+
dataclass_transform,
|
|
10
|
+
overload,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .field import _DeclFormSpecifier
|
|
14
|
+
from .namespace import Namespace
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ################################ TYPING ######################################
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ################################ METACLASS ###################################
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _cmdargument(type):
|
|
27
|
+
|
|
28
|
+
# ################## DEFAULTS ##########################
|
|
29
|
+
|
|
30
|
+
_cmdargparse_default_form: ClassVar = "-"
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def default_form(cls, form: _DeclFormSpecifier, /) -> None:
|
|
34
|
+
"""Sets the global default declaration form specifier."""
|
|
35
|
+
|
|
36
|
+
cls._cmdargparse_default_form = form
|
|
37
|
+
|
|
38
|
+
# ################## DECORATOR #########################
|
|
39
|
+
|
|
40
|
+
@overload
|
|
41
|
+
@dataclass_transform(eq_default=False)
|
|
42
|
+
def __call__(
|
|
43
|
+
cls,
|
|
44
|
+
_cls: Type[T],
|
|
45
|
+
/,
|
|
46
|
+
) -> Type[T]: ...
|
|
47
|
+
|
|
48
|
+
@overload
|
|
49
|
+
@dataclass_transform(eq_default=False)
|
|
50
|
+
def __call__(
|
|
51
|
+
cls,
|
|
52
|
+
/,
|
|
53
|
+
*,
|
|
54
|
+
form: _DeclFormSpecifier,
|
|
55
|
+
) -> Callable[[Type[T]], Type[T]]: ...
|
|
56
|
+
|
|
57
|
+
@dataclass_transform(eq_default=False)
|
|
58
|
+
def __call__(
|
|
59
|
+
cls,
|
|
60
|
+
_cls: Type[T] | None = None,
|
|
61
|
+
/,
|
|
62
|
+
*,
|
|
63
|
+
form: _DeclFormSpecifier | None = None,
|
|
64
|
+
) -> Callable[[Type[T]], Type[T]] | Type[T]:
|
|
65
|
+
"""
|
|
66
|
+
Decorate as command argument object.
|
|
67
|
+
|
|
68
|
+
:param form:
|
|
69
|
+
The default declaration form specifier.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if _cls is None:
|
|
73
|
+
return functools.partial(
|
|
74
|
+
cls.__call__,
|
|
75
|
+
default=form,
|
|
76
|
+
) # type: ignore
|
|
77
|
+
|
|
78
|
+
setattr(
|
|
79
|
+
_cls,
|
|
80
|
+
"_cmdargparse_default_form",
|
|
81
|
+
form or cls._cmdargparse_default_form,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return dataclasses.dataclass(
|
|
85
|
+
init=False,
|
|
86
|
+
repr=False,
|
|
87
|
+
eq=False,
|
|
88
|
+
)(_cls)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ################################ CLASS #######################################
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class cmdargument(
|
|
95
|
+
# Allow the type checker to find the namespace attributes.
|
|
96
|
+
(Namespace if TYPE_CHECKING else object),
|
|
97
|
+
metaclass=_cmdargument,
|
|
98
|
+
):
|
|
99
|
+
pass
|
cmdargparse/command.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import dataclasses
|
|
3
|
+
import types
|
|
4
|
+
import typing
|
|
5
|
+
from typing import Any, Callable, Optional, Tuple, Type, TypeVar
|
|
6
|
+
|
|
7
|
+
import cmd2
|
|
8
|
+
|
|
9
|
+
from .field import _DeclFormSpecifier, _FieldDecls
|
|
10
|
+
from .namespace import Namespace
|
|
11
|
+
from .parser import ArgumentParser
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ################################ TYPING ######################################
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
TSELF = TypeVar("TSELF", cmd2.Cmd, cmd2.CommandSet)
|
|
18
|
+
TARGUMENT = TypeVar("TARGUMENT", bound=object)
|
|
19
|
+
TRETURN = TypeVar("TRETURN", Optional[bool], bool, None)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ################################ METACLASS ###################################
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _cmdcommand(type):
|
|
26
|
+
|
|
27
|
+
# ################## DECORATOR #########################
|
|
28
|
+
|
|
29
|
+
def __call__(
|
|
30
|
+
cls,
|
|
31
|
+
argtype: Type[TARGUMENT],
|
|
32
|
+
/,
|
|
33
|
+
) -> Callable[
|
|
34
|
+
[Callable[[TSELF, TARGUMENT], TRETURN]],
|
|
35
|
+
Callable[[TSELF, TARGUMENT], TRETURN],
|
|
36
|
+
]:
|
|
37
|
+
"""Decorate as command function."""
|
|
38
|
+
|
|
39
|
+
if hasattr(argtype, "_cmdargparse_command_decorator"):
|
|
40
|
+
return getattr(argtype, "_cmdargparse_command_decorator")
|
|
41
|
+
|
|
42
|
+
parser = ArgumentParser()
|
|
43
|
+
|
|
44
|
+
_dcfields = dataclasses.fields(
|
|
45
|
+
argtype # pyright: ignore[reportArgumentType]
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
default_form: _DeclFormSpecifier
|
|
49
|
+
default_form = getattr(argtype, "_cmdargparse_default_form")
|
|
50
|
+
|
|
51
|
+
for field in _dcfields:
|
|
52
|
+
decls: _FieldDecls | None
|
|
53
|
+
decls = field.metadata["decls"]
|
|
54
|
+
|
|
55
|
+
name_or_flags = (
|
|
56
|
+
() # Arguments are solely specified using the 'dest' keyword.
|
|
57
|
+
if decls is None
|
|
58
|
+
else argparse_name_or_flags(
|
|
59
|
+
field.name,
|
|
60
|
+
decls,
|
|
61
|
+
default_form=default_form,
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
args: dict[str, Any]
|
|
66
|
+
args = field.metadata["args"]
|
|
67
|
+
|
|
68
|
+
assert "dest" not in args, (
|
|
69
|
+
"Specifying 'dest' directly is not supported."
|
|
70
|
+
# <format-break>
|
|
71
|
+
)
|
|
72
|
+
args["dest"] = field.name
|
|
73
|
+
|
|
74
|
+
if (
|
|
75
|
+
args.get("action", None)
|
|
76
|
+
in ("store_true", "store_false", "store_const")
|
|
77
|
+
# <format-break>
|
|
78
|
+
):
|
|
79
|
+
# Remove arguments for 'const' and 'type' if specified.
|
|
80
|
+
if args["action"] != "store_const":
|
|
81
|
+
args.pop("const", ...)
|
|
82
|
+
args.pop("type", ...)
|
|
83
|
+
|
|
84
|
+
parser.add_argument(*name_or_flags, **args)
|
|
85
|
+
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
# Resolve the type from the fields type annotation.
|
|
89
|
+
if args.get("type", None) is None:
|
|
90
|
+
args["type"] = argparse_type(field.type)
|
|
91
|
+
|
|
92
|
+
# Remove custom unspecified arguments.
|
|
93
|
+
if args.get("choicesmap", None) is None:
|
|
94
|
+
args.pop("choicesmap", ...)
|
|
95
|
+
|
|
96
|
+
parser.add_argument(*name_or_flags, **args)
|
|
97
|
+
|
|
98
|
+
setattr(
|
|
99
|
+
argtype,
|
|
100
|
+
"_cmdargparse_command_decorator",
|
|
101
|
+
_cmdargparse_command_decorator := cmd2.with_argparser(
|
|
102
|
+
parser,
|
|
103
|
+
ns_provider=cls.namespace_provider(argtype),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return _cmdargparse_command_decorator # type: ignore
|
|
108
|
+
|
|
109
|
+
# ################## HELPERS ###########################
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def namespace_provider(
|
|
113
|
+
argtype: Type[TARGUMENT],
|
|
114
|
+
/,
|
|
115
|
+
) -> Callable[
|
|
116
|
+
[cmd2.Cmd | cmd2.CommandSet],
|
|
117
|
+
argparse.Namespace,
|
|
118
|
+
]:
|
|
119
|
+
# Inherit all attributes that are assumed to be constants.
|
|
120
|
+
# (This includes all uppercase attributes not starting with an '_'.)
|
|
121
|
+
namespace = {
|
|
122
|
+
attr: getattr(argtype, attr)
|
|
123
|
+
for attr in dir(argtype)
|
|
124
|
+
if (
|
|
125
|
+
not attr.startswith("_")
|
|
126
|
+
and attr.isupper()
|
|
127
|
+
# <format-break>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def _provider(cmd: cmd2.Cmd | cmd2.CommandSet) -> Namespace:
|
|
132
|
+
return Namespace(**namespace)
|
|
133
|
+
|
|
134
|
+
return _provider
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ################################ CLASS #######################################
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class cmdcommand(metaclass=_cmdcommand):
|
|
141
|
+
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ################################ HELPERS #####################################
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def argparse_name_or_flags(
|
|
149
|
+
field: str,
|
|
150
|
+
/,
|
|
151
|
+
decls: _FieldDecls,
|
|
152
|
+
*,
|
|
153
|
+
default_form: _DeclFormSpecifier,
|
|
154
|
+
) -> Tuple[str, ...]:
|
|
155
|
+
(decl, altdecl) = (decls.decl, decls.altdecl)
|
|
156
|
+
|
|
157
|
+
form = decls.form or default_form
|
|
158
|
+
more_decls = decls.more_decls or ()
|
|
159
|
+
|
|
160
|
+
# In the augmented declaration specification format the automatically
|
|
161
|
+
# derived declaration is replaced if it has the same form.
|
|
162
|
+
if (decl is None and altdecl is not None) and (
|
|
163
|
+
(form == "-" and not altdecl.startswith("--"))
|
|
164
|
+
or (form == "--" and altdecl.startswith("--"))
|
|
165
|
+
):
|
|
166
|
+
decl, altdecl = altdecl, None
|
|
167
|
+
|
|
168
|
+
return tuple(
|
|
169
|
+
_decl
|
|
170
|
+
for _decl in (
|
|
171
|
+
decl or (form + field),
|
|
172
|
+
altdecl,
|
|
173
|
+
*more_decls,
|
|
174
|
+
)
|
|
175
|
+
if _decl is not None
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def argparse_type(type: Any, /) -> Type[Any]:
|
|
180
|
+
if isinstance(type, str):
|
|
181
|
+
raise TypeError("forward reference type annotations are not supported")
|
|
182
|
+
|
|
183
|
+
origin = typing.get_origin(type)
|
|
184
|
+
if origin is None:
|
|
185
|
+
return type
|
|
186
|
+
|
|
187
|
+
if origin is typing.Annotated:
|
|
188
|
+
(argtype, *_) = typing.get_args(type)
|
|
189
|
+
return argparse_type(argtype)
|
|
190
|
+
|
|
191
|
+
# We assume the first argument of the union type is the primary type that
|
|
192
|
+
# should be passed to the `argparse` module.
|
|
193
|
+
if isinstance(type, types.UnionType):
|
|
194
|
+
(argtype, *_) = typing.get_args(type)
|
|
195
|
+
return argtype
|
|
196
|
+
elif origin is typing.Union:
|
|
197
|
+
(argtype, *_) = typing.get_args(type)
|
|
198
|
+
return argtype
|
|
199
|
+
|
|
200
|
+
raise TypeError("unsupported type annotation")
|
cmdargparse/custom.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import (
|
|
3
|
+
TYPE_CHECKING,
|
|
4
|
+
Annotated,
|
|
5
|
+
Any,
|
|
6
|
+
Generic,
|
|
7
|
+
Protocol,
|
|
8
|
+
Self,
|
|
9
|
+
Sequence,
|
|
10
|
+
TypeAlias,
|
|
11
|
+
TypeIs,
|
|
12
|
+
TypeVar,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .namespace import Namespace
|
|
16
|
+
from .parser import ArgumentParser
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ################################ GLOBALS #####################################
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = (
|
|
23
|
+
# fmt: off
|
|
24
|
+
"TANY",
|
|
25
|
+
"decorator",
|
|
26
|
+
"is_single_value", "is_multiple_values",
|
|
27
|
+
# fmt: on
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ################################ TYPING ######################################
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
T = TypeVar("T")
|
|
35
|
+
T_ct = TypeVar("T_ct", contravariant=True)
|
|
36
|
+
|
|
37
|
+
TANY: TypeAlias = Any
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ################################ DECORATORS ##################################
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _ArgparseActionDunderCallCallable(Protocol):
|
|
44
|
+
def __call__(
|
|
45
|
+
_self,
|
|
46
|
+
self: Annotated[Any, Self],
|
|
47
|
+
parser: argparse.ArgumentParser,
|
|
48
|
+
namespace: argparse.Namespace,
|
|
49
|
+
values: str | Sequence[Any] | None,
|
|
50
|
+
option_string: str | None = None,
|
|
51
|
+
) -> None: ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _ActionDunderCallCallable(Protocol, Generic[T_ct]):
|
|
55
|
+
def __call__(
|
|
56
|
+
_self,
|
|
57
|
+
self: Annotated[Any, Self],
|
|
58
|
+
parser: ArgumentParser,
|
|
59
|
+
namespace: Namespace,
|
|
60
|
+
values: Sequence[T_ct] | T_ct,
|
|
61
|
+
option_string: str | None = None,
|
|
62
|
+
) -> None: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class decorator:
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def call(
|
|
69
|
+
func: _ActionDunderCallCallable[T_ct],
|
|
70
|
+
/,
|
|
71
|
+
) -> Annotated[Any, _ArgparseActionDunderCallCallable]:
|
|
72
|
+
"""Supresses `reportIncompatibleMethodOverride` on `Action.__call__`."""
|
|
73
|
+
return func # pyright: ignore[reportReturnType]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ################################ FUNCTIONS ###################################
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def is_single_value(
|
|
80
|
+
values: Sequence[TANY] | TANY,
|
|
81
|
+
/,
|
|
82
|
+
) -> TypeIs[TANY]:
|
|
83
|
+
"""Typing helper for the `values` argument of `Action.__call__`."""
|
|
84
|
+
# The `argparse` module passes multiple values as a list.
|
|
85
|
+
return not isinstance(values, list)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def is_multiple_values(
|
|
89
|
+
values: Sequence[TANY] | TANY,
|
|
90
|
+
/,
|
|
91
|
+
) -> TypeIs[Sequence[TANY]]:
|
|
92
|
+
"""Typing helper for the `values` argument of `Action.__call__`."""
|
|
93
|
+
# The `argparse` module passes multiple values as a list.
|
|
94
|
+
return isinstance(values, list)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ################################ TEMPALTES ###################################
|
|
98
|
+
if TYPE_CHECKING:
|
|
99
|
+
|
|
100
|
+
class ActionTemplate(argparse.Action):
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
*args: Annotated[Any, "passthrough"],
|
|
105
|
+
**kwargs: Annotated[Any, "passthrough"],
|
|
106
|
+
) -> None:
|
|
107
|
+
assert not args, (
|
|
108
|
+
"There should be no positional arguments if not explicitly "
|
|
109
|
+
"specified. (The `argparse` module passes by keyword.)"
|
|
110
|
+
)
|
|
111
|
+
super().__init__(*args, **kwargs)
|
|
112
|
+
|
|
113
|
+
@decorator.call
|
|
114
|
+
def __call__(
|
|
115
|
+
self,
|
|
116
|
+
parser: ArgumentParser,
|
|
117
|
+
namespace: Namespace,
|
|
118
|
+
values: Sequence[TANY] | TANY,
|
|
119
|
+
option_string: str | None = None,
|
|
120
|
+
) -> None: ...
|