Python argparse: Make at least one argument required

前端 未结 11 1595
谎友^
谎友^ 2020-12-13 01:39

I\'ve been using argparse for a Python program that can -process, -upload or both:

parser = argparse.ArgumentParser(de         


        
相关标签:
11条回答
  • 2020-12-13 01:47

    If not the 'or both' part (I have initially missed this) you could use something like this:

    parser = argparse.ArgumentParser(description='Log archiver arguments.')
    parser.add_argument('--process', action='store_const', const='process', dest='mode')
    parser.add_argument('--upload',  action='store_const', const='upload', dest='mode')
    args = parser.parse_args()
    if not args.mode:
        parser.error("One of --process or --upload must be given")
    

    Though, probably it would be a better idea to use subcommands instead.

    0 讨论(0)
  • 2020-12-13 01:47

    Requirements Review

    • use argparse (I will ignore this one)
    • allow one or two actions to be called (at least one required).
    • try to by Pythonic (I would rather call it "POSIX"-like)

    There are also some implicit requirements when living on command line:

    • explain the usage to the user in a way which is easy to understand
    • options shall be optional
    • allow specifying flags and options
    • allow combining with other parameters (like file name or names).

    Sample solution using docopt (file managelog.py):

    """Manage logfiles
    Usage:
        managelog.py [options] process -- <logfile>...
        managelog.py [options] upload -- <logfile>...
        managelog.py [options] process upload -- <logfile>...
        managelog.py -h
    
    Options:
        -V, --verbose      Be verbose
        -U, --user <user>  Username
        -P, --pswd <pswd>  Password
    
    Manage log file by processing and/or uploading it.
    If upload requires authentication, you shall specify <user> and <password>
    """
    if __name__ == "__main__":
        from docopt import docopt
        args = docopt(__doc__)
        print args
    

    Try to run it:

    $ python managelog.py
    Usage:
        managelog.py [options] process -- <logfile>...
        managelog.py [options] upload -- <logfile>...
        managelog.py [options] process upload -- <logfile>...
        managelog.py -h
    

    Show the help:

    $ python managelog.py -h
    Manage logfiles
    Usage:
        managelog.py [options] process -- <logfile>...
        managelog.py [options] upload -- <logfile>...
        managelog.py [options] process upload -- <logfile>...
        managelog.py -h
    
    Options:
        -V, --verbose      Be verbose
        -U, --user <user>  Username
        -P, --pswd <pswd>  P    managelog.py [options] upload -- <logfile>...
    
    Manage log file by processing and/or uploading it.
    If upload requires authentication, you shall specify <user> and <password>
    

    And use it:

    $ python managelog.py -V -U user -P secret upload -- alfa.log beta.log
    {'--': True,
     '--pswd': 'secret',
     '--user': 'user',
     '--verbose': True,
     '-h': False,
     '<logfile>': ['alfa.log', 'beta.log'],
     'process': False,
     'upload': True}
    

    Short alternative short.py

    There can be even shorter variant:

    """Manage logfiles
    Usage:
        short.py [options] (process|upload)... -- <logfile>...
        short.py -h
    
    Options:
        -V, --verbose      Be verbose
        -U, --user <user>  Username
        -P, --pswd <pswd>  Password
    
    Manage log file by processing and/or uploading it.
    If upload requires authentication, you shall specify <user> and <password>
    """
    if __name__ == "__main__":
        from docopt import docopt
        args = docopt(__doc__)
        print args
    

    Usage looks like this:

    $ python short.py -V process upload  -- alfa.log beta.log
    {'--': True,
     '--pswd': None,
     '--user': None,
     '--verbose': True,
     '-h': False,
     '<logfile>': ['alfa.log', 'beta.log'],
     'process': 1,
     'upload': 1}
    

    Note, that instead of boolean values for "process" and "upload" keys there are counters.

    It turns out, we cannot prevent duplication of these words:

    $ python short.py -V process process upload  -- alfa.log beta.log
    {'--': True,
     '--pswd': None,
     '--user': None,
     '--verbose': True,
     '-h': False,
     '<logfile>': ['alfa.log', 'beta.log'],
     'process': 2,
     'upload': 1}
    

    Conclusions

    Designing good command line interface can be challenging sometime.

    There are multiple aspects of command line based program:

    • good design of command line
    • selecting/using proper parser

    argparse offers a lot, but restricts possible scenarios and can become very complex.

    With docopt things go much shorter while preserving readability and offering high degree of flexibility. If you manage getting parsed arguments from dictionary and do some of conversions (to integer, opening files..) manually (or by other library called schema), you may find docopt good fit for command line parsing.

    0 讨论(0)
  • 2020-12-13 01:48

    Maybe use sub-parsers?

    import argparse
    
    parser = argparse.ArgumentParser(description='Log archiver arguments.')
    subparsers = parser.add_subparsers(dest='subparser_name', help='sub-command help')
    parser_process = subparsers.add_parser('process', help='Process logs')
    parser_upload = subparsers.add_parser('upload', help='Upload logs')
    args = parser.parse_args()
    
    print("Subparser: ", args.subparser_name)
    

    Now --help shows:

    $ python /tmp/aaa.py --help
    usage: aaa.py [-h] {process,upload} ...
    
    Log archiver arguments.
    
    positional arguments:
      {process,upload}  sub-command help
        process         Process logs
        upload          Upload logs
    
    optional arguments:
      -h, --help        show this help message and exit
    $ python /tmp/aaa.py
    usage: aaa.py [-h] {process,upload} ...
    aaa.py: error: too few arguments
    $ python3 /tmp/aaa.py upload
    Subparser:  upload
    

    You can add additional options to these sub-parsers as well. Also instead of using that dest='subparser_name' you can also bind functions to be directly called on given sub-command (see docs).

    0 讨论(0)
  • 2020-12-13 01:49
    if not (args.process or args.upload):
        parser.error('No action requested, add -process or -upload')
    
    0 讨论(0)
  • 2020-12-13 01:49

    Use append_const to a list of actions and then check that the list is populated:

    parser.add_argument('-process', dest=actions, const="process", action='append_const')
    parser.add_argument('-upload',  dest=actions, const="upload", action='append_const')
    
    args = parser.parse_args()
    
    if(args.actions == None):
        parser.error('Error: No actions requested')
    

    You can even specify the methods directly within the constants.

    def upload:
        ...
    
    parser.add_argument('-upload',  dest=actions, const=upload, action='append_const')
    args = parser.parse_args()
    
    if(args.actions == None):
        parser.error('Error: No actions requested')
    
    else:
        for action in args.actions:
            action()
    
    0 讨论(0)
  • 2020-12-13 01:58

    For http://bugs.python.org/issue11588 I am exploring ways of generalizing the mutually_exclusive_group concept to handle cases like this.

    With this development argparse.py, https://github.com/hpaulj/argparse_issues/blob/nested/argparse.py I am able to write:

    parser = argparse.ArgumentParser(prog='PROG', 
        description='Log archiver arguments.')
    group = parser.add_usage_group(kind='any', required=True,
        title='possible actions (at least one is required)')
    group.add_argument('-p', '--process', action='store_true')
    group.add_argument('-u', '--upload',  action='store_true')
    args = parser.parse_args()
    print(args)
    

    which produces the following help:

    usage: PROG [-h] (-p | -u)
    
    Log archiver arguments.
    
    optional arguments:
      -h, --help     show this help message and exit
    
    possible actions (at least one is required):
      -p, --process
      -u, --upload
    

    This accepts inputs like '-u', '-up', '--proc --up' etc.

    It ends up running a test similar to https://stackoverflow.com/a/6723066/901925, though the error message needs to be clearer:

    usage: PROG [-h] (-p | -u)
    PROG: error: some of the arguments process upload is required
    

    I wonder:

    • are the parameters kind='any', required=True clear enough (accept any of the group; at least one is required)?

    • is usage (-p | -u) clear? A required mutually_exclusive_group produces the same thing. Is there some alternative notation?

    • is using a group like this more intuitive than phihag's simple test?

    0 讨论(0)
提交回复
热议问题