Add objects for argparse str options

A lot of common argparse usage is hard to grok due to the need to remember string literals.

Enumerated options could be added to the library to cover normal usage (still allowing strings due to the option to user define options)

e.g.

from argparse import ArgumentParser, ACTION_STORE_TRUE

parser = ArgumentParser()
parser.add_argument("-b", "--my-bool", action=ACTION_STORE_TRUE)

Hi George,

Thank you for this idea.

I am not sure what is the best path forward here, but from my experience this can be improved.

As far as I can remember I had argparse abstracted so that my object has methods: add_join, add_true, etc… But still need to use argparse as it is in independent scripts.

Quick question. Are you simply suggesting? Or do you wish to implement it if this is something that is deemed to be needed?

I would be happy to put in a PR if it is deemed valuable.

I want to know whether I am missing the reason why something like this doesn’t exist (as far as I can tell it’s because the user can add custom actions so an enum may be deemed too clunky)

I can see why you wouldn’t want to include enum.Enum, but you could have a base ArgumentAction(object) that is inherited by module level classes such as ActionStoreTrue and have the action parameter be typed ArgumentAction | str or similar.

A lot of it is likely to be simply because argparse predates the enum module by many years, and the “churn” involved in switching to an enum-based API would be significant. And backward compatibility would mean we’d have to maintain both possibilities for an extended period (possibly indefinitely, as there’s little reason to justify expecting all the users with currently working argparse-based code to change).

It’s possible that using a StrEnum would be possible - I believe it’s designed to replace literal strings in a compatible manner, but there would still be an impact. One major impact which people often fail to consider is the amount of training material that would need to be rewritten to describe the new approach - and especially the complexity that would be introduced for material that wanted to support older versions of Python while still teaching the “correct” approach for new code.

Basically, I suspect the answer is that no-one has felt that it’s worth their time to take on something like this. But if you’re interested in doing so, I hope the above has given you something to consider.

This is exactly how it is implemented at the moment.

And then those classes are registered to dict[string, ActionBase] for string lookup.

Complicating things with multiple types to define action is probably not a good idea. I don’t think making any changes to argparse without a very good reason is a good idea to be honest.

The only think that I would not be against is simply defining module level string constants. So that they can be used as you indicated in your initial post.

But personally, I would not make use of them as I like my code terse and those would almost surely be longer than string names.

But this is my opinion, maybe others think differently.

If you want a good API, why not use Typer?

Your example would just be:

cli = typer.Typer()

@cli.command()
def main(my_bool: Annotated[bool, typer.Option("--my-bool", "-b")]):
    ...

I’m not entirely sure why this registry exists in the first place, other than to hide class names from the end user. The documentation only alludes to using the name of a custom Action subclass as the action argument, without mentioning how you would add your own classes to the registry.

Nothing (except aversion to using module-private names), though, stops you from writing things like

from argparse import ArgumentParser, _StoreTrueAction 
parser.add_argument("-b", "--my-bool", action=_StoreTrueAction)

Predefined constants wouldn’t be any easier to remember than the existing strings; they would help address the problem of trying to use invalid strings as arguments.

If you had

class Actions(enum.StrEnum):
    store_true = enum.auto()
    ...

and typeshed was updated to have _ActionStr: TypeAlias = Union[str, argparse.Actions]

Your IDE would be able to help you set action without having to lookup the correct literals.

Note that that could be done today using a Literal["store_true", ...], but is deliberately avoided in typeshed (see the issue for rationale).

1 Like