Source code for bibcat.core.config
# !/usr/bin/env python
# -*- coding: utf-8 -*-
#
import os
import pathlib
from typing import Any, Dict
import yaml # type: ignore
from deepmerge import Merger # type: ignore
from yaml import SafeLoader, SequenceNode
# For instance, fraction_TVT:list of floats concatenates the values twice. To prevent it, created a new class.
merger = Merger([(list, ["override"]), (dict, ["merge"]), (set, ["union"])], ["override"], ["override"])
[docs]
class ddict(Dict[str, Any]):
"""Create a dottable dictionary
This allows for dictionary keys to be accessed
like class attributes. For example, in x = {'a': 1, 'b': 2},
one can access x['a'] or x.a
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""override init"""
# initialize normal dict
super().__init__(*args, **kwargs)
# Convert any nested dictionaries into ddict instances
for key, value in self.items():
if isinstance(value, dict):
self[key] = ddict(value)
def __setattr__(self, name: str, value: Any) -> None:
self[name] = value
def __delattr__(self, name: str) -> None:
del self[name]
def __getattr__(self, name: str) -> Any:
"""override getattr"""
# Allow attribute access to existing dictionary keys
if name in self:
return self[name]
raise AttributeError(f"'ddict' object has no attribute '{name}'")
# Custom constructor for !tuple tag
[docs]
def tuple_constructor(loader: SafeLoader, node: SequenceNode) -> tuple:
return tuple(loader.construct_sequence(node))
# Register the custom constructor for !tuple tag with the SafeLoader
yaml.SafeLoader.add_constructor("!tuple", tuple_constructor)
# Read yaml file
[docs]
def read_yaml(filename: pathlib.Path) -> dict:
"""Read a yaml configuration file
Expands any environment variables in the yaml file
Parameters
----------
filename : str
the filepath to the configuration
Returns
-------
dict
the yaml configuration
"""
with open(filename, "r") as f:
data: Dict[str, Any] = yaml.load(os.path.expandvars(f.read()), Loader=yaml.SafeLoader)
return data
[docs]
def get_custom_config() -> dict | None:
"""Look up and read in any custom configuration
Looks for a user custom configuration for bibcat and reads
it in if found. Looks for a "bibcat_config.yaml" file in
a user environment variable directory, $BIBCAT_CONFIG_DIR,
or the user's home directory.
Returns
-------
dict | None
the custom yaml configuration
"""
# build custom config path
# look for config in BIBCAT_CONFIG_DIR envvar or in user home directory
bc_dir = os.getenv("BIBCAT_CONFIG_DIR")
user_dir = os.path.expanduser("~")
root = bc_dir or user_dir
path = pathlib.Path(root) / "bibcat_config.yaml"
# if file doesn't exist, return
if not path.exists():
return None
# read the config
return read_yaml(path)
[docs]
def get_default_config() -> ddict:
"""Read in the bibcat default configuration
Reads in the bibcat configuration from a yaml file.
The default config is in etc/bibcat_config.yaml.
Returns
-------
dict
the bibcat config object
"""
# get the default configuration
root = pathlib.Path(__file__).resolve().parent.parent
config_path = root / "etc/bibcat_config.yaml"
config = read_yaml(config_path)
return ddict(config)
[docs]
def get_config() -> ddict:
"""Read in the bibcat configuration
Reads in the bibcat configuration from a yaml file.
The default config is in etc/bibcat_config.yaml. It
looks for an optional user configuration for bibcat,
and merges it with the default. All user configs take
precedence and override any default configs.
Returns
-------
dict
the bibcat config object
"""
# get the default_config()
config = get_default_config()
# get any custom configuration
custom_config = get_custom_config()
# merge the two
if custom_config:
config = merger.merge(config, custom_config)
return ddict(config)