Analizuj pliki konfiguracyjne, środowisko i argumenty wiersza polecenia, aby uzyskać jedną kolekcję opcji

110

Standardowa biblioteka Pythona zawiera moduły do analizowania plików konfiguracyjnych ( configparser ), odczytu zmiennych środowiskowych ( os.environ ) i analizowania argumentów wiersza poleceń ( argparse ). Chcę napisać program, który robi to wszystko, a także:

  • Ma kaskadę wartości opcji :

    • domyślne wartości opcji, nadpisane przez
    • config pliku, nadpisane przez
    • zmienne środowiskowe, nadpisane przez
    • opcje wiersza poleceń.
  • Zezwala na jedną lub więcej lokalizacji plików konfiguracyjnych określonych w wierszu poleceń np. --config-file foo.confI odczytuje je (zamiast lub oprócz zwykłego pliku konfiguracyjnego). To nadal musi być zgodne z powyższą kaskadą.

  • Umożliwia definicje opcji w jednym miejscu w celu określenia zachowania podczas analizowania plików konfiguracyjnych i wiersza poleceń.

  • Łączy przeanalizowane opcje w jeden zbiór wartości opcji, do których reszta programu będzie miała dostęp bez dbania o to, skąd pochodzą.

Wszystko, czego potrzebuję, znajduje się najwyraźniej w standardowej bibliotece Pythona, ale nie współpracują ze sobą płynnie.

Jak mogę to osiągnąć przy minimalnym odchyleniu od standardowej biblioteki Pythona?

duży nos
źródło
6
Naprawdę podoba mi się to pytanie. Od dawna zastanawiałem się nad zrobieniem czegoś takiego ... Cieszę się, że jterracedałem tu nagrodę, która popchnęła mnie na tyle, by spróbować swoich sił w zrobieniu czegoś takiego :)
mgilson
4
Świetne pytanie! To niesamowite, że popularny pakiet (lub sama biblioteka standardowa) nie rozwiązał tego dawno temu.
Zearin

Odpowiedzi:

33

Moduł argparse sprawia, że ​​nie jest to szalone, o ile jesteś zadowolony z pliku konfiguracyjnego, który wygląda jak wiersz poleceń. (Myślę, że to zaleta, ponieważ użytkownicy będą musieli nauczyć się tylko jednej składni). Ustawienie fromfile_prefix_chars na, na przykład @, sprawia, że

my_prog --foo=bar

jest równa

my_prog @baz.conf

jeśli @baz.conftak,

--foo
bar

Możesz nawet foo.confautomatycznie wyszukiwać kod, modyfikującargv

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

Format tych plików konfiguracyjnych można modyfikować, tworząc podklasę ArgumentParser i dodając metodę convert_arg_line_to_args .

Alex Szatmary
źródło
Dopóki ktoś nie zapewni lepszej alternatywy, to jest właściwa odpowiedź. Używałem argparse i nawet nie patrzyłem na tę funkcję. Miły!
Lemur
ale to nie ma odpowiedzi na zmienne środowiskowe?
jterrace
1
@jterrace: Ta odpowiedź SO może zadziałać dla Ciebie: stackoverflow.com/a/10551190/400793
Alex Szatmary
27

AKTUALIZACJA: W końcu zabrałem się za umieszczenie tego na pypi. Zainstaluj najnowszą wersję przez:

   pip install configargparser

Pełna pomoc i instrukcje są tutaj .

Oryginalny post

Oto małe coś, co razem zhakowałem. Zapraszam do sugerowania ulepszeń / raportów o błędach w komentarzach:

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

DO ZROBIENIA

Ta implementacja jest wciąż niekompletna. Oto częściowa lista rzeczy do zrobienia:

Zgodność z udokumentowanym zachowaniem

  • (łatwy) Napisz funkcję, która domyśla się destod argsw add_argument, zamiast opierania się na Actionobiekcie
  • (trywialne) Napisz parse_argsfunkcję, która używa parse_known_args. (np. kopia parse_argsz cpythonimplementacji, aby zagwarantować, że wywołuje parse_known_args.)

Mniej łatwe rzeczy…

Jeszcze tego nie próbowałem. Jest mało prawdopodobne - ale nadal możliwe! - że może po prostu zadziałać…

mgilson
źródło
czy możesz wrzucić to do repozytorium github, aby każdy mógł to poprawić?
brent.payne
1
@ brent.payne - github.com/mgilson/configargparser - Jeśli zamierzam wydać to jako prawdziwy kod, postanowiłem dziś wieczorem poświęcić trochę czasu, aby go trochę wyczyścić. :-)
mgilson
3
FWIW, w końcu udało mi się umieścić to na pypi - powinieneś być w stanie zainstalować to przezpip install configargparser
mgilson
@mgilson - zaktualizowałem Twój post. Ten pakiet zasługuje na większe wykorzystanie!
ErichBSchulz
12

Jest biblioteka, która robi dokładnie to o nazwie configglue .

configglue to biblioteka, która skleja ze sobą optparse.OptionParser i ConfigParser.ConfigParser języka Python, dzięki czemu nie musisz się powtarzać, gdy chcesz wyeksportować te same opcje do pliku konfiguracyjnego i interfejsu wiersza poleceń.

To także obsługuje zmienne środowiskowe.

Istnieje również inna biblioteka o nazwie ConfigArgParse, która jest

Drop-in zamiennik argparse, który umożliwia ustawianie opcji również za pomocą plików konfiguracyjnych i / lub zmiennych środowiskowych.

Może Cię zainteresuje PyCon, rozmowa o konfiguracji autorstwa Łukasza Langi - Let Them Configure!

Piotr Dobrogost
źródło
I zapytał , czy są jakieś plany obsługujące moduł argparse.
Piotr Dobrogost
10

Chociaż nie próbowałem tego samodzielnie, istnieje biblioteka ConfigArgParse, która stwierdza, że ​​robi większość rzeczy, które chcesz:

Drop-in zamiennik argparse, który umożliwia ustawianie opcji również za pomocą plików konfiguracyjnych i / lub zmiennych środowiskowych.

rutsky
źródło
1
Wypróbowałem to, ConfigArgParse jest bardzo wygodny i rzeczywiście jest zamiennikiem.
maxschlepzig
7

Wydaje się, że biblioteka standardowa nie rozwiązuje to, pozostawiając każdy programista do brukowanych configparseri argparsei os.environwszyscy razem na przylegający sposobów.

duży nos
źródło
5

O ile wiem, standardowa biblioteka Pythona tego nie zapewnia. Rozwiązałem to samodzielnie, pisząc kod do użycia optparseiConfigParser przeanalizowałem wiersz poleceń i pliki konfiguracyjne, a także zapewniłem na nich warstwę abstrakcji. Jednak potrzebowałbyś tego jako osobnej zależności, co z twojego wcześniejszego komentarza wydaje się nie do przyjęcia.

Jeśli chcesz spojrzeć na kod, który napisałem, jest on pod adresem http://liw.fi/cliapp/ . Jest zintegrowany z moją biblioteką „struktury aplikacji wiersza poleceń”, ponieważ to duża część tego, co musi robić framework.


źródło
4

Ostatnio próbowałem czegoś takiego, używając „optparse”.

Ustawiłem go jako podklasę OptonParser z poleceniami „--Store” i „--Check”.

Poniższy kod powinien prawie Cię pokryć. Wystarczy, że zdefiniujesz własne metody „load” i „store”, które akceptują / zwracają słowniki, i jesteś bardzo gotowy.


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
suki
źródło
ale nadal przydatne, jeśli chcesz zachować kompatybilność ze starszymi wersjami Pythona
MarioVilas
3

Aby spełnić wszystkie te wymagania, zaleciłbym napisanie własnej biblioteki, która używa zarówno parsowania [opt | arg], jak i configparser dla podstawowej funkcjonalności.

Biorąc pod uwagę pierwsze dwa i ostatni warunek, powiedziałbym, że chcesz:

Krok pierwszy: wykonaj przebieg parsera wiersza poleceń, który szuka tylko opcji --config-file.

Krok drugi: przeanalizuj plik konfiguracyjny.

Krok trzeci: skonfiguruj drugi przebieg parsera wiersza poleceń, używając danych wyjściowych przebiegu pliku konfiguracyjnego jako wartości domyślnych.

Trzecie wymaganie prawdopodobnie oznacza, że ​​musisz zaprojektować swój własny system definicji opcji, aby ujawnić wszystkie funkcje optparse i configparser, na których Ci zależy, oraz napisać kilka instalacji hydraulicznych, aby wykonać konwersje pomiędzy nimi.

Russell Borogove
źródło
Jest to raczej dalsze od „minimalnego odchylenia od standardowej biblioteki Pythona”, niż się spodziewałem.
bignose
2

Oto moduł, który zhakowałem razem, który odczytuje argumenty wiersza poleceń, ustawienia środowiska, pliki ini i wartości kluczy. Jest również dostępny w skrócie .

"""
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
Ziad Sawalha
źródło
-1

Biblioteka Confect I zbudowany jest właśnie spełniają większość wymagań.

  • Może wielokrotnie ładować plik konfiguracyjny poprzez podane ścieżki do plików lub nazwę modułu.
  • Ładuje konfiguracje ze zmiennych środowiskowych z podanym przedrostkiem.
  • Może dołączyć opcje wiersza poleceń do kliknięcia poleceń

    (przepraszam, to nie jest argumentacja, ale kliknięcie jest lepsze i znacznie bardziej zaawansowane.confect Może obsługiwać argparse w przyszłej wersji).

  • Co najważniejsze, confectładuje pliki konfiguracyjne Pythona, a nie JSON / YMAL / TOML / INI. Podobnie jak plik profilu IPython lub plik ustawień DJANGO, plik konfiguracyjny Pythona jest elastyczny i łatwiejszy w utrzymaniu.

Więcej informacji można znaleźć w pliku README.rst w repozytorium projektu . Należy pamiętać, że obsługuje on tylko Python3.6 w górę.

Przykłady

Dołączanie opcji wiersza poleceń

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

Automatycznie tworzy obszerną wiadomość pomocy ze wszystkimi zadeklarowanymi właściwościami i wartościami domyślnymi.

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

Ładowanie zmiennych środowiskowych

Do załadowania zmiennych środowiskowych wystarczy jedna linia

conf.load_envvars('proj_X')
d2207197
źródło
> przepraszam, to nie argparse, ale klikanie jest lepsze i dużo bardziej zaawansowane […] Niezależnie od zalet biblioteki zewnętrznej, nie jest to odpowiedź na pytanie.
bignose