Analice los archivos de configuración, el entorno y los argumentos de la línea de comandos para obtener una única colección de opciones


110

La biblioteca estándar de Python tiene módulos para el análisis de archivos de configuración ( configparser ), lectura de variables de entorno ( os.environ ) y análisis de argumentos de línea de comandos ( argparse ). Quiero escribir un programa que haga todo eso, y también:

  • Tiene una cascada de valores de opciones :

    • valores de opción predeterminados, anulados por
    • Opciones de archivo de configuración, anulado por
    • variables de entorno, anuladas por
    • opciones de la línea de comandos.
  • Permite una o más ubicaciones de archivos de configuración especificadas en la línea de comando con --config-file foo.conf, por ejemplo , y las lee (ya sea en lugar de, o adicionalmente, al archivo de configuración habitual). Esto aún debe obedecer a la cascada anterior.

  • Permite definiciones de opciones en un solo lugar para determinar el comportamiento de análisis de los archivos de configuración y la línea de comandos.

  • Unifica las opciones analizadas en una sola colección de valores de opciones para que el resto del programa acceda sin importar de dónde provienen.

Todo lo que necesito aparentemente está en la biblioteca estándar de Python, pero no funcionan juntos sin problemas.

¿Cómo puedo lograr esto con una desviación mínima de la biblioteca estándar de Python?


6
Me gusta mucho esta pregunta. He estado considerando hacer algo como esto durante mucho tiempo ... Me alegro de haber jterracedado una recompensa aquí para empujarme al límite lo suficiente como para intentar hacer algo como esto :)
mgilson

4
¡Excelente pregunta! Es sorprendente que esto no haya sido resuelto por un paquete popular (o por la propia biblioteca estándar) hace mucho tiempo.
Zearin

Respuestas:


33

El módulo argparse hace que esto no sea una locura, siempre y cuando esté satisfecho con un archivo de configuración que parece una línea de comandos. (Creo que esto es una ventaja, porque los usuarios solo tendrán que aprender una sintaxis). Establecer fromfile_prefix_chars en, por ejemplo @, hace que,

my_prog --foo=bar

es equivalente a

my_prog @baz.conf

si @baz.confes,

--foo
bar

Incluso puede hacer que su código se busque foo.confautomáticamente modificandoargv

if os.path.exists('foo.conf'):
    argv = ['@foo.conf'] + argv
args = argparser.parse_args(argv)

El formato de estos archivos de configuración se puede modificar creando una subclase de ArgumentParser y agregando un método convert_arg_line_to_args .


Hasta que alguien ofrezca una alternativa mejor, esta es la respuesta correcta. He estado usando argparse y ni siquiera miré esta función. ¡Agradable!
Lemur

pero esto no tiene una respuesta para las variables de entorno?
jterrace

1
@jterrace: Esta respuesta SO puede funcionar para usted: stackoverflow.com/a/10551190/400793
Alex Szatmary

27

ACTUALIZACIÓN: finalmente pude poner esto en pypi. Instale la última versión a través de:

   pip install configargparser

Aquí encontrará ayuda e instrucciones completas .

Publicación original

Aquí hay algo que pirateé juntos. No dude en sugerir mejoras / informes de errores en los comentarios:

import argparse
import ConfigParser
import os

def _identity(x):
    return x

_SENTINEL = object()


class AddConfigFile(argparse.Action):
    def __call__(self,parser,namespace,values,option_string=None):
        # I can never remember if `values` is a list all the time or if it
        # can be a scalar string; this takes care of both.
        if isinstance(values,basestring):
            parser.config_files.append(values)
        else:
            parser.config_files.extend(values)


class ArgumentConfigEnvParser(argparse.ArgumentParser):
    def __init__(self,*args,**kwargs):
        """
        Added 2 new keyword arguments to the ArgumentParser constructor:

           config --> List of filenames to parse for config goodness
           default_section --> name of the default section in the config file
        """
        self.config_files = kwargs.pop('config',[])  #Must be a list
        self.default_section = kwargs.pop('default_section','MAIN')
        self._action_defaults = {}
        argparse.ArgumentParser.__init__(self,*args,**kwargs)


    def add_argument(self,*args,**kwargs):
        """
        Works like `ArgumentParser.add_argument`, except that we've added an action:

           config: add a config file to the parser

        This also adds the ability to specify which section of the config file to pull the 
        data from, via the `section` keyword.  This relies on the (undocumented) fact that
        `ArgumentParser.add_argument` actually returns the `Action` object that it creates.
        We need this to reliably get `dest` (although we could probably write a simple
        function to do this for us).
        """

        if 'action' in kwargs and kwargs['action'] == 'config':
            kwargs['action'] = AddConfigFile
            kwargs['default'] = argparse.SUPPRESS

        # argparse won't know what to do with the section, so 
        # we'll pop it out and add it back in later.
        #
        # We also have to prevent argparse from doing any type conversion,
        # which is done explicitly in parse_known_args.  
        #
        # This way, we can reliably check whether argparse has replaced the default.
        #
        section = kwargs.pop('section', self.default_section)
        type = kwargs.pop('type', _identity)
        default = kwargs.pop('default', _SENTINEL)

        if default is not argparse.SUPPRESS:
            kwargs.update(default=_SENTINEL)
        else:  
            kwargs.update(default=argparse.SUPPRESS)

        action = argparse.ArgumentParser.add_argument(self,*args,**kwargs)
        kwargs.update(section=section, type=type, default=default)
        self._action_defaults[action.dest] = (args,kwargs)
        return action

    def parse_known_args(self,args=None, namespace=None):
        # `parse_args` calls `parse_known_args`, so we should be okay with this...
        ns, argv = argparse.ArgumentParser.parse_known_args(self, args=args, namespace=namespace)
        config_parser = ConfigParser.SafeConfigParser()
        config_files = [os.path.expanduser(os.path.expandvars(x)) for x in self.config_files]
        config_parser.read(config_files)

        for dest,(args,init_dict) in self._action_defaults.items():
            type_converter = init_dict['type']
            default = init_dict['default']
            obj = default

            if getattr(ns,dest,_SENTINEL) is not _SENTINEL: # found on command line
                obj = getattr(ns,dest)
            else: # not found on commandline
                try:  # get from config file
                    obj = config_parser.get(init_dict['section'],dest)
                except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): # Nope, not in config file
                    try: # get from environment
                        obj = os.environ[dest.upper()]
                    except KeyError:
                        pass

            if obj is _SENTINEL:
                setattr(ns,dest,None)
            elif obj is argparse.SUPPRESS:
                pass
            else:
                setattr(ns,dest,type_converter(obj))

        return ns, argv


if __name__ == '__main__':
    fake_config = """
[MAIN]
foo:bar
bar:1
"""
    with open('_config.file','w') as fout:
        fout.write(fake_config)

    parser = ArgumentConfigEnvParser()
    parser.add_argument('--config-file', action='config', help="location of config file")
    parser.add_argument('--foo', type=str, action='store', default="grape", help="don't know what foo does ...")
    parser.add_argument('--bar', type=int, default=7, action='store', help="This is an integer (I hope)")
    parser.add_argument('--baz', type=float, action='store', help="This is an float(I hope)")
    parser.add_argument('--qux', type=int, default='6', action='store', help="this is another int")
    ns = parser.parse_args([])

    parser_defaults = {'foo':"grape",'bar':7,'baz':None,'qux':6}
    config_defaults = {'foo':'bar','bar':1}
    env_defaults = {"baz":3.14159}

    # This should be the defaults we gave the parser
    print ns
    assert ns.__dict__ == parser_defaults

    # This should be the defaults we gave the parser + config defaults
    d = parser_defaults.copy()
    d.update(config_defaults)
    ns = parser.parse_args(['--config-file','_config.file'])
    print ns
    assert ns.__dict__ == d

    os.environ['BAZ'] = "3.14159"

    # This should be the parser defaults + config defaults + env_defaults
    d = parser_defaults.copy()
    d.update(config_defaults)
    d.update(env_defaults)
    ns = parser.parse_args(['--config-file','_config.file'])
    print ns
    assert ns.__dict__ == d

    # This should be the parser defaults + config defaults + env_defaults + commandline
    commandline = {'foo':'3','qux':4} 
    d = parser_defaults.copy()
    d.update(config_defaults)
    d.update(env_defaults)
    d.update(commandline)
    ns = parser.parse_args(['--config-file','_config.file','--foo=3','--qux=4'])
    print ns
    assert ns.__dict__ == d

    os.remove('_config.file')

QUE HACER

Esta implementación aún está incompleta. Aquí hay una lista parcial de TODO:

Cumplir con el comportamiento documentado

  • (fácil) Escribe una función que se deduzca destdesde argsadentro add_argument, en lugar de depender del Actionobjeto
  • (trivial) Escribe una parse_argsfunción que use parse_known_args. (por ejemplo, copia parse_argsde la cpythonimplementación para garantizar las llamadas parse_known_args).

Cosas menos fáciles ...

No he probado nada de esto todavía. Es poco probable, ¡pero aún es posible! Que simplemente funcione ...

  • (¿difícil?) Exclusión mutua
  • (¿difícil?) Grupos de argumentos (si se implementan, estos grupos deberían obtener un sectionen el archivo de configuración).
  • (¿difícil?) Subcomandos (los subcomandos también deben obtener un sectionen el archivo de configuración).

¿Te importaría poner esto en un repositorio de github para que todos puedan mejorarlo?
brent.payne

1
@ brent.payne - github.com/mgilson/configargparser - Si voy a publicar esto como código real, decidí tomarme un poco de tiempo esta noche para limpiarlo un poco. :-)
mgilson

3
FWIW, finalmente pude poner esto en pypi - Debería poder instalarlo a través depip install configargparser
mgilson

@mgilson - Actualicé tu publicación. ¡Este paquete merece más uso!
ErichBSchulz

12

Hay una biblioteca que hace exactamente esto llamada configglue .

configglue es una biblioteca que pega optparse.OptionParser y ConfigParser.ConfigParser de python, para que no tenga que repetirlo cuando desee exportar las mismas opciones a un archivo de configuración y una interfaz de línea de comandos.

También admite variables de entorno.

También hay otra biblioteca llamada ConfigArgParse que es

Un reemplazo directo para argparse que permite que las opciones también se establezcan a través de archivos de configuración y / o variables de entorno.

Quizás le interese la charla de PyCon sobre la configuración de ukasz Langa - ¡Déjelos configurar!


Me pregunté si hay planes para soportar un módulo argparse.
Piotr Dobrogost

10

Si bien no lo he probado por mi cuenta, existe la biblioteca ConfigArgParse que indica que hace la mayoría de las cosas que desea:

Un reemplazo directo para argparse que permite que las opciones también se establezcan a través de archivos de configuración y / o variables de entorno.


1
Lo probé, ConfigArgParse es muy conveniente y, de hecho, un reemplazo directo.
maxschlepzig

7

Parece que la biblioteca estándar no se ocupa de esto, dejando a cada programador de cantos rodados configparsery argparseyos.environ todos juntos de una manera torpe.


5

La biblioteca estándar de Python no proporciona esto, hasta donde yo sé. Resolví esto por mí mismo escribiendo código para usar optparseyConfigParser analizar la línea de comandos y los archivos de configuración, y proporcionar una capa de abstracción sobre ellos. Sin embargo, necesitaría esto como una dependencia separada, que según su comentario anterior parece ser desagradable.

Si desea ver el código que escribí, está en http://liw.fi/cliapp/ . Está integrado en mi biblioteca de "marco de aplicación de línea de comandos", ya que eso es una gran parte de lo que debe hacer el marco.


4

Probé algo como esto recientemente, usando "optparse".

Lo configuré como una subclase de OptonParser, con un comando '--Store' y '--Check'.

El siguiente código debería tenerlo cubierto. Solo necesita definir sus propios métodos de 'carga' y 'almacenamiento' que aceptan / devuelven diccionarios y ya está listo.


class SmartParse(optparse.OptionParser):
    def __init__(self,defaults,*args,**kwargs):
        self.smartDefaults=defaults
        optparse.OptionParser.__init__(self,*args,**kwargs)
        fileGroup = optparse.OptionGroup(self,'handle stored defaults')
        fileGroup.add_option(
            '-S','--Store',
            dest='Action',
            action='store_const',const='Store',
            help='store command line settings'
        )
        fileGroup.add_option(
            '-C','--Check',
            dest='Action',
            action='store_const',const='Check',
            help ='check stored settings'
        )
        self.add_option_group(fileGroup)
    def parse_args(self,*args,**kwargs):
        (options,arguments) = optparse.OptionParser.parse_args(self,*args,**kwargs)
        action = options.__dict__.pop('Action')
        if action == 'Check':
            assert all(
                value is None 
                for (key,value) in options.__dict__.iteritems() 
            )
            print 'defaults:',self.smartDefaults
            print 'config:',self.load()
            sys.exit()
        elif action == 'Store':
            self.store(options.__dict__)
            sys.exit()
        else:
            config=self.load()
            commandline=dict(
                [key,val] 
                for (key,val) in options.__dict__.iteritems() 
                if val is not None
            )
            result = {}
            result.update(self.defaults)
            result.update(config)
            result.update(commandline)
            return result,arguments
    def load(self):
        return {}
    def store(self,optionDict):
        print 'Storing:',optionDict

pero sigue siendo útil si desea seguir siendo compatible con versiones anteriores de Python
MarioVilas

3

Para cumplir con todos esos requisitos, recomendaría escribir su propia biblioteca que use tanto [opt | arg] parse como configparser para la funcionalidad subyacente.

Dados los dos primeros y el último requisito, diría que quiere:

Paso uno: Realice una pasada del analizador de línea de comandos que solo busque la opción --config-file.

Paso dos: Analice el archivo de configuración.

Paso tres: configure un segundo paso del analizador de línea de comandos utilizando la salida del paso del archivo de configuración como valores predeterminados.

El tercer requisito probablemente signifique que tiene que diseñar su propio sistema de definición de opciones para exponer toda la funcionalidad de optparse y configparser que le interesan, y escribir algunas conexiones para hacer conversiones intermedias.


Esto está bastante más lejos de la "desviación mínima de la biblioteca estándar de Python" de lo que esperaba.
bignose

2

Aquí hay un módulo que pirateé y que lee argumentos de línea de comandos, configuraciones de entorno, archivos ini y valores de llavero. También está disponible en resumen .

"""
Configuration Parser

Configurable parser that will parse config files, environment variables,
keyring, and command-line arguments.



Example test.ini file:

    [defaults]
    gini=10

    [app]
    xini = 50

Example test.arg file:

    --xfarg=30

Example test.py file:

    import os
    import sys

    import config


    def main(argv):
        '''Test.'''
        options = [
            config.Option("xpos",
                          help="positional argument",
                          nargs='?',
                          default="all",
                          env="APP_XPOS"),
            config.Option("--xarg",
                          help="optional argument",
                          default=1,
                          type=int,
                          env="APP_XARG"),
            config.Option("--xenv",
                          help="environment argument",
                          default=1,
                          type=int,
                          env="APP_XENV"),
            config.Option("--xfarg",
                          help="@file argument",
                          default=1,
                          type=int,
                          env="APP_XFARG"),
            config.Option("--xini",
                          help="ini argument",
                          default=1,
                          type=int,
                          ini_section="app",
                          env="APP_XINI"),
            config.Option("--gini",
                          help="global ini argument",
                          default=1,
                          type=int,
                          env="APP_GINI"),
            config.Option("--karg",
                          help="secret keyring arg",
                          default=-1,
                          type=int),
        ]
        ini_file_paths = [
            '/etc/default/app.ini',
            os.path.join(os.path.dirname(os.path.abspath(__file__)),
                         'test.ini')
        ]

        # default usage
        conf = config.Config(prog='app', options=options,
                             ini_paths=ini_file_paths)
        conf.parse()
        print conf

        # advanced usage
        cli_args = conf.parse_cli(argv=argv)
        env = conf.parse_env()
        secrets = conf.parse_keyring(namespace="app")
        ini = conf.parse_ini(ini_file_paths)
        sources = {}
        if ini:
            for key, value in ini.iteritems():
                conf[key] = value
                sources[key] = "ini-file"
        if secrets:
            for key, value in secrets.iteritems():
                conf[key] = value
                sources[key] = "keyring"
        if env:
            for key, value in env.iteritems():
                conf[key] = value
                sources[key] = "environment"
        if cli_args:
            for key, value in cli_args.iteritems():
                conf[key] = value
                sources[key] = "command-line"
        print '\n'.join(['%s:\t%s' % (k, v) for k, v in sources.items()])


    if __name__ == "__main__":
        if config.keyring:
            config.keyring.set_password("app", "karg", "13")
        main(sys.argv)

Example results:

    $APP_XENV=10 python test.py api --xarg=2 @test.arg
    <Config xpos=api, gini=1, xenv=10, xini=50, karg=13, xarg=2, xfarg=30>
    xpos:   command-line
    xenv:   environment
    xini:   ini-file
    karg:   keyring
    xarg:   command-line
    xfarg:  command-line


"""
import argparse
import ConfigParser
import copy
import os
import sys

try:
    import keyring
except ImportError:
    keyring = None


class Option(object):
    """Holds a configuration option and the names and locations for it.

    Instantiate options using the same arguments as you would for an
    add_arguments call in argparse. However, you have two additional kwargs
    available:

        env: the name of the environment variable to use for this option
        ini_section: the ini file section to look this value up from
    """

    def __init__(self, *args, **kwargs):
        self.args = args or []
        self.kwargs = kwargs or {}

    def add_argument(self, parser, **override_kwargs):
        """Add an option to a an argparse parser."""
        kwargs = {}
        if self.kwargs:
            kwargs = copy.copy(self.kwargs)
            try:
                del kwargs['env']
            except KeyError:
                pass
            try:
                del kwargs['ini_section']
            except KeyError:
                pass
        kwargs.update(override_kwargs)
        parser.add_argument(*self.args, **kwargs)

    @property
    def type(self):
        """The type of the option.

        Should be a callable to parse options.
        """
        return self.kwargs.get("type", str)

    @property
    def name(self):
        """The name of the option as determined from the args."""
        for arg in self.args:
            if arg.startswith("--"):
                return arg[2:].replace("-", "_")
            elif arg.startswith("-"):
                continue
            else:
                return arg.replace("-", "_")

    @property
    def default(self):
        """The default for the option."""
        return self.kwargs.get("default")


class Config(object):
    """Parses configuration sources."""

    def __init__(self, options=None, ini_paths=None, **parser_kwargs):
        """Initialize with list of options.

        :param ini_paths: optional paths to ini files to look up values from
        :param parser_kwargs: kwargs used to init argparse parsers.
        """
        self._parser_kwargs = parser_kwargs or {}
        self._ini_paths = ini_paths or []
        self._options = copy.copy(options) or []
        self._values = {option.name: option.default
                        for option in self._options}
        self._parser = argparse.ArgumentParser(**parser_kwargs)
        self.pass_thru_args = []

    @property
    def prog(self):
        """Program name."""
        return self._parser.prog

    def __getitem__(self, key):
        return self._values[key]

    def __setitem__(self, key, value):
        self._values[key] = value

    def __delitem__(self, key):
        del self._values[key]

    def __contains__(self, key):
        return key in self._values

    def __iter__(self):
        return iter(self._values)

    def __len__(self):
        return len(self._values)

    def get(self, key, *args):
        """
        Return the value for key if it exists otherwise the default.
        """
        return self._values.get(key, *args)

    def __getattr__(self, attr):
        if attr in self._values:
            return self._values[attr]
        else:
            raise AttributeError("'config' object has no attribute '%s'"
                                 % attr)

    def build_parser(self, options, **override_kwargs):
        """."""
        kwargs = copy.copy(self._parser_kwargs)
        kwargs.update(override_kwargs)
        if 'fromfile_prefix_chars' not in kwargs:
            kwargs['fromfile_prefix_chars'] = '@'
        parser = argparse.ArgumentParser(**kwargs)
        if options:
            for option in options:
                option.add_argument(parser)
        return parser

    def parse_cli(self, argv=None):
        """Parse command-line arguments into values."""
        if not argv:
            argv = sys.argv
        options = []
        for option in self._options:
            temp = Option(*option.args, **option.kwargs)
            temp.kwargs['default'] = argparse.SUPPRESS
            options.append(temp)
        parser = self.build_parser(options=options)
        parsed, extras = parser.parse_known_args(argv[1:])
        if extras:
            valid, pass_thru = self.parse_passthru_args(argv[1:])
            parsed, extras = parser.parse_known_args(valid)
            if extras:
                raise AttributeError("Unrecognized arguments: %s" %
                                     ' ,'.join(extras))
            self.pass_thru_args = pass_thru + extras
        return vars(parsed)

    def parse_env(self):
        results = {}
        for option in self._options:
            env_var = option.kwargs.get('env')
            if env_var and env_var in os.environ:
                value = os.environ[env_var]
                results[option.name] = option.type(value)
        return results

    def get_defaults(self):
        """Use argparse to determine and return dict of defaults."""
        parser = self.build_parser(options=self._options)
        parsed, _ = parser.parse_known_args([])
        return vars(parsed)

    def parse_ini(self, paths=None):
        """Parse config files and return configuration options.

        Expects array of files that are in ini format.
        :param paths: list of paths to files to parse (uses ConfigParse logic).
                      If not supplied, uses the ini_paths value supplied on
                      initialization.
        """
        results = {}
        config = ConfigParser.SafeConfigParser()
        config.read(paths or self._ini_paths)
        for option in self._options:
            ini_section = option.kwargs.get('ini_section')
            if ini_section:
                try:
                    value = config.get(ini_section, option.name)
                    results[option.name] = option.type(value)
                except ConfigParser.NoSectionError:
                    pass
        return results

    def parse_keyring(self, namespace=None):
        """."""
        results = {}
        if not keyring:
            return results
        if not namespace:
            namespace = self.prog
        for option in self._options:
            secret = keyring.get_password(namespace, option.name)
            if secret:
                results[option.name] = option.type(secret)
        return results

    def parse(self, argv=None):
        """."""
        defaults = self.get_defaults()
        args = self.parse_cli(argv=argv)
        env = self.parse_env()
        secrets = self.parse_keyring()
        ini = self.parse_ini()

        results = defaults
        results.update(ini)
        results.update(secrets)
        results.update(env)
        results.update(args)

        self._values = results
        return self

    @staticmethod
    def parse_passthru_args(argv):
        """Handles arguments to be passed thru to a subprocess using '--'.

        :returns: tuple of two lists; args and pass-thru-args
        """
        if '--' in argv:
            dashdash = argv.index("--")
            if dashdash == 0:
                return argv[1:], []
            elif dashdash > 0:
                return argv[0:dashdash], argv[dashdash + 1:]
        return argv, []

    def __repr__(self):
        return "<Config %s>" % ', '.join([
            '%s=%s' % (k, v) for k, v in self._values.iteritems()])


def comma_separated_strings(value):
    """Handles comma-separated arguments passed in command-line."""
    return map(str, value.split(","))


def comma_separated_pairs(value):
    """Handles comma-separated key/values passed in command-line."""
    pairs = value.split(",")
    results = {}
    for pair in pairs:
        key, pair_value = pair.split('=')
        results[key] = pair_value
    return results


-1

El confecto de biblioteca que construí es precisamente para satisfacer la mayoría de sus necesidades.

  • Puede cargar el archivo de configuración varias veces a través de rutas de archivo o nombre de módulo dadas.
  • Carga configuraciones de variables de entorno con un prefijo determinado.
  • Puede adjuntar opciones de línea de comando a algunos comandos de clic

    (lo siento, no es argparse, pero hacer clic es mejor y mucho más avanzado. confectPodría admitir argparse en la versión futura).

  • Lo más importante es que confectcarga archivos de configuración de Python, no JSON / YMAL / TOML / INI. Al igual que el archivo de perfil de IPython o el archivo de configuración DJANGO, el archivo de configuración de Python es flexible y más fácil de mantener.

Para obtener más información, consulte README.rst en el repositorio del proyecto . Tenga en cuenta que solo admite Python3.6 hasta.

Ejemplos

Adjuntar opciones de línea de comando

import click
from proj_X.core import conf

@click.command()
@conf.click_options
def cli():
    click.echo(f'cache_expire = {conf.api.cache_expire}')

if __name__ == '__main__':
    cli()

Crea automáticamente un mensaje de ayuda completo con todas las propiedades y valores predeterminados declarados.

$ python -m proj_X.cli --help
Usage: cli.py [OPTIONS]

Options:
  --api-cache_expire INTEGER  [default: 86400]
  --api-cache_prefix TEXT     [default: proj_X_cache]
  --api-url_base_path TEXT    [default: api/v2/]
  --db-db_name TEXT           [default: proj_x]
  --db-username TEXT          [default: proj_x_admin]
  --db-password TEXT          [default: your_password]
  --db-host TEXT              [default: 127.0.0.1]
  --help                      Show this message and exit.

Cargando variables de entorno

Solo necesita una línea para cargar variables de entorno

conf.load_envvars('proj_X')

> lo siento, no es argparse, pero hacer clic es mejor y mucho más avanzado […] Independientemente de los méritos de una biblioteca de terceros, eso hace que esto no sea una respuesta a la pregunta.
bignose
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.