classification
Title: argparse: capturing actions
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: monkeyman79, paul.j3, r.david.murray, rhettinger, v+python
Priority: normal Keywords: patch

Created on 2021-01-27 23:52 by monkeyman79, last changed 2021-01-28 20:32 by v+python.

Pull Requests
URL Status Linked Edit
PR 24357 open monkeyman79, 2021-01-28 00:19
Messages (8)
msg385824 - (view) Author: Tadek Kijkowski (monkeyman79) * Date: 2021-01-27 23:52
This is spinoff from issue42973.

The purpose of this issue is to provide set of additional action classes for argparse, that could capture mutual relationship between command line arguments for more advanced parsers, where order of command line arguments is meaningful. It is hoped that this will work together with enhancement introduced in issue42973, providing also ability to capture relationship between options and positional parameters.

There will be four new action classes: 'extend_capture', 'append_capture', 'store_capture' and 'capture'.

Here are excerpts from documentation being prepared for the new action classes and an example of use:

* 'extend_capture' - Captures preceding argument values listed in the
  'capture' and 'capture_reset' keyword arguments and creates a dictionary object
  from those values, then adds actual command-line value to the dictionary using
  the 'key' keyword argument, finally the created dictionary is appended to the
  'dest' list. After capturing, all arguments listed in 'capture_reset' are reset
  to their default values. If there are more than one command-line values, a new
  dictionary object is created, and appended to the list for each value, with
  'capture_reset' arguments reset to their default values after first value is
  added.
  Example usage:

    >>> parser = argparse.ArgumentParser()
    >>> parser.add_argument("--load-addr", type=lambda s: int(s, 16))
    >>> parser.add_argument("--exec-addr", type=lambda s: int(s, 16))
    >>> parser.add_argument("--replace", action="store_true")
    >>> parser.add_argument("--file", nargs="*", action="extend_capture",
    ...     capture="*", capture_reset=["load_addr", "exec_addr"])
    >>> parser.parse_args("--replace --load-addr 1900 --exec-addr 8023 "
    ...     "--file CALC !BOOT".split())
    Namespace(load_addr=None, exec_addr=None, replace=True, file=[
        {'replace': True, 'exec_addr': 32803, 'load_addr': 6400, 'file': 'CALC'},
        {'replace': True, 'exec_addr': None, 'load_addr': None, 'file': '!BOOT'}])

capture

The list of attributes to capture. This can be either a single attribute name,
a list (or other iterable type) of names or special value '*'. Name of
attribute associated with each argument is determined by the dest keyword
passed to add_argument method when the argument was
created. If capture_ is '*', all attributes are captured, except for this
argument's own value.

This keyword argument is valid only for 'extend_capture',
'append_capture', 'store_capture' and 'capture' actions.


capture_reset

The list of attributes to capture and reset to default value. As with capture,
this can be '*' to capture and reset all attributes except for this
argument's own value.

This keyword argument is valid only for 'extend_capture',
'append_capture', 'store_capture' and 'capture' actions.


key

The key to use for adding this argument's own command-line value to dictionary
of captured values. If this keyword argument is not specified, the dest is
used.

This keyword argument is valid only for 'extend_capture',
'append_capture' and 'store_capture' actions.
msg385826 - (view) Author: Glenn Linderman (v+python) * Date: 2021-01-28 00:34
I'm a little confused by the mention of the "key" keyword argument. I suspect that is an internal concept to argparse, possibly passed that way to internal methods, but on the add_argument interface, it doesn't exist... instead there is "name or flags" positional arguments, from which, together with the "dest" argument, the "key" keyword argument is derived.  This is described under the explanation for the "dest" parameter.

But in the user documentation, the use of "key" is a surprise (to me, maybe I missed something).

Perhaps using "dest" rather than "key" in this documentation would be better? Or perhaps there is a precedent in the argparse documentation for what to call it?
msg385832 - (view) Author: Tadek Kijkowski (monkeyman79) * Date: 2021-01-28 02:41
> I'm a little confused by the mention of the "key" keyword argument. I suspect that is an internal concept to argparse, possibly passed that way to internal methods, but on the add_argument interface, it doesn't exist... instead there is "name or flags" positional arguments, from which, together with the "dest" argument, the "key" keyword argument is derived.  This is described under the explanation for the "dest" parameter.

Hmm... that may be confusing. The "key" keyword argument is not internal concept, is new parameter that can be passed to add_argument, specifically for 'capture' actions. The "key" name may be unfortunate, maybe it could be "dest_key" or "dict_key"? It can't be "dest", because it is goes to add_argument together with "dest". In most cases it is not needed and "dest" is used as dictionary key, but user may want to override that.
Like in this example:

    >>> parser = argparse.ArgumentParser()
    >>> parser.add_argument("--user", default=None)
    >>> parser.add_argument("--server", default="localhost")
    >>> parser.add_argument("src1", action="store_capture", key="file", capture_reset=["user", "server"])
    >>> parser.add_argument("src2", action="store_capture", key="file", capture_reset=["user", "server"])
    >>> parser.add_argument("dst", action="store_capture", key="file", capture_reset=["user", "server"])
    >>> parser.parse_args("first --user guest --server no_such second --server not_found third".split())
    Namespace(user=None, server='localhost',
        src1={'user': None, 'server': 'localhost', 'file': 'first'},
        src2={'user': 'guest', 'server': 'no_such', 'file': 'second'},
        dst={'user': None, 'server': 'not_found', 'file': 'third'})

The 'dest' is 'src1', 'src2' and 'dst' respectively, but we want to have unified layout of the dictionaries,
so 'key' is 'file' in all three cases.

Oh, and I forgot to update add_argument signature in rst earlier.

ArgumentParser.add_argument(name or flags..., [action], [nargs], \
                           [const], [default], [type], [choices], [required], \
                           [help], [metavar], [dest], [capture], \
                           [capture_reset], [key])
...
...
   * dest - The name of the attribute to be added to the object returned by
     parse_args.

-> these are new:

   * capture - A name or a list of names of attributes to capture by one of
     capture actions.

   * capture_reset - A name or a list of name of attributes to capture and
     reset to default value by one of capture actions.

   * key - The key to use putting command-line argument to dictionary object
     created by capture actions.
msg385835 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-01-28 05:10
> capture mutual relationship between command line arguments for more
> advanced parsers, where order of command line arguments is meaningful

I don't think this is a pattern we should encourage.  It doesn't seem to arise often and it makes the API much more complex for end users.

Also, I'm concerned that adding more action classes will make argparse harder to learn for the average user.  Already, the module suffers from sprawl and has unexpected interactions between the components.  The docs and tutorial are voluminous and hard to digest in a single sitting.

ISTM the scope of argparse was never intended to capture all possible patterns for command line argument parsing.  Instead, it aimed at to address the common cases.
msg385836 - (view) Author: Glenn Linderman (v+python) * Date: 2021-01-28 05:18
So the missing signature is why I didn't understand, probably. At least, it seems reasonable to blame that :)  You didn't include [version] in the signature, but that isn't your fault: it isn't in the original and should be (see action "version").

So key is optional and defaults to dest... I saw that, but was already confused.  This latest example clears up why you might want to overrride dest for use in different arguments... very similar to why dest is allowed to be specified instead of its default value.

And dest could be used, it is sufficient, but allowing specification of key to override it is more flexible, and could save user code in some circumstances, with little cost to the implementation.

Sounds good to me now.

I was also surprised by introduction of a "capture" action by itself, and look forward to documentation for it, as things progress. I can guess about store_capture and append_capture from the definition of extend_capture.
msg385837 - (view) Author: Glenn Linderman (v+python) * Date: 2021-01-28 05:24
Raymond said:
ISTM the scope of argparse was never intended to capture all possible patterns for command line argument parsing.  Instead, it aimed at to address the common cases.

I say:
Sounds like another wet blanket argpment. Refer to the section "Why aren't getopt and optparse enough?" in the argparse PEP 389.  It is clearn that argparse was intended to implement more complex cases than could be easily implemented with the prior getopt and optparse. The concepts of variable numbers of arguments for an option, and of subcommonds, introduced initially in argparse, are far more complex than the enhancements proposed here.
msg385843 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2021-01-28 06:32
> Sounds like another wet blanket argpment

Please watch your tone.  It borders on being abusive.

It is entirely appropriate to set limits on the scope of what a module is trying to achieve.  It is appropriate to consider module learnability.  It is appropriate to limit sprawl and feature creep.  It is appropriate to consider whether the pattern being encouraged and supported by the PR is intellectually manageable by end users.

It is correct that argparse set out to handle more complex cases than optparse.  But even then, it was aiming to cases that commonly arose it the wild (subparsers are not uncommon).  It never aspired to be all things to all users.
msg385882 - (view) Author: Glenn Linderman (v+python) * Date: 2021-01-28 20:32
Raymond Hettinger <raymond.hettinger@gmail.com> added the comment:

>> Sounds like another wet blanket argpment

> Please watch your tone.  It borders on being abusive.

I considered that as a canonical description of the type of negativity presented by your comment. It was not intended to be abusive, just a descriptive summary.


> It is entirely appropriate to set limits on the scope of what a module is trying to achieve.  It is appropriate to consider module learnability.  It is appropriate to limit sprawl and feature creep.  It is appropriate to consider whether the pattern being encouraged and supported by the PR is intellectually manageable by end users.


I agree that all those considerations are worthwhile, but I see this as a small extension that adds significant power. No one is forced to use the new features, or the old features, or argparse at all. There are already features in argparse that I simply ignore, because I have no need for them. It doesn't make using the feature I do use more difficult. I just look for what I need, and use those features.


> It is correct that argparse set out to handle more complex cases than optparse.  But even then, it was aiming to cases that commonly arose it the wild (subparsers are not uncommon).  It never aspired to be all things to all users.

And unfortunately, in so doing, it failed to support some cases that optparse could handle, although for the cases that argparse could handle, the user code to use it was far simpler.  This was improved somewhat by the addition of parse_intermixed_args.

Since argparse is provided in stdlib, it seems very reasonable to me to enhance it to support command line parsing paradigms used effectively since 1979, at least (ref: tar).
History
Date User Action Args
2021-01-28 20:32:47v+pythonsetmessages: + msg385882
2021-01-28 06:32:05rhettingersetmessages: + msg385843
2021-01-28 05:24:46v+pythonsetmessages: + msg385837
2021-01-28 05:18:25v+pythonsetmessages: + msg385836
2021-01-28 05:10:26rhettingersetmessages: + msg385835
2021-01-28 02:41:12monkeyman79setmessages: + msg385832
2021-01-28 00:34:17v+pythonsetmessages: + msg385826
2021-01-28 00:19:16monkeyman79setkeywords: + patch
stage: patch review
pull_requests: + pull_request23181
2021-01-27 23:53:56monkeyman79setnosy: + rhettinger, v+python, r.david.murray, paul.j3
2021-01-27 23:52:12monkeyman79create