python - parser - Subcomando predeterminado, o manejo de subcomandos con argparse
python arguments example (7)
¿Cómo puedo tener un sub-command predeterminado, o manejar el caso donde no se da un argparse
usando argparse
?
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser(''hi'')
a.parse_args()
Aquí me gustaría que se seleccionara un comando, o que los argumentos se manejaran solo en función del siguiente nivel de analizador (en este caso, el analizador de nivel superior).
joiner@X:~/src> python3 default_subcommand.py usage: default_subcommand.py [-h] {hi} ... default_subcommand.py: error: too few arguments
Aquí hay una manera más agradable de agregar un método set_default_subparser
:
if cmd in [None, ''hi'']:
print(''command "hi"'')
En Python 2.7, puedes anular el comportamiento de error en una subclase (una pena que no haya una forma mejor de diferenciar el error):
import argparse
class ExceptionArgParser(argparse.ArgumentParser):
def error(self, message):
if "invalid choice" in message:
# throw exception (of your choice) to catch
raise RuntimeError(message)
else:
# restore normal behaviour
super(ExceptionArgParser, self).error(message)
parser = ExceptionArgParser()
subparsers = parser.add_subparsers(title=''Modes'', dest=''mode'')
default_parser = subparsers.add_parser(''default'')
default_parser.add_argument(''a'', nargs="+")
other_parser = subparsers.add_parser(''other'')
other_parser.add_argument(''b'', nargs="+")
try:
args = parser.parse_args()
except RuntimeError:
args = default_parser.parse_args()
# force the mode into namespace
setattr(args, ''mode'', ''default'')
print args
En Python 3.2 (y 2.7) obtendrá ese error, pero no en 3.3 y 3.4 (sin respuesta). Por lo tanto, en 3.3 / 3.4 puede probar que parsed_args
sea un Namespace
vacío.
Una solución más general es agregar un método set_default_subparser()
(tomado del paquete ruamel.std.argparse ) y llamar a ese método justo antes de parse_args()
:
import argparse
import sys
def set_default_subparser(self, name, args=None, positional_args=0):
"""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 last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
def do_hi():
print(''inside hi'')
a = argparse.ArgumentParser()
b = a.add_subparsers()
sp = b.add_parser(''hi'')
sp.set_defaults(func=do_hi)
a.set_default_subparser(''hi'')
parsed_args = a.parse_args()
if hasattr(parsed_args, ''func''):
parsed_args.func()
Esto funcionará con 2.6 (si argparse
está instalado desde PyPI), 2.7, 3.2, 3.3, 3.4. Y te permite hacer ambas cosas.
python3 default_subcommand.py
y
python3 default_subcommand.py hi
Con el mismo efecto.
Permitiendo elegir un nuevo subparser por defecto, en lugar de uno de los existentes.
La primera versión del código permite configurar uno de los subparsers definidos previamente como predeterminado. La siguiente modificación permite agregar un nuevo subparser predeterminado, que luego podría usarse para procesar específicamente el caso cuando el usuario no seleccionó un subparser (diferentes líneas marcadas en el código)
def set_default_subparser(self, name, args=None, positional_args=0):
"""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
existing_default = False # check if default parser previously defined
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 sp_name == name: # check existance of default parser
existing_default = True
if not subparser_found:
# If the default subparser is not among the existing ones,
# create a new parser.
# As this is called just before ''parse_args'', the default
# parser created here will not pollute the help output.
if not existing_default:
for x in self._subparsers._actions:
if not isinstance(x, argparse._SubParsersAction):
continue
x.add_parser(name)
break # this works OK, but should I check further?
# insert default in last position before global positional
# arguments, this implies no global options are specified after
# first positional argument
if args is None:
sys.argv.insert(len(sys.argv) - positional_args, name)
else:
args.insert(len(args) - positional_args, name)
argparse.ArgumentParser.set_default_subparser = set_default_subparser
a = argparse.ArgumentParser()
b = a.add_subparsers(dest =''cmd'')
sp = b.add_parser(''hi'')
sp2 = b.add_parser(''hai'')
a.set_default_subparser(''hey'')
parsed_args = a.parse_args()
print(parsed_args)
La opción "predeterminada" aún no aparecerá en la ayuda:
python test_parser.py -h
usage: test_parser.py [-h] {hi,hai} ...
positional arguments:
{hi,hai}
optional arguments:
-h, --help show this help message and exit
Sin embargo, ahora es posible diferenciar y manejar por separado llamando a uno de los subparsers proporcionados, y llamando al subparser predeterminado cuando no se proporcionó ningún argumento:
$ python test_parser.py hi
Namespace(cmd=''hi'')
$ python test_parser.py
Namespace(cmd=''hey'')
Para referencia posterior:
...
b = a.add_subparsers(dest=''cmd'')
b.set_defaults(cmd=''hey'') # <-- this makes hey as default
b.add_parser(''hi'')
así, estos dos serán iguales:
- python main.py hey
- python main.py
Parece que he encontrado la solución al final yo mismo.
Si el comando es opcional, esto hace que el comando sea una opción . En mi configuración de analizador original, tenía un comando de package
que podía tomar una serie de pasos posibles, o realizaría todos los pasos si no se daba ninguno. Esto hace que el paso sea una elección:
parser = argparse.ArgumentParser()
command_parser = subparsers.add_parser(''command'')
command_parser.add_argument(''--step'', choices=[''prepare'', ''configure'', ''compile'', ''stage'', ''package''])
...other command parsers
parsed_args = parser.parse_args()
if parsed_args.step is None:
do all the steps...
Puede agregar un argumento con un valor predeterminado que se usará cuando no se establezca nada, creo.
Vea esto: http://docs.python.org/dev/library/argparse.html#default
Editar:
Lo siento, leí tu pregunta un poco rápido.
No creo que tengas una forma directa de hacer lo que quieres a través de argparse. Pero puede verificar la longitud de sys.argv y, si su longitud es 1 (solo el nombre del script), podría pasar manualmente los parámetros predeterminados para el análisis, haciendo algo como esto:
import argparse
a = argparse.ArgumentParser()
b = a.add_subparsers()
b.add_parser(''hi'')
if len(sys.argv) == 1:
a.parse_args([''hi''])
else:
a.parse_args()
Creo que debería hacer lo que quieras, pero estoy de acuerdo en que sería bueno tener esto fuera de la caja.
Tal vez lo que estás buscando es el argumento dest
de add_subparsers
:
( Advertencia: funciona en Python 3.4, pero no en 2.7 )
class DefaultSubcommandArgParse(argparse.ArgumentParser):
__default_subparser = None
def set_default_subparser(self, name):
self.__default_subparser = name
def _parse_known_args(self, arg_strings, *args, **kwargs):
in_args = set(arg_strings)
d_sp = self.__default_subparser
if d_sp is not None and not {''-h'', ''--help''}.intersection(in_args):
for x in self._subparsers._actions:
subparser_found = (
isinstance(x, argparse._SubParsersAction) and
in_args.intersection(x._name_parser_map.keys())
)
if subparser_found:
break
else:
# insert default in first position, this implies no
# global options without a sub_parsers specified
arg_strings = [d_sp] + arg_strings
return super(DefaultSubcommandArgParse, self)._parse_known_args(
arg_strings, *args, **kwargs
)
Ahora solo puedes usar el valor de cmd
:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest=''cmd'')
parser_hi = subparsers.add_parser(''hi'')
parser.parse_args([]) # Namespace(cmd=None)