0

I am using argparse library to parse arguments to my python script. This is my code:

 parser = argparse.ArgumentParser(
        prog="confgit",
        description="Git overhead for version control of your config files",
        formatter_class=argparse.RawTextHelpFormatter, )

    parser.add_argument(
        "-c", "--config",
        type=str,
        default=DEFAULT_CONFIG_PATH,
        dest="CONFIG_PATH",
        help="load alternative config")

    subparsers = parser.add_subparsers(help="Commands:")

    subparsers.add_parser("include", help="Include file or directory in to repository").add_argument(
        "file_to_include",
        type=str,
        action="store",
        nargs="?",
        const="",
        default=False,
        help="include file or directory in to repository")
    subparsers.add_parser("exclude", help="Exclude file or directory in to repository").add_argument(
        "exclude",
        type=str,
        action="store",
        help="exclude file or directory from repository")

    print(parser.parse_args())

I would like to be able to store parameters not matching any subparser as a string. For example running myprogram include test.txt --config .config/cfg.txt will result in:

Namespace(CONFIG_PATH='.config/cfg.txt', file_to_include='test.txt')

and running myprogram some text here will result in:

Namespace(CONFIG_PATH='.config/default.txt', input="some other text")

How can I achieve this ?
Thank you for help

1
  • Usually we assign the add_subparsers result to a variable, and then use that with the following add_argument. Your chaining works for one argument, but is confusing to this old argparse user. Commented Dec 3, 2020 at 3:38

2 Answers 2

1

The helps from your code:

1940:~/mypy$ python3 stack65119253.py -h
usage: confgit [-h] [-c CONFIG_PATH] {include,exclude} ...

Git overhead for version control of your config files

positional arguments:
  {include,exclude}     Commands:
    include             Include file or directory in to repository
    exclude             Exclude file or directory in to repository

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_PATH, --config CONFIG_PATH
                        load alternative config

So you can provide a optional '-c' with value.

The subparsers argument is a positional with 2 choices. It isn't required, but if you do provide a string it will be tested against those strings.

1941:~/mypy$ python3 stack65119253.py include -h
usage: confgit include [-h] [file_to_include]

positional arguments:
  file_to_include  include file or directory in to repository

optional arguments:
  -h, --help       show this help message and exit
1941:~/mypy$ python3 stack65119253.py exclude -h
usage: confgit exclude [-h] exclude

positional arguments:
  exclude     exclude file or directory from repository

optional arguments:
  -h, --help  show this help message and exit

For example:

1946:~/mypy$ python3 stack65119253.py -c foobar
Namespace(CONFIG_PATH='foobar')

1946:~/mypy$ python3 stack65119253.py -c foobar test
usage: confgit [-h] [-c CONFIG_PATH] {include,exclude} ...
confgit: error: invalid choice: 'test' (choose from 'include', 'exclude')

1947:~/mypy$ python3 stack65119253.py -c foobar include
Namespace(CONFIG_PATH='foobar', file_to_include=False)

argparse assigns strings to positionals by position. It does not assign by value. That is, it does not test for some value, and based on that decide whether it qualifies. The choices testing comes after assignment. Use optionals if you want to assign by value.

parser.add_argument('--include', nargs='?')
parser.add_argument('--exclude')

parse+known_args is a way of dealing with unrecognized arguments, but it doesn't get around the invalid choices error.

Sign up to request clarification or add additional context in comments.

2 Comments

I can partially work around it by having the nargs positional come before, but for some reason the last argument gets tested against the subparser command instead of slurped by the nargs. I suppose that is only logical because then it wouldn't be possible to have an nargs positional before a subcommand and have the subcommand work.
When you have 2 positionals in a row, one with ? and the other 'plain', it reserves one string the the 2nd. The subparsers positional is a bit of an odd ball due to glitch in past patches, and behaves as a required positional in this context, but not-required in the larger picture. As a general rule, positionals before subparsers should not have a variable nargs.
1

If the user doesn't quote the string "some other text" you will simply have to treat it as 3 different arguments ["some", "other", "text"]. But to handle it as closely as what you seem to desire you simply need to use the nargs option on an argument called input. The argparse page has an example at the very top with the "number accumulator".

    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')

Use * for 0 or more arguments instead of + and replace integers with whatever argument name you want.


Upon further investigation what I wrote above won't work when you have subparsers. I would suggest making your include and exclude subcommands into options. Wouldn't it be sensible to want to do both anyway? In your current configuration you could only either include or exclude.

1 Comment

In my attempt I haven't been able to get it to work, as it is telling me that one of the subparser arguments are required, though in python 3.7 they introduced optional subparsers, so I'm not entirely sure why it isn't working. The basic idea should work though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.