lista - python no se reconoce como un comando interno o externo
¿Cuál es la mejor manera de permitir que las opciones de configuración se anulen en la línea de comando en Python? (7)
Tengo una aplicación Python que necesita bastantes (~ 30) parámetros de configuración. Hasta ahora, utilicé la clase OptionParser para definir los valores predeterminados en la aplicación en sí, con la posibilidad de cambiar los parámetros individuales en la línea de comando al invocar la aplicación.
Ahora me gustaría usar archivos de configuración ''apropiados'', por ejemplo, de la clase ConfigParser. Al mismo tiempo, los usuarios deberían poder cambiar los parámetros individuales en la línea de comando.
Me preguntaba si hay alguna manera de combinar los dos pasos, por ejemplo, usar optparse (o el argparse más nuevo) para manejar las opciones de línea de comando, pero leyendo los valores predeterminados de un archivo de configuración en la sintaxis ConfigParse.
¿Alguna idea de cómo hacer esto de una manera fácil? Realmente no me gusta invocar manualmente ConfigParse, y luego configurar manualmente todos los valores predeterminados de todos los optinos a los valores apropiados ...
Actualización: esta respuesta todavía tiene problemas; por ejemplo, no puede manejar required
argumentos required
y requiere una sintaxis de configuración incómoda. En cambio, ConfigArgParse parece ser exactamente lo que pide esta pregunta, y es un reemplazo transparente y ConfigArgParse .
Un problema con la current es que no habrá error si los argumentos en el archivo de configuración no son válidos. Aquí hay una versión con un inconveniente diferente: deberá incluir el prefijo --
o -
en las teclas.
Aquí está el código python ( enlace Gist con licencia MIT):
# Filename: main.py
import argparse
import configparser
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(''--config_file'', help=''config file'')
args, left_argv = parser.parse_known_args()
if args.config_file:
with open(args.config_file, ''r'') as f:
config = configparser.SafeConfigParser()
config.read([args.config_file])
parser.add_argument(''--arg1'', help=''argument 1'')
parser.add_argument(''--arg2'', type=int, help=''argument 2'')
for k, v in config.items("Defaults"):
parser.parse_args([str(k), str(v)], args)
parser.parse_args(left_argv, args)
print(args)
Aquí hay un ejemplo de un archivo de configuración:
# Filename: config_correct.conf
[Defaults]
--arg1=Hello!
--arg2=3
Ahora, corriendo
> python main.py --config_file config_correct.conf --arg1 override
Namespace(arg1=''override'', arg2=3, config_file=''test_argparse.conf'')
Sin embargo, si nuestro archivo de configuración tiene un error:
# config_invalid.conf
--arg1=Hello!
--arg2=''not an integer!''
Ejecutar el script producirá un error, como desee:
> python main.py --config_file config_invalid.conf --arg1 override
usage: test_argparse_conf.py [-h] [--config_file CONFIG_FILE] [--arg1 ARG1]
[--arg2 ARG2]
main.py: error: argument --arg2: invalid int value: ''not an integer!''
La desventaja principal es que esto usa parser.parse_args
un tanto halagüeña para obtener la comprobación de errores de ArgumentParser, pero no conozco ninguna alternativa a esto.
Eche un vistazo a ConfigArgParse : es un nuevo paquete de PyPI ( fuente abierta ) que sirve como reemplazo en reemplazo de argparse con soporte adicional para archivos de configuración y variables de entorno.
Estoy usando ConfigParser y argparse con subcomandos para manejar tales tareas. La línea importante en el siguiente código es:
subp.set_defaults(**dict(conffile.items(subn)))
Esto establecerá los valores predeterminados del subcomando (desde argparse) a los valores en la sección del archivo de configuración.
Un ejemplo más completo es a continuación:
####### content of example.cfg:
# [sub1]
# verbosity=10
# gggg=3.5
# [sub2]
# host=localhost
import ConfigParser
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
parser_sub1 = subparsers.add_parser(''sub1'')
parser_sub1.add_argument(''-V'',''--verbosity'', type=int, dest=''verbosity'')
parser_sub1.add_argument(''-G'', type=float, dest=''gggg'')
parser_sub2 = subparsers.add_parser(''sub2'')
parser_sub2.add_argument(''-H'',''--host'', dest=''host'')
conffile = ConfigParser.SafeConfigParser()
conffile.read(''example.cfg'')
for subp, subn in ((parser_sub1, "sub1"), (parser_sub2, "sub2")):
subp.set_defaults(**dict(conffile.items(subn)))
print parser.parse_args([''sub1'',])
# Namespace(gggg=3.5, verbosity=10)
print parser.parse_args([''sub1'', ''-V'', ''20''])
# Namespace(gggg=3.5, verbosity=20)
print parser.parse_args([''sub1'', ''-V'', ''20'', ''-G'',''42''])
# Namespace(gggg=42.0, verbosity=20)
print parser.parse_args([''sub2'', ''-H'', ''www.example.com''])
# Namespace(host=''www.example.com'')
print parser.parse_args([''sub2'',])
# Namespace(host=''localhost'')
Intenta de esta manera
# encoding: utf-8
import imp
import argparse
class LoadConfigAction(argparse._StoreAction):
NIL = object()
def __init__(self, option_strings, dest, **kwargs):
super(self.__class__, self).__init__(option_strings, dest)
self.help = "Load configuration from file"
def __call__(self, parser, namespace, values, option_string=None):
super(LoadConfigAction, self).__call__(parser, namespace, values, option_string)
config = imp.load_source(''config'', values)
for key in (set(map(lambda x: x.dest, parser._actions)) & set(dir(config))):
setattr(namespace, key, getattr(config, key))
Úselo:
parser.add_argument("-C", "--config", action=LoadConfigAction)
parser.add_argument("-H", "--host", dest="host")
Y crea una configuración de ejemplo:
# Example config: /etc/myservice.conf
import os
host = os.getenv("HOST_NAME", "localhost")
No puedo decir que sea la mejor manera, pero tengo una clase OptionParser que hice que hace precisamente eso: actúa como optparse.OptionParser con los valores predeterminados procedentes de una sección de archivo de configuración. Puedes tenerlo...
class OptionParser(optparse.OptionParser):
def __init__(self, **kwargs):
import sys
import os
config_file = kwargs.pop(''config_file'',
os.path.splitext(os.path.basename(sys.argv[0]))[0] + ''.config'')
self.config_section = kwargs.pop(''config_section'', ''OPTIONS'')
self.configParser = ConfigParser()
self.configParser.read(config_file)
optparse.OptionParser.__init__(self, **kwargs)
def add_option(self, *args, **kwargs):
option = optparse.OptionParser.add_option(self, *args, **kwargs)
name = option.get_opt_string()
if name.startswith(''--''):
name = name[2:]
if self.configParser.has_option(self.config_section, name):
self.set_default(name, self.configParser.get(self.config_section, name))
Siéntase libre de navegar por la fuente . Las pruebas están en un directorio de hermanos.
fromfile_prefix_chars
Tal vez no sea la API perfecta, pero vale la pena conocerla. main.py
:
#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser(fromfile_prefix_chars=''@'')
parser.add_argument(''-a'', default=13)
parser.add_argument(''-b'', default=42)
print(parser.parse_args())
Entonces:
$ printf -- ''-a/n1/n-b/n2/n'' > opts.txt
$ ./main.py
Namespace(a=13, b=42)
$ ./main.py @opts.txt
Namespace(a=''1'', b=''2'')
$ ./main.py @opts.txt -a 3 -b 4
Namespace(a=''3'', b=''4'')
$ ./main.py -a 3 -b 4 @opts.txt
Namespace(a=''1'', b=''2'')
Documentación: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Probado en Python 3.6.5, Ubuntu 18.04.
Acabo de descubrir que puedes hacer esto con argparse.ArgumentParser.parse_known_args()
. Comience por usar parse_known_args()
para analizar un archivo de configuración desde la línea de comandos, luego léalo con ConfigParser y establezca los valores predeterminados, y luego analice el resto de las opciones con parse_args()
. Esto le permitirá tener un valor predeterminado, anularlo con un archivo de configuración y luego anularlo con una opción de línea de comando. P.ej:
Predeterminado sin intervención del usuario:
$ ./argparse-partial.py
Option is "default"
Predeterminado del archivo de configuración:
$ cat argparse-partial.config
[Defaults]
option=Hello world!
$ ./argparse-partial.py -c argparse-partial.config
Option is "Hello world!"
Valor predeterminado del archivo de configuración, anulado por línea de comando:
$ ./argparse-partial.py -c argparse-partial.config --option override
Option is "override"
argprase-partial.py sigue. Es un poco complicado manejar -h
para obtener ayuda de manera adecuada.
import argparse
import ConfigParser
import sys
def main(argv=None):
# Do argv default this way, as doing it in the functional
# declaration sets it at compile time.
if argv is None:
argv = sys.argv
# Parse any conf_file specification
# We make this parser with add_help=False so that
# it doesn''t parse -h and print help.
conf_parser = argparse.ArgumentParser(
description=__doc__, # printed with -h/--help
# Don''t mess with format of description
formatter_class=argparse.RawDescriptionHelpFormatter,
# Turn off help, so we print all options in response to -h
add_help=False
)
conf_parser.add_argument("-c", "--conf_file",
help="Specify config file", metavar="FILE")
args, remaining_argv = conf_parser.parse_known_args()
defaults = { "option":"default" }
if args.conf_file:
config = ConfigParser.SafeConfigParser()
config.read([args.conf_file])
defaults.update(dict(config.items("Defaults")))
# Parse rest of arguments
# Don''t suppress add_help here so it will handle -h
parser = argparse.ArgumentParser(
# Inherit options from config_parser
parents=[conf_parser]
)
parser.set_defaults(**defaults)
parser.add_argument("--option")
args = parser.parse_args(remaining_argv)
print "Option is /"{}/"".format(args.option)
return(0)
if __name__ == "__main__":
sys.exit(main())