0

I need to write a django management command (not related to django) for starting workers. I take the inspiration from docker-compose up and would like the following behavior:

>>> services = ['a', 'b', 'c']
>>> import argparse
>>> ap = argparse.ArgumentParser()
>>> ap.add_argument('services', ???)
>>> ap.parse_args(''.split())
Namespace(services=[])
>>> ap.parse_args('a b'.split())
Namespace(services=['a', 'b'])
>>> ap.parse_args('a b d'.split())
: error: argument services: invalid choice: 'd' (choose from 'a', 'b', 'c')
>>> ap.parse_args('a b b'.split())
: error: argument services: duplicated choice: 'b'

Currently, I have tested several approaches, but the main roadblock is that if choices=services is supplied, nargs='*' doesn't allow 0 arguments anymore.

>>> ap.add_argument('services', choices=services, nargs='*')
>>> ap.parse_args(''.split())
usage: [-h] [{a,b,c} [{a,b,c} ...]]
: error: argument services: invalid choice: [] (choose from 'a', 'b', 'c')

While there is one working solution (having a custom type) and one workaround (just validate later) they don't feel right. (One other solution is posted as answer, but I'd love to see better solutions if it exists at all.)

(It looks like the "no duplicate" feature isn't native in Python and would need overriding argparse.Action, probably starting here.)

Edit: I have given up on insisting that it must work like the spec above. The workaround is much nicer. I simply do ap.add_argument('--a', dest='services', action='append_const', const=AaaService), and my namespace has a services attr that contains all the service classes I want to run.

T Tse
  • 786
  • 7
  • 19
  • Post parsing checking will be simplest, since you both want to handle none, and duplicates. You don't get extra points for trying to do this within `argparse`. – hpaulj Sep 27 '19 at 01:03

2 Answers2

1

This is a known issue, the result of some special handling of the default for a '*' positional.

Python argparse: type inconsistencies when combining 'choices', 'nargs' and 'default'

https://bugs.python.org/issue9625

https://bugs.python.org/issue27227

There's nothing wrong with doing some your own value validation after parsing. A general purpose parsing tool can't handle every case that users throw at it!

hpaulj
  • 221,503
  • 14
  • 230
  • 353
0

One other solution is to add [], the literal empty list, into choices.

>>> ap.add_argument('services', choices=services + [[]], nargs='*')
>>> ap.parse_args(''.split())
Namespace(services=[])

This is due to nargs producing an empty list and choices validating against the result. The choices kwarg becomes ['a', 'b', 'c', []], which looks weird, but that is what it takes for the two options to work together.

T Tse
  • 786
  • 7
  • 19