Source code for poppy.core.configuration

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os.path as osp
from poppy.core.logger import logger
import shutil
import jsonschema
import json
import sys

from poppy.core.generic.dot_dict import DotDict
from poppy.core.generic.metaclasses import ManagerMeta

__all__ = ["Configuration", "ConfigurationNotFound"]




[docs]class ConfigurationNotFound(Exception): """ Exception for the case a configuration file is not found. """
[docs]class Configuration(DotDict, metaclass=ManagerMeta): """ Class to manage parameters from the configuration file easily, retrieve them and setting them again. """ def __init__(self, filename=None, schema=None, name=None, *args, **kwargs): """ To set the filename of the configuration file to read. """ # Configure as usual super(Configuration, self).__init__(*args, **kwargs) # store the file name self.filename = filename # store the schema self.schema = schema # set the name used to register the configuration self.name = name
[docs] def read(self): """ Read the configuration file in the json format and set the parameters inside the instance itself. """ # check the filename is set if not self.filename: logger.error( "The filename must be set into the configuration class" ) return # standard verification for the file if osp.isfile(self.filename): # open the file and read its structure in json with open(self.filename, 'rt') as f: # put parameters into dictionary logger.debug( "Reading parameters from the configuration file " + "{0}".format(self.filename) ) self.update(json.load(f, object_pairs_hook=DotDict)) else: raise ConfigurationNotFound( "The configuration file {0} doesn't exist".format( self.filename ) ) # if the file extends another one, make a merge self.extends()
[docs] def read_validate(self): """ Read the configuration file in JSON format and validate with the schema provided. """ # read the file as usual self.read() # check there is a schema if not self.schema: logger.error( "The path to the schema for {0} is not given".format( self.filename ) ) sys.exit(-1) # read the schema schema = self.__class__(self.schema) schema.read() # check the validity of the descriptor against the schema jsonschema.validate( self, schema, format_checker=jsonschema.FormatChecker(), )
[docs] def write(self): """ Overwrite the content of the configuration file by the parameters actually set, and make a copy of the old file before doing it in order to preserve some errors when a problem occurred in the application. A clean copy of the configuration file should also always be available in SVN repository of the program. """ # returns if filename not set if not self.filename: return # start by doing a copy of the old file (preserving mtime and atime) logger.debug("Backup the file into {0}".format(self.filename + "~")) shutil.copy2(self.filename, self.filename + "~") # now dump the parameters inside the json file logger.debug("Writing parameters to {0}".format(self.filename)) with open(self.filename, "w") as f: json.dump(self, f, ensure_ascii=False, indent=4)
[docs] def extends(self): """ Check if the file extends another one, read it and make the merge between the source file and this one. """ # check presence of the extends keyword if "extends" not in self: return # get the path of the file to extend path = self["extends"] # check that path is absolute or not if not osp.isabs(path): # relative path, make it absolute relatively to the current file # get path of the current file current = osp.abspath(osp.dirname(self.filename)) # join the path to the current file path = osp.abspath(osp.join(current, path)) # read the parent file parent = self.__class__(path) parent.read() # merge the two dictionaries self.merge(parent, self) self.update(parent)
@classmethod
[docs] def merge(cls, destination, source): """ Recursively merge b dictionary into a dictionary. """ # loop over items in the overriding dict for key, value in source.items(): # check the value is a dictionary if isinstance(value, dict): # get the field with the key in the destination node = destination.get(key, {}) # merge recursively the value cls.merge(node, value) else: # change the value destination[key] = value