Analysieren Sie Konfigurationsdateien, Umgebungs- und Befehlszeilenargumente, um eine einzige Sammlung von Optionen zu erhalten

110

Die Standardbibliothek von Python enthält Module für das Parsen von Konfigurationsdateien ( configparser ), das Lesen von Umgebungsvariablen ( os.environ ) und das Parsen von Befehlszeilenargumenten ( argparse ). Ich möchte ein Programm schreiben, das all das kann, und auch:

  • Hat eine Kaskade von Optionswerten :

    • Standardoptionswerte, überschrieben von
    • Konfigurationsdateioptionen, überschrieben von
    • Umgebungsvariablen, überschrieben von
    • Befehlszeilenoptionen.
  • Ermöglicht einen oder mehrere in der Befehlszeile mit z. B. angegebene Speicherorte für Konfigurationsdateien--config-file foo.conf und liest diesen (entweder anstelle oder zusätzlich zu der üblichen Konfigurationsdatei). Dies muss immer noch der obigen Kaskade entsprechen.

  • Ermöglicht Optionsdefinitionen an einem einzigen Ort , um das Analyseverhalten für Konfigurationsdateien und die Befehlszeile zu bestimmen.

  • Vereinheitlicht die analysierten Optionen in einer einzigen Sammlung von Optionswerten, auf die der Rest des Programms zugreifen kann, ohne sich darum zu kümmern, woher sie stammen.

Alles, was ich brauche, befindet sich anscheinend in der Python-Standardbibliothek, aber sie arbeiten nicht reibungslos zusammen.

Wie kann ich dies mit minimaler Abweichung von der Python-Standardbibliothek erreichen?

große Nase
quelle
6
Diese Frage gefällt mir sehr gut. Ich habe lange darüber nachgedacht, so etwas zu tun ... Ich bin froh jterrace, dass ich hier ein Kopfgeld gegeben habe, um mich so weit über den Rand zu schieben, dass ich versuchen kann, so etwas zu tun :)
mgilson
4
Ausgezeichnete Frage! Es ist erstaunlich, dass dies vor langer Zeit nicht durch ein beliebtes Paket (oder durch die Standardbibliothek selbst) gelöst wurde.
Zearin

Antworten:

33

Das argparse-Modul macht dies nicht verrückt, solange Sie mit einer Konfigurationsdatei zufrieden sind, die wie eine Befehlszeile aussieht. (Ich denke, dies ist ein Vorteil, da Benutzer nur eine Syntax lernen müssen.) Wenn Sie beispielsweise fromfile_prefix_chars auf einstellen , wird Folgendes@ erreicht:

my_prog --foo=bar

ist äquivalent zu

my_prog @baz.conf

wenn @baz.confja,

--foo
bar

Sie können Ihren Code sogar foo.confautomatisch ändern lassen, indem Sie ihn ändernargv

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

Das Format dieser Konfigurationsdateien kann geändert werden, indem eine Unterklasse von ArgumentParser erstellt und eine Methode convert_arg_line_to_args hinzugefügt wird.

Alex Szatmary
quelle
Bis jemand eine bessere Alternative bietet, ist dies die richtige Antwort. Ich habe Argparse verwendet und mir diese Funktion nicht einmal angesehen. Nett!
Lemur
Aber dies hat keine Antwort auf Umgebungsvariablen?
Jterrace
1
@jterrace: Diese SO-Antwort kann für Sie arbeiten: stackoverflow.com/a/10551190/400793
Alex Szatmary
27

UPDATE: Ich bin endlich dazu gekommen, dies auf Pypi zu setzen. Installieren Sie die neueste Version über:

   pip install configargparser

Vollständige Hilfe und Anweisungen finden Sie hier .

Ursprünglicher Beitrag

Hier ist etwas, das ich zusammen gehackt habe. Fühlen Sie sich frei, Verbesserungen / Fehlerberichte in den Kommentaren vorzuschlagen:

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

MACHEN

Diese Implementierung ist noch unvollständig. Hier ist eine teilweise TODO-Liste:

Entspricht dem dokumentierten Verhalten

  • (leicht) eine Funktion schreiben , die Figuren aus destvon argsin add_argument, anstatt sich auf die zu verlassen ActionObjekt
  • (trivial) Schreiben Sie eine parse_argsFunktion, die verwendet parse_known_args. (zB Kopie parse_argsvon der cpythonImplementierung, um sicherzustellen, dass sie aufgerufen wird parse_known_args.)

Weniger einfaches Zeug…

Ich habe noch nichts davon ausprobiert. Es ist unwahrscheinlich - aber immer noch möglich! -, dass es einfach funktionieren könnte ...

  • (schwer?) Gegenseitiger Ausschluss
  • (schwer?) Argumentgruppen (Wenn implementiert, sollten diese Gruppen eine sectionin der Konfigurationsdatei erhalten.)
  • (schwer?) Unterbefehle (Unterbefehle sollten auch sectionin der Konfigurationsdatei einen erhalten.)
mgilson
quelle
Stört es Sie, dies in ein Github-Repo zu werfen, damit jeder dies verbessern kann?
brent.payne
1
@ brent.payne - github.com/mgilson/configargparser - Wenn ich dies als echten Code veröffentlichen will , habe ich beschlossen, mir heute Abend etwas Zeit zu nehmen, um es ein wenig aufzuräumen. :-)
mgilson
3
FWIW, ich bin endlich dazu gekommen, dies auf pypi zu setzen - Sie sollten es überpip install configargparser
mgilson
@mgilson - Ich habe deinen Beitrag aktualisiert. Dieses Paket verdient mehr Verwendung!
ErichBSchulz
12

Es gibt eine Bibliothek, die genau dies tut und als Konfigurationskleber bezeichnet wird .

configglue ist eine Bibliothek, die die Optionen optparse.OptionParser und ConfigParser.ConfigParser von Python zusammenfügt, sodass Sie sich nicht wiederholen müssen, wenn Sie dieselben Optionen in eine Konfigurationsdatei und eine Befehlszeilenschnittstelle exportieren möchten.

Es werden auch Umgebungsvariablen unterstützt .

Es gibt auch eine andere Bibliothek namens ConfigArgParse , die ist

Ein Drop-In-Ersatz für argparse, mit dem Optionen auch über Konfigurationsdateien und / oder Umgebungsvariablen festgelegt werden können.

Vielleicht interessiert Sie PyCon auch über die Konfiguration von Łukasz Langa - Let Them Configure!

Piotr Dobrogost
quelle
Ich fragte, ob es Pläne gibt, das Argparse-Modul zu unterstützen.
Piotr Dobrogost
10

Obwohl ich es nicht selbst ausprobiert habe, gibt es die ConfigArgParse- Bibliothek, die angibt, dass sie die meisten gewünschten Aufgaben ausführt :

Ein Drop-In-Ersatz für argparse, mit dem Optionen auch über Konfigurationsdateien und / oder Umgebungsvariablen festgelegt werden können.

Rutsky
quelle
1
Ich habe es versucht, ConfigArgParse ist sehr praktisch und in der Tat ein Ersatz.
Maxschlepzig
7

Es scheint , die Standard - Bibliothek ist dies nicht ein , jeden Programmierer zu Cobble verlassen configparserund argparseund os.environalle zusammen in klobigen Wegen.

große Nase
quelle
5

Die Python-Standardbibliothek bietet dies meines Wissens nicht. Ich löste diese für mich durch das Schreiben von Code zu verwenden , optparseund ConfigParserdie Befehlszeilen und Konfigurationsdateien und bietet eine Abstraktionsschicht auf sie zu analysieren. Sie würden dies jedoch als separate Abhängigkeit benötigen, was aus Ihrem früheren Kommentar unangenehm erscheint.

Wenn Sie sich den Code ansehen möchten, den ich geschrieben habe, finden Sie ihn unter http://liw.fi/cliapp/ . Es ist in meine Bibliothek "Command Line Application Framework" integriert, da dies einen großen Teil der Aufgaben des Frameworks ausmacht.


quelle
4

Ich habe kürzlich so etwas mit "optparse" ausprobiert.

Ich habe es als Unterklasse von OptonParser mit den Befehlen '--Store' und '--Check' eingerichtet.

Der folgende Code sollte Sie so ziemlich abgedeckt haben. Sie müssen nur Ihre eigenen "Laden" - und "Speichern" -Methoden definieren, die Wörterbücher akzeptieren / zurückgeben, und Sie sind viel Beute.


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
quelle
aber immer noch nützlich, wenn Sie mit älteren Versionen von Python kompatibel bleiben möchten
MarioVilas
3

Um all diese Anforderungen zu erfüllen, würde ich empfehlen, eine eigene Bibliothek zu schreiben, die sowohl [opt | arg] parse als auch configparser für die zugrunde liegende Funktionalität verwendet.

Angesichts der ersten beiden und der letzten Anforderung würde ich sagen, dass Sie Folgendes möchten:

Schritt eins: Führen Sie einen Befehlszeilen-Parser-Pass durch, der nur nach der Option --config-file sucht.

Schritt zwei: Analysieren Sie die Konfigurationsdatei.

Schritt drei: Richten Sie einen zweiten Befehlszeilen-Parser-Pass ein, wobei Sie standardmäßig die Ausgabe des Konfigurationsdateipasses verwenden.

Die dritte Anforderung bedeutet wahrscheinlich, dass Sie Ihr eigenes Optionsdefinitionssystem entwerfen müssen, um alle Funktionen von optparse und configparser verfügbar zu machen, die Ihnen wichtig sind, und einige Installationsarbeiten schreiben müssen, um dazwischen Konvertierungen durchzuführen.

Russell Borogove
quelle
Dies ist etwas weiter von der „minimalen Abweichung von der Python-Standardbibliothek“ entfernt, als ich gehofft hatte.
Bignose
2

Hier ist ein Modul, das ich zusammen gehackt habe und das auch Befehlszeilenargumente, Umgebungseinstellungen, INI-Dateien und Schlüsselringwerte liest. Es ist auch im Kern verfügbar .

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

Sie können hierfür ChainMap verwenden. Schauen Sie sich mein Beispiel an, das ich in "Wie lassen sich Konfigurationsoptionen in der Befehlszeile in Python am besten überschreiben?" SO Frage.

Vlad Bezden
quelle
-1

Die Bibliothek confect I gebaut ist genau die meisten Ihrer Bedürfnisse zu erfüllen.

  • Es kann die Konfigurationsdatei mehrmals über bestimmte Dateipfade oder Modulnamen laden.
  • Es lädt Konfigurationen aus Umgebungsvariablen mit einem bestimmten Präfix.
  • Es kann Kommandozeilen - Optionen zu einigen anhängen Klick - Befehle

    (Entschuldigung, es ist kein Argparse, aber das Klicken ist besser und viel fortgeschrittener. confectMöglicherweise wird Argparse in der zukünftigen Version unterstützt.)

  • Am wichtigsten ist, dass confectPython-Konfigurationsdateien geladen werden, nicht JSON / YMAL / TOML / INI. Genau wie die IPython-Profildatei oder die DJANGO-Einstellungsdatei ist die Python-Konfigurationsdatei flexibel und einfacher zu warten.

Weitere Informationen finden Sie in der README.rst im Projekt-Repository . Beachten Sie, dass nur Python3.6 unterstützt wird.

Beispiele

Anhängen von Befehlszeilenoptionen

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

Es wird automatisch eine umfassende Hilfemeldung mit allen deklarierten Eigenschaften und Standardwerten erstellt.

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

Umgebungsvariablen laden

Es wird nur eine Zeile zum Laden von Umgebungsvariablen benötigt

conf.load_envvars('proj_X')
d2207197
quelle
> Entschuldigung, es ist kein Streit, aber das Klicken ist besser und viel fortgeschrittener. […] Unabhängig von den Vorzügen einer Bibliothek eines Drittanbieters ist dies keine Antwort auf die Frage.
Bignose