Introduction
Les fonctionnalités essentielles à aborder lorsqu’on souhaite apprendre et utiliser très rapidement Python :
- Python - Comprendre et démystifier virtualenv (Publication : Janvier 2020)
- Lire et écrire des fichiers JSON, traiter des données JSON avec Python (Publication : Avril 2020)
- Manipuler les arguments d’un programme Python avec les packages argparse et getopt (Publication : Avril 2020)
- Configuration applicative : variables d’environnement, fichiers ini et YAML (Publication : Avril 2020)
- Gestion des requêtes HTTP avec les packages requests et httplib2 (Publication : Avril 2020)
Bien entendu, aucune valeur de configuration applicative codée en dur dans des programmes, programmes Python ou non.
La configuration peut être extraite à partir :
- de variables d’environnement
- de fichiers Ini
- de fichiers JSON
- de fichiers XML
- de fichiers YAML
Dans ce chapître, comment lire (écrire) des données de configuration avec Python à partir de variables d’environnement,
de fichiers INI avec configparser
et de fichiers YAML avec le package PyYAML
.
XML n’est pas abordé ici. Le format XML format est de moins en moins utilisé de nos jours, JSON et YAML ont des formats plus lisibles humainement, de plus les parsers XML sont un peu lourds.
Le format JSON n’est pas couvert également dans ce billet, un article dédié est publié sur ce sujet : Python, lire et écrire des données JSON avec le package natif json
Variables d’environnement, module os
sqlpac@vpsfrsqlpac2$ export CFG=/home/sqlpac/cfg sqlpac@vpsfrsqlpac2$ echo $CFG
/home/sqlpac/cfg
Pour lire les variables d’environnement, importer le module os
et appeler la méthode getenv
ou
la méthode get
de la classe environ
:
import os confdir = os.getenv('CFG') homedir = os.environ.get('HOME') print(confdir) print(homedir)
/home/sqlpac/cfg /home/sqlpac
Lorsque la variable d’environnement n’existe pas : la méthode getenv
et environ.get
retournent None
.
La variable d’environment peut être extraite directement avec la syntaxe os.environ["Environment Variable"]
sans utiliser les méthodes get
.
import os
confdir = os.environ['CFG']
Mais l’exception KeyError
doit être gérée dans le cas où la variable d’environnement n’existe pas au lieu de
tester si None
est retourné lors de l’utilisation des méthodes get
import os varenv = 'CFG2' try: confdir = os.environ[varenv] except KeyError: print('Environment variable %s does not exist' % (varenv))
Environment variable CFG2 does not exist
Le plus important : comment définir une variable d’environnement disponible et accessible dans les sous programmes (shell, Python…) ?
Il suffit de définir la variable d’environnement os.environ['var']
dans le programme parent,
la variable d’environnement est alors disponible dans les sous programmes.
subprogram.py
|
build.bash
|
import os os.environ['VERSION']='4.2' os.system('python3 subprogram.py') os.system('./build.bash')
Python subprogram.py : 4.2 Shell ./build.bash : 4.2
Fichiers Ini, module configparser
Lire un fichier INI
Un fichier INI exemple (les sections imbriquées ne peuvent pas y être implémentées) :
sqlpac.ini
[sqlpac]
version=5.8
verbosity=2
debug=false
user=sqlpac
wwwurl=https://www.sqlpac.com/
rpc=https://www.sqlpac.com/rpc-secure/
[referential]
dir=https://www.sqlpac.com/referentiel/docs
[googleindexing]
apikey=ApIkeYdGF_kBtPVdAwIM7F0Fu87qWMoykfyl9hfnG2
jsonfile=google-auth-indexing.json
scopes=https://www.googleapis.com/auth/indexing
notification=https://indexing.googleapis.com/v3/urlNotifications/metadata?url=
publish=https://indexing.googleapis.com/v3/urlNotifications:publish
[mobiletest]
serviceurl=https://searchconsole.googleapis.com/v1/urlTestingTools/mobileFriendlyTest:run
Utiliser le package configparser
pour lire un fichier INI.
Un objet est créé avec configparser.ConfigParser()
et sa méthode read
est appelée avec le chemin du fichier ini
en argument :
import configparser
cfg = configparser.ConfigParser()
cfg.read('sqlpac.ini')
La méthode sections
renvoie les sections dans un objet list
:
import configparser cfg = configparser.ConfigParser() cfg.read('sqlpac.ini') print(cfg.sections())
['sqlpac', 'referential', 'googleindexing', 'mobiletest']
Les variables sont extraites avec la syntaxe usuelle :
print(cfg['sqlpac']['debug']) print(cfg['sqlpac']['version']) for key in cfg['googleindexing']: print(cfg['googleindexing'][key])
false 5.8 ApIkeYdGF_kBtPVdAwIM7F0Fu87qWMoykfyl9hfnG2 google-auth-indexing.json https://www.googleapis.com/auth/indexing https://indexing.googleapis.com/v3/urlNotifications/metadata?url= https://indexing.googleapis.com/v3/urlNotifications:publish
L’objet Config
ne devine pas les types de données, le type string
est appliqué.
Pour convertir vers le type de données correct,
utiliser la méthode get
appropriée :
version = cfg['sqlpac'].getfloat('version')
verbosity = cfg['sqlpac'].getint('verbosity')
debug = cfg['sqlpac'].getboolean('debug')
Comme avec un dictionnaire, utiliser les méthodes get
pour indiquer les valeurs par défaut en cas d’échec (fallback) lorsque la clé n’existe pas :
debug = cfg['sqlpac'].getboolean('debug', False)
Utilisation de variables, interpolations
Pour éviter la redondance, des variables peuvent être définies dans des fichiers INI :
[sqlpac]
wwwurl=https://www.sqlpac.com
rpc=%(wwwurl)s/rpc-secure
Par défaut, l’interpolation basique (basic interpolation) est activée dans ConfigParser
. %(var)s
est évaluée à la demande
où var
est définie dans la même section, il n’y a pas besoin de définir et utiliser les variables dans un ordre spécifique.
print(cfg['sqlpac']['rpc'])
https://www.sqlpac.com/rpc-secure
L’interpolation basique évalue les variables pour les directives dans une même section. Quand l’évaluation est nécessaire inter sections ("cross" sections),
l’interpolation étendue doit être définie dans l’objet ConfigParser
. Dans les interpolations étendues, les variables
ont la nomenclature ${section:var}
et lorsque la section n’est pas indiquée, la section de la variable est utilisée.
[sqlpac]
wwwurl=https://www.sqlpac.com
rpc=${wwwurl}/rpc-secure
[referential]
dir=${sqlpac:wwwurl}/referentiel/docs
import configparser from configparser import ExtendedInterpolation cfg = configparser.ConfigParser(interpolation=ExtendedInterpolation()) cfg.read('sqlpac.ini') print(cfg['sqlpac']['rpc']) print(cfg['referential']['dir'])
https://www.sqlpac.com/rpc-secure https://www.sqlpac.com/referentiel/docs
Les interpolations basique et étendue sont exclusives. Il n’est pas possible d’utiliser les 2 à la fois.
Avec respectivement l’interpolation basique et étendue, doubler le caractère %
et $
pour échapper le caractère si il est utilisé dans les valeurs des directives de configuration, sinon ils sont candidats
à l’interpolation.
notif=50%% done # % added to bypass basic interpolation
price=$$10 # $ added to bypass extended interpolation
Délimiteurs, commentaires
Les valeurs par défaut pour les délimiteurs et commentaires sont les suivants :
delimiters=('=', ':')
comment_prefixes=('#', ';')
La première occurence dans une ligne est considérée comme le marqueur de délimiteur ou de commentaire dans la ligne en question.
Bien entendu, ils sont modifiables lors de la création de l’objet ConfigParser
:
cfg = configparser.ConfigParser(interpolation=ExtendedInterpolation(),
delimiters=('=', ':', '~'),
comment_prefixes=('#', ';' ,'@'))
Écrire un fichier INI
Beaucoup moins utilisé, mais bon à savoir, pour écrire un fichier INI à partir d’un objet dictionnaire, utiliser la méthode write
:
import configparser
config = configparser.ConfigParser()
config['sqlpac'] = {}
config['sqlpac']['wwwurl'] = 'https://www.sqlpac.com'
config['sqlpac']['rpc'] = '${wwwurl}/rpc-secure'
with open('sqlpac2.ini', 'w') as cfgfile:
config.write(cfgfile)
sqlpac2.ini
[sqlpac]
wwwurl = https://www.sqlpac.com
rpc = ${wwwurl}/rpc-secure
Un autre codage en utilisant les méthodes add_section
et set
:
import configparser
config = configparser.ConfigParser()
config.add_section('sqlpac');
config.set('sqlpac','wwwurl','https://www.sqlpac.com')
config.set('sqlpac','rpc','${wwwurl}/rpc-secure')
with open('sqlpac2.ini', 'w') as cfgfile:
config.write(cfgfile)
YAML
YAML - Ain’t Markup Language : ce n’est pas un langage de balisage. Il est encore plus lisible humainement que le format JSON.
Translation du fichier INI au format YAML
Écrivons le précédent fichier sqlpac.ini
au format YAML :
sqlpac.yaml
sqlpac:
version: 5.8
verbosity: 2
debug: false
user: sqlpac
wwwurl: https://www.sqlpac.com
rpc: https://www.sqlpac.com/rpc-secure
referential:
dir: https://www.sqlpac.com/referentiel/docs
googleindexing:
apikey: ApIkeYdGF_kBtPVdAwIM7F0Fu87qWMoykfyl9hfnG2
jsonfile: google-auth-indexing.json
scopes: https://www.googleapis.com/auth/indexing
rooturl: https://indexing.googleapis.com/v3
endpoints:
notification: https://indexing.googleapis.com/v3/urlNotifications/metadata?url=
publish: https://indexing.googleapis.com/v3/urlNotifications:publish
mobiletest:
serviceurl: https://searchconsole.googleapis.com/v1/urlTestingTools/mobileFriendlyTest:run
YAML est très intéressant, on peut introduire la sous section endpoints
, ce qui n’était pas possible
dans le fichier INI.
Mais la mauvaise nouvelle : les variables ne sont plus possibles comme cela a été fait dans le fichier ini avec les interfaces d’interpolation de l’objet ConfigParser
.
Dans les spécifications YAML, la définition de variables n’est pas possible. Des ancres (Anchors) peuvent être définies mais elles ne sont utilisables que pour dupliquer des valeurs, la concaténation est interdite, la syntaxe ci-dessous génère une erreur :
sqlpac:
wwwurl: &url https://www.sqlpac.com
rpc: *url/rpc-secure
Installation de pyYAML
Le parser YAML n’est pas natif avec Python, un package optionnel doit être installé. Si il n’est pas déjà installé, installer le package PyYAML :
pip3 search PyYAML
PyYAML (5.3.1) - YAML parser and emitter for Python
pip3 install PyYAML
Successfully built PyYAML Installing collected packages: PyYAML Successfully installed PyYAML-5.3.1
Chargement des fichiers YAML
Pour charger un fichier YAML, importer le module yaml
et appeler la méthode load
:
import yaml with open("sqlpac.yaml", "r") as ymlfile: cfg = yaml.load(ymlfile, Loader=yaml.FullLoader) print(cfg["googleindexing"]["endpoints"]) print(cfg["googleindexing"]["endpoints"]["notification"])
{'notification': 'https://indexing.googleapis.com/v3/urlNotifications/metadata?url=', 'publish': 'https://indexing.googleapis.com/v3/urlNotifications:publish'} https://indexing.googleapis.com/v3/urlNotifications/metadata?url=
Depuis la version 5.1, la méthode load
doit être appelée avec l’option Loader
, sinon un avertissement
est levé :
YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe.
Please read https://msg.pyyaml.org/load for full details.
Les valeurs possibles pour l’option Loader
sont :
BaseLoader
: charge uniquement le format YAML le plus basique.SafeLoader
: charge un sous ensemble du langage YAML sécurisé. Recommandé pour le chargement de données non fiables.FullLoader
: charge le langage YAML complet mais évite l’exécution de code arbitraire.UnsafeLoader
: le chargeur originel qui pouvait être exploité par des données en entrée non fiables.
Qu’en est-il des types de données ? Le type de données approprié est appliqué lors du chargement, aucune conversion nécessaire comparativement aux fichiers INI et
configparser
:
import yaml with open("sqlpac.yaml", "r") as ymlfile: cfg = yaml.load(ymlfile, Loader=yaml.FullLoader) for key in ('version','verbosity','debug'): print('%s : %s, %s' % (key, cfg["sqlpac"][key], type(cfg["sqlpac"][key])))
version : 5.8, <class 'float'> verbosity : 2, <class 'int'> debug : False, <class 'bool'>
Le type de données peut être forcé dans le fichier YAML, par exemple si on veut la directive version
en type string
et non float
:
sqlpac: version: !!str 5.8 …
version : 5.8, <class 'str'>
Écrire des fichiers YAML
Pour écrire un fichier YAML, construire un dictionnaire et utiliser la méthode dump
:
import yaml
cfgyaml = {}
cfgyaml["sqlpac"] = {}
cfgyaml["sqlpac"]["user"] = "sqlpac"
cfgyaml["sqlpac"]["wwwurl"] = "https://www.sqlpac.com"
cfgyaml["google"] = {}
cfgyaml["google"]["apis"] = ["googleindexing", "googleanalytics"]
with open("sqlpac2.yaml", "w") as f:
yaml.dump(cfgyaml, f, sort_keys=False)
sqlpac2.yaml
sqlpac:
user: sqlpac
wwwurl: https://www.sqlpac.com
google:
apis:
- googleindexing
- googleanalytics
L’option sort_keys
dans la méthode dump
est disponible uniquement à partir de PyYAML 5.1 livré en Mars 2019,
par défaut les clés sont triés.
Conclusion : INI ou YAML pour le fichier de configuration ?
Quand des listes, dictionnaires imbriqués sont intensivement utilisés dans la configuration : YAML est le plus adapté, mais impossible d’utiliser des variables.
De plus YAML est indépendant d’un langage de programmation si il doit être échangé avec d’autres plateformes.
Le fichier INI avec configparser
est le meilleur choix quand des variables sont nécessaires,
mais il faut dans ce cas gérer les conversions et le fichier INI devient dépendant du langage Python et de la plateforme.