Python argparse: argumentos mutuamente excluyentes con argumento opcional y posicional

Me gustaría obtener esto con la biblioteca argparse:

PROG --yesterday | begin-date [end-date] 

Traté de combinar la exclusión mutua y los grupos de discusión, pero no tuve éxito.

Este progtwig solo debe aceptar que:

 PROG --yesterday PROG 2015-11-12 PROG 2015-11-12 2015-11-15 

¿Es posible hacer esto con argparse?


Gracias hpaulj . Ver el resultado final:

 import argparse from datetime import datetime import pytz def argument_date(str_date): try: return datetime.strptime(str_date, "%Y-%m-%d").replace(tzinfo=pytz.utc) except ValueError as e: raise argparse.ArgumentTypeError(e) parser = argparse.ArgumentParser(prog='PROG') parser.usage = """PROG [-h] [--yesterday | start [end]]""" parser.add_argument('start', type=argument_date, nargs='?', help='Start date (format YYYY-MM-DD)') parser.add_argument('end', type=argument_date, nargs='?', help='End date (format YYYY-MM-DD)') parser.add_argument('--yesterday', action='store_true', help='Only yesterday') args = parser.parse_args() if args.yesterday and args.start: raise parser.error("--yesterday option is not incompatible with start argument") if not args.yesterday and not args.start: raise parser.error("--yesterday option or start argument should be filled") if args.end and (args.start >= args.end): raise parser.error("end argument should be granter than start") 

Su mejor opción es probar los valores después del análisis y, si es necesario, proporcionar su propio usage personalizado.

Un grupo mutuamente exclusivo puede trabajar con una posición opcional, por ejemplo,

 group = parser.add_mutually_exclusive_group() group.add_argument('-y','--yesterday', action='store_true') group.add_argument('dates',nargs='?') 

Estaba pensando que funcionaría con nargs='*' , pero obtengo ValueError: mutually exclusive arguments must be optional error ValueError: mutually exclusive arguments must be optional .

Entonces, un valor posicional opcional funciona, pero no hay forma de usar esta prueba con 2 valores posicionales opcionales.

 parser.add_argument('--yesterday',action='store_true') parser.add_argument('start',nargs='?') parser.add_argument('end',nargs='?') 

Y luego prueba para args.yesterday , args.start is None y args.end is None . Si alguna combinación de estos es incorrecta, aumente parser.error('....') .

Siempre que pueda distinguir entre los valores predeterminados y los dados por el usuario, las pruebas después del análisis son tan buenas como cualquier otra cosa que podría forzar al analizador.

También es una buena idea pensar qué mensaje de uso tiene sentido para sus usuarios. p.ej

Por ejemplo:

 PROG [--yesterday | [start [end]]] 

No es algo que argparse pueda generar automáticamente.

--yesterday es redundante, ya que es solo un atajo para configurar start_date para el día de ayer. En su lugar, deje que “yesterday” sea un valor permitido para start_date . De hecho, puede generalizar datetime para permitir otras abreviaturas, para cualquier argumento, según se desee. Por ejemplo:

 def argument_date(str_date): # Not the most efficient to roundtrip like this, but # fits well with your existing code now = datetime.datetime.utcnow().date() if str_date == "yesterday": str_date = str(now - datetime.timedelta(1)) elif str_date == "today" str_date = str(now) try: return datetime.strptime(str_date, "%Y-%m-%d").replace(tzinfo=pytz.utc) except ValueError as e: raise argparse.ArgumentTypeError(e) 

Una vez que hayas hecho esto, tu código simplemente se convierte en:

 parser = argparse.ArgumentParser(prog='PROG') parser.add_argument('start', type=argument_date, help='Start date (YYYY-MM-DD, yesterday, today)') parser.add_argument('end', type=argument_date, nargs='?', help='End date (YYYY-MM-DD, yesterday, today)')