cmd2.annotated
cmd2.annotated
Build argparse parsers from type-annotated function signatures.
Experimental
This module is experimental and its behavior may change in future releases.
The with_annotated decorator inspects a command function's type hints and
default values to build a Cmd2ArgumentParser. Argument and
Option metadata classes give finer per-parameter control via
typing.Annotated.
Parameters without defaults become positional arguments; parameters with defaults
become --option flags; keyword-only parameters (after *) are always options.
A bool option is a flag, not a value: when absent it means False (or None
for bool | None), so it defaults to that and is never required. A *args
parameter becomes a variadic positional accepting zero or more values (nargs='*'),
collected into a tuple. Underscores in a parameter name become dashes in the generated
flag (dry_run -> --dry-run); pass an explicit Option("--my_flag") to opt out.
Positional-only parameters (before /) and **kwargs raise TypeError. The parameter
names dest and subcommand are reserved; cmd2_statement receives the parsed
Statement and (with base_command=True) cmd2_subcommand_func receives the subcommand handler:
class MyApp(cmd2.Cmd):
@cmd2.with_annotated
def do_greet(self, name: str, count: int = 1, loud: bool = False):
for _ in range(count):
msg = f"Hello {name}"
self.poutput(msg.upper() if loud else msg)
Use Annotated with Argument or Option for finer
control over individual parameters:
from typing import Annotated
class MyApp(cmd2.Cmd):
def color_choices(self) -> cmd2.Choices:
return cmd2.Choices.from_values(["red", "green", "blue"])
@cmd2.with_annotated
def do_paint(
self,
item: str,
color: Annotated[str, Option("--color", "-c", choices_provider=color_choices, help_text="Color to use")] = "blue",
):
self.poutput(f"Painting {item} {color}")
How annotations map to argparse settings:
str-- default string argumentint,float-- setstype=booloption ----flag / --no-flagviaBooleanOptionalAction; defaults toFalse(orNoneforbool | None) when omitted, so it is neverrequired- positional
bool-- parsed fromtrue/false,yes/no,on/off,1/0 pathlib.Path-- setstype=Pathenum.Enumsubclass --type=converter,choicesfrom member valuesdecimal.Decimal-- setstype=DecimalLiteral[...]--type=converterandchoicesfrom the literal valueslist[T]/set[T]/tuple[T, ...]--nargs='+'(or'*'with a default or| None)tuple[T, T](fixed arity, same type) --nargs=Nwithtype=T*args: T-- variadic positional (nargs='*');Tis each value's type, not the collected tuple.Annotated[T, Argument(...)]metadata is honoredT | None(no default) -- positional withnargs='?'(0-or-1 tokens)T | None = None----flagoption withdefault=None
A value option with no default is made required (omitting it would pass None,
violating a non-Optional hint); annotate it T | None or give it a default to make it
omittable.
Explicit Option(action=...) is type-checked so the parsed result matches the
declared type:
store_true/store_false-- require aboolparameter (type=is dropped; argparse supplies theFalse/Truedefault)count-- requires anintparameter; defaults to0(Noneforint | None)append/extend-- require alist[T]parameter and default to[](appendtakes one value per flag;extendtakesnargsvalues per flag)store_const/append_const-- store theOption(const=...)value (type=is dropped). The action is inferred from the type whenaction=is omitted: a scalarOption(const=X)becomesstore_const(present ->const, absent -> the default, which must exist or beT | None); alist[T]Option(const=X)becomesappend_const(each flag appendsconst; defaults to[]). A scalarOption(const=X)given an explicitnargs(e.g.nargs='?') instead keeps thestoreaction for argparse's optional-value idiom (absent -> default, bare flag ->const,flag VALUE-> convertedVALUE); theconstis stored verbatim and must match the declared type.constis validated against the declared type and is rejected on a positionalArgument(argparse ignores it there)- a custom
argparse.Actionsubclass -- passed straight through toadd_argument. The user's class owns storage, so the collection-casting wrapper is dropped and the action-specific type/const/collection-shape constraints are skipped. The type-inferred converter, default, andrequiredare still applied; the action receives them like any hand-builtadd_argumentcall.action='help'andaction='version'are not supported.
The zero-argument actions above (store_true / store_false / count / store_const /
append_const) take no value from the command line, so the value-oriented metadata inferred from
the type is dropped before add_argument is called: the type= converter, the static
choices, and any inferred tab-completer (e.g. the path completer for Path). There is nothing
to complete or convert on a value-less action. A completer that was only inferred from the type is
dropped silently, but a completer / choices_provider you supply explicitly on such an action
is a contradiction and raises TypeError (matching argparse, which rejects it outright). Actions
that do consume values (append / extend on a list[T], or a plain value option) keep the
inferred converter and completer unchanged.
The metadata classes refuse a handful of add_argument kwargs that the decorator derives from the
signature itself, so passing them through Argument(...) / Option(...) raises TypeError:
type (from the annotation), dest (from the parameter name), help (use the help_text
parameter, which maps to it -- a raw help would silently shadow it), and -- on Argument only
-- action / required (which have no meaning on a positional). Every other add_argument
parameter, including those registered via
register_argparse_argument_parameter, passes through unchanged.
A default may be supplied either as the function-signature default (param: T = v) or as
Argument(default=v) / Option(default=v) -- the two forms are equivalent. Specifying both at
once raises TypeError (the value would have two sources of truth), and argparse.SUPPRESS is
rejected as a default from either source because it would remove the keyword argument the function
expects.
Parser-level customization is forwarded to Cmd2ArgumentParser's constructor via PEP
692 **parser_kwargs: Unpack[Cmd2ParserKwargs]. Anything the parser ctor accepts -- description,
epilog, prog, usage, parents, argument_default, prefix_chars,
fromfile_prefix_chars, conflict_handler, add_help, allow_abbrev, exit_on_error,
formatter_class, completer_class, and on Python >= 3.14 suggest_on_error / color --
flows straight through; the Cmd2ParserKwargs TypedDict is the single source of truth
and gives type-checkers/IDEs autocomplete on the decorator's call site. parser_class stays as
its own explicit kwarg because it selects the class itself, not a value passed to it. Two
behaviors layer on top of the raw passthrough: if description is omitted, the first paragraph
of func.__doc__ (up to the first blank line) is used so docstrings double as help text without
leaking :param: directives; and prog is rejected with subcommand_to because cmd2
rewrites it from the parent command's hierarchy. Mutually exclusive groups accept
Group(required=True) to require exactly one member; the same flag on a plain groups= entry
raises ValueError (argparse's add_argument_group has no required).
Unsupported patterns (raise TypeError):
- a non-Optional type with a
Nonedefault (e.g.name: str = None); annotate itT | Noneor use a non-None default.Any/object/unannotated are exempt - a scalar type with no converter (e.g.
datetime.datetime,uuid.UUID,bytes, or any custom class), which would silently arrive as a plain string. Supported scalars arestr,int,float,bool,decimal.Decimal,pathlib.Path,enum.Enumsubclasses, andLiteral[...](str/Any/objectpass through raw) str | int-- a union of multiple non-None types is ambiguoustuple[int, str, float]-- mixed element types (argparse applies onetype=per argument)*args: tuple[T, ...](or any collection element) -- the annotation is each value's type, so a collection element means a tuple-of-collections; annotate the element, e.g.*args: str*args: Annotated[T, Option(...)]--*argsis always positional; useArgument()*args: Annotated[T, Argument(nargs=N)]--*argsarity is fixed tonargs='*'- a keyword-only parameter annotated with
Argument()-- it marks a positional; useOption() - a required option (no default, not
T | None) in amutually_exclusive_groupsgroup -- only one member is supplied, so the others arrive asNone; give it a default orT | None Annotated[T, Argument(nargs=N)]producing a list ('*','+', integer>= 1) on a non-collectionT; uselist[T]ortuple[T, ...]to match the runtime shapeAnnotated[tuple[T, T], Argument(nargs=N)]whereNdiffers from the tuple's arityOption(action=...)whose result type mismatches the declared type, an unsupported action, or a non-list action on a collection (useappend/extend/append_constwithlist[T])- a variable-arity positional (
T | None,list[T],tuple[T, ...]) followed by another positional -- it must come last (def f(self, a: str, *rest: str)is fine)
When combining Annotated with Optional, the union should go inside:
Annotated[T | None, meta]. Annotated[T, meta] | None is ambiguous and raises -- unless the
inner type already carries the None (Annotated[T | None, meta] | None), in which case the
redundant outer | None is accepted as equivalent to Annotated[T | None, meta].
Path and Enum annotations also get automatic tab completion. A user-supplied
choices_provider or completer drives completion in place of the inferred static
choices, while the inferred type converter is kept so values still coerce to the
declared type (an Enum to its member, Literal[1, 2] to int) and out-of-type
values are rejected at parse time. An Enum accepts both member values and member names on the
command line (completion and --help show the values).
An explicit choices= is reconciled with the inferred type rather than fighting it: its values are
run through the inferred type converter so they match argparse's post-conversion comparison
(Annotated[int, Option('--n', choices=['1', '2'])] becomes choices=[1, 2], so --n 1
matches; a value the converter rejects is a build-time TypeError), and an explicit choices=
takes precedence over a type-inferred completer (the Path completer is dropped so the choices
drive both validation and completion). A choices_provider / completer you supply yourself
still wins over choices=.
Cmd2ParserKwargs
Bases: TypedDict
Forwarded ctor kwargs for Cmd2ArgumentParser (PEP 692 Unpack).
Single source of truth mirroring the parser's __init__: add a field here to expose a new
ctor kwarg on the decorator's call site. All optional (total=False); suggest_on_error
and color only take effect on Python >= 3.14.
Argument
Argument(
*,
help_text=None,
metavar=None,
nargs=None,
choices=None,
choices_provider=None,
completer=None,
table_columns=None,
suppress_tab_hint=None,
const=_UNSET,
default=_UNSET,
**extra_kwargs,
)
Bases: _BaseArgMetadata
Metadata for a positional argument in an Annotated type hint.
Initialise shared metadata fields.
const is the value stored on a present flag with no argument (Option only:
store_const/append_const); _UNSET distinguishes "no const" from const=None.
default mirrors the signature default (Option(default=v) == ... = v); supplying
both, or argparse.SUPPRESS, is rejected. extra_kwargs forwards any other
add_argument parameter (incl. those from
register_argparse_argument_parameter) straight through.
Source code in cmd2/annotated.py
to_kwargs
Return non-None mapped fields, an explicit const, and any passthrough extra_kwargs.
Source code in cmd2/annotated.py
Option
Bases: _BaseArgMetadata
Metadata for an optional/flag argument in an Annotated type hint.
Positional *names are the flag strings (e.g. "--color", "-c"); when omitted
the decorator generates --param-name (underscores become dashes).
Initialise Option metadata.
action is a supported string action (store_true/store_false/count/
append/extend/store_const/append_const) or a custom
argparse.Action subclass (passed through; it owns storage, so the inferred
action and the action-specific constraints are skipped).
Source code in cmd2/annotated.py
to_kwargs
Return non-None fields as an argparse kwargs dict.
Source code in cmd2/annotated.py
Group
Argument-group definition for with_annotated(groups=...) / mutually_exclusive_groups=....
Initialise an argument group definition.
| PARAMETER | DESCRIPTION |
|---|---|
members
|
parameter names to place in the group (at least one)
TYPE:
|
title
|
group title shown as a help section header
TYPE:
|
description
|
group description shown under the title
TYPE:
|
required
|
TYPE:
|
Source code in cmd2/annotated.py
build_parser_from_function
build_parser_from_function(
func,
*,
skip_params=_SKIP_PARAMS,
groups=None,
mutually_exclusive_groups=None,
parser_class=None,
**parser_kwargs,
)
Inspect a function's signature and build a Cmd2ArgumentParser.
The lower-level entry point behind with_annotated. parser_kwargs is forwarded to
the parser ctor (see Cmd2ParserKwargs); when description is omitted, the first
paragraph of func.__doc__ is used.
| PARAMETER | DESCRIPTION |
|---|---|
func
|
the command function to inspect
TYPE:
|
skip_params
|
parameter names to exclude from the parser
TYPE:
|
groups
|
TYPE:
|
mutually_exclusive_groups
|
TYPE:
|
parser_class
|
custom parser class (defaults to the configured default)
TYPE:
|
parser_kwargs
|
forwarded
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Cmd2ArgumentParser
|
a fully configured |
Source code in cmd2/annotated.py
1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 | |
with_annotated
with_annotated(
func: None = ...,
*,
ns_provider: Callable[..., Namespace] | None = ...,
preserve_quotes: bool = ...,
with_unknown_args: bool = ...,
base_command: bool = ...,
subcommand_to: str | None = ...,
help: str | None = ...,
aliases: Sequence[str] = ...,
deprecated: bool = ...,
groups: tuple[Group, ...] | None = ...,
mutually_exclusive_groups: tuple[Group, ...]
| None = ...,
parser_class: type[Cmd2ArgumentParser] | None = ...,
subcommand_required: bool = ...,
subcommand_metavar: str = ...,
subcommand_title: str | None = ...,
subcommand_description: str | None = ...,
**parser_kwargs: Unpack[Cmd2ParserKwargs],
) -> Callable[[Callable[..., Any]], Callable[..., Any]]
with_annotated(
func=None,
*,
ns_provider=None,
preserve_quotes=False,
with_unknown_args=False,
base_command=False,
subcommand_to=None,
help=None,
aliases=(),
deprecated=False,
groups=None,
mutually_exclusive_groups=None,
parser_class=None,
subcommand_required=True,
subcommand_metavar="SUBCOMMAND",
subcommand_title=None,
subcommand_description=None,
**parser_kwargs,
)
Decorate a do_* method to build its argparse parser from type annotations.
| PARAMETER | DESCRIPTION |
|---|---|
func
|
the command function (when used without parentheses)
TYPE:
|
ns_provider
|
callable returning a prepopulated Namespace (not with
TYPE:
|
preserve_quotes
|
preserve quotes in arguments (not with
TYPE:
|
with_unknown_args
|
capture unknown args as the
TYPE:
|
base_command
|
add
TYPE:
|
subcommand_to
|
parent command name; function must be named
TYPE:
|
help
|
subcommand help text (only with
TYPE:
|
aliases
|
alternative subcommand names (only with
TYPE:
|
deprecated
|
mark the subcommand deprecated in
TYPE:
|
groups
|
TYPE:
|
mutually_exclusive_groups
|
TYPE:
|
parser_class
|
custom parser class (defaults to the configured default)
TYPE:
|
subcommand_required
|
whether a subcommand must be supplied (
TYPE:
|
subcommand_metavar
|
metavar for the subcommands group (
TYPE:
|
subcommand_title
|
title for the subcommands
TYPE:
|
subcommand_description
|
description for that section (
TYPE:
|
parser_kwargs
|
any
TYPE:
|
Source code in cmd2/annotated.py
2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 | |