Skip to content

feat: Annotated based argparse, and auto completer inference#1614

Open
KelvinChung2000 wants to merge 7 commits intopython-cmd2:mainfrom
KelvinChung2000:feat/annotated-argparse
Open

feat: Annotated based argparse, and auto completer inference#1614
KelvinChung2000 wants to merge 7 commits intopython-cmd2:mainfrom
KelvinChung2000:feat/annotated-argparse

Conversation

@KelvinChung2000
Copy link
Copy Markdown

This is a full rework of #1612. Instead of wrapping Typer. We now extract the types and build the argparse parser.

I want to mark it as a draft for now, as some of the stuff will likely need a bit more cleanup. Please have a look at the documentation and example, and let me know if I missed anything obvious.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 23, 2026

Codecov Report

❌ Patch coverage is 97.08029% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.38%. Comparing base (b8651b0) to head (4b31c86).

Files with missing lines Patch % Lines
cmd2/annotated.py 97.68% 5 Missing ⚠️
cmd2/decorators.py 92.85% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1614      +/-   ##
==========================================
- Coverage   99.53%   99.38%   -0.16%     
==========================================
  Files          21       22       +1     
  Lines        4758     5031     +273     
==========================================
+ Hits         4736     5000     +264     
- Misses         22       31       +9     
Flag Coverage Δ
unittests 99.38% <97.08%> (-0.16%) ⬇️

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.

@KelvinChung2000
Copy link
Copy Markdown
Author

I started working on this before I closed that PR. While this PR is still using LLMs, I have a much better understanding of what it is writing, since it is based on Python type processing, which I am much more familiar with than click. As mentioned, this code needs some more cleanup before it's ready for review; that's why this is a draft. However, I would like some feedback on the documentation and the example to make sure I haven't missed anything obvious. If you'd prefer to defer until it is fully ready, that's fine as well.

@tleonhardt
Copy link
Copy Markdown
Member

I'm curious to see where this goes. I can't make any promises in advance, but it sounds like a potentially interesting feature. Though, I would prefer for all the tests to pass before I spend any time reviewing it.

If the code isn't too complex so that it appears to integrate with the rest of cmd2 in a way that is easy to maintain I could see it being a valuable addition.

If for some reason it doesn't immediately integrate well, there may be the possibility of creating a new open-source module that generates argparse argument parsers from type annotations.

@KelvinChung2000 KelvinChung2000 force-pushed the feat/annotated-argparse branch from 1834450 to 05c602e Compare March 24, 2026 15:09
Copy link
Copy Markdown
Member

@tleonhardt tleonhardt left a comment

Choose a reason for hiding this comment

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

Overall I like the user experience of this better - there is one essentially consistent behavior throughout for help and completion all based on argparse.

I'm a little concerned about how much code this adds and if that might be a maintenance burden.

I left a few comments where I think there are a couple minor edge case bugs. I'll need to experiment some more with this once you address the comments here.

@KelvinChung2000
Copy link
Copy Markdown
Author

KelvinChung2000 commented Mar 26, 2026

I am still working on some edge cases (particularly in groups and subcommands) and trying to simplify the code. If you still think it's too much, at least it is modular enough to be extracted and run as pip install cmd2[annotated] or similar.

@tleonhardt
Copy link
Copy Markdown
Member

I'm not yet decided on whether its too much or not. I do really like the capability it provides and it is starting to shape up to something that feels like it could have a good user and developer experience. I just know we've been bitten a couple times in the past where we accepted features we shouldn't have and they turned into maintenance headaches.

@KelvinChung2000 KelvinChung2000 force-pushed the feat/annotated-argparse branch 3 times, most recently from bcbaf18 to 73c241b Compare March 26, 2026 23:42
@KelvinChung2000 KelvinChung2000 marked this pull request as ready for review March 26, 2026 23:43
@KelvinChung2000 KelvinChung2000 force-pushed the feat/annotated-argparse branch from ecd5b98 to 8f0d70f Compare March 27, 2026 13:58
hints = {}

for name, param in sig.parameters.items():
if name == 'self':
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.

If a user follows a different naming convention for the method's first parameter than self (e.g., this or cls), it will be incorrectly treated as a command-line argument. It is safer to skip the first parameter by position, as cmd2 commands are intended to be methods.

Perhaps something like this?

parameters = list(sig.parameters.values())
if parameters:
    parameters.pop(0)

for param in parameters:
    name = param.name


if isinstance(action_type, type) and issubclass(action_type, enum.Enum):
return [CompletionItem(str(m.value), display_meta=m.name) for m in action_type]

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.

If I understand correctly, positional boolean arguments lack tab completion. While positional boolean args are pretty uncommon, positional arguments using _parse_bool (which is assigned by the decorator) don't have explicit choices, so the completer will only show a generic hint. Providing standard boolean strings as completion items would improve the UX.

Recommend adding something like this here:

# Check for boolean converter assigned by @with_annotated
if action_type.__name__ == '_parse_bool':
    return [CompletionItem(v) for v in ['true', 'false', 'yes', 'no', 'on', 'off', '1', '0']]

command_name = fn.__name__[len(constants.COMMAND_FUNC_PREFIX) :]

@functools.wraps(fn)
def cmd_wrapper(*args: Any, **_kwargs: Any) -> bool | None:
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.

The cmd_wrapper captures **_kwargs but they are never used or passed to the wrapped function fn. If with_annotated is used in a context where other decorators pass keyword arguments, they will be lost.

Recommend something like:

def cmd_wrapper(*args: Any, **kwargs: Any) -> bool | None:
...
func_kwargs.update(kwargs)
result: bool | None = fn(owner, **func_kwargs)
return result

@KelvinChung2000
Copy link
Copy Markdown
Author

I'm not sure why, but the current version on GitHub appears to be the one before I added group support and did the code cleanup. Luckly vscode have cache the code changes...

@tleonhardt
Copy link
Copy Markdown
Member

I'm not sure why, but the current version on GitHub appears to be the one before I added group support and did the code cleanup. Luckly vscode have cache the code changes...

I'm not sure what happened there. Sorry about that. Let me know when you've pushed other changes and I can look again.

If there are persistent issues, we can invite you as a Contributor so you can create a branch directly as long as you have 2-factor auth enabled in GitHub.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants