feat: Experimental annotated argparse#1666
Conversation
Adds @with_annotated decorator that builds argparse parsers from type-annotated function signatures. Supports Annotated[T, Argument(...)] / Annotated[T, Option(...)] metadata, automatic positional/option detection, optional unwrapping, collections, enums, literals, Path completion, subcommands via subcommand_to=, base_command=True with cmd2_handler dispatch, and argument/mutually-exclusive groups. - New module cmd2/annotated.py with Argument, Option, with_annotated, and build_parser_from_function helpers - Comprehensive test suite in tests/test_annotated.py - Example in examples/annotated_example.py - Docs updates in docs/features/argument_processing.md
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1666 +/- ##
==========================================
- Coverage 99.53% 99.50% -0.03%
==========================================
Files 22 23 +1
Lines 4928 5689 +761
==========================================
+ Hits 4905 5661 +756
- Misses 23 28 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
|
I plan to review this PR this weekend. @kmvanbrunt @bambu I'd very much appreciate your feedback as well. |
|
How can I do the following?
|
- aliases param: Sequence[str] = () to match as_subcommand_to() - update aliases None checks now that it defaults to () - type with_annotated via @overload (no longer untyped decorator) - drop experimental annotated exports from cmd2/__init__.py - import from cmd2.annotated in example/tests/docs; fix example mypy
- Group(*members, title=, description=) for titled argument-group sections (groups= now accepts bare tuples or Group) - description= and epilog= for the generated parser - formatter_class= for a custom help formatter - parser_class= for a custom parser class Includes tests, example command, and docs.
Adds VerbatimHelpFormatter and StrictArgumentParser subclasses and a do_report command so all four parser-customization features have a runnable demonstration, not just docs/tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop bare tuple[str, ...] support; entries must be Group instances. Removes the _group_members shim and adds an explicit TypeError guard (_require_group) so a wrong type fails clearly instead of with an AttributeError. Updates tests and docs accordingly.
Adds a do_export command so mutually_exclusive_groups has a runnable demo alongside the existing groups demo.
…ally subtle conversion edge-case bug
|
I have updated the implementation to be as declarative as possible. So, in terms of maintenance or edge cases, you can add/ban them by just adding a new rule. Hopefully, I have now covered most of the missing features. |
|
Actually, do you want me to split the mega file and store it under something like |
|
@KelvinChung2000 I'm flexible. I can see an argument for keeping it all self-contained within one file. But I could also see the argument for splitting it into multiple files underneath the @kmvanbrunt Would you have a preference here? |
|
@KelvinChung2000 I chatted with @kmvanbrunt - for now please leave everything in the one big file |
Also: - Fix minor edge case silent failure bug - Add description of this new feature to CHANGELOG under an "Experimental Features" section for the 4.0.0 release
|
@KelvinChung2000 I pushed a change to add I also pushed a fix for the minor edge case bug I mentioned last time. From my perspective, this is good to merge. If anyone has any further comments or recommendations, put them forth sooner rather than later. I'll merge this a day or two from now if there are no further suggestions. |
…ed as the leaf command If a subcommand is also a `base_command` (e.g., intermediate subcommands), has `subcommand_required=False`, and is executed without any further subcommands provided by the user, it becomes the leaf command. The `cmd_wrapper` of the top-level command will set `ns.cmd2_handler` to point to this nested `handler` itself. Extracting it through `_filtered_namespace_kwargs` and passing it to the command function causes the function to call its own handler repeatedly via `if cmd2_handler: cmd2_handler()`, leading to an infinite recursion (stack overflow). This commit adds code to detect and short-circuit that potential infinite recursion. Also: Bump a
Document that this feature works in a manner similar to Typer to help make it more discoverable for users.
|
@KelvinChung2000 Thank you for bearing with us on the long road to get here and congrats on getting your first |
Adds
@with_annotated, a type-hint-driven alternative to@with_argparserthat builds the parser automatically from a command's signature (positional/option inference, enum/literal/path/collection handling, subcommands, groups, mutex). Marked experimental.cmd2/annotated.pyplusArgument/Optionmetadata classes exported fromcmd2dry_run→--dry-run); opt out viaOption("--my_flag")docs/features/annotated.mdwith an experimental admonition;argument_processing.mdkeeps a short pointerexamples/annotated_example.pyand test suite intests/test_annotated.py