Skip to content

argparse: Argparse: Add subnamespace flag to _SubParsersAction.add_parser() to allow nested Namespaces #149461

@jb2170

Description

@jb2170

Feature or enhancement

Proposal:

Summary

We add the keyword-only flag subnamespace to _SubParsersAction.add_parser(), which when set to True tells the parent parser to store the subparser's parsed arguments contained in their own Namespace, nested within the parent's Namespace.

This allows hierarchical nested Namespaces to form when calling parser.parse_args(), which mirror the hierarchical nature of nested subparsers.

This also solves the problem of parsers and their subparsers having conflicting dest parameters which are subject to shadowing / conflict resolution. This has been a long standing issue for argparse #59633

By default subnamespace is False for backwards compatibility, making this feature opt-in.

Mini Example

Taken from the [implementation tests][tests]

import argparse

inet = argparse.ArgumentParser(add_help=False)
inet.add_argument("address")
inet.add_argument("port", type=int)
inet.add_argument("--use-proxy", action="store_true")

unix = argparse.ArgumentParser(add_help=False)
unix.add_argument("path")

parser = argparse.ArgumentParser(prog="my-socat")
parser.add_argument("--key-file")
action = parser.add_subparsers(required=True, dest="action")

parser_bind = action.add_parser("bind", subnamespace=True)
parser_bind.add_argument("--fork", action="store_true")
bind_family = parser_bind.add_subparsers(required=True, dest="family")

parser_bind_inet = bind_family.add_parser("inet", subnamespace=True, parents=[inet])
parser_bind_unix = bind_family.add_parser("unix", subnamespace=True, parents=[unix])

parser_connect = action.add_parser("connect", subnamespace=True)
connect_family = parser_connect.add_subparsers(required=True, dest="family")

parser_connect_inet = connect_family.add_parser("inet", subnamespace=True, parents=[inet])
parser_connect_unix = connect_family.add_parser("unix", subnamespace=True, parents=[unix])

args = parser.parse_args(["bind", "unix", "/foo/bar/socket"])

assert args == argparse.Namespace(
    key_file=None,
    action="bind",
    bind=argparse.Namespace(
        fork=False,
        family="unix",
        unix=argparse.Namespace(
            path="/foo/bar/socket"
        )
    )
)

assert args.bind.unix.path == "/foo/bar/socket"

Closes / supersedes #59633 #103639 #103640

Implementation PR incoming

Has this already been discussed elsewhere?

I have already discussed this feature proposal on Discourse

Links to previous discussion of this feature:

Full discussion thread and rationale: https://discuss.python.org/t/107202

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    Status

    Doc issues

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions