"""
The Configuration API.
"""
from __future__ import annotations
from abc import abstractmethod
from collections.abc import Callable
from contextlib import chdir
from typing import Generic, TypeVar, TypeAlias, TYPE_CHECKING, Self, Any
import aiofiles
from aiofiles.os import makedirs
from betty.assertion import AssertionChain, assert_file_path
from betty.assertion.error import AssertionFailedGroup
from betty.locale.localizable import plain
from betty.serde.dump import Dumpable
from betty.serde.format import FORMAT_REPOSITORY, format_for
from betty.serde.load import Loadable
if TYPE_CHECKING:
from pathlib import Path
_ConfigurationListener: TypeAlias = Callable[[], None]
ConfigurationListener: TypeAlias = "Configuration | _ConfigurationListener"
[docs]
class Configuration(Loadable, Dumpable):
"""
Any configuration object.
"""
[docs]
def update(self, other: Self) -> None:
"""
Update this configuration with the values from ``other``.
"""
self.load(other.dump())
[docs]
class DefaultConfigurable(Configurable[_ConfigurationT], Generic[_ConfigurationT]):
"""
A configurable type that can provide its own default configuration.
"""
[docs]
async def assert_configuration_file(
configuration: _ConfigurationT,
) -> AssertionChain[Path, _ConfigurationT]:
"""
Assert that configuration can be loaded from a file.
"""
available_formats = {
available_format: await FORMAT_REPOSITORY.new_target(available_format)
async for available_format in FORMAT_REPOSITORY
}
def _assert(configuration_file_path: Path) -> _ConfigurationT:
with (
AssertionFailedGroup().assert_valid() as errors,
# Change the working directory to allow relative paths to be resolved
# against the configuration file's directory path.
chdir(configuration_file_path.parent),
):
with open(configuration_file_path) as f:
read_configuration = f.read()
with errors.catch(plain(f"in {str(configuration_file_path.resolve())}")):
configuration_file_format = available_formats[
format_for(list(available_formats), configuration_file_path.suffix)
]
configuration.load(configuration_file_format.load(read_configuration))
return configuration
return assert_file_path() | _assert