diff --git a/docs/_generated/config.md b/docs/_generated/config.md index bef3710..9d81f6a 100644 --- a/docs/_generated/config.md +++ b/docs/_generated/config.md @@ -879,3 +879,135 @@ Attributes: "utils": {} } ``` + +## Full example Config + +```{eval-rst} +.. code-block:: json + + { + "general": { + "data_folder_path": null, + "data_output_subpath": "output", + "data_cache_subpath": "cache", + "latitude": 52.52, + "longitude": 13.405 + }, + "logging": { + "level": "INFO" + }, + "devices": { + "batteries": [ + { + "device_id": "battery1", + "hours": null, + "capacity_wh": 8000, + "charging_efficiency": 0.88, + "discharging_efficiency": 0.88, + "max_charge_power_w": 5000, + "initial_soc_percentage": 0, + "min_soc_percentage": 0, + "max_soc_percentage": 100 + } + ], + "inverters": [], + "home_appliances": [] + }, + "measurement": { + "load0_name": "Household", + "load1_name": null, + "load2_name": null, + "load3_name": null, + "load4_name": null + }, + "optimization": { + "hours": 48, + "penalty": 10, + "ev_available_charge_rates_percent": [ + 0.0, + 0.375, + 0.5, + 0.625, + 0.75, + 0.875, + 1.0 + ] + }, + "prediction": { + "hours": 48, + "historic_hours": 48 + }, + "elecprice": { + "provider": "ElecPriceAkkudoktor", + "charges_kwh": 0.21, + "provider_settings": null + }, + "load": { + "provider": "LoadAkkudoktor", + "provider_settings": null + }, + "pvforecast": { + "provider": "PVForecastAkkudoktor", + "planes": [ + { + "surface_tilt": 10.0, + "surface_azimuth": 10.0, + "userhorizon": [ + 10.0, + 20.0, + 30.0 + ], + "peakpower": 5.0, + "pvtechchoice": "crystSi", + "mountingplace": "free", + "loss": 14.0, + "trackingtype": 0, + "optimal_surface_tilt": false, + "optimalangles": false, + "albedo": null, + "module_model": null, + "inverter_model": null, + "inverter_paco": 6000, + "modules_per_string": 20, + "strings_per_inverter": 2 + }, + { + "surface_tilt": 20.0, + "surface_azimuth": 20.0, + "userhorizon": [ + 5.0, + 15.0, + 25.0 + ], + "peakpower": 3.5, + "pvtechchoice": "crystSi", + "mountingplace": "free", + "loss": 14.0, + "trackingtype": 1, + "optimal_surface_tilt": false, + "optimalangles": false, + "albedo": null, + "module_model": null, + "inverter_model": null, + "inverter_paco": 4000, + "modules_per_string": 20, + "strings_per_inverter": 2 + } + ], + "provider_settings": null + }, + "weather": { + "provider": "WeatherImport", + "provider_settings": null + }, + "server": { + "host": "0.0.0.0", + "port": 8503, + "verbose": false, + "startup_eosdash": true, + "eosdash_host": "0.0.0.0", + "eosdash_port": 8504 + }, + "utils": {} + } +``` diff --git a/scripts/generate_config_md.py b/scripts/generate_config_md.py index 9f7c79f..3b68395 100755 --- a/scripts/generate_config_md.py +++ b/scripts/generate_config_md.py @@ -22,6 +22,8 @@ logger = get_logger(__name__) documented_types: set[PydanticBaseModel] = set() undocumented_types: dict[PydanticBaseModel, tuple[str, list[str]]] = dict() +global_config_dict: dict[str, Any] = dict() + def get_title(config: PydanticBaseModel) -> str: if config.__doc__ is None: @@ -209,8 +211,12 @@ def generate_config_table_md( table += ".. code-block:: json\n\n" if has_examples_list: input_dict = build_nested_structure(toplevel_keys[:-1], ins_dict_list) + if not extra_config: + global_config_dict[toplevel_keys[0]] = ins_dict_list else: input_dict = build_nested_structure(toplevel_keys, ins_dict_list[0]) + if not extra_config: + global_config_dict[toplevel_keys[0]] = ins_dict_list[0] table += textwrap.indent(json.dumps(input_dict, indent=4), " ") table += "\n" table += "```\n\n" @@ -258,6 +264,16 @@ def generate_config_md(config_eos: ConfigEOS) -> str: field_type, [field_name], f"EOS_{field_name.upper()}__", True ) + # Full config + markdown += "## Full example Config\n\n" + markdown += "```{eval-rst}\n" + markdown += ".. code-block:: json\n\n" + # Test for valid config first + config_eos.merge_settings_from_dict(global_config_dict) + markdown += textwrap.indent(json.dumps(global_config_dict, indent=4), " ") + markdown += "\n" + markdown += "```\n\n" + # Assure there is no double \n at end of file markdown = markdown.rstrip("\n") markdown += "\n" @@ -290,7 +306,8 @@ def main(): except Exception as e: print(f"Error during Configuration Specification generation: {e}", file=sys.stderr) - sys.exit(1) + # keep throwing error to debug potential problems (e.g. invalid examples) + raise e if __name__ == "__main__": diff --git a/src/akkudoktoreos/core/pydantic.py b/src/akkudoktoreos/core/pydantic.py index 22dd9c1..b06e0e6 100644 --- a/src/akkudoktoreos/core/pydantic.py +++ b/src/akkudoktoreos/core/pydantic.py @@ -14,6 +14,7 @@ Key Features: import json import re +from copy import deepcopy from typing import Any, Dict, List, Optional, Type, Union from zoneinfo import ZoneInfo @@ -45,7 +46,7 @@ def merge_models(source: BaseModel, update_dict: dict[str, Any]) -> dict[str, An return update_dict source_dict = source.model_dump(exclude_unset=True) - merged_dict = deep_update(source_dict, update_dict) + merged_dict = deep_update(source_dict, deepcopy(update_dict)) return merged_dict