Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Argparse: certain action types dont accept metavar, but should? #109792

Closed
DanCardin opened this issue Sep 23, 2023 · 4 comments
Closed

Argparse: certain action types dont accept metavar, but should? #109792

DanCardin opened this issue Sep 23, 2023 · 4 comments
Labels
pending The issue will be closed if no feedback is provided stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@DanCardin
Copy link

DanCardin commented Sep 23, 2023

Feature or enhancement

Proposal:

Given some arbitrary argument with a dest value of foo.bar.baz (justification below), you will end up with helptext like FOO.BAR.BAZ. This is the explicit usecase for the use of metavar. You set metavar to the value you want it to show up as, and you're good.

Unfortunately, you aren't allowed to supply metavar in some scenarios. I haven't enumerated all of them yet, but, for example, if you set action='help', action='store_true', or action='store_false', you'll be routed through _HelpAction, _StoreTrueAction, or _StoreFalseAction, none of which accept a metavar argument. There may be more.

The superclasses of all three do accept metavar, but in usercode i can just subclass them like so:

class _HelpAction(argparse._HelpAction):
    def __init__(self, metavar=None, **kwargs):
        self.metavar = metavar
        super().__init__(**kwargs)


class _StoreTrueAction(argparse._StoreTrueAction):
    def __init__(self, metavar=None, **kwargs):
        self.metavar = metavar
        super().__init__(**kwargs)


class _StoreFalseAction(argparse._StoreFalseAction):
    def __init__(self, metavar=None, **kwargs):
        self.metavar = metavar
        super().__init__(**kwargs)

class ArgumentParser(argparse.ArgumentParser):
    def __init__(self, *args, **kwargs):
        self.register("action", "store_true", _StoreTrueAction)
        self.register("action", "store_false", _StoreFalseAction)

With no other changes, the rendered helptext names of the options properly accept and render the metavar value instead of the dest.


Some context/justification for why this is meaningful. I am currently using this snippet from the internet to, with relatively little effort, "properly" parse multiply nested subparsers in a way that maintains the nested relationship of the input args when they're written:

class Nestedspace(argparse.Namespace):
    def __setattr__(self, name, value):
        if "." in name:
            group, name = name.split(".", 1)
            ns = getattr(self, group, Nestedspace())
            setattr(ns, name, value)
            self.__dict__[group] = ns
        else:
            self.__dict__[name] = value

Which produces {'foo': {'bar': {'baz': 4}}} for some doubly nested subparser with a baz argument. Whereas it'd have just produced {'baz': 4} by "default".

This strategy requires setting the dest value to foo.bar.baz for the arg in question.

Having done that, you end up with [-k FOO.BAR.BAZ] in the help text, with no way to customize it.


Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

I dont know whether this is relevant to issues like #103678, where they're removing metavar from not-these-classes-but-boolean-action-option, which naively seems related. perhaps it's unrelated though.


if it were just a matter of making the in-repo equivalents to these changes (plus tests), i'd be willing to contribue them, but i figured it might be this was for a reason, and wanted to make sure it'd be accepted before submitting a PR

@DanCardin DanCardin added the type-feature A feature request or enhancement label Sep 23, 2023
@AlexWaygood AlexWaygood added the stdlib Python modules in the Lib dir label Sep 24, 2023
@serhiy-storchaka
Copy link
Member

metavar is used to format the help output and some error messages. Neither of the actions mention above has argument, they do not need metavar. How did you get [-k FOO.BAR.BAZ] in the help text for any of these actions?

@serhiy-storchaka serhiy-storchaka added the pending The issue will be closed if no feedback is provided label Sep 28, 2024
@DanCardin
Copy link
Author

I'm...actually not sure how, given my example i would have gotten -k <something>.

Fwiw, this is/was in the context of writing a CLI library wrapping argparse. My original reason for wanting this is probably less relevant, because I now replace the argparse --help text also. With that said, i do see some places where metavar is used in error messages.

Perhaps this isn't actually a relevant problem to real, working CLIs in practice (I know I reported this in response to a real-world case, but I can't point to what that case was now, unfortunately.

It is possible to craft CLIs (if not useful ones) that expose the argument name (which, given my motivating example in the OP, is problematic and would ideally be the metavar):

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('foo', action='store_true')
print(parser.parse_args())

It certainly is simpler as a lower level programmatic user of the library for all action types to accept the same set of base-level (those which directly inform the parser of how to act, like option_strings, dest, required, etc) arguments for all action types, regardless of whether or not they're "practical".


With that said, i also have my workaround at this point so it's not particularly burdensome to me, and it is admittedly a niche problem

@serhiy-storchaka
Copy link
Member

Actions like 'store_true' do not consume arguments. Using them with positional arguments is meaningless and will fail when try to parse arguments. See also #85935.

Closing this issue due to a lack of useful cases.

@DanCardin
Copy link
Author

Mostly, I was just pointing out a place where there seems to be extra effort taken to omit parameters that dont have an obvious use (but also there's no negative to specifying them).

Where _StoreTrueAction could (modulo "breaking" changes to a private type) literally just be _StoreTrueAction = functools.partial(_StoreConstAction, const=True) behavior-wise. Because _StoreConstAction which applies to all the same of your arguments, does accept metavar.

And I know that cappa (a cli library with optional argparse backend), at least was using them to internally track something, although it doesn't seem to now... And it does make it easier to programmatically interact with the argparse api, without special casing each action type. With that said, the usecase is relatively niche, and it's easy enough for me to subclass and ignore the intended argparse interface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending The issue will be closed if no feedback is provided stdlib Python modules in the Lib dir type-feature A feature request or enhancement
Projects
Status: Doc issues
Development

No branches or pull requests

3 participants