У меня есть приложение Python, которому требуется довольно много (~ 30) параметров конфигурации. До сих пор я использовал класс OptionParser для определения значений по умолчанию в самом приложении с возможностью изменения отдельных параметров в командной строке при вызове приложения.
Теперь я хотел бы использовать «правильные» файлы конфигурации, например, из класса ConfigParser. В то же время пользователи по-прежнему должны иметь возможность изменять отдельные параметры в командной строке.
Мне было интересно, есть ли способ объединить два шага, например, использовать optparse (или более новый argparse) для обработки параметров командной строки, но прочитать значения по умолчанию из файла конфигурации в синтаксисе ConfigParse.
Есть идеи, как это сделать простым способом? Мне не очень нравится вручную вызывать ConfigParse, а затем вручную устанавливать все значения по умолчанию для всех параметров на соответствующие значения ...
источник
Ответы:
Я только что обнаружил, что это можно сделать с помощью
argparse.ArgumentParser.parse_known_args()
. Начните с использованияparse_known_args()
для анализа файла конфигурации из командной строки, затем прочтите его с помощью ConfigParser и установите значения по умолчанию, а затем проанализируйте остальные параметры с помощьюparse_args()
. Это позволит вам иметь значение по умолчанию, переопределить его с помощью файла конфигурации, а затем переопределить его с помощью параметра командной строки. Например:По умолчанию без ввода данных пользователем:
$ ./argparse-partial.py Option is "default"
По умолчанию из файла конфигурации:
$ cat argparse-partial.config [Defaults] option=Hello world! $ ./argparse-partial.py -c argparse-partial.config Option is "Hello world!"
По умолчанию из файла конфигурации, переопределено командной строкой:
$ ./argparse-partial.py -c argparse-partial.config --option override Option is "override"
argprase-partial.py следует. Это немного сложно
-h
для правильного обращения за помощью.import argparse import ConfigParser import sys def main(argv=None): # Do argv default this way, as doing it in the functional # declaration sets it at compile time. if argv is None: argv = sys.argv # Parse any conf_file specification # We make this parser with add_help=False so that # it doesn't parse -h and print help. conf_parser = argparse.ArgumentParser( description=__doc__, # printed with -h/--help # Don't mess with format of description formatter_class=argparse.RawDescriptionHelpFormatter, # Turn off help, so we print all options in response to -h add_help=False ) conf_parser.add_argument("-c", "--conf_file", help="Specify config file", metavar="FILE") args, remaining_argv = conf_parser.parse_known_args() defaults = { "option":"default" } if args.conf_file: config = ConfigParser.SafeConfigParser() config.read([args.conf_file]) defaults.update(dict(config.items("Defaults"))) # Parse rest of arguments # Don't suppress add_help here so it will handle -h parser = argparse.ArgumentParser( # Inherit options from config_parser parents=[conf_parser] ) parser.set_defaults(**defaults) parser.add_argument("--option") args = parser.parse_args(remaining_argv) print "Option is \"{}\"".format(args.option) return(0) if __name__ == "__main__": sys.exit(main())
источник
another=%(option)s you are cruel
тогдаanother
она всегда будет разрешена,Hello world you are cruel
даже еслиoption
она переопределена чем-то другим в командной строке .. argghh-parser!--version
параметр в свое приложение, лучше добавить его,conf_parser
чем в,parser
и выйти из приложения после печати справки. Если добавить--version
кparser
и запуску приложения с--version
флагом, чем ваше приложение без необходимости пытаться открыть и синтаксический анализargs.conf_file
файл конфигурации (который может быть неправильно сформирован или даже несуществующим, что приводит к исключению).Ознакомьтесь с ConfigArgParse - это новый пакет PyPI (с открытым исходным кодом ), который служит заменой argparse с добавленной поддержкой файлов конфигурации и переменных среды.
источник
Я использую ConfigParser и argparse с подкомандами для обработки таких задач. Важная строка в приведенном ниже коде:
Это установит значения по умолчанию для подкоманды (из argparse) в значения в разделе файла конфигурации.
Ниже приведен более полный пример:
####### content of example.cfg: # [sub1] # verbosity=10 # gggg=3.5 # [sub2] # host=localhost import ConfigParser import argparse parser = argparse.ArgumentParser() subparsers = parser.add_subparsers() parser_sub1 = subparsers.add_parser('sub1') parser_sub1.add_argument('-V','--verbosity', type=int, dest='verbosity') parser_sub1.add_argument('-G', type=float, dest='gggg') parser_sub2 = subparsers.add_parser('sub2') parser_sub2.add_argument('-H','--host', dest='host') conffile = ConfigParser.SafeConfigParser() conffile.read('example.cfg') for subp, subn in ((parser_sub1, "sub1"), (parser_sub2, "sub2")): subp.set_defaults(**dict(conffile.items(subn))) print parser.parse_args(['sub1',]) # Namespace(gggg=3.5, verbosity=10) print parser.parse_args(['sub1', '-V', '20']) # Namespace(gggg=3.5, verbosity=20) print parser.parse_args(['sub1', '-V', '20', '-G','42']) # Namespace(gggg=42.0, verbosity=20) print parser.parse_args(['sub2', '-H', 'www.example.com']) # Namespace(host='www.example.com') print parser.parse_args(['sub2',]) # Namespace(host='localhost')
источник
Я не могу сказать, что это лучший способ, но у меня есть созданный мной класс OptionParser, который делает именно это - действует как optparse.OptionParser со значениями по умолчанию, взятыми из раздела файла конфигурации. Ты можешь иметь это...
class OptionParser(optparse.OptionParser): def __init__(self, **kwargs): import sys import os config_file = kwargs.pop('config_file', os.path.splitext(os.path.basename(sys.argv[0]))[0] + '.config') self.config_section = kwargs.pop('config_section', 'OPTIONS') self.configParser = ConfigParser() self.configParser.read(config_file) optparse.OptionParser.__init__(self, **kwargs) def add_option(self, *args, **kwargs): option = optparse.OptionParser.add_option(self, *args, **kwargs) name = option.get_opt_string() if name.startswith('--'): name = name[2:] if self.configParser.has_option(self.config_section, name): self.set_default(name, self.configParser.get(self.config_section, name))
Не стесняйтесь просматривать источник . Тесты находятся в родственном каталоге.
источник
Вы можете использовать ChainMap
A ChainMap groups multiple dicts or other mappings together to create a single, updateable view. If no maps are specified, a single empty dictionary is provided so that a new chain always has at least one mapping.
Вы можете комбинировать значения из командной строки, переменных среды, файла конфигурации, и в случае, если значение отсутствует, определите значение по умолчанию.
import os from collections import ChainMap, defaultdict options = ChainMap(command_line_options, os.environ, config_file_options, defaultdict(lambda: 'default-value')) value = options['optname'] value2 = options['other-option'] print(value, value2) 'optvalue', 'default-value'
источник
dicts
обновлений в желаемом порядке приоритета? Сdefaultdict
есть возможно преимущество , так как новые или неподдерживаемые параметры могут быть установлены , но это отдельно отChainMap
. Полагаю, я что-то упускаю.Обновление: у этого ответа все еще есть проблемы; например, он не может обрабатывать
required
аргументы и требует неудобного синтаксиса конфигурации. Вместо этого ConfigArgParse кажется именно тем, о чем спрашивает этот вопрос, и является прозрачной заменой.Одна из проблем с током заключается в том, что он не приведет к ошибке, если аргументы в файле конфигурации недопустимы. Вот версия с другой обратной стороной: вам нужно включить префикс
--
или-
в ключи.Вот код Python ( ссылка Gist с лицензией MIT):
# Filename: main.py import argparse import configparser if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--config_file', help='config file') args, left_argv = parser.parse_known_args() if args.config_file: with open(args.config_file, 'r') as f: config = configparser.SafeConfigParser() config.read([args.config_file]) parser.add_argument('--arg1', help='argument 1') parser.add_argument('--arg2', type=int, help='argument 2') for k, v in config.items("Defaults"): parser.parse_args([str(k), str(v)], args) parser.parse_args(left_argv, args) print(args)
Вот пример файла конфигурации:
# Filename: config_correct.conf [Defaults] --arg1=Hello! --arg2=3
Теперь бегу
> python main.py --config_file config_correct.conf --arg1 override Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
Однако, если в нашем файле конфигурации есть ошибка:
# config_invalid.conf --arg1=Hello! --arg2='not an integer!'
При запуске скрипта возникает ошибка:
> python main.py --config_file config_invalid.conf --arg1 override usage: test_argparse_conf.py [-h] [--config_file CONFIG_FILE] [--arg1 ARG1] [--arg2 ARG2] main.py: error: argument --arg2: invalid int value: 'not an integer!'
Основным недостатком является то, что это используется
parser.parse_args
несколько хакерски, чтобы получить проверку ошибок от ArgumentParser, но я не знаю никаких альтернатив этому.источник
Попробуйте так
# encoding: utf-8 import imp import argparse class LoadConfigAction(argparse._StoreAction): NIL = object() def __init__(self, option_strings, dest, **kwargs): super(self.__class__, self).__init__(option_strings, dest) self.help = "Load configuration from file" def __call__(self, parser, namespace, values, option_string=None): super(LoadConfigAction, self).__call__(parser, namespace, values, option_string) config = imp.load_source('config', values) for key in (set(map(lambda x: x.dest, parser._actions)) & set(dir(config))): setattr(namespace, key, getattr(config, key))
Используй это:
parser.add_argument("-C", "--config", action=LoadConfigAction) parser.add_argument("-H", "--host", dest="host")
И создадим пример конфига:
# Example config: /etc/myservice.conf import os host = os.getenv("HOST_NAME", "localhost")
источник
fromfile_prefix_chars
Возможно, не идеальный API, но о нем стоит знать.
main.py
:#!/usr/bin/env python3 import argparse parser = argparse.ArgumentParser(fromfile_prefix_chars='@') parser.add_argument('-a', default=13) parser.add_argument('-b', default=42) print(parser.parse_args())
Затем:
$ printf -- '-a\n1\n-b\n2\n' > opts.txt $ ./main.py Namespace(a=13, b=42) $ ./main.py @opts.txt Namespace(a='1', b='2') $ ./main.py @opts.txt -a 3 -b 4 Namespace(a='3', b='4') $ ./main.py -a 3 -b 4 @opts.txt Namespace(a='1', b='2')
Документация: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Протестировано на Python 3.6.5, Ubuntu 18.04.
источник