Phân tích cú pháp tệp cấu hình, môi trường và đối số dòng lệnh, để có được một bộ sưu tập các tùy chọn


110

Thư viện chuẩn của Python có các mô-đun để phân tích cú pháp tệp cấu hình ( configparser ), đọc biến môi trường ( os.environ ) và phân tích cú pháp đối số dòng lệnh ( argparse ). Tôi muốn viết một chương trình thực hiện tất cả những điều đó, và cả:

  • Có một loạt các giá trị tùy chọn :

    • giá trị tùy chọn mặc định, bị ghi đè bởi
    • tùy chọn tệp cấu hình, bị ghi đè bởi
    • biến môi trường, bị ghi đè bởi
    • tùy chọn dòng lệnh.
  • Cho phép một hoặc nhiều vị trí tệp cấu hình được chỉ định trên dòng lệnh với ví dụ --config-file foo.conf, và đọc vị trí đó (thay vì hoặc bổ sung vào tệp cấu hình thông thường). Điều này vẫn phải tuân theo các tầng trên.

  • Cho phép các định nghĩa tùy chọn ở một nơi duy nhất để xác định hành vi phân tích cú pháp cho các tệp cấu hình và dòng lệnh.

  • Hợp nhất các tùy chọn đã phân tích cú pháp thành một tập hợp các giá trị tùy chọn duy nhất để phần còn lại của chương trình có thể truy cập mà không cần quan tâm chúng đến từ đâu.

Mọi thứ tôi cần dường như nằm trong thư viện chuẩn Python, nhưng chúng không hoạt động trơn tru với nhau.

Làm cách nào để đạt được điều này với độ lệch tối thiểu so với thư viện chuẩn Python?


6
Tôi thực sự thích câu hỏi này. Tôi đã xem xét làm một cái gì đó như thế này trong một thời gian dài ... Tôi rất vui vì jterraceđã đưa ra một tiền thưởng vào đây để đẩy tôi qua đủ cạnh để thử tay của tôi lúc làm một cái gì đó giống như :) này
mgilson

4
Câu hỏi xuất sắc ! Thật ngạc nhiên là cách đây rất lâu rồi điều này đã không được giải quyết bởi một gói phổ biến (hoặc bởi chính thư viện tiêu chuẩn).
Zearin

Câu trả lời:


33

Mô-đun argparse làm cho điều này không thành vấn đề, miễn là bạn hài lòng với một tệp cấu hình trông giống như dòng lệnh. (Tôi nghĩ đây là một lợi thế, bởi vì người dùng sẽ chỉ phải học một cú pháp.) Ví dụ: đặt fromfile_prefix_chars thành @, làm cho nó trở nên như vậy,

my_prog --foo=bar

tương đương với

my_prog @baz.conf

nếu @baz.conflà,

--foo
bar

Bạn thậm chí có thể foo.conftự động tìm kiếm mã của mình bằng cách sửa đổiargv

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

Định dạng của các tệp cấu hình này có thể sửa đổi được bằng cách tạo một lớp con của ArgumentParser và thêm phương thức convert_arg_line_to_args .


Cho đến khi ai đó cung cấp một giải pháp thay thế tốt hơn, đây là câu trả lời phù hợp. Tôi đã sử dụng argparse, và thậm chí không xem xét tính năng này. Đẹp!
Lemur

nhưng điều này không có câu trả lời cho các biến môi trường?
jterrace

1
@jterrace: Câu trả lời SO Điều này có thể làm việc cho bạn: stackoverflow.com/a/10551190/400793
Alex Szatmary

27

CẬP NHẬT: Cuối cùng thì tôi cũng đã đặt được cái này trên pypi. Cài đặt phiên bản mới nhất qua:

   pip install configargparser

Có đầy đủ trợ giúp và hướng dẫn ở đây .

Bài gốc

Đây là một vài thứ mà tôi đã hack cùng nhau. Vui lòng đề xuất các cải tiến / báo cáo lỗi trong phần nhận xét:

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

LÀM

Việc triển khai này vẫn chưa hoàn thiện. Đây là một phần danh sách VIỆC CẦN LÀM:

Tuân theo hành vi được ghi lại

  • (dễ dàng) Viết một hàm tìm ra desttừ bên argstrong add_argument, thay vì dựa vào Actionđối tượng
  • (tầm thường) Viết một parse_argshàm sử dụng parse_known_args. (ví dụ: sao chép parse_argstừ việc cpythontriển khai để đảm bảo nó gọi parse_known_args.)

Nội dung ít dễ dàng hơn…

Tôi chưa thử bất kỳ cái nào trong số này. Điều đó khó xảy ra — nhưng vẫn có thể! —Thì nó có thể hoạt động…


bạn có phiền ném cái này vào github repo để mọi người có thể cải thiện cái này không?
brent.payne

1
@ brent.payne - github.com/mgilson/configargparser - Nếu tôi muốn phát hành mã này dưới dạng mã thực, tôi quyết định dành một chút thời gian tối nay để làm sạch nó một chút. :-)
mgilson

3
FWIW, cuối cùng tôi nhận được xung quanh để đặt điều này trên pypi - Bạn nên có thể cài đặt nó thông quapip install configargparser
mgilson

@mgilson - Tôi đã cập nhật bài đăng của bạn. Gói này xứng đáng được sử dụng nhiều hơn!
ErichBSchulz

12

Có một thư viện thực hiện chính xác điều này được gọi là configglue .

configglue là một thư viện kết hợp optparse.OptionParser và ConfigParser.ConfigParser của python lại với nhau, để bạn không phải lặp lại khi muốn xuất các tùy chọn giống nhau sang tệp cấu hình và giao diện dòng lệnh.

Nó cũng hỗ trợ các biến môi trường.

Ngoài ra còn có một thư viện khác được gọi là ConfigArgParse ,

Một thay thế drop-in cho argparse cho phép các tùy chọn cũng được thiết lập thông qua các tệp cấu hình và / hoặc các biến môi trường.

Bạn có thể quan tâm PyCon nói về cấu hình của Łukasz Langa - Let Them Configure!


Tôi đã hỏi nếu có bất kỳ kế hoạch nào để hỗ trợ mô-đun argparse.
Piotr Dobrogost,

10

Mặc dù tôi chưa tự mình thử, nhưng có thư viện ConfigArgParse nói rằng nó thực hiện hầu hết những điều bạn muốn:

Một thay thế drop-in cho argparse cho phép các tùy chọn cũng được thiết lập thông qua các tệp cấu hình và / hoặc các biến môi trường.


1
Tôi đã thử nó, ConfigArgParse rất tiện lợi và thực sự là một sự thay thế thả xuống.
maxschlepzig

7

Có vẻ như thư viện tiêu chuẩn không giải quyết vấn đề này, khiến mỗi lập trình viên phải tập hợp configparserargparseos.environtất cả lại với nhau theo những cách khó hiểu.


5

Thư viện chuẩn Python không cung cấp điều này, theo như tôi biết. Tôi đã giải quyết vấn đề này cho chính mình bằng cách viết mã để sử dụng optparseConfigParserphân tích cú pháp dòng lệnh và tệp cấu hình, đồng thời cung cấp một lớp trừu tượng ở trên cùng của chúng. Tuy nhiên, bạn sẽ cần điều này như một phần phụ thuộc riêng biệt, mà từ nhận xét trước đó của bạn có vẻ không ổn.

Nếu bạn muốn xem mã mà tôi đã viết, thì đó là tại http://liw.fi/cliapp/ . Nó được tích hợp vào thư viện "khuôn khổ ứng dụng dòng lệnh" của tôi, vì đó là một phần lớn những gì mà khung công tác cần làm.


4

Gần đây, tôi đã thử một cái gì đó như thế này, sử dụng "optparse".

Tôi đã thiết lập nó làm lớp con của OptonParser, với lệnh '--Store' và lệnh '--Check'.

Đoạn mã dưới đây sẽ giúp bạn hiểu rõ hơn. Bạn chỉ cần xác định các phương thức 'tải' và 'lưu trữ' của riêng mình, các phương thức này chấp nhận / trả lại từ điển và bạn đang chuẩn bị nhiều thứ.


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

nhưng vẫn hữu ích nếu bạn muốn duy trì khả năng tương thích với các phiên bản cũ hơn của Python
MarioVilas

3

Để đạt được tất cả các yêu cầu đó, tôi khuyên bạn nên viết thư viện của riêng bạn sử dụng cả phân tích cú pháp [opt | arg] và configparser cho chức năng cơ bản.

Với hai yêu cầu đầu tiên và yêu cầu cuối cùng, tôi muốn nói rằng bạn muốn:

Bước một: Thực hiện chuyển trình phân tích cú pháp dòng lệnh chỉ tìm tùy chọn --config-file.

Bước hai: Phân tích cú pháp tệp cấu hình.

Bước ba: Thiết lập đường chuyền phân tích cú pháp dòng lệnh thứ hai bằng cách sử dụng đầu ra của đường truyền tệp cấu hình làm giá trị mặc định.

Yêu cầu thứ ba có thể có nghĩa là bạn phải thiết kế hệ thống định nghĩa tùy chọn của riêng mình để hiển thị tất cả chức năng của optparse và configparser mà bạn quan tâm, đồng thời viết một số đường ống dẫn nước để thực hiện chuyển đổi ở giữa.


Điều này là xa hơn từ "độ lệch tối thiểu từ thư viện tiêu chuẩn Python" hơn tôi mong đợi.
bignose

2

Đây là một mô-đun mà tôi đã hack cùng nhau để đọc các đối số dòng lệnh, cài đặt môi trường, tệp ini và các giá trị khóa. Nó cũng có sẵn trong một ý chính .

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

Thư viện confect tôi đã xây dựng được một cách chính xác để đáp ứng hầu hết nhu cầu của bạn.

  • Nó có thể tải tệp cấu hình nhiều lần thông qua các đường dẫn tệp nhất định hoặc tên mô-đun.
  • Nó tải các cấu hình từ các biến môi trường với một tiền tố nhất định.
  • Nó có thể đính kèm các tùy chọn dòng lệnh cho một số lệnh nhấp chuột

    (xin lỗi, nó không phải là argparse, nhưng click tốt hơn và nâng cao hơn nhiều. confectCó thể hỗ trợ argparse trong bản phát hành trong tương lai).

  • Quan trọng nhất, confecttải các tệp cấu hình Python không phải JSON / YMAL / TOML / INI. Cũng giống như tệp hồ sơ IPython hoặc tệp cài đặt DJANGO, tệp cấu hình Python linh hoạt và dễ bảo trì hơn.

Để biết thêm thông tin, vui lòng kiểm tra README. đầu tiên trong kho dự án . Hãy lưu ý rằng nó chỉ hỗ trợ Python3.6 trở lên.

Ví dụ

Đính kèm các tùy chọn dòng lệnh

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

Nó tự động tạo một thông báo trợ giúp toàn diện với tất cả các thuộc tính và giá trị mặc định được khai báo.

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

Đang tải các biến môi trường

Nó chỉ cần một dòng để tải các biến môi trường

conf.load_envvars('proj_X')

> xin lỗi, nó không phải là argparse, nhưng click tốt hơn và nâng cao hơn nhiều […] Bất kể giá trị của thư viện bên thứ ba, điều đó khiến đây không phải là câu trả lời cho câu hỏi.
bignose 29/09/18
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.