Add Documentation 2 (#334)

Add documentation that covers:

- configuration
- prediction

Add Python scripts that support automatic documentation generation for
configuration data defined with pydantic.

Adapt EOS configuration to provide more methods for REST API and
automatic documentation generation.

Adapt REST API to allow for EOS configuration file load and save.
Sort REST API on generation of openapi markdown for docs.

Move logutil to core/logging to allow configuration of logging by standard config.

Make Akkudoktor predictions always start extraction of prediction data at start of day.
Previously extraction started at actual hour. This is to support the code that assumes
prediction data to start at start of day.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2025-01-05 14:41:07 +01:00
committed by GitHub
parent 03ec729e50
commit d4e31d556a
52 changed files with 4517 additions and 462 deletions

View File

@@ -20,6 +20,8 @@ from pydantic import Field, ValidationError, computed_field
# settings
from akkudoktoreos.config.configabc import SettingsBaseModel
from akkudoktoreos.core.coreabc import SingletonMixin
from akkudoktoreos.core.logging import get_logger
from akkudoktoreos.core.logsettings import LoggingCommonSettings
from akkudoktoreos.devices.devices import DevicesCommonSettings
from akkudoktoreos.measurement.measurement import MeasurementCommonSettings
from akkudoktoreos.optimization.optimization import OptimizationCommonSettings
@@ -34,7 +36,6 @@ from akkudoktoreos.prediction.pvforecastimport import PVForecastImportCommonSett
from akkudoktoreos.prediction.weather import WeatherCommonSettings
from akkudoktoreos.prediction.weatherimport import WeatherImportCommonSettings
from akkudoktoreos.server.server import ServerCommonSettings
from akkudoktoreos.utils.logutil import get_logger
from akkudoktoreos.utils.utils import UtilsCommonSettings
logger = get_logger(__name__)
@@ -90,6 +91,7 @@ class ConfigCommonSettings(SettingsBaseModel):
class SettingsEOS(
ConfigCommonSettings,
LoggingCommonSettings,
DevicesCommonSettings,
MeasurementCommonSettings,
OptimizationCommonSettings,
@@ -188,7 +190,13 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
@property
def config_default_file_path(self) -> Path:
"""Compute the default config file path."""
return Path(__file__).parent.parent.joinpath("data/default.config.json")
return self.package_root_path.joinpath("data/default.config.json")
@computed_field # type: ignore[prop-decorator]
@property
def package_root_path(self) -> Path:
"""Compute the package root path."""
return Path(__file__).parent.parent.resolve()
# Computed fields
@computed_field # type: ignore[prop-decorator]
@@ -200,6 +208,15 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys()))
return key_list
# Computed fields
@computed_field # type: ignore[prop-decorator]
@property
def config_keys_read_only(self) -> List[str]:
"""Returns the keys of all read only fields in the configuration."""
key_list = []
key_list.extend(list(self.__pydantic_decorators__.computed_fields.keys()))
return key_list
def __init__(self) -> None:
"""Initializes the singleton ConfigEOS instance.
@@ -239,7 +256,7 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
settings (SettingsEOS): The settings to apply globally.
force (Optional[bool]): If True, overwrites the existing settings completely.
If False, the new settings are merged to the existing ones with priority for
the new ones.
the new ones. Defaults to False.
Raises:
ValueError: If settings are already set and `force` is not True or
@@ -349,14 +366,23 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
return cfile, True
return config_dirs[0].joinpath(self.CONFIG_FILE_NAME), False
def from_config_file(self) -> None:
"""Loads the configuration file settings for EOS.
def settings_from_config_file(self) -> tuple[SettingsEOS, Path]:
"""Load settings from the configuration file.
If the config file does not exist, it will be created.
Returns:
tuple of settings and path
settings (SettingsEOS): The settings defined by the EOS configuration file.
path (pathlib.Path): The path of the configuration file.
Raises:
ValueError: If the configuration file is invalid or incomplete.
"""
config_file, exists = self._get_config_file_path()
config_dir = config_file.parent
# Create config directory and copy default config if file does not exist
if not exists:
config_dir.mkdir(parents=True, exist_ok=True)
try:
@@ -366,18 +392,39 @@ class ConfigEOS(SingletonMixin, SettingsEOS):
config_file = self.config_default_file_path
config_dir = config_file.parent
# Load and validate the configuration file
with config_file.open("r", encoding=self.ENCODING) as f_in:
try:
json_str = f_in.read()
ConfigEOS._file_settings = SettingsEOS.model_validate_json(json_str)
settings = SettingsEOS.model_validate_json(json_str)
except ValidationError as exc:
raise ValueError(f"Configuration '{config_file}' is incomplete or not valid: {exc}")
return settings, config_file
def from_config_file(self) -> tuple[SettingsEOS, Path]:
"""Load the configuration file settings for EOS.
Returns:
tuple of settings and path
settings (SettingsEOS): The settings defined by the EOS configuration file.
path (pathlib.Path): The path of the configuration file.
Raises:
ValueError: If the configuration file is invalid or incomplete.
"""
# Load settings from config file
ConfigEOS._file_settings, config_file = self.settings_from_config_file()
# Update configuration in memory
self.update()
# Everthing worked, remember the values
self._config_folder_path = config_dir
# Everything worked, remember the values
self._config_folder_path = config_file.parent
self._config_file_path = config_file
return ConfigEOS._file_settings, config_file
def to_config_file(self) -> None:
"""Saves the current configuration to the configuration file.