Analyser les fichiers de configuration, l'environnement et les arguments de ligne de commande pour obtenir une seule collection d'options


110

La bibliothèque standard de Python a des modules pour l' analyse des fichiers de configuration ( configparser ), la lecture des variables d'environnement ( os.environ ) et l' analyse des arguments de ligne de commande ( argparse ). Je veux écrire un programme qui fait tout cela, et aussi:

  • A une cascade de valeurs d'option :

    • valeurs d'option par défaut, remplacées par
    • options du fichier de configuration, remplacées par
    • variables d'environnement, remplacées par
    • options de ligne de commande.
  • Autorise un ou plusieurs emplacements de fichier de configuration spécifiés sur la ligne de commande avec par exemple --config-file foo.conf, et lit cela (soit à la place, soit en plus du fichier de configuration habituel). Cela doit toujours obéir à la cascade ci-dessus.

  • Permet aux définitions d'options en un seul endroit de déterminer le comportement d'analyse des fichiers de configuration et de la ligne de commande.

  • Unifie les options analysées en une seule collection de valeurs d'options pour que le reste du programme puisse y accéder sans se soucier d'où elles viennent.

Tout ce dont j'ai besoin se trouve apparemment dans la bibliothèque standard Python, mais ils ne fonctionnent pas correctement ensemble.

Comment puis-je y parvenir avec un écart minimum par rapport à la bibliothèque standard Python?


6
J'aime vraiment cette question. J'envisage de faire quelque chose comme ça depuis longtemps ... Je suis heureux d'avoir jterracedonné une prime ici pour me pousser assez loin pour essayer de faire quelque chose comme ça :)
mgilson

4
Excellente question! C'est étonnant que cela n'ait pas été résolu par un paquet populaire (ou par la bibliothèque standard elle-même) il y a longtemps.
Zearin

Réponses:


33

Le module argparse rend cela pas fou, tant que vous êtes satisfait d'un fichier de configuration qui ressemble à une ligne de commande. (Je pense que c'est un avantage, car les utilisateurs n'auront à apprendre qu'une seule syntaxe.) Définir fromfile_prefix_chars à, par exemple @, fait en sorte que,

my_prog --foo=bar

est équivalent à

my_prog @baz.conf

si @baz.confc'est,

--foo
bar

Vous pouvez même faire rechercher votre code foo.confautomatiquement en modifiantargv

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

Le format de ces fichiers de configuration est modifiable en créant une sous-classe de ArgumentParser et en ajoutant une méthode convert_arg_line_to_args .


Jusqu'à ce que quelqu'un propose une meilleure alternative, c'est la bonne réponse. J'utilise argparse et je n'ai même pas regardé cette fonctionnalité. Agréable!
Lemur

mais cela n'a pas de réponse pour les variables d'environnement?
jterrace

1
@jterrace: Cette réponse SO peut fonctionner pour vous: stackoverflow.com/a/10551190/400793
Alex Szatmary

27

MISE À JOUR: J'ai finalement réussi à mettre cela sur pypi. Installez la dernière version via:

   pip install configargparser

Une aide et des instructions complètes sont disponibles ici .

Message original

Voici un petit quelque chose que j'ai piraté ensemble. N'hésitez pas à suggérer des améliorations / rapports de bogues dans les commentaires:

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

FAIRE

Cette implémentation est encore incomplète. Voici une liste partielle de TODO:

Conforme au comportement documenté

  • (facile) Ecrire une fonction qui comprend dest de argsdans add_argument, au lieu de compter sur l' Actionobjet
  • (trivial) Ecrire une parse_argsfonction qui utiliseparse_known_args . (par exemple une copie parse_argsde l' cpythonimplémentation pour garantir qu'elle appelle parse_known_args.)

Moins de choses faciles…

Je n'ai encore rien essayé de tout ça. Il est peu probable - mais toujours possible! - que cela puisse fonctionner…

  • (dur?) Exclusion mutuelle
  • (difficile?) Groupes d'arguments (s'ils sont implémentés, ces groupes doiventsection dans le fichier de configuration.)
  • (difficile?) Sous-commandes (Les sous-commandes devraient également avoir un sectiondans le fichier de configuration.)

cela vous dérange-t-il de le jeter dans un dépôt github pour que tout le monde puisse s'améliorer?
brent.payne

1
@ brent.payne - github.com/mgilson/configargparser - Si je veux publier ceci en tant que vrai code, j'ai décidé de prendre un peu de temps ce soir pour le nettoyer un peu. :-)
mgilson

3
FWIW, j'ai finalement réussi à mettre cela sur pypi - Vous devriez pouvoir l'installer viapip install configargparser
mgilson

@mgilson - J'ai mis à jour votre message. Ce package mérite plus d'utilisation!
ErichBSchulz

12

Il existe une bibliothèque qui fait exactement cela appelé configglue .

configglue est une bibliothèque qui rassemble les options optparse.OptionParser et ConfigParser.ConfigParser de python, afin que vous n'ayez pas à vous répéter lorsque vous souhaitez exporter les mêmes options vers un fichier de configuration et une interface de ligne de commande.

Il prend également en charge les variables d'environnement.

Il existe également une autre bibliothèque appelée ConfigArgParse qui est

Un remplacement pour argparse qui permet également de définir des options via des fichiers de configuration et / ou des variables d'environnement.

Vous pourriez être intéressé par PyCon parler de configuration par Łukasz Langa - Laissez-les configurer!


J'ai demandé s'il y avait des plans pour supporter le module argparse.
Piotr Dobrogost

10

Bien que je ne l'ai pas essayé par moi-même, il existe la bibliothèque ConfigArgParse qui indique qu'elle fait la plupart des choses que vous voulez:

Un remplacement pour argparse qui permet également de définir des options via des fichiers de configuration et / ou des variables d'environnement.


1
Je l'ai essayé, ConfigArgParse est très pratique et en fait un remplacement instantané.
maxschlepzig

7

Il semble que la bibliothèque standard ne répond pas, laissant chaque programmeur pour bricoler configparseret argparseet os.environtous ensemble de manière maladroites.


5

La bibliothèque standard Python ne fournit pas cela, pour autant que je sache. J'ai résolu cela moi-même en écrivant du code à utiliser optparseetConfigParser à analyser la ligne de commande et les fichiers de configuration, et en fournissant une couche d'abstraction par-dessus. Cependant, vous en auriez besoin en tant que dépendance distincte, ce qui, d'après votre commentaire précédent, ne semble pas acceptable.

Si vous voulez regarder le code que j'ai écrit, c'est sur http://liw.fi/cliapp/ . Il est intégré dans ma bibliothèque "framework d'application en ligne de commande", car c'est une grande partie de ce que le framework doit faire.


4

J'ai récemment essayé quelque chose comme ça, en utilisant "optparse".

Je l'ai configuré comme sous-classe d'OptonParser, avec une commande «--Store» et une commande «--Check».

Le code ci-dessous devrait à peu près vous couvrir. Vous avez juste besoin de définir vos propres méthodes 'load' et 'store' qui acceptent / retournent des dictionnaires et vous êtes une proie bien définie.


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

mais toujours utile si vous souhaitez rester compatible avec les anciennes versions de Python
MarioVilas

3

Pour répondre à toutes ces exigences, je recommanderais d'écrire votre propre bibliothèque qui utilise à la fois [opt | arg] parse et configparser pour la fonctionnalité sous-jacente.

Compte tenu des deux premières et de la dernière exigence, je dirais que vous voulez:

Première étape: effectuez une passe d'analyseur de ligne de commande qui ne recherche que l'option --config-file.

Deuxième étape: analysez le fichier de configuration.

Troisième étape: configurez une deuxième passe d'analyseur de ligne de commande en utilisant la sortie de la passe du fichier de configuration comme valeur par défaut.

La troisième exigence signifie probablement que vous devez concevoir votre propre système de définition d'options pour exposer toutes les fonctionnalités d'optparse et de configparser qui vous intéressent, et rédiger un peu de plomberie pour effectuer des conversions entre les deux.


C'est plutôt plus éloigné de «l'écart minimum par rapport à la bibliothèque standard Python» que je ne l'espérais.
bignose

2

Voici un module que j'ai piraté ensemble qui lit les arguments de ligne de commande, les paramètres d'environnement, les fichiers ini et les valeurs de trousseau de clés. Il est également disponible dans un résumé .

"""
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

Le confect de bibliothèque que j'ai construit est précisément pour répondre à la plupart de vos besoins.

  • Il peut charger le fichier de configuration plusieurs fois via des chemins de fichiers ou un nom de module donnés.
  • Il charge les configurations à partir de variables d'environnement avec un préfixe donné.
  • Il peut attacher des options de ligne de commande à certaines commandes de clic

    (désolé, ce n'est pas argparse, mais le clic est meilleur et beaucoup plus avancé. confectpourrait prendre en charge argparse dans la prochaine version).

  • Plus important encore, confectcharge les fichiers de configuration Python et non JSON / YMAL / TOML / INI. Tout comme le fichier de profil IPython ou le fichier de paramètres DJANGO, le fichier de configuration Python est flexible et plus facile à gérer.

Pour plus d'informations, veuillez consulter le README.rst dans le référentiel du projet . Sachez qu'il ne prend en charge que Python3.6 up.

Exemples

Joindre des options de ligne de commande

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

Il crée automatiquement un message d'aide complet avec toutes les propriétés et valeurs par défaut déclarées.

$ 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.

Chargement des variables d'environnement

Il suffit d'une seule ligne pour charger les variables d'environnement

conf.load_envvars('proj_X')

> désolé, ce n'est pas argparse, mais le clic est meilleur et beaucoup plus avancé […] Indépendamment des mérites d'une bibliothèque tierce, cela n'en fait pas une réponse à la question.
bignose
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.