mutually - parser python example
argparse subparser opcional(para--version) (7)
Aunque la respuesta de @ eumiro aborda la opción --version
, solo puede hacerlo porque es un caso especial para optparse. Para permitir invocaciones generales de:
prog
prog --verbose
prog --verbose main
prog --verbose db
y hacer que prog --version
funcione de la misma manera que prog --verbose main
(y prog main --verbose
) puede agregar un método a Argumentparser y llamar a ese con el nombre del subparser predeterminado, justo antes de invocar a parse_args()
:
import argparse
import sys
def set_default_subparser(self, name, args=None):
"""default subparser selection. Call after setup, just before parse_args()
name: is the name of the subparser to call by default
args: if set is the argument list handed to parse_args()
, tested with 2.7, 3.2, 3.3, 3.4
it works with 2.6 assuming argparse is installed
"""
subparser_found = False
for arg in sys.argv[1:]:
if arg in [''-h'', ''--help'']: # global help if no subparser
break
else:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
for sp_name in x._name_parser_map.keys():
if sp_name in sys.argv[1:]:
subparser_found = True
if not subparser_found:
# insert default in first position, this implies no
# global options without a sub_parsers specified
if args is None:
sys.argv.insert(1, name)
else:
args.insert(0, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_main(args):
print ''main verbose'', args.verbose
def do_db(args):
print ''db verbose:'', args.verbose
parser = argparse.ArgumentParser()
parser.add_argument(''--verbose'', action=''store_true'')
parser.add_argument(''--version'', action=''version'', version=''%(prog)s 2.0'')
subparsers = parser.add_subparsers()
sp = subparsers.add_parser(''main'')
sp.set_defaults(func=do_main)
sp.add_argument(''--verbose'', action=''store_true'')
sp = subparsers.add_parser(''db'')
sp.set_defaults(func=do_db)
parser.set_default_subparser(''main'')
args = parser.parse_args()
if hasattr(args, ''func''):
args.func(args)
El método set_default_subparser()
es parte del paquete ruamel.std.argparse .
Tengo el siguiente código (usando Python 2.7):
# shared command line options, like --version or --verbose
parser_shared = argparse.ArgumentParser(add_help=False)
parser_shared.add_argument(''--version'', action=''store_true'')
# the main parser, inherits from `parser_shared`
parser = argparse.ArgumentParser(description=''main'', parents=[parser_shared])
# several subcommands, which can''t inherit from the main parser, since
# it would expect subcommands ad infinitum
subparsers = parser.add_subparsers(''db'', parents=[parser_shared])
...
args = parser.parse_args()
Ahora me gustaría poder llamar a este programa, por ejemplo, con la --version
adjunta al programa normal o algún subcomando:
$ prog --version
0.1
$ prog db --version
0.1
Básicamente, necesito declarar subparsers opcionales. Soy consciente de que esto no es realmente compatible , pero ¿hay soluciones alternativas o alternativas?
Editar: El mensaje de error que estoy recibiendo:
$ prog db --version
# works fine
$ prog --version
usage: ....
prog: error: too few arguments
Como se explica en http://bugs.python.org/issue9253 (argparse: subparsers opcionales), a partir de Python 3.3, los subparsers ahora son opcionales. Este fue un resultado no deseado de un cambio en la forma en que parse_args
verificó los argumentos requeridos.
Encontré un fudge que restaura el comportamiento anterior (subparsers requeridos), estableciendo explícitamente el atributo required
de la acción subparsers
.
parser = ArgumentParser(prog=''test'')
subparsers = parser.add_subparsers()
subparsers.required = True # the fudge
subparsers.dest = ''command''
subparser = subparsers.add_parser("foo", help="run foo")
parser.parse_args()
Ver ese tema para más detalles. Espero que si y cuando este problema se soluciona correctamente, se requerirán subparsers por defecto, con algún tipo de opción para establecer su atributo required
en False
. Pero hay un gran atraso de parches argparse
.
De acuerdo con la documentación, --version
with action=''version''
(y no con action=''store_true''
) imprime automáticamente el número de versión:
parser.add_argument(''--version'', action=''version'', version=''%(prog)s 2.0'')
Esto parece implementar la idea básica de un subparser opcional. Analizamos los argumentos estándar que se aplican a todos los subcomandos. Entonces, si queda algo, invocamos el analizador sobre el resto. Los argumentos principales son un elemento principal del subcomando, por lo que la -h aparece correctamente. Planeo ingresar una solicitud interactiva si no hay subcomandos presentes.
import argparse
p1 = argparse.ArgumentParser( add_help = False )
p1.add_argument( ‘–flag1′ )
p2 = argparse.ArgumentParser( parents = [ p1 ] )
s = p2.add_subparsers()
p = s.add_parser( ‘group’ )
p.set_defaults( group=True )
( init_ns, remaining ) = p1.parse_known_args( )
if remaining:
p2.parse_args( args = remaining, namespace=init_ns )
else:
print( ‘Enter interactive loop’ )
print( init_ns )
FWIW, también me encontré con esto y terminé "resolviéndolo" al no usar subparsers (ya tenía mi propio sistema para imprimir ayuda, así que no perdí nada allí).
En su lugar, hago esto:
parser.add_argument("command", nargs="?",
help="name of command to execute")
args, subcommand_args = parser.parse_known_args()
... y luego el subcomando crea su propio analizador (similar a un subparser) que opera solo en subcommand_args
.
Mientras esperamos que se entregue esta función , podemos usar un código como este:
# Make sure that main is the default sub-parser
if ''-h'' not in sys.argv and ''--help'' not in sys.argv:
if len(sys.argv) < 2:
sys.argv.append(''main'')
if sys.argv[1] not in (''main'', ''test''):
sys.argv = [sys.argv[0], ''main''] + sys.argv[1:]
Sí, acabo de marcar svn
, que se usa como un ejemplo de objeto en la documentación add_subparsers()
, y solo admite ''--version'' en el comando principal:
python zacharyyoung$ svn log --version
Subcommand ''log'' doesn''t accept option ''--version''
Type ''svn help log'' for usage.
Todavía:
# create common parser
parent_parser = argparse.ArgumentParser(''parent'', add_help=False)
parent_parser.add_argument(''--version'', action=''version'', version=''%(prog)s 2.0'')
# create the top-level parser
parser = argparse.ArgumentParser(parents=[parent_parser])
subparsers = parser.add_subparsers()
# create the parser for the "foo" command
parser_foo = subparsers.add_parser(''foo'', parents=[parent_parser])
Cuyos rendimientos:
python zacharyyoung$ ./arg-test.py --version
arg-test.py 2.0
python zacharyyoung$ ./arg-test.py foo --version
arg-test.py foo 2.0