103 votes

Quel est le meilleur moyen de permettre la substitution des options de configuration à la ligne de commande en Python ?

J'ai une application Python qui a besoin de plusieurs (~30) paramètres de configuration. Jusqu'à présent, j'utilisais la classe OptionParser pour définir des valeurs par défaut dans l'application elle-même, avec la possibilité de changer des paramètres individuels en ligne de commande lors de l'invocation de l'application.

Maintenant, j'aimerais utiliser des fichiers de configuration « appropriés », par exemple de la classe ConfigParser. En même temps, les utilisateurs devraient toujours pouvoir changer des paramètres individuels en 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 en ligne de commande, mais lire les valeurs par défaut à partir d'un fichier de configuration au format ConfigParse.

Des idées sur la façon de faire cela de manière simple ? Je n'ai pas vraiment envie d'invoquer manuellement ConfigParse, puis de définir manuellement toutes les valeurs par défaut de toutes les options aux valeurs appropriées...

110voto

trolley Points 13

J'ai découvert que vous pouvez le faire avec argparse.ArgumentParser.parse_known_args(). Commencez par utiliser parse_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 avec parse_args(). Cela vous permettra d'avoir une valeur par défaut, de la remplacer par un fichier de configuration, puis de la remplacer par une option de ligne de commande. Par exemple :

Valeur par défaut sans saisie utilisateur :

$ ./argparse-partial.py
L'option est "par défaut"

Valeur par défaut du fichier de configuration :

$ cat argparse-partial.config 
[Defaults]
option=Bonjour le monde!
$ ./argparse-partial.py -c argparse-partial.config 
L'option est "Bonjour le monde!"

Valeur par défaut du fichier de configuration, remplacée par une option de ligne de commande :

$ ./argparse-partial.py -c argparse-partial.config --option remplacer
L'option est "remplacer"

argprase-partial.py suit. Il est légèrement compliqué de gérer -h pour obtenir de l'aide correctement.

import argparse
import ConfigParser
import sys

def main(argv=None):
    # Définir les arguments par défaut de cette manière, car le faire dans la déclaration de la fonction les définit au moment de la compilation.
    if argv is None:
        argv = sys.argv

    # Analyser toute spécification conf_file
    # Nous créons ce parseur avec add_help=False pour qu'il n'analyse pas -h et n'affiche pas l'aide.
    conf_parser = argparse.ArgumentParser(
        description=__doc__, # imprimée avec -h/--help
        # Ne modifiez pas le format de la description
        formatter_class=argparse.RawDescriptionHelpFormatter,
        # Désactivez l'aide, pour afficher toutes les options en réponse à -h
        add_help=False
        )
    conf_parser.add_argument("-c", "--conf_file",
                        help="Spécifiez le fichier de configuration", metavar="FICHIER")
    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")))

    # Analyser le reste des arguments
    # Ne supprimez pas add_help ici pour gérer -h
    parser = argparse.ArgumentParser(
        # Hériter des options du config_parser
        parents=[conf_parser]
        )
    parser.set_defaults(**defaults)
    parser.add_argument("--option")
    args = parser.parse_args(remaining_argv)
    print "L'option est \"{}\"".format(args.option)
    return(0)

if __name__ == "__main__":
    sys.exit(main())

28voto

user553965 Points 50

Découvrez ConfigArgParse - il s'agit d'un nouveau package PyPI (open source) qui sert de remplacement immédiat pour argparse avec un support supplémentaire pour les fichiers de configuration et les variables d'environnement.

9voto

xubuntix Points 1338

J'utilise ConfigParser et argparse avec des sous-commandes pour gérer de telles tâches. La ligne importante dans le code ci-dessous est :

subp.set_defaults(**dict(conffile.items(subn)))

Ceci définira les valeurs par défaut de la sous-commande (à partir d'argparse) aux valeurs de la section du fichier de configuration.

Un exemple plus complet est ci-dessous :

####### contenu du fichier 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')

5voto

Blair Conrad Points 56195

Je ne peux pas dire que c'est la meilleure façon, 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 le code source. Les tests se trouvent dans un répertoire voisin.

4voto

Achal Dave Points 1103

Mise à jour : Cette réponse comporte toujours des problèmes ; par exemple, elle ne peut pas gérer les arguments required et nécessite une syntaxe de configuration maladroite. Au lieu de cela, ConfigArgParse semble être exactement ce que cette question demande, et est un remplacement transparent et direct.

Un problème avec le code actuel est qu'il n'affichera pas d'erreur si les arguments dans le fichier de configuration sont invalides. Voici une version avec un autre inconvénient : vous devrez inclure le préfixe -- ou - dans les clés.

Voici le code python (Lien Gist avec licence MIT) :

# Nom du fichier : main.py
import argparse

import configparser

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--config_file', help='fichier de configuration')
    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 :

# Nom du fichier : config_correct.conf
[Defaults]
--arg1=Hello!
--arg2=3

Maintenant, en exécutant

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

Exécuter le script produira une erreur, comme souhaité :

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

L'inconvénient principal est que cela utilise parser.parse_args de manière quelque peu hacky afin d'obtenir la vérification d'erreur de ArgumentParser, mais je ne suis pas au courant d'alternatives à cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X