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

0voto

Maciej Kucharz Points 772

Pour faire cela de manière élégante, vous devrez probablement construire votre propre classe basée sur la classe OptionParser.

0voto

Zachary Young Points 2148

parse_args() peut prendre un Namespace existant et fusionner le Namespace existant avec les args/options qu'il analyse actuellement; les options args/options dans le "parsing actuel" ont la priorité et remplacent tout ce qui se trouve dans le Namespace existant:

foo_parser = argparse.ArgumentParser()
foo_parser.add_argument('--foo')

ConfigNamespace = argparse.Namespace()
setattr(ConfigNamespace, 'foo', 'foo')

args = foo_parser.parse_args([], namespace=ConfigNamespace)
print(args)
# Namespace(foo='foo')

# la valeur `bar` remplacera la valeur `foo` de ConfigNamespace
args = foo_parser.parse_args(['--foo', 'bar'], namespace=ConfigNamespace)
print(args)
# Namespace(foo='bar')

Je l'ai simulé pour une véritable option de fichier de configuration. Je l'analyse deux fois, une fois, comme une "pré-analyse" pour voir si l'utilisateur a passé un fichier de configuration, et ensuite de nouveau pour l'analyse finale qui intègre le Namespace du fichier de configuration facultatif.

J'ai ce fichier de configuration JSON très simple, config.ini:

[DEFAULT]
delimiter = |

et quand j'exécute ceci:

import argparse
import configparser

parser = argparse.ArgumentParser()
parser.add_argument('-c', '--config-file', type=str)
parser.add_argument('-d', '--delimiter', type=str, default=',')

# Analyser les args de la ligne de commande pour voir si un fichier de configuration est spécifié
pre_args = parser.parse_args()

# Même si la configuration n'est pas spécifiée, besoin d'un Namespace vide à passer à `parse_args()` final
ConfigNamespace = argparse.Namespace()

if pre_args.config_file:
    config = configparser.ConfigParser()
    config.read(pre_args.config_file)

    for name, val in config['DEFAULT'].items():
        setattr(ConfigNamespace, name, val)

# Analyser de nouveau les args de la ligne de commande, fusion avec ConfigNamespace, 
# les args de la ligne de commande ont la priorité
args = parser.parse_args(namespace=ConfigNamespace)

print(args)

avec différents paramètres de ligne de commande, j'obtiens:

./main.py
Namespace(config_file=None, delimiter=',')

./main.py -c config.ini
Namespace(config_file='config.ini', delimiter='|')

./main.py -c config.ini -d \;
Namespace(config_file='config.ini', delimiter=';')

0voto

edan Points 426

J'ai trouvé que les réponses existantes étaient insuffisantes, surtout lorsqu'il s'agit de sous-commandes et d'arguments requis.

Voici la solution avec laquelle j'ai fini par :

# Créez un analyseur séparé pour l'argument --config-file
config_parser = argparse.ArgumentParser(
    add_help=False
)
config_parser.add_argument(
    "--config-file",
    metavar="FICHIER"
)

# Analyser l'argument du fichier de configuration, mais conserver tous les autres arguments
# (nous en aurons besoin plus tard, pour l'analyseur principal)
args, remaining_argv = config_parser.parse_known_args()
config_data = {}
if args.config_file:
    # config_data = 

# Utilisez la configuration pour générer de nouveaux arguments CLI
argv = remaining_argv
for k, v in config_data.items():
    argv.append(f"--{k}")
    argv.append(str(v))

# Configurez votre analyseur principal
parser = argparse.ArgumentParser(
    # ...
)
# Analyser les arguments
args = parser.parse_args(argv)

Peut-être un peu bizarre, mais ça fonctionne bien pour mon cas d'utilisation.

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