tutorial reconoce lista interno instalar externo ejecutar desde descargar consola como comandos comando python command-line configuration-files

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())