J'ai une application Python qui a besoin de quelques paramètres de configuration (~ 30). Jusqu'à présent, j'ai utilisé la classe OptionParser pour définir les valeurs par défaut dans l'application elle-même, avec la possibilité de modifier des paramètres individuels sur la ligne de commande lors de l'appel de l'application.
Maintenant, je voudrais utiliser des fichiers de configuration «appropriés», par exemple de la classe ConfigParser. Dans le même temps, les utilisateurs devraient toujours pouvoir modifier des paramètres individuels sur la ligne de commande.
Je me demandais s'il y avait un moyen de combiner les deux étapes, par exemple utiliser optparse (ou le plus récent argparse) pour gérer les options de ligne de commande, mais en lisant les valeurs par défaut à partir d'un fichier de configuration dans la syntaxe ConfigParse.
Des idées pour faire cela de manière simple? Je n'ai pas vraiment envie d'appeler manuellement ConfigParse, puis de définir manuellement toutes les valeurs par défaut de toutes les options sur les valeurs appropriées ...
la source
Réponses:
Je viens de découvrir que vous pouvez le faire avec
argparse.ArgumentParser.parse_known_args()
. Commencez par utiliserparse_known_args()
pour analyser un fichier de configuration à partir de la ligne de commande, puis lisez-le avec ConfigParser et définissez les valeurs par défaut, puis analysez le reste des options avecparse_args()
. Cela vous permettra d'avoir une valeur par défaut, de la remplacer avec un fichier de configuration, puis de la remplacer avec une option de ligne de commande. Par exemple:Par défaut sans entrée utilisateur:
$ ./argparse-partial.py Option is "default"
Valeur par défaut du fichier de configuration:
$ cat argparse-partial.config [Defaults] option=Hello world! $ ./argparse-partial.py -c argparse-partial.config Option is "Hello world!"
Valeur par défaut du fichier de configuration, remplacée par la ligne de commande:
$ ./argparse-partial.py -c argparse-partial.config --option override Option is "override"
argprase-partial.py suit. Il est un peu compliqué à gérer
-h
correctement pour obtenir de l'aide.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())
la source
another=%(option)s you are cruel
alorsanother
serait toujours à résoudre ,Hello world you are cruel
même sioption
est overriden à quelque chose d' autre dans la ligne de commande .. argghh-analyseur!--version
option à votre application, il est préférable de l'ajouterconf_parser
àparser
et de quitter l'application après avoir imprimé l'aide. Si vous ajoutez--version
àparser
et vous commencez l' application avec le--version
drapeau, que votre application essayer inutilement d'ouvrir et d' analyser leargs.conf_file
fichier de configuration (qui peut être malformé ou même inexistante, ce qui conduit à exception).Découvrez ConfigArgParse - c'est un nouveau package PyPI ( open source ) qui remplace argparse avec un support supplémentaire pour les fichiers de configuration et les variables d'environnement.
la source
J'utilise ConfigParser et argparse avec des sous-commandes pour gérer ces tâches. La ligne importante dans le code ci-dessous est:
Cela définira les valeurs par défaut de la sous-commande (de argparse) sur les valeurs de la section du fichier de configuration.
Un exemple plus complet est ci-dessous:
####### 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')
la source
Je ne peux pas dire que c'est le meilleur moyen, mais j'ai une classe OptionParser que j'ai créée qui fait exactement cela - agit comme optparse.OptionParser avec des valeurs par défaut provenant d'une section de fichier de configuration. Vous pouvez l'avoir...
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))
N'hésitez pas à parcourir la source . Les tests sont dans un répertoire frère.
la source
Vous pouvez utiliser 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.
Vous pouvez combiner les valeurs de la ligne de commande, des variables d'environnement, du fichier de configuration et, si la valeur n'est pas là, définir une valeur par défaut.
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'
la source
dicts
mise à jour dans l'ordre de priorité souhaité? Ildefaultdict
y a peut-être un avantage car des options nouvelles ou non prises en charge peuvent être définies, mais elles sont distinctes deChainMap
. Je suppose qu'il me manque quelque chose.Mise à jour: cette réponse a toujours des problèmes; par exemple, il ne peut pas gérer les
required
arguments et nécessite une syntaxe de configuration peu pratique. Au lieu de cela, ConfigArgParse semble être exactement ce que cette question demande et constitue un remplacement transparent et instantané .Un problème avec le courant est qu'il n'entraînera pas d'erreur si les arguments dans le fichier de configuration sont invalides. Voici une version avec un inconvénient différent: vous devrez inclure le préfixe
--
ou-
dans les clés.Voici le code python ( lien Gist avec licence 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)
Voici un exemple de fichier de configuration:
# Filename: config_correct.conf [Defaults] --arg1=Hello! --arg2=3
Maintenant, en cours d'exécution
> python main.py --config_file config_correct.conf --arg1 override Namespace(arg1='override', arg2=3, config_file='test_argparse.conf')
Cependant, si notre fichier de configuration contient une erreur:
# config_invalid.conf --arg1=Hello! --arg2='not an integer!'
L'exécution du script produira une erreur, comme vous le souhaitez:
> 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!'
Le principal inconvénient est que cela utilise
parser.parse_args
quelque peu hackly afin d'obtenir la vérification des erreurs d'ArgumentParser, mais je ne connais aucune alternative à cela.la source
Essayez de cette façon
# 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))
Utilise le:
parser.add_argument("-C", "--config", action=LoadConfigAction) parser.add_argument("-H", "--host", dest="host")
Et créez un exemple de configuration:
# Example config: /etc/myservice.conf import os host = os.getenv("HOST_NAME", "localhost")
la source
fromfile_prefix_chars
Peut-être pas l'API parfaite, mais cela vaut la peine d'être connu.
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())
Ensuite:
$ 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')
Documentation: https://docs.python.org/3.6/library/argparse.html#fromfile-prefix-chars
Testé sur Python 3.6.5, Ubuntu 18.04.
la source