Source code for id_translation.toml.meta._config_metadata
import json
import logging
import sys
from hashlib import sha256
from pathlib import Path
from typing import TYPE_CHECKING, Any
from id_translation.toml._load_toml import load_toml_file
from ._base_metadata import BaseMetadata
from ._metaconf import Metaconf
LOGGER = logging.getLogger(__package__).getChild("Translator").getChild("config")
if TYPE_CHECKING:
from id_translation import Translator
[docs]
class ConfigMetadata(BaseMetadata):
"""Metadata pertaining to how a :class:`.Translator` instance was initialized from TOML configuration.
Equivalence:
Configs are equivalent if and only if...
- They have the same top-level dependency versions, and
- Use the same fully qualified class name, and
- The main configuration is equal after parsing, and
- They have the same number of auxiliary (`"extra"`) fetcher configurations, and
- All auxiliary fetcher configurations are equal after parsing.
The :class:`Metaconf` is not explicitly included in the equivalence check, but changing it will invalidate cached
instances. Environment variable changes may also invalidate the cache if :attr:`EnvConf.allow_interpolation` is set
and interpolations such as ``${VAR}`` are present.
Args:
main: Absolute path and fingerprint of the main translation configuration."
extra_fetchers: Absolute path and fingerprint of configuration files for auxiliary fetchers.
clazz: String representation of the class type.
metaconf: A :class:`Metaconf` instance that determines, among other things, how other config paths are processed.
kwargs: Forwarded to base class.
"""
def __init__(
self,
main: tuple[Path, str],
extra_fetchers: tuple[tuple[Path, str], ...],
clazz: str,
metaconf: Metaconf,
**kwargs: Any,
) -> None:
if "versions" not in kwargs:
kwargs["versions"] = {
"python": metaconf.equivalence.python_version.format(v=sys.version_info),
**self.get_package_versions(metaconf.equivalence.extra_packages),
}
super().__init__(**kwargs)
self.main = main
self.extra_fetchers = extra_fetchers
self.clazz = clazz
self.metaconf = metaconf
def _to_dict(self, to_json: dict[str, Any]) -> dict[str, Any]:
return {
"main": tuple(map(str, to_json.pop("main"))),
"extra_fetchers": [tuple(map(str, t)) for t in to_json.pop("extra_fetchers")],
"metaconf": to_json.pop("metaconf").as_dict(),
"class": to_json.pop("clazz"),
}
@classmethod
def _deserialize(cls, from_json: dict[str, Any]) -> dict[str, Any]:
def to_path_tuple(args: list[str]) -> tuple[Path, str]:
return Path(args[0]), args[1]
return dict(
main=to_path_tuple(from_json.pop("main")),
extra_fetchers=tuple(map(to_path_tuple, from_json.pop("extra_fetchers"))),
clazz=from_json.pop("class"),
metaconf=Metaconf.from_dict(from_json.pop("metaconf")),
)
def _is_equivalent(self, other: BaseMetadata) -> str: # pragma: no cover
assert isinstance(other, ConfigMetadata) # noqa: S101
if self.clazz != other.clazz:
return f"Class not equal. Expected '{self.clazz}', but got '{other.clazz}'"
if self.main[1] != other.main[1]:
return f"Main configuration changed. Expected fingerprint {self.main[1]}, but got {other.main[1]}"
if self.metaconf != other.metaconf:
return f"Meta configuration changed: {self.metaconf} != {other.metaconf}."
if len(self.extra_fetchers) != len(other.extra_fetchers):
return (
f"Number of auxiliary fetchers changed. Expected {len(self.extra_fetchers)}"
f" fetchers but got {len(other.extra_fetchers)}"
)
for i, (path, fingerprint) in enumerate(self.extra_fetchers):
_, other_fingerprint = other.extra_fetchers[i]
if fingerprint != other_fingerprint:
return (
f"Configuration changed for auxiliary fetcher #{i} at {path}. "
f"Expected fingerprint {fingerprint}, but got {other_fingerprint}"
)
return ""
[docs]
@staticmethod
def from_toml_paths(
path: str,
extra_fetchers: list[str],
clazz: type["Translator[Any, Any, Any]"],
) -> "ConfigMetadata":
"""Convenience function for creating ``ConfigMetadata`` instances."""
metaconf_path = str(Path(path).with_name("metaconf.toml"))
metaconf = Metaconf.from_path_or_default(metaconf_path)
def _create_path_tuple(str_path: str) -> tuple[Path, str]:
p = Path(str_path).expanduser().absolute()
content = load_toml_file(
p,
allow_interpolation=metaconf.env.allow_interpolation,
allow_nested=metaconf.env.allow_nested,
allow_blank=metaconf.env.allow_blank,
)
return p, sha256(json.dumps(content, sort_keys=True).encode()).hexdigest()
return ConfigMetadata(
main=_create_path_tuple(path),
extra_fetchers=tuple(map(_create_path_tuple, extra_fetchers)),
clazz=clazz.__module__ + "." + clazz.__qualname__, # Fully qualified name
metaconf=metaconf,
)