Skip to content

Commit 7dc2f2b

Browse files
Merge pull request #104 from matthias-wende-frequenz/load_paths
Refactor config file arguments of MicrogridData and MicrogridConfig classes
2 parents 8706709 + b592389 commit 7dc2f2b

File tree

4 files changed

+73
-23
lines changed

4 files changed

+73
-23
lines changed

RELEASE_NOTES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
## New Features
1212

1313
- Added consistent logger setup across all modules for structured logging and improved observability. Example notebooks updated to demonstrate logger usage.
14+
- The signature for passing config files MicrogridConfig.load_config() has been changed to accept a path a list of paths and a directory containing the config files.
15+
- `MicrogridData` class needs to be initialized with a `MicrogridConfig` object instead of a path to config file(s).
1416

1517
## Bug Fixes
1618

1719
- Fixed a bug in the notification `Scheduler` where tasks could overrun the configured duration due to imprecise sleep and stop logic. The scheduler now correctly tracks elapsed time, respects task execution duration, and stops reliably after the intended interval.
18-
- Fixed an issue where `EmailNotification` did not properly initialise its scheduler. Also fixed an example in the docstring.
20+
- Fixed an issue where `EmailNotification` did not properly initialise its scheduler. Also fixed an example in the docstring.

src/frequenz/data/microgrid/component_data.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,20 @@ def __init__(
2424
server_url: str,
2525
auth_key: str,
2626
sign_secret: str,
27-
microgrid_config_path: str | list[str],
27+
microgrid_configs: dict[str, MicrogridConfig] | None = None,
2828
) -> None:
2929
"""Initialize microgrid data.
3030
3131
Args:
3232
server_url: URL of the reporting service.
3333
auth_key: Authentication key to the service.
3434
sign_secret: Secret for signing requests.
35-
microgrid_config_path: Path(s) to the config file with microgrid components.
36-
37-
Raises:
38-
ValueError: If no microgrid config path is provided.
35+
microgrid_configs: MicrogridConfig dict mapping microgrid IDs to MicrogridConfigs.
3936
"""
37+
self._microgrid_configs = microgrid_configs
4038
self._client = ReportingApiClient(
4139
server_url=server_url, auth_key=auth_key, sign_secret=sign_secret
4240
)
43-
paths = (
44-
[microgrid_config_path]
45-
if isinstance(microgrid_config_path, str)
46-
else microgrid_config_path
47-
)
48-
if len(paths) < 1:
49-
raise ValueError("At least one microgrid config path must be provided")
50-
self._microgrid_configs = MicrogridConfig.load_configs(*paths)
5141

5242
@property
5343
def microgrid_ids(self) -> list[str]:
@@ -98,6 +88,8 @@ async def metric_data( # pylint: disable=too-many-arguments
9888
ctype: mcfg.formula(ctype, metric.upper()) for ctype in component_types
9989
}
10090

91+
logging.debug("Formulas: %s", formulas)
92+
10193
metric_enum = Metric[metric.upper()]
10294
data = [
10395
sample

src/frequenz/data/microgrid/config.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33

44
"""Configuration for microgrids."""
55

6+
import logging
67
import re
78
import tomllib
89
from dataclasses import dataclass
10+
from pathlib import Path
911
from typing import Any, Literal, cast, get_args
1012

13+
_logger = logging.getLogger(__name__)
14+
1115
ComponentType = Literal["grid", "pv", "battery", "consumption", "chp", "ev"]
1216
"""Valid component types."""
1317

@@ -286,22 +290,71 @@ def formula(self, component_type: str, metric: str) -> str:
286290
return formula
287291

288292
@staticmethod
289-
def load_configs(*paths: str) -> dict[str, "MicrogridConfig"]:
293+
def load_configs(
294+
microgrid_config_files: str | Path | list[str | Path] | None = None,
295+
microgrid_config_dir: str | Path | None = None,
296+
) -> dict[str, "MicrogridConfig"]:
290297
"""Load multiple microgrid configurations from a file.
291298
292299
Configs for a single microgrid are expected to be in a single file.
293300
Later files with the same microgrid ID will overwrite the previous configs.
294301
295302
Args:
296-
*paths: Path(es) to the config file(s).
303+
microgrid_config_files: Path to a single microgrid config file or list of paths.
304+
microgrid_config_dir: Directory containing multiple microgrid config files.
297305
298306
Returns:
299307
Dictionary of single microgrid formula configs with microgrid IDs as keys.
308+
309+
Raises:
310+
ValueError: If no config files or dir is provided, or if no config files are found.
300311
"""
301-
microgrid_configs = {}
302-
for config_path in paths:
303-
with open(config_path, "rb") as f:
312+
if microgrid_config_files is None and microgrid_config_dir is None:
313+
raise ValueError(
314+
"No microgrid config path or directory provided. "
315+
"Please provide at least one."
316+
)
317+
318+
config_files: list[Path] = []
319+
320+
if microgrid_config_files:
321+
if isinstance(microgrid_config_files, str):
322+
config_files = [Path(microgrid_config_files)]
323+
elif isinstance(microgrid_config_files, Path):
324+
config_files = [microgrid_config_files]
325+
elif isinstance(microgrid_config_files, list):
326+
config_files = [Path(f) for f in microgrid_config_files]
327+
328+
if microgrid_config_dir:
329+
if Path(microgrid_config_dir).is_dir():
330+
config_files += list(Path(microgrid_config_dir).glob("*.toml"))
331+
else:
332+
raise ValueError(
333+
f"Microgrid config directory {microgrid_config_dir} "
334+
"is not a directory"
335+
)
336+
337+
if len(config_files) == 0:
338+
raise ValueError(
339+
"No microgrid config files found. "
340+
"Please provide at least one valid config file."
341+
)
342+
343+
microgrid_configs: dict[str, "MicrogridConfig"] = {}
344+
345+
for config_path in config_files:
346+
if not config_path.is_file():
347+
_logger.warning("Config path %s is not a file, skipping.", config_path)
348+
continue
349+
350+
with config_path.open("rb") as f:
304351
cfg_dict = tomllib.load(f)
305352
for microgrid_id, mcfg in cfg_dict.items():
353+
_logger.debug(
354+
"Loading microgrid config for ID %s from %s",
355+
microgrid_id,
356+
config_path,
357+
)
306358
microgrid_configs[microgrid_id] = MicrogridConfig(mcfg)
359+
307360
return microgrid_configs

tests/test_config.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""Tests for the frequenz.lib.notebooks.config module."""
55

6+
from pathlib import Path
67
from typing import Any, cast
78

89
import pytest
@@ -122,12 +123,14 @@ def test_load_configs(mocker: MockerFixture) -> None:
122123
1.ctype.battery.component = [301, 302, 303, 304, 305, 306]
123124
1.pv.PV1.peak_power = 5000
124125
1.pv.PV1.rated_power = 4500
125-
1.pv.PV2peak_power = 8000
126-
1.pv.PV2rated_power = 7000
126+
1.pv.PV2.peak_power = 8000
127+
1.pv.PV2.rated_power = 7000
127128
1.battery.BAT1.capacity = 10000
128129
"""
129-
mocker.patch("builtins.open", mocker.mock_open(read_data=toml_data.encode("utf-8")))
130-
configs = MicrogridConfig.load_configs("mock_path.toml")
130+
mock_file = mocker.mock_open(read_data=toml_data.encode("utf-8"))
131+
mocker.patch("pathlib.Path.open", mock_file)
132+
mocker.patch("pathlib.Path.is_file", mocker.Mock(return_value=True))
133+
configs = MicrogridConfig.load_configs(Path("mock_path.toml"))
131134

132135
assert "1" in configs
133136
assert configs["1"].meta.name == "Test Grid"

0 commit comments

Comments
 (0)