import logging
import os
import pathlib
import sys
from collections import OrderedDict
from configparser import (
ConfigParser,
ExtendedInterpolation,
InterpolationMissingOptionError,
)
log = logging.getLogger(__name__)
[docs]class Config:
"""
A *wrapper* class for the configparser module in standard python library.
:param str identifier: a unique identifier for this app.
:param str install_path: Full filepath to application package location
:param str proj_path: Full filepath to project location
:param str default_config_string: initial config text for configparser
"""
def __init__(
self,
identifier,
install_path,
proj_path,
default_config_string,
read_from_files=True,
):
self.IDENTIFIER = identifier
if not install_path:
install_path = os.path.dirname(__file__)
# install_path = os.path.expanduser(install_path)
if not proj_path:
proj_path = os.getcwd()
config_path = pathlib.Path(proj_path).joinpath("config")
_config = ConfigParser(interpolation=ExtendedInterpolation())
# This trick allows the option key to remain case-sensitive
_config.optionxform = str
self._config = _config
_config.add_section("install")
_config.set("install", "path", install_path)
_config.add_section("proj")
_config.set("proj", "path", proj_path)
_config.read_string(default_config_string)
# if getfilesystemencoding returns ascii then we will not be able to read config files with exotic characters.
# we must therefore choose something else; getdefaultencoding ()
self.conf_encoding = sys.getfilesystemencoding()
if self.conf_encoding == "ascii":
self.conf_encoding = sys.getdefaultencoding()
# self.read("{install_path}/data".format(install_path=install_path))
if read_from_files:
self.read_default(config_path)
self.verify(proj_path, config_path)
if read_from_files and "config" in self._config:
for ident, path in self.get_dict("config").items():
self._config.read(path, encoding=self.conf_encoding)
# this file should not be editable from "Config Files" in webadmin.
# Used to lock specific configurations. must therefore be read last.
if read_from_files:
self._config.read(
"{config_path}/default.lock".format(config_path=config_path),
encoding=self.conf_encoding,
)
# liste over konfiger som ikke skal være synlig i webadmin
# settes med set_hidden_value-funksjonen
self._hidden = {}
[docs] def read_default(self, config_path):
log.info("Read config from %s", config_path)
self._config.read(
"{config_path}/default.conf".format(config_path=config_path),
encoding=self.conf_encoding,
)
self._config.read(
"{config_path}/default.{os_name}.conf".format(
config_path=config_path, os_name=os.name
),
encoding=self.conf_encoding,
)
def __call__(self, section, key, defaultvalue=None, add_if_not_exists=True):
return self.config(section, key, defaultvalue)
[docs] def config(self, section, key, defaultvalue=None, add_if_not_exists=True):
try:
if defaultvalue is None:
return self._config[section][key]
else:
# typecast basert på defaultvalue
return type(defaultvalue)(self._config[section][key])
except (KeyError, InterpolationMissingOptionError):
if add_if_not_exists:
self.set_config(section, key, str(defaultvalue))
return defaultvalue
[docs] def set_config(self, section, key, value):
self.add_section(section)
self._config.set(section, key, value)
[docs] def add_section(self, section):
if not self._config.has_section(section):
self._config.add_section(section)
[docs] def set_hidden_value(self, section, key):
if not section in self._hidden:
self._hidden[section] = []
if not key in self._hidden[section]:
self._hidden[section].append(key)
[docs] def is_hidden_value(self, section, key):
if not section in self._hidden:
return False
return key in self._hidden[section]
[docs] def get_dict(self, section):
return OrderedDict(self._config[section])
[docs] def get_full_list(self):
def walker():
for section, keyval in self._config.items():
try:
for key, val in keyval.items():
if self.is_hidden_value(section, key):
yield section, key, "****"
else:
yield section, key, val
except InterpolationMissingOptionError as error:
yield section, "ERROR", repr(error)
return walker()
[docs] def verify(self, proj_path, config_path):
proj_path = pathlib.Path(proj_path)
if not proj_path.is_dir():
raise ValueError(
"Error parsing config-files. proj-path {} not found".format(proj_path)
)
config_path = pathlib.Path(config_path)
if not config_path.is_dir():
raise ValueError(
"Error parsing config-files. config-path {} not found".format(
config_path
)
)
default_conf = config_path.joinpath("default.conf")
if not default_conf.is_file():
raise ValueError(
"Error parsing config-files. configfile {} not found".format(
default_conf
)
)
_ident = self.config("general", "identifier", "")
if _ident != self.IDENTIFIER:
raise ValueError(
"Error parsing config-files. [general]identifier {} not found {}".format(
self.IDENTIFIER, _ident
)
)
if self.config("general", "version", 0.0) != 1:
raise ValueError(
"Error parsing config-files. [general]version {} not found".format(1)
)
return True