Parse config files, environment и аргументы командной строки, чтобы получить один набор параметров

Стандартная библиотека Python имеет модули для синтаксического анализа конфигурационных файлов ( configparser ), чтения переменных среды ( os.environ ) и анализа аргументов командной строки ( argparse ). Я хочу написать программу, которая делает все это, а также:

Все, что мне нужно, очевидно, находится в стандартной библиотеке Python, но они не работают вместе плавно.

Как я могу достичь этого с минимальным отклонением от стандартной библиотеки Python?

  • Каков самый простой кросс-платформенный способ отображения графических диалогов в Python?
  • Память Нд-Аррая
  • Подача данных изображения в тензорном потоке для передачи обучения
  • Что именно импортирует импорт?
  • Имитировать Python нажатия клавиш для управления игрой
  • Вложенная перекрестная проверка в сетке поиска предварительно вычисленных ядер в scikit-learn
  • Как отключить ведение журнала во время выполнения модульных тестов в Python Django?
  • Как обновить сам пипс внутри моей виртуальной среды?
  • 8 Solutions collect form web for “Parse config files, environment и аргументы командной строки, чтобы получить один набор параметров”

    Модуль argparse делает это не орехами, если вы довольны конфигурационным файлом, который выглядит как командная строка. (Я думаю, что это преимущество, потому что пользователям нужно будет только изучить один синтаксис.) Настройка fromfile_prefix_chars , например, @ , делает так,

     my_prog --foo=bar 

    эквивалентно

     my_prog @baz.conf 

    если @baz.conf ,

     --foo bar 

    Вы даже можете заставить свой код искать foo.conf автоматически, изменяя argv

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

    Формат этих файлов конфигурации можно изменить, создав подкласс ArgumentParser и добавив метод convert_arg_line_to_args .

    Вот что-то, что я взломал. Не стесняйтесь предлагать улучшения / сообщения об ошибках в комментариях:

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

    ДЕЛАТЬ

    Эта реализация все еще не завершена. Вот частичный список TODO:

    • (легко) Взаимодействие с параметрами парсера по умолчанию
    • (легко) Если преобразование типов не работает, проверьте, как argparse обрабатывает сообщения об ошибках

    Соответствовать документированному поведению

    • (легко) Напишите функцию, которая add_argument dest из args в add_argument , вместо того, чтобы полагаться на объект Action
    • (тривиально) Напишите функцию parse_args которая использует parse_known_args . (например, копировать parse_args из реализации cpython чтобы гарантировать, что он вызывает parse_known_args .)

    Менее простые вещи …

    Я еще не пробовал этого. Это маловероятно, но все же возможно! – это могло бы просто работать …

    • (трудно?) Взаимное исключение
    • (hard?) Аргументные группы (если они реализованы, эти группы должны получить section в файле конфигурации.)
    • (hard?) Sub Commands (Подкоманды также должны получить section в файле конфигурации.)

    Там есть библиотека, которая выполняет именно это под названием configglue .

    configglue – это библиотека, которая склеивает optparse python.OptionParser и ConfigParser.ConfigParser, так что вам не нужно повторять себя, когда вы хотите экспортировать одни и те же параметры в файл конфигурации и интерфейс командной строки.

    Он также поддерживает переменные среды.

    Существует также другая библиотека под названием ConfigArgParse, которая

    Замена замены для argparse, которая позволяет настраивать параметры также через конфигурационные файлы и / или переменные среды.

    Возможно, вас заинтересует PyCon о настройке Łukasz Langa – Let Them Configure!

    Кажется, стандартная библиотека не обращается к этому, оставляя каждого программиста, чтобы configparser и argparse и os.environ все вместе неуклюжими способами.

    Насколько я знаю, стандартная библиотека Python не предоставляет этого. Я решил это для себя, написав код, чтобы использовать optparse и ConfigParser для синтаксического анализа командной строки и файлов конфигурации и обеспечения уровня абстракции поверх них. Однако вам понадобится это как отдельная зависимость, которая из вашего предыдущего комментария кажется неприятной.

    Если вы хотите посмотреть код, который я написал, он находится по адресу http://liw.fi/cliapp/ . Он интегрирован в мою библиотеку приложений командной строки, так как это значительная часть того, что необходимо сделать.

    Чтобы удовлетворить все эти требования, я бы рекомендовал написать собственную библиотеку, которая использует оба параметра [opt | arg] и configparser для базовых функций.

    Учитывая первые два и последнее требование, я бы сказал, что вы хотите:

    Шаг первый: Проведите сеанс анализатора командной строки, который ищет только параметр -config-file.

    Шаг второй: проанализируйте конфигурационный файл.

    Шаг третий: настройте второй сеанс синтаксического анализа командной строки, используя вывод файла конфигурации, как по умолчанию.

    Третье требование, вероятно, означает, что вы должны разработать свою собственную систему определения опций, чтобы показать все функциональные возможности optparse и configparser, о которых вы заботитесь, и написать какую-нибудь сантехнику, чтобы делать конверсии между ними.

    В последнее время меня попробовали что-то подобное, используя «optparse».

    Я настроил его как подкласс класса OptonParser с командой «-Store» и «Check».

    Код, приведенный ниже, в значительной степени затронул вас. Вам просто нужно определить свои собственные методы «загрузки» и «хранения», которые принимают / возвращают словари, и вы очень много жертвы.

     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 по 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 

    Хотя я не пробовал это самостоятельно, есть библиотека ConfigArgParse, в которой говорится, что она делает большинство вещей, которые вы хотите:

    Замена замены для argparse, которая позволяет настраивать параметры также через конфигурационные файлы и / или переменные среды.

    Python - лучший язык программирования в мире.