Parse file konfigurasi, lingkungan, dan argumen baris perintah, untuk mendapatkan satu kumpulan opsi

110

Pustaka standar Python memiliki modul untuk penguraian file konfigurasi ( configparser ), pembacaan variabel lingkungan ( os.environ ), dan penguraian argumen baris perintah ( argparse ). Saya ingin menulis program yang melakukan semua itu, dan juga:

  • Memiliki rangkaian nilai opsi :

    • nilai opsi default, diganti dengan
    • opsi file konfigurasi, diganti dengan
    • variabel lingkungan, diganti dengan
    • opsi baris perintah.
  • Mengizinkan satu atau lebih lokasi file konfigurasi yang ditentukan pada baris perintah dengan misalnya --config-file foo.conf, dan membacanya (baik sebagai pengganti, atau tambahan, file konfigurasi biasa). Ini masih harus mematuhi kaskade di atas.

  • Memungkinkan definisi opsi di satu tempat untuk menentukan perilaku parsing untuk file konfigurasi dan baris perintah.

  • Menyatukan opsi yang diurai menjadi satu kumpulan nilai opsi untuk akses program lainnya tanpa peduli dari mana asalnya.

Semua yang saya butuhkan tampaknya ada di pustaka standar Python, tetapi mereka tidak bekerja sama dengan lancar.

Bagaimana saya bisa mencapai ini dengan deviasi minimum dari pustaka standar Python?

hidung besar
sumber
6
Saya sangat suka pertanyaan ini. Saya telah mempertimbangkan untuk melakukan hal seperti ini untuk waktu yang lama ... Saya senang jterracememberikan hadiah di sini untuk mendorong saya cukup keras untuk mencoba melakukan sesuatu seperti ini :)
mgilson
4
Pertanyaan bagus ! Sungguh menakjubkan bahwa ini belum dipecahkan oleh paket populer (atau oleh pustaka standar itu sendiri) sejak lama.
Zearin

Jawaban:

33

Modul argparse membuatnya tidak murahan, selama Anda senang dengan file konfigurasi yang terlihat seperti baris perintah. (Saya pikir ini adalah keuntungan, karena pengguna hanya perlu mempelajari satu sintaks.) Pengaturan fromfile_prefix_chars ke, misalnya @, membuatnya jadi,

my_prog --foo=bar

setara dengan

my_prog @baz.conf

jika @baz.confada,

--foo
bar

Anda bahkan dapat melihat kode Anda foo.confsecara otomatis dengan memodifikasiargv

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

Format file konfigurasi ini dapat dimodifikasi dengan membuat subclass dari ArgumentParser dan menambahkan metode convert_arg_line_to_args .

Alex Szatmary
sumber
Sampai seseorang memberikan alternatif yang lebih baik, inilah jawaban yang tepat. Saya telah menggunakan argparse, dan bahkan tidak melihat fitur ini. Bagus!
Lemur
tetapi ini tidak memiliki jawaban untuk variabel lingkungan?
jterrace
1
@jterrace: Jawaban SO ini mungkin cocok untuk Anda: stackoverflow.com/a/10551190/400793
Alex Szatmary
27

UPDATE: Saya akhirnya sempat meletakkan ini di pypi. Instal versi terbaru melalui:

   pip install configargparser

Bantuan dan instruksi lengkap ada di sini .

Posting asli

Ini sedikit sesuatu yang saya retas bersama. Jangan ragu untuk menyarankan perbaikan / laporan bug di komentar:

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

MELAKUKAN

Implementasi ini masih belum selesai. Berikut sebagian daftar TODO:

Sesuai dengan perilaku yang didokumentasikan

  • (mudah) Tuliskan fungsi yang menggambarkan destdari argsdalam add_argument, alih-alih mengandalkan Actionobjek
  • (sepele) Tulis parse_argsfungsi yang digunakan parse_known_args. (misalnya menyalin parse_argsdari cpythonimplementasi untuk menjamin panggilannya parse_known_args.)

Hal yang Kurang Mudah…

Saya belum mencoba semua ini. Ini tidak mungkin — tapi masih mungkin! —Bahwa itu bisa berhasil…

  • (sulit?) Saling Pengecualian
  • (hard?) Grup Argumen (Jika diterapkan, grup ini harus mendapatkan a sectiondi file konfigurasi.)
  • (hard?) Perintah Sub (Sub-perintah juga harus mendapatkan a sectiondi file konfigurasi.)
mgilson.dll
sumber
apakah Anda keberatan untuk memasukkan ini ke dalam repo github sehingga semua orang dapat meningkatkannya?
brent.payne
1
@ brent.payne - github.com/mgilson/configargparser - Jika saya akan merilis ini sebagai kode asli, saya memutuskan untuk meluangkan sedikit waktu malam ini untuk membersihkannya sedikit. :-)
mgilson
3
FWIW, saya akhirnya bisa memasang ini di pypi - Anda harus dapat menginstalnya melaluipip install configargparser
mgilson
@mgilson - Saya memperbarui kiriman Anda. Paket ini layak lebih banyak digunakan!
ErichBSchulz
12

Ada perpustakaan yang melakukan hal ini yang disebut configglue .

configglue adalah pustaka yang menempelkan optparse.OptionParser dan ConfigParser.ConfigParser python, sehingga Anda tidak perlu mengulanginya sendiri saat ingin mengekspor opsi yang sama ke file konfigurasi dan antarmuka baris perintah.

Ini juga mendukung variabel lingkungan.

Ada juga perpustakaan lain yang disebut ConfigArgParse yaitu

Pengganti drop-in untuk argparse yang memungkinkan opsi juga disetel melalui file konfigurasi dan / atau variabel lingkungan.

Anda mungkin tertarik dengan PyCon berbicara tentang konfigurasi oleh Łukasz Langa - Biarkan Mereka Mengkonfigurasi!

Piotr Dobrogost
sumber
Saya bertanya apakah ada rencana untuk mendukung modul argparse.
Piotr Dobrogost
10

Meskipun saya belum mencobanya sendiri, ada pustaka ConfigArgParse yang menyatakan bahwa ia melakukan sebagian besar hal yang Anda inginkan:

Pengganti drop-in untuk argparse yang memungkinkan opsi juga disetel melalui file konfigurasi dan / atau variabel lingkungan.

rutsky
sumber
1
Saya mencobanya, ConfigArgParse sangat nyaman dan memang pengganti drop-in.
maxschlepzig
7

Tampaknya pustaka standar tidak menangani hal ini, membiarkan setiap pemrogram menjadi berbelit configparser- belit dan argparsedan os.environsemuanya bersama-sama dengan cara yang kikuk.

hidung besar
sumber
5

Perpustakaan standar Python tidak menyediakan ini, sejauh yang saya tahu. Saya menyelesaikan ini sendiri dengan menulis kode untuk digunakan optparsedan ConfigParseruntuk mengurai baris perintah dan file konfigurasi, dan memberikan lapisan abstraksi di atasnya. Namun, Anda akan membutuhkan ini sebagai ketergantungan terpisah, yang dari komentar Anda sebelumnya tampaknya tidak menyenangkan.

Kalau mau lihat kode yang saya tulis di http://liw.fi/cliapp/ . Ini terintegrasi ke dalam pustaka "kerangka aplikasi baris perintah" saya, karena itulah bagian besar dari apa yang perlu dilakukan kerangka kerja.


sumber
4

Saya mencoba sesuatu seperti ini baru-baru ini, menggunakan "optparse".

Saya mengaturnya sebagai sub-kelas OptonParser, dengan perintah '--Store' dan '--Check'.

Kode di bawah ini seharusnya sudah cukup banyak untuk Anda. Anda hanya perlu menentukan metode 'muat' dan 'simpan' Anda sendiri yang menerima / mengembalikan kamus dan Anda sudah siap.


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
sumber
tetapi masih berguna jika Anda ingin tetap kompatibel dengan versi Python yang lebih lama
MarioVilas
3

Untuk memenuhi semua persyaratan tersebut, saya akan merekomendasikan untuk menulis perpustakaan Anda sendiri yang menggunakan [opt | arg] parse dan configparser untuk fungsionalitas yang mendasarinya.

Diberikan dua persyaratan pertama dan terakhir, saya katakan Anda menginginkan:

Langkah satu: Lakukan parser baris perintah yang hanya mencari opsi --config-file.

Langkah kedua: Parse file konfigurasi.

Langkah ketiga: siapkan pass parser baris perintah kedua menggunakan output pass file konfigurasi sebagai default.

Persyaratan ketiga kemungkinan berarti Anda harus merancang sistem definisi opsi Anda sendiri untuk mengekspos semua fungsionalitas optparse dan configparser yang Anda minati, dan menulis beberapa saluran air untuk melakukan konversi di antaranya.

Russell Borogove
sumber
Ini lebih jauh dari "deviasi minimum dari pustaka standar Python" daripada yang saya harapkan.
bignose
2

Berikut adalah modul yang saya retas bersama yang membaca argumen baris perintah, pengaturan lingkungan, file ini, dan nilai keyring juga. Ini juga tersedia dalam intinya .

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

Anda dapat menggunakan ChainMap untuk ini. Lihat contoh saya yang saya berikan di "Manakah cara terbaik untuk mengizinkan opsi konfigurasi diganti pada baris perintah dengan Python?" SO pertanyaan.

Vlad Bezden
sumber
-1

Konfigurasi perpustakaan yang saya bangun tepat untuk memenuhi sebagian besar kebutuhan Anda.

  • Itu dapat memuat file konfigurasi beberapa kali melalui jalur file atau nama modul yang diberikan.
  • Ini memuat konfigurasi dari variabel lingkungan dengan awalan yang diberikan.
  • Itu dapat melampirkan opsi baris perintah ke beberapa klik perintah

    (maaf, ini bukan argparse, tapi klik lebih baik dan lebih canggih.confect mungkin mendukung argparse di rilis mendatang).

  • Yang terpenting, confectmuat file konfigurasi Python, bukan JSON / YMAL / TOML / INI. Sama seperti file profil IPython atau file pengaturan DJANGO, file konfigurasi Python fleksibel dan lebih mudah untuk dipelihara.

Untuk informasi lebih lanjut, silakan periksa README.rst di repositori proyek . Ketahuilah bahwa ini hanya mendukung Python3.6 up.

Contoh

Melampirkan opsi baris perintah

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

Ini secara otomatis membuat pesan bantuan yang komprehensif dengan semua properti dan nilai default yang dideklarasikan.

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

Memuat variabel lingkungan

Ini hanya membutuhkan satu baris untuk memuat variabel lingkungan

conf.load_envvars('proj_X')
d2207197
sumber
> maaf, ini bukan argparse, tetapi klik lebih baik dan jauh lebih canggih […] Terlepas dari manfaat pustaka pihak ketiga, itu membuat ini bukan jawaban untuk pertanyaan.
bignose