@@ -64,7 +64,9 @@ required.
6464### Basic usage
6565
6666Parameters without defaults become positional arguments. Parameters with defaults become ` --option `
67- flags. The function receives typed keyword arguments directly instead of an ` argparse.Namespace ` .
67+ flags. Keyword-only parameters (after ` * ` ) always become options, and without a default they become
68+ required options. The function receives typed keyword arguments directly instead of an
69+ ` argparse.Namespace ` .
6870
6971``` py
7072from cmd2 import with_annotated
@@ -89,14 +91,14 @@ The decorator converts Python type annotations into `add_argument()` calls:
8991| -------------------------------------------------------- | --------------------------------------------------- |
9092| ` str ` | default (no ` type= ` needed) |
9193| ` int ` , ` float ` | ` type=int ` or ` type=float ` |
92- | ` bool ` (default ` False ` ) | ` --flag ` with ` action='store_true' ` |
93- | ` bool ` (default ` True ` ) | ` --no-flag ` with ` action='store_false' ` |
94+ | ` bool ` with a default | boolean optional flag via ` BooleanOptionalAction ` |
9495| positional ` bool ` | parsed from ` true/false ` , ` yes/no ` , ` on/off ` , ` 1/0 ` |
9596| ` Path ` | ` type=Path ` |
9697| ` Enum ` subclass | ` type=converter ` , ` choices ` from member values |
9798| ` decimal.Decimal ` | ` type=decimal.Decimal ` |
9899| ` Literal[...] ` | ` type=literal-converter ` , ` choices ` from values |
99100| ` Collection[T] ` / ` list[T] ` / ` set[T] ` / ` tuple[T, ...] ` | ` nargs='+' ` (or ` '*' ` if it has a default) |
101+ | ` tuple[T, T] ` | fixed ` nargs=N ` with ` type=T ` |
100102| ` T \| None ` | unwrapped to ` T ` , treated as optional |
101103
102104When collection types are used with ` @with_annotated ` , parsed values are passed to the command
@@ -106,6 +108,15 @@ function as:
106108- ` set[T] ` as ` set `
107109- ` tuple[T, ...] ` as ` tuple `
108110
111+ Unsupported patterns raise ` TypeError ` , including:
112+
113+ - unions with multiple non-` None ` members such as ` str | int `
114+ - mixed-type tuples such as ` tuple[int, str] `
115+ - ` Annotated[T, meta] | None ` ; write ` Annotated[T | None, meta] ` instead
116+
117+ The parameter names ` dest ` and ` subcommand ` are reserved and may not be used as annotated parameter
118+ names.
119+
109120### Annotated metadata
110121
111122For finer control, use ` typing.Annotated ` with [ Argument] [ cmd2.annotated.Argument ] or
@@ -171,15 +182,69 @@ def do_greet(self, name: str, count: int = 1, loud: bool = False):
171182 self .poutput(msg.upper() if loud else msg)
172183```
173184
174- The annotated version is more concise and gives you typed parameters. The argparse version gives you
175- more control (e.g. ` ns_provider ` , subcommand handlers via ` cmd2_handler ` ).
185+ The annotated version is more concise and gives you typed parameters. It also supports several
186+ advanced cmd2 features directly, including ` ns_provider ` , ` with_unknown_args ` , and typed
187+ subcommands.
176188
177189### Decorator options
178190
179- ` @with_annotated ` accepts the same keyword arguments as ` @with_argparser ` :
191+ ` @with_annotated ` currently supports :
180192
193+ - ` ns_provider ` -- prepopulate the namespace before parsing, mirroring ` @with_argparser `
181194- ` preserve_quotes ` -- if ` True ` , quotes in arguments are preserved
182195- ` with_unknown_args ` -- if ` True ` , unrecognised arguments are passed as ` _unknown `
196+ - ` subcommand_to ` -- register the function as an annotated subcommand under a parent command
197+ - ` base_command ` -- create a base command whose parser also adds subparsers and exposes
198+ ` cmd2_handler `
199+ - ` help ` -- help text for an annotated subcommand
200+ - ` aliases ` -- aliases for an annotated subcommand
201+
202+ ``` py
203+ @with_annotated (with_unknown_args = True )
204+ def do_rawish (self , name : str , _unknown : list[str ] | None = None ):
205+ self .poutput((name, _unknown))
206+ ```
207+
208+ ### Annotated subcommands
209+
210+ ` @with_annotated ` can also build typed subcommand trees without manually constructing subparsers.
211+
212+ ``` py
213+ @with_annotated (base_command = True )
214+ def do_manage (self , * , cmd2_handler ):
215+ handler = cmd2_handler.get()
216+ if handler:
217+ handler()
218+
219+ @with_annotated (subcommand_to = " manage" , help = " list projects" )
220+ def manage_list (self ):
221+ self .poutput(" listing" )
222+ ```
223+
224+ For nested subcommands, ` subcommand_to ` can be space-delimited, for example
225+ ` subcommand_to="manage project" ` . The intermediate level must also be declared as a subcommand that
226+ creates its own subparsers:
227+
228+ ``` py
229+ @with_annotated (subcommand_to = " manage" , base_command = True , help = " manage projects" )
230+ def manage_project (self , * , cmd2_handler ):
231+ handler = cmd2_handler.get()
232+ if handler:
233+ handler()
234+
235+ @with_annotated (subcommand_to = " manage project" , help = " add a project" )
236+ def manage_project_add (self , name : str ):
237+ self .poutput(f " added { name} " )
238+ ```
239+
240+ ### Lower-level parser building
241+
242+ If you need parser grouping or mutually-exclusive groups while still using annotation-driven parser
243+ generation, [ cmd2.annotated.build_parser_from_function] [ cmd2.annotated.build_parser_from_function ]
244+ also supports:
245+
246+ - ` groups=((...), (...)) `
247+ - ` mutually_exclusive_groups=((...), (...)) `
183248
184249``` py
185250@with_annotated (preserve_quotes = True )
@@ -211,7 +276,7 @@ def do_load(self, args):
211276```
212277
213278With ` @with_annotated ` , the same inference happens because ` Path ` and ` Enum ` annotations generate
214- ` type=Path ` and ` type=converter ` in the underlying parser.
279+ the equivalent parser configuration automatically .
215280
216281## Argument Parsing
217282
0 commit comments