#!/usr/bin/python3
# -*- coding: utf-8 -*-
########################################################################
#
# This file is part of python module <pyspc>.
# Copyright (C) 2013-2020 R. Marty
# (renaud.marty@developpement-durable.gouv.fr)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see COPYING.txt).
# If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
"""
Objets natifs et convention de pyspc - Fichiers de configuration
"""
import configparser
import collections
from pyspc.core._config import CONVERT, IO
[docs]
class Config(collections.OrderedDict, CONVERT, IO):
"""
Fichiers de configuration
Attributes
----------
filename : str
Nom du fichier de configuration
"""
[docs]
def __init__(self, filename=None):
"""
Initialisation de l'instance Config
Parameters
----------
filename : str
Nom du fichier de configuration
"""
super().__init__()
self.filename = filename
def __str__(self):
"""
Afficher les méta-données de l'instance <Config>
"""
cname = self.__class__.__name__
text = """
*************************************
************ {cname} *****************
*************************************
* NOM FICHIER = {filename}
* CONFIGURATION {content}
*************************************
"""
content = ""
for k, v in self.items():
if isinstance(v, str):
content += f'\n * + {k} = {v}'
elif isinstance(v, (dict, collections.OrderedDict)):
content += f'\n * + {k}'
for k2, v2 in v.items():
content += f'\n * + {k2} = {v2}'
return text.format(cname=cname, filename=self.filename,
content=content)
[docs]
def convert(self, functions=None):
"""
Convertir les valeurs de la configuration
Parameters
----------
functions : dict
Fonctions de conversion à appliquer
- clé : (section, option)
- valeur : fonction de conversion
"""
# ---------------------------------------------------------------------
# 0- Contrôles
# ---------------------------------------------------------------------
if not self:
raise ValueError('La configuration est vide')
if not isinstance(functions, dict):
raise ValueError('Fonctions de conversion mal-définies')
# ---------------------------------------------------------------------
# 1- Conversion
# ---------------------------------------------------------------------
for key in functions:
section = key[0]
option = key[1]
if section in self and option in self[section]:
try:
self[section][option] = \
functions[key](self[section][option])
except ValueError:
pass
except TypeError:
pass
[docs]
def list_ordered_options(self):
"""
Lister les options UNIQUES de la configuration, avec tri
"""
ops = []
for s in self:
ops.extend([o for o in self[s] if o not in ops])
return ops
[docs]
def list_unique_options(self):
"""
Lister les options UNIQUES de la configuration, avec tri
"""
return sorted(list({o for s in self for o in self[s]}))
[docs]
def list_sections_options(self):
"""
Lister les sections et options de la configuration
"""
return [(s, o) for s in self for o in self[s]]
[docs]
def read(self, encoding='utf-8'):
"""
Lire un fichier de configuration
Parameters
----------
encoding : str
Encodage du fichier de configuration 'utf-8' par défaut
"""
cfg_parser = configparser.ConfigParser()
cfg_parser.optionxform = str
self.clear()
with open(self.filename, 'r', encoding=encoding) as f:
cfg_parser.read_file(f, self.filename)
for section in cfg_parser.sections():
self.setdefault(section, collections.OrderedDict())
for option in cfg_parser.options(section):
self[section].setdefault(option,
cfg_parser.get(section, option))
[docs]
def update_config(self, config=None, overwrite=None, strict=None):
"""
Mettre à jour la configuration à partir d'un dictionnaire
Parameters
----------
config : dict, Config
Eléments à mettre à jour. {(section, option) : valeur}
overwrite : bool
Forcer l'écriture si existant, par défaut: True
strict : bool
Ne considérer que les clés existantes, par défaut: True
"""
# ---------------------------------------------------------------------
# 0- Contrôles
# ---------------------------------------------------------------------
if isinstance(config, Config):
config = {(s, o): config[s][o] for s in config for o in config[s]}
if not isinstance(config, dict):
raise ValueError('Fichiers de configuration mal-definis')
if overwrite is None:
overwrite = True
if not isinstance(overwrite, bool):
raise ValueError("L'argument 'overwrite' doit etre un booleen")
if strict is None:
strict = True
if not isinstance(strict, bool):
raise ValueError("L'argument 'strict' doit etre un booleen")
# ---------------------------------------------------------------------
# 1- Clés de configuration
# ---------------------------------------------------------------------
so = set(self.list_sections_options())
new = set(list(config.keys()))
# if strict:
# for n in new.difference(so):
# print("La cle {} n'existe pas dans la configuration actuelle"
# ". Veuillez mettre strict=False pour forcer la "
# "mise-a-jour".format(n))
# ---------------------------------------------------------------------
# 2- Mise à jour 'strict == True'
# ---------------------------------------------------------------------
for n in new.intersection(so):
s = n[0]
o = n[1]
if not overwrite:
self[s].setdefault(o, config[n])
else:
self[s][o] = config[n]
# ---------------------------------------------------------------------
# 3- Mise à jour complémentaire si 'strict == False'
# ---------------------------------------------------------------------
if not strict:
for n in sorted(new.difference(so)):
s = n[0]
o = n[1]
self.setdefault(s, collections.OrderedDict())
self[s][o] = config[n]
[docs]
def write(self, encoding='utf-8', newline='\n',
func_sec=None, func_opt=None):
"""
Écrire un fichier de configuration.
Parameters
----------
encoding : str
Encodage du fichier de configuration, 'utf-8' par défaut
newline : str
Charactère de nouvelle ligne, '\\n' par défaut
func_sec : function, dict
Fonction appliquée pour convertir les sections en str.
Peut-être défini par un dictionnaire {section: function}
func_opt : function, dict
Fonction appliquée pour convertir les options en str
Peut-être défini par un dictionnaire {(section, option): function}
"""
cfg_parser = configparser.ConfigParser()
cfg_parser.optionxform = str
if func_sec is None:
func_sec = str
func_sec = {section: func_sec.get(section, str)
if isinstance(func_sec, dict) else func_sec
for section in self.keys()}
if func_opt is None:
func_opt = str
func_opt = {(section, option): func_opt.get((section, option), str)
if isinstance(func_opt, dict) else func_opt
for section in self.keys() for option in self[section]}
for section in self.keys():
sas = func_sec[section](section)
cfg_parser.add_section(sas)
for option, value in self[section].items():
oas = func_opt[(section, option)](option)
value = str(value)
cfg_parser.set(sas, oas, value)
with open(self.filename, 'w', encoding=encoding, newline=newline) as f:
cfg_parser.write(f)