Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
- `load_configs()` and `load_configs_from_files()` now accept directory paths in
addition to individual TOML files. When a directory is provided, all
`*.toml` files in that directory are loaded in sorted order.
6 changes: 1 addition & 5 deletions src/frequenz/gridpool/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@

from frequenz.microgrid_component_graph import ComponentGraphConfig, FormulaOverrides

from .load import (
load_configs,
load_configs_from_api,
load_configs_from_files,
)
from .load import load_configs, load_configs_from_api, load_configs_from_files
from .microgrid import (
BatteryConfig,
ComponentCategory,
Expand Down
33 changes: 19 additions & 14 deletions src/frequenz/gridpool/config/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,7 @@
pv_inverter_ids,
pv_meter_ids,
)
from .microgrid import (
ComponentTypeConfig,
Metadata,
MicrogridConfig,
merge_config_maps,
)
from .microgrid import ComponentTypeConfig, Metadata, MicrogridConfig, merge_config_maps

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -123,16 +118,20 @@ async def load_configs(
return merge_config_maps(base=configs, override=override_configs)


# pylint: disable=too-many-branches
def load_configs_from_files(
microgrid_config_files: str | Path | list[str | Path] | None = None,
) -> dict[str, "MicrogridConfig"]:
"""Load multiple microgrid configurations from one or more files.
"""Load multiple microgrid configurations from one or more paths/files.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just removed the support from this to reduce the complexity. If you need support for folders let's add another function that calls loads the files from a folder and calls load_configs_from_files on them.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If downstream still wants it, they can just call load_configs_from_files(list(Path(dir).glob("*.toml"))) etc.


Configs for a single microgrid are expected to be in a single file.
Later files with the same microgrid ID will overwrite the previous configs.
Directory paths are expanded to all `.toml` files they contain.

Args:
microgrid_config_files: Path to a single microgrid config file or list of paths.
microgrid_config_files:
Path to a single microgrid config file or directory, or a list of
file and directory paths.

Returns:
Dictionary of single microgrid formula configs with microgrid IDs as keys.
Expand All @@ -148,12 +147,18 @@ def load_configs_from_files(
config_files: list[Path] = []

if microgrid_config_files:
if isinstance(microgrid_config_files, str):
config_files = [Path(microgrid_config_files)]
elif isinstance(microgrid_config_files, Path):
config_files = [microgrid_config_files]
elif isinstance(microgrid_config_files, list):
config_files = [Path(f) for f in microgrid_config_files]
# normalize to a list of items
if isinstance(microgrid_config_files, (str, Path)):
paths = [microgrid_config_files]
else:
paths = microgrid_config_files

for item in paths:
config_path = Path(item)
if config_path.is_dir():
config_files.extend(sorted(config_path.glob("*.toml")))
else:
config_files.append(config_path)

if len(config_files) == 0:
raise ValueError(
Expand Down
31 changes: 31 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@ def test_load_configs(mocker: MockerFixture) -> None:
assert battery_system.capacity == 10000


def test_load_configs_from_directory(tmp_path: Path) -> None:
"""Loading from a directory picks up its TOML files."""
config_dir = tmp_path / "configs"
config_dir.mkdir()
(config_dir / "mg-1.toml").write_text(
"""
1.meta.microgrid_id = 1
1.meta.name = "Test Grid 1"
1.meta.gid = 1
""",
encoding="utf-8",
)
(config_dir / "mg-2.toml").write_text(
"""
2.meta.microgrid_id = 2
2.meta.name = "Test Grid 2"
2.meta.gid = 2
""",
encoding="utf-8",
)
(config_dir / "ignore.txt").write_text("not toml", encoding="utf-8")

configs = load_configs_from_files(config_dir)

assert set(configs) == {"1", "2"}
assert configs["1"].meta is not None
assert configs["1"].meta.name == "Test Grid 1"
assert configs["2"].meta is not None
assert configs["2"].meta.name == "Test Grid 2"


def _assert_optional_field(value: float | None, expected: float) -> None:
"""Validate an optional field.

Expand Down
27 changes: 27 additions & 0 deletions tests/test_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

"""Tests for loading microgrid configs from the Assets API."""

from pathlib import Path
from unittest.mock import AsyncMock, MagicMock

import pytest
Expand Down Expand Up @@ -107,6 +108,32 @@ async def test_load_configs_forwards_the_component_graph_config() -> None:
}


async def test_load_configs_accepts_directories_for_file_layers(
tmp_path: Path,
) -> None:
"""`load_configs` can derive API IDs from config files found in a directory."""
config_dir = tmp_path / "configs"
config_dir.mkdir()
(config_dir / "mg-10.toml").write_text(
"""
10.meta.microgrid_id = 10
10.meta.name = "Directory Grid"
10.meta.gid = 10
""",
encoding="utf-8",
)

configs = await load_configs(
default_files=config_dir,
assets_client=_mock_client(),
)

assert "10" in configs
assert configs["10"].ctype["pv"].formula == {
"AC_POWER_ACTIVE": "COALESCE(#4, #2, 0.0)"
}


async def test_load_configs_rejects_a_component_graph_config_without_a_client() -> None:
"""A component graph config is only meaningful with an Assets API client."""
with pytest.raises(ValueError, match="requires an assets_client"):
Expand Down
Loading