Cómo manejar los subcomandos CLI con argparse

Necesito implementar una interfaz de línea de comandos en la que el progtwig acepte subcomandos.

Por ejemplo, si el progtwig se llama “foo”, el CLI se vería como

foo cmd1  foo cmd2 foo cmd3  

cmd1 y cmd3 deben usarse con al menos una de sus opciones y los tres argumentos de cmd* son siempre exclusivos.

Estoy tratando de usar subparsers en argparse, pero no tengo éxito por el momento. El problema es con cmd2 , que no tiene argumentos:

Si bash agregar la entrada del subparser sin argumentos, el espacio de nombres devuelto por parse_args no contendrá ninguna información que me parse_args que esta opción fue seleccionada (consulte el ejemplo a continuación). si bash agregar cmd2 como argumento al parser (no al subparser), argparse esperará que el argumento de cmd2 sea ​​seguido por cualquiera de los argumentos de subparsers.

¿Hay una manera sencilla de lograr esto con argparse ? El caso de uso debe ser bastante común …

Aquí sigue lo que he intentado hasta ahora que está más cerca de lo que necesito:

 parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(help='Functions') parser_1 = subparsers.add_parser('cmd1', help='...') parser_1.add_argument('cmd1_option1', type=str, help='...') parser_2 = subparsers.add_parser(cmd2, help='...') parser_3 = subparsers.add_parser('cmd3', help='...') parser_3.add_argument('cmd3_options', type=int, help='...') args = parser.parse_args() 

En primer lugar, los subparsers nunca se insertan en el espacio de nombres. En el ejemplo que publicaste si intentas ejecutar el script como:

 $python3 test_args.py cmd1 1 Namespace(cmd1_option1='1') 

donde test_args.py contiene el código que proporcionó (con la import argparse al principio e print(args) al final).

Tenga en cuenta que no hay ninguna mención a cmd1 solo a su argumento. Esto es por diseño .

Como se señaló en los comentarios, puede agregar esa información que pasa el argumento dest a la llamada add_subparsers .

La forma habitual de manejar estas circunstancias es usar el método set_defaults de los subparsers:

 import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(help='Functions') parser_1 = subparsers.add_parser('cmd1', help='...') parser_1.add_argument('cmd1_option1', type=str, help='...') parser_1.set_defaults(parser1=True) parser_2 = subparsers.add_parser('cmd2', help='...') parser_2.set_defaults(parser2=True) parser_3 = subparsers.add_parser('cmd3', help='...') parser_3.add_argument('cmd3_options', type=int, help='...') parser_3.set_defaults(parser_3=True) args = parser.parse_args() print(args) 

Lo que resulta en:

 $python3 test_args.py cmd1 1 Namespace(cmd1_option1='1', parser1=True) $python3 test_args.py cmd2 Namespace(parser2=True) 

En general, los subparsers diferentes, la mayoría de las veces, manejarán los argumentos de maneras completamente diferentes. El patrón habitual es tener diferentes funciones para ejecutar los diferentes comandos y usar set_defaults para establecer un atributo de func . Cuando analizas los argumentos, simplemente llamas a eso llamable:

 subparsers = parser.add_subparsers() parser_1 = subparsers.add_parser(...) parser_1.set_default(func=do_command_one) parser_k = subparsers.add_parser(...) parser_k.set_default(func=do_command_k) args = parser.parse_args() if args.func: args.func(args) 

La identidad del subparser se puede agregar al Namespace principal si el comando add_subparsers recibe un dest .

De la documentación:

Sin embargo, si es necesario verificar el nombre del subparser que se invocó, el argumento de la palabra clave dest a la llamada add_subparsers () funcionará:

 >>> parser = argparse.ArgumentParser() >>> subparsers = parser.add_subparsers(dest='subparser_name') >>> subparser1 = subparsers.add_parser('1') >>> subparser1.add_argument('-x') >>> subparser2 = subparsers.add_parser('2') >>> subparser2.add_argument('y') >>> parser.parse_args(['2', 'frobble']) Namespace(subparser_name='2', y='frobble') 

Por defecto, dest es argparse.SUPPRESS , que evita que los subparsers agreguen el nombre al namespace .