Skip to content

feat: Experimental annotated argparse#1666

Open
KelvinChung2000 wants to merge 18 commits into
mainfrom
feat/annotated-argparse
Open

feat: Experimental annotated argparse#1666
KelvinChung2000 wants to merge 18 commits into
mainfrom
feat/annotated-argparse

Conversation

@KelvinChung2000
Copy link
Copy Markdown

Adds @with_annotated, a type-hint-driven alternative to @with_argparser that builds the parser automatically from a command's signature (positional/option inference, enum/literal/path/collection handling, subcommands, groups, mutex). Marked experimental.

  • New module cmd2/annotated.py plus Argument / Option metadata classes exported from cmd2
  • Underscored param names auto-dasherize in generated flags (dry_run--dry-run); opt out via Option("--my_flag")
  • Dedicated docs page docs/features/annotated.md with an experimental admonition; argument_processing.md keeps a short pointer
  • Example app examples/annotated_example.py and test suite in tests/test_annotated.py

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
Copy link
Copy Markdown

codecov Bot commented May 15, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.59%. Comparing base (bbd689f) to head (4fc726c).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1666      +/-   ##
==========================================
+ Coverage   99.55%   99.59%   +0.03%     
==========================================
  Files          22       23       +1     
  Lines        4920     5376     +456     
==========================================
+ Hits         4898     5354     +456     
  Misses         22       22              
Flag Coverage Δ
unittests 99.59% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@tleonhardt
Copy link
Copy Markdown
Member

I plan to review this PR this weekend. @kmvanbrunt @bambu I'd very much appreciate your feedback as well.

@tleonhardt tleonhardt added this to the 4.0.0 milestone May 15, 2026
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread cmd2/annotated.py Outdated
Comment thread examples/annotated_example.py
Comment thread cmd2/annotated.py Outdated
@kmvanbrunt
Copy link
Copy Markdown
Member

@KelvinChung2000

How can I do the following?

  1. Set the title and description for an argument group?
  2. Set the description and epilog for a parser?
  3. Set a custom help formatter class for a parser?
  4. Use a custom parser class?

Comment thread cmd2/__init__.py Outdated
KelvinChung2000 and others added 8 commits May 19, 2026 15:14
- 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.
Comment thread cmd2/annotated.py Outdated
@tleonhardt tleonhardt changed the title feat: Experiemtal annotated argparse feat: Experimental annotated argparse May 20, 2026
Comment thread cmd2/annotated.py
_option_string: str | None = None,
) -> None:
result = values
if self._container_factory is not None and isinstance(values, list):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this code is going to work correctly in all circumstances.

If a user applies Argument(nargs="?") to a collection type (like tuple[int, ...]), argparse returns a single value instead of a list. This skips the isinstance(values, list) check, leaving the value uncast and passing a single integer to the command function instead of the expected tuple.

Copy link
Copy Markdown
Author

@KelvinChung2000 KelvinChung2000 May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you supply ?, this means you want either 0 or 1 answer, which I assume would be more natural to write a: Annotated[int | None, Argument()] or a: int | None.

But anyway, this is something I have missed. I have covered int | None = None but not int | None.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants