distgit-bugzilla-sync/distgit_bugzilla_sync/config.py
Pierre-Yves Chibon 392627ecab Run black over the entire project
Signed-off-by: Pierre-Yves Chibon <pingou@pingoured.fr>
2020-04-29 11:41:28 +02:00

169 lines
5.9 KiB
Python

# distgit_bugzilla_sync.config - handle configuration
#
# Copyright © 2018-2019 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use, modify,
# copy, or redistribute it subject to the terms and conditions of the GNU
# General Public License v.2, or (at your option) any later version. This
# program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY expressed or implied, including the implied warranties 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; if not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the source
# code or documentation are not subject to the GNU General Public License and
# may only be used or replicated with the express permission of Red Hat, Inc.
import copy
import os
from typing import Optional, Sequence
import toml
_here = os.path.dirname(__file__)
_default_conf_root = os.path.join(_here, "default-config-files")
_system_conf_root = os.path.join(os.path.sep, "etc", "distgit-bugzilla-sync")
config_files = {
"default": {
"configuration": os.path.join(_default_conf_root, "configuration.toml"),
"email_overrides": os.path.join(_default_conf_root, "email_overrides.toml"),
},
"system": {
"configuration": os.path.join(_system_conf_root, "configuration.toml"),
"email_overrides": os.path.join(_system_conf_root, "email_overrides.toml"),
},
}
class ConfigDict(dict):
"""Dictionary class for configuration data.
This contains a modified update() method which allows deep-merging other
dictionaries."""
def update(self, other: dict):
"""Merge data from another dictionary into this one.
Fails if any node isn't of the same type in both, to detect
configuration format issues early."""
self_set = set(self)
other_set = set(other)
type_error = False
# keys that are in both self and other
for k in self_set & other_set:
self_val = self[k]
other_val = other[k]
if isinstance(other_val, dict):
if isinstance(self_val, ConfigDict):
self_val.update(other_val)
elif isinstance(self_val, dict):
self[k] = ConfigDict(self_val)
self[k].update(other_val)
else:
type_error = True
elif isinstance(other_val, type(self_val)):
self[k] = other_val
else:
type_error = True
if type_error:
raise TypeError(
f"[{k!r}] values of incompatible types: {self_val!r}, {other_val!r}"
)
# add keys -> values which are missing
for k in other_set - self_set:
v = other[k]
if isinstance(v, dict):
self[k] = ConfigDict(v)
else:
self[k] = v
def load_configuration(
addl_config_files: Optional[Sequence[str]] = None,
addl_email_overrides_files: Optional[Sequence[str]] = None,
):
"""Load stored configuration.
This function loads default, system-wide, and if specified, additional
configuration files into the `config` and `email_overrides`
dictionaries."""
if addl_config_files is None:
addl_config_files = ()
if addl_email_overrides_files is None:
addl_email_overrides_files = ()
# Load default files.
try:
default_config = toml.load(config_files["default"]["configuration"])
except FileNotFoundError as e:
raise RuntimeError(
f"Default configuration file {config_files['default']['configuration']} not found."
) from e
try:
default_email_overrides = toml.load(config_files["default"]["email_overrides"])
except FileNotFoundError as e:
raise RuntimeError(
f"Default email overrides file {config_files['default']['email_overrides']} not found."
) from e
# Attempt to load system-wide files.
try:
system_config = toml.load(config_files["system"]["configuration"])
except FileNotFoundError:
system_config = {}
try:
system_email_overrides = toml.load(config_files["system"]["email_overrides"])
except FileNotFoundError:
system_email_overrides = {}
# Load additional files (say, specified on the command line), if any.
addl_config = ConfigDict()
for addl_config_file in addl_config_files:
try:
addl_config.update(toml.load(addl_config_file))
except FileNotFoundError as e:
raise RuntimeError(
f"Additional configuration file {addl_config_file} not found."
) from e
addl_email_overrides = ConfigDict()
for addl_email_overrides_file in addl_email_overrides_files:
try:
addl_email_overrides.update(toml.load(addl_email_overrides_file))
except FileNotFoundError as e:
raise RuntimeError(
f"Additional email overrides file {addl_email_overrides_file} not found."
) from e
config.clear()
config.update(default_config)
config.update(system_config)
config.update(addl_config)
email_overrides.clear()
email_overrides.update(default_email_overrides)
email_overrides.update(system_email_overrides)
email_overrides.update(addl_email_overrides)
for env in config["environments"].values():
# Fill environments with default data.
env_values = copy.deepcopy(config)
del env_values["environments"]
# Values specified in the environments should take precedence.
env_values.update(env)
env.clear()
env.update(env_values)
config = ConfigDict()
email_overrides = ConfigDict()