Source code for doitoml.sources._config
"""Handles discovering, loading, and normalizing configuration."""
import abc
import os
import re
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, cast
from doitoml.constants import DEFAULTS
from doitoml.errors import ConfigError
from doitoml.types import Strings
from ._source import Parser, Source
#: the prefix key in ``doitoml`` configuration
PREFIX = "prefix"
if TYPE_CHECKING:
from doitoml import DoiTOML
from doitoml.dsl import Getter
[docs]
class ConfigSource(Source):
"""A source of ``doitoml`` configuration."""
@property
def prefix(self) -> str:
"""Get the prefix for this configuration source."""
return str(self.raw_config.get(PREFIX, ""))
@abc.abstractproperty
def raw_config(self) -> Dict[str, Any]:
"""Extract a raw ``doitoml`` configuration from this source."""
def __eq__(self, other: object) -> bool:
return isinstance(other, ConfigSource) and other.path == self.path
def __repr__(self) -> str:
"""Format the source for warnings and errors."""
return (
f"<{self.__class__.__name__}"
f" prefix='{self.prefix}' "
f" path='{os.path.relpath(str(self.path), str(Path.cwd()))}'"
f">"
)
[docs]
class WrapperConfigSource(ConfigSource):
"""A config source which wraps another source with a specific root."""
child_source: Source
bit_prefix: Strings
_raw_config: Dict[str, Any]
def __init__(self, child_source: Source, bit_prefix: Strings) -> None:
"""Initialize a source, remembering its child and ``get`` bits."""
self.bit_prefix = bit_prefix
self.child_source = child_source
self.path = child_source.path
[docs]
def read(self) -> Any: # pragma: no cover
message = "A wrapper source cannot `read`."
raise NotImplementedError(message)
[docs]
def parse(self, data: str) -> Any: # noqa: ARG002
message = "A wrapper source cannot `parse`." # pragma: no cover
raise NotImplementedError(message) # pragma: no cover
@property
def raw_config(self) -> Dict[str, Any]:
"""Get the ``doitoml`` configuration from the prefixed child source."""
parsed = self.child_source.get(self.bit_prefix)
if not isinstance(parsed, dict): # pragma: no cover
message = (
f"Expected dictionary from source {self.child_source}:{self.bit_prefix}"
)
raise ConfigError(message)
return parsed
[docs]
def get(self, bits: List[Any]) -> Any: # pragma: no cover
"""Get a specific ``doitoml`` configuration piece."""
return self.child_source.get([*self.bit_prefix, *bits])
[docs]
class ConfigParser(Parser):
"""A parser that knows how to find well-known sources."""
@abc.abstractproperty
def pattern(self) -> re.Pattern[str]:
"""Provide pattern of well-known file names this parser can load."""
@abc.abstractproperty
def well_known(self) -> Tuple[str, ...]:
"""Provide concrete, well-known relative file names this parser can load."""
@abc.abstractmethod
def __call__(self, path: Path) -> ConfigSource: # pragma: no cover
"""Load a path as a config source."""