AlanK
AlanK

Reputation: 9833

argparse - Different mandatory / available parameters per action

I'm looking to create an argument parser with the following structure:

options [ 'backup', 'consistency_check', 'backup_and_consistency_check']

--database [ required ]
--action [ required choice from list options ]
  --where_to_backup_to [ only required if --action is 'backup' ]
  --what_file_to_consistency_check [ only required if --action is 'consistency_check']
--clean [ optional ]
  --force [ optional if --clean is also in arguments ]

How can I implement optional arguments using the ArgumentParser module, depending on the choice which was made as another command line argument.

I'm looking to make the ArgParse fail if for example the command line arguments are

--d database_name --a backup --what_file_to_consistency_check /var/tmp/file.bak

This is what I've gotten so far (I know it's very little but I don't want to go in the complete wrong direction with subparsers if I haven't gotten it right from the start)

actions = ['backup', 'consistency_check', 'backup_and_consistency_check']

def create_parser():
    parser = ArgumentParser(description='Parser for Backup / Consistency Check')

    parser.add_argument('--database', '-d', dest='db', help='Database name', choices=get_active_database_list())

    parser.add_argument('--action', '-a', dest='action', help='Action option', choices=actions)
    # --where_to_backup_to [ only if action = backup ]
    # --what_file_to_consistency_check [ only if action = cc ]
    parser.add_argument('--clean', '-c', dest='clean', help='Clean')
    # --force [ only available if --clean is also in arguments ]

    return parser

Upvotes: 1

Views: 607

Answers (3)

hpaulj
hpaulj

Reputation: 231375

If subparsers seem too complex at the moment, I think you can still get a useful parser without them:

def create_parser():
    parser = ArgumentParser(description='Parser for Backup / Consistency Check')

    parser.add_argument('--database', '-d', dest='db', help='Database name', choices=get_active_database_list())

    parser.add_argument('--action', '-a', help='Action option', choices=actions)
    parser.add_argument('target', help='target for backup or check')
    parser.add_argument('--clean', '-c', help='Clean') # default dest is 'clean'
    parser.add_argument('--force', help='force clean')
    return parser

If database is required, you might want to add a required=True parameter to it. Or make it a positional. Otherwise consider what you will do if the user does not supply it. I.e. if args.db is None? Is there a default database that you can use?

It looks like all the action choices require a file or dir argument - the target for backup or checking. Does it matter whether the user calls it '--where_to_backup_to' or '--what_file_to_consistency_check'? By using a positional here I'm requiring them to give some sort of name, but it's up to you to interpret it according to the 'action'.

It looks like force is just a stronger version of clean. What do you think the user wants if they specify --force but not --clean? Here I accept both and let your code choose which makes most sense.

My philosophy is that the primary goal of a parser is figuring out what the user wants. Error checking is most useful when it prevents ambiguous input. But it shouldn't be picky. A simple parser design is usually better than an overly complex one.

Upvotes: 1

Charles Duffy
Charles Duffy

Reputation: 295363

The conventional way to do this would look more like the following:

def create_parser():
    parser = ArgumentParser(description='Parser for Backup / Consistency Check')

    parser.add_argument('--database', '-d', dest='db', help='Database name', choices=get_active_database_list())
    parser.add_argument('--timeout', '-t', dest='timeout', help='Timeout limit (in minutes)')

    subparsers = parser.add_subparsers()

    parser_backup = subparsers.add_parser('backup', help='Run a backup')
    parser_backup.set_defaults(action='backup') # or even pass the backup function itself, vs a string
    parser_backup.add_argument('dest', help='Where to backup to') # where to backup to

    parser_check = subparsers.add_parser('consistency_check', help='Run a consistency check')
    parser_check.set_defaults(action='consistency_check')
    parser_check.add_argument('source', help='What file to check for consistency')

    return parser

...with usage as:

# here, action='backup' and dest='/path/to/dest'
yourtool -d db -t 15 backup /path/to/dest

...or...

# here, action='consistency_check' and source='/path/to/content/to/check'
yourtool -d db -t 15 consistency_check /path/to/content/to/check

Upvotes: 1

Alexander Davydov
Alexander Davydov

Reputation: 395

I think making action a positional parameter with dynamic option parser is a good option:

if __name__ == "__main__":

    action = sys.argv[1]

    parser = create_parser(action)
    args = parser.parse_args()

Upvotes: 1

Related Questions