From 1e1bac9fdb15604290454207e2007cef3b32c807 Mon Sep 17 00:00:00 2001 From: Dominique Lasserre Date: Sat, 18 Jan 2025 14:07:08 +0100 Subject: [PATCH] EOSdash: Support nested types --- single_test_optimization.py | 6 --- src/akkudoktoreos/server/eos.py | 6 ++- src/akkudoktoreos/server/eosdash.py | 83 ++++++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/single_test_optimization.py b/single_test_optimization.py index a1389a1..6d3cd16 100755 --- a/single_test_optimization.py +++ b/single_test_optimization.py @@ -153,9 +153,6 @@ def prepare_optimization_real_parameters() -> OptimizationParameters: "max_charge_power_w": 11040, "initial_soc_percentage": 5, }, - "inverter": { - "max_power_wh": 10000, - }, "temperature_forecast": temperature_forecast, "start_solution": start_solution, } @@ -310,9 +307,6 @@ def prepare_optimization_parameters() -> OptimizationParameters: "max_charge_power_w": 11040, "initial_soc_percentage": 5, }, - "inverter": { - "max_power_wh": 10000, - }, "temperature_forecast": temperature_forecast, "start_solution": start_solution, } diff --git a/src/akkudoktoreos/server/eos.py b/src/akkudoktoreos/server/eos.py index f2eddd6..a62c6f6 100755 --- a/src/akkudoktoreos/server/eos.py +++ b/src/akkudoktoreos/server/eos.py @@ -161,7 +161,11 @@ def start_eosdash() -> subprocess.Popen: reload = False else: host = args.host - port = config_eos.server.server_eosdash_port if config_eos.server.server_eosdash_port else (args.port + 1) + port = ( + config_eos.server.server_eosdash_port + if config_eos.server.server_eosdash_port + else (args.port + 1) + ) eos_host = args.host eos_port = args.port log_level = args.log_level diff --git a/src/akkudoktoreos/server/eosdash.py b/src/akkudoktoreos/server/eosdash.py index ce012d4..9b0f0bb 100644 --- a/src/akkudoktoreos/server/eosdash.py +++ b/src/akkudoktoreos/server/eosdash.py @@ -1,11 +1,16 @@ import argparse import os +from functools import reduce +from typing import Any, Union import uvicorn from fasthtml.common import H1, FastHTML, Table, Td, Th, Thead, Titled, Tr +from pydantic.fields import ComputedFieldInfo, FieldInfo +from pydantic_core import PydanticUndefined from akkudoktoreos.config.config import get_config from akkudoktoreos.core.logging import get_logger +from akkudoktoreos.core.pydantic import PydanticBaseModel logger = get_logger(__name__) @@ -14,14 +19,78 @@ config_eos = get_config() # Command line arguments args = None + +def get_default_value(field_info: Union[FieldInfo, ComputedFieldInfo], regular_field: bool) -> Any: + default_value = "" + if regular_field: + if (val := field_info.default) is not PydanticUndefined: + default_value = val + else: + default_value = "N/A" + return default_value + + +def resolve_nested_types(field_type: Any, parent_types: list[str]) -> list[tuple[Any, list[str]]]: + resolved_types: list[tuple[Any, list[str]]] = [] + + origin = getattr(field_type, "__origin__", field_type) + if origin is Union: + for arg in getattr(field_type, "__args__", []): + if arg is not type(None): + resolved_types.extend(resolve_nested_types(arg, parent_types)) + else: + resolved_types.append((field_type, parent_types)) + + return resolved_types + + configs = [] -for field_name in config_eos.model_fields: - config = {} - config["name"] = field_name - config["value"] = getattr(config_eos, field_name) - config["default"] = config_eos.model_fields[field_name].default - config["description"] = config_eos.model_fields[field_name].description - configs.append(config) +inner_types: set[type[PydanticBaseModel]] = set() +for field_name, field_info in list(config_eos.model_fields.items()) + list( + config_eos.model_computed_fields.items() +): + + def extract_nested_models( + subfield_info: Union[ComputedFieldInfo, FieldInfo], parent_types: list[str] + ) -> None: + regular_field = isinstance(subfield_info, FieldInfo) + subtype = subfield_info.annotation if regular_field else subfield_info.return_type + + if subtype in inner_types: + return + + nested_types = resolve_nested_types(subtype, []) + found_basic = False + for nested_type, nested_parent_types in nested_types: + if not isinstance(nested_type, type) or not issubclass(nested_type, PydanticBaseModel): + if found_basic: + continue + + config = {} + config["name"] = ".".join(parent_types) + try: + config["value"] = reduce(getattr, [config_eos] + parent_types) + except AttributeError: + # Parent value(s) are not set in current config + config["value"] = "" + config["default"] = get_default_value(subfield_info, regular_field) + config["description"] = ( + subfield_info.description if subfield_info.description else "" + ) + configs.append(config) + found_basic = True + else: + new_parent_types = parent_types + nested_parent_types + inner_types.add(nested_type) + for nested_field_name, nested_field_info in list( + nested_type.model_fields.items() + ) + list(nested_type.model_computed_fields.items()): + extract_nested_models( + nested_field_info, + new_parent_types + [nested_field_name], + ) + + extract_nested_models(field_info, [field_name]) app = FastHTML()