|
8 | 8 | import tomllib |
9 | 9 | from dataclasses import field |
10 | 10 | from pathlib import Path |
11 | | -from typing import Any, Literal, cast, get_args |
| 11 | +from typing import Any, ClassVar, Literal, Self, Type, cast, get_args |
12 | 12 |
|
| 13 | +from marshmallow import Schema |
13 | 14 | from marshmallow_dataclass import dataclass |
14 | 15 |
|
15 | 16 | _logger = logging.getLogger(__name__) |
@@ -198,24 +199,6 @@ class MicrogridConfig: |
198 | 199 | ctype: dict[str, ComponentTypeConfig] = field(default_factory=dict) |
199 | 200 | """Mapping of component category types to ac power component config.""" |
200 | 201 |
|
201 | | - def __init__(self, config_dict: dict[str, Any]) -> None: |
202 | | - """Initialize the microgrid configuration. |
203 | | -
|
204 | | - Args: |
205 | | - config_dict: Dictionary with component type as key and config as value. |
206 | | - """ |
207 | | - self.meta = Metadata(**(config_dict.get("meta") or {})) |
208 | | - |
209 | | - self.pv = config_dict.get("pv") or {} |
210 | | - self.wind = config_dict.get("wind") or {} |
211 | | - self.battery = config_dict.get("battery") or {} |
212 | | - |
213 | | - self.ctype = { |
214 | | - ctype: ComponentTypeConfig(**cfg) |
215 | | - for ctype, cfg in config_dict.get("ctype", {}).items() |
216 | | - if ComponentTypeConfig.is_valid_type(ctype) |
217 | | - } |
218 | | - |
219 | 202 | def component_types(self) -> list[str]: |
220 | 203 | """Get a list of all component types in the configuration.""" |
221 | 204 | return list(self.ctype.keys()) |
@@ -282,6 +265,67 @@ def formula(self, component_type: str, metric: str) -> str: |
282 | 265 |
|
283 | 266 | return formula |
284 | 267 |
|
| 268 | + Schema: ClassVar[Type[Schema]] = Schema |
| 269 | + |
| 270 | + @classmethod |
| 271 | + def _load_table_entries(cls, data: dict[str, Any]) -> dict[str, Self]: |
| 272 | + """Load microgrid configurations from table entries. |
| 273 | +
|
| 274 | + Args: |
| 275 | + data: The loaded TOML data. |
| 276 | +
|
| 277 | + Returns: |
| 278 | + A dict mapping microgrid IDs to MicrogridConfig instances. |
| 279 | +
|
| 280 | + Raises: |
| 281 | + ValueError: If top-level keys are not numeric microgrid IDs |
| 282 | + or if there is a microgrid ID mismatch. |
| 283 | + TypeError: If microgrid data is not a dict. |
| 284 | + """ |
| 285 | + if not all(str(k).isdigit() for k in data.keys()): |
| 286 | + raise ValueError("All top-level keys must be numeric microgrid IDs.") |
| 287 | + |
| 288 | + mgrids = {} |
| 289 | + for mid, entry in data.items(): |
| 290 | + if not mid.isdigit(): |
| 291 | + raise ValueError( |
| 292 | + f"Table reader: Microgrid ID key must be numeric, got {mid}" |
| 293 | + ) |
| 294 | + if not isinstance(entry, dict): |
| 295 | + raise TypeError("Table reader: Each microgrid entry must be a dict") |
| 296 | + |
| 297 | + mgrid = cls.Schema().load(entry) |
| 298 | + if mgrid.meta is None or mgrid.meta.microgrid_id is None: |
| 299 | + raise ValueError( |
| 300 | + "Table reader: Each microgrid entry must have a meta.microgrid_id" |
| 301 | + ) |
| 302 | + if int(mgrid.meta.microgrid_id) != int(mid): |
| 303 | + raise ValueError( |
| 304 | + f"Table reader: Microgrid ID mismatch: key {mid} != {mgrid.meta.microgrid_id}" |
| 305 | + ) |
| 306 | + |
| 307 | + mgrids[mid] = mgrid |
| 308 | + |
| 309 | + return mgrids |
| 310 | + |
| 311 | + @classmethod |
| 312 | + def load_from_file(cls, config_path: Path) -> dict[int, Self]: |
| 313 | + """ |
| 314 | + Load and validate configuration settings from a TOML file. |
| 315 | +
|
| 316 | + Args: |
| 317 | + config_path: the path to the TOML configuration file. |
| 318 | +
|
| 319 | + Returns: |
| 320 | + A dict mapping microgrid IDs to MicrogridConfig instances. |
| 321 | + """ |
| 322 | + with config_path.open("rb") as f: |
| 323 | + data = tomllib.load(f) |
| 324 | + |
| 325 | + assert isinstance(data, dict) |
| 326 | + |
| 327 | + return cls._load_table_entries(data) |
| 328 | + |
285 | 329 | @staticmethod |
286 | 330 | def load_configs( |
287 | 331 | microgrid_config_files: str | Path | list[str | Path] | None = None, |
@@ -340,14 +384,7 @@ def load_configs( |
340 | 384 | _logger.warning("Config path %s is not a file, skipping.", config_path) |
341 | 385 | continue |
342 | 386 |
|
343 | | - with config_path.open("rb") as f: |
344 | | - cfg_dict = tomllib.load(f) |
345 | | - for microgrid_id, mcfg in cfg_dict.items(): |
346 | | - _logger.debug( |
347 | | - "Loading microgrid config for ID %s from %s", |
348 | | - microgrid_id, |
349 | | - config_path, |
350 | | - ) |
351 | | - microgrid_configs[microgrid_id] = MicrogridConfig(mcfg) |
| 387 | + mcfgs = MicrogridConfig.load_from_file(config_path) |
| 388 | + microgrid_configs.update({str(key): value for key, value in mcfgs.items()}) |
352 | 389 |
|
353 | 390 | return microgrid_configs |
0 commit comments