Skip to content
Merged
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ Optional prefix formatting:
gridpool-cli print-formulas <microgrid_id> --prefix "{microgrid_id}.{component}"
```

The per-category formulas (`pv`, `battery`, `chp`, `ev`) read the component
first and use the meter as the fallback. Use
`--prefer-meters-in-component-formulas` for the opposite order, which is what
versions before component graph v0.5.0 produced:

```bash
gridpool-cli print-formulas <microgrid_id> --prefer-meters-in-component-formulas
```

### Render component graph

Rendering requires optional dependencies. Install with:
Expand Down Expand Up @@ -94,6 +103,14 @@ If no microgrid IDs are given, they are taken from the supplied files:
gridpool-cli generate-config --override existing.toml > microgrid.toml
```

`--prefer-meters-in-component-formulas` works here too, so a regenerated
config can keep the meter-first order of the per-category formulas:

```bash
gridpool-cli generate-config <microgrid_id> \
--prefer-meters-in-component-formulas > microgrid.toml
```

## Contributing

If you want to know how to build this project and contribute to it, please
Expand Down
9 changes: 6 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
This release updates the microgrid component graph library to v0.5.0 and adds a way to configure how the component graph is built.

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
* The per-category formulas (`pv`, `battery`, `chp`, `ev`) now read the component first and use the meter as the fallback. This is the opposite of the old order, and it changes the generated config. Regenerate stored configs with this release and review the diff.
* To keep the old order, pass `ComponentGraphConfig(prefer_meters_in_component_formulas=True)` to `load_configs`, `load_configs_from_api` or `ComponentGraphGenerator`, or use the new `--prefer-meters-in-component-formulas` flag of the CLI.
* The update brings more changes, for example to the `consumption` formula and to the graph validation error messages. See the [v0.5.0 release notes](https://github.com/frequenz-floss/frequenz-microgrid-component-graph-python/releases/tag/v0.5.0) of the component graph library for the full list.

## New Features

<!-- Here goes the main new features and examples or instructions on how to use them -->
* `load_configs` and `load_configs_from_api` take a `component_graph_config` argument, and `ComponentGraphGenerator` takes it as `config`. It controls how the component graph is built and how its formulas are generated. `ComponentGraphConfig` and `FormulaOverrides` are re-exported from `frequenz.gridpool` and `frequenz.gridpool.config`.
* The `generate-config` and `print-formulas` CLI commands take a `--prefer-meters-in-component-formulas` flag, which reads the meter before the component in the per-category formulas.

## Bug Fixes

Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ requires-python = ">= 3.11, < 4"
dependencies = [
"marshmallow-dataclass >= 8.7.1, < 9",
"typing-extensions >= 4.14.1, < 5",
"frequenz-microgrid-component-graph >= 0.4.0, < 0.5",
"frequenz-microgrid-component-graph >= 0.5.0, < 0.6",
"frequenz-client-assets >= 0.3.1, < 0.4",
]
dynamic = ["version"]
Expand Down Expand Up @@ -95,7 +95,9 @@ dev-pytest = [
"pytest-mock == 3.15.1",
"pytest-asyncio == 1.4.0",
"async-solipsism == 0.9",
"frequenz-gridpool[cli]", # The tests cover the cli package
# The tests cover the cli package and the graph renderer. render-graph
# already pulls in cli.
"frequenz-gridpool[render-graph]",
]
dev = [
"frequenz-gridpool[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest,render-graph]",
Expand Down
4 changes: 4 additions & 0 deletions src/frequenz/gridpool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""High-level interface to grid pools for the Frequenz platform."""

from frequenz.microgrid_component_graph import ComponentGraphConfig, FormulaOverrides

from ._graph_generator import ComponentGraphGenerator
from .config import (
Metadata,
Expand All @@ -15,7 +17,9 @@
)

__all__ = [
"ComponentGraphConfig",
"ComponentGraphGenerator",
"FormulaOverrides",
"Metadata",
"MicrogridConfig",
"load_configs",
Expand Down
10 changes: 8 additions & 2 deletions src/frequenz/gridpool/_graph_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
from frequenz.client.common.microgrid import MicrogridId
from frequenz.client.common.microgrid.electrical_components import ElectricalComponentId
from frequenz.microgrid_component_graph import ComponentGraph
from frequenz.microgrid_component_graph import ComponentGraph, ComponentGraphConfig

_logger = logging.getLogger(__name__)

Expand All @@ -42,14 +42,20 @@ class ComponentGraphGenerator:
def __init__(
self,
client: AssetsApiClient,
config: ComponentGraphConfig | None = None,
) -> None:
"""Initialize this instance.

Args:
client: The Assets API client to use for fetching components and
connections.
config: How to build the graph and generate its formulas. See
`ComponentGraphConfig`. Defaults to that class's own defaults.
"""
self._client: AssetsApiClient = client
self._config: ComponentGraphConfig = (
config if config is not None else ComponentGraphConfig()
)

async def get_component_graph(
self, microgrid_id: MicrogridId
Expand Down Expand Up @@ -100,7 +106,7 @@ async def get_component_graph(

graph = ComponentGraph[
ElectricalComponent, ComponentConnection, ElectricalComponentId
](components, connections)
](components, connections, self._config)

return graph

Expand Down
48 changes: 46 additions & 2 deletions src/frequenz/gridpool/cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
from frequenz.client.assets import AssetsApiClient
from frequenz.client.common.microgrid import MicrogridId

from frequenz.gridpool import ComponentGraphGenerator, MicrogridConfig, load_configs
from frequenz.gridpool import (
ComponentGraphConfig,
ComponentGraphGenerator,
MicrogridConfig,
load_configs,
)
from frequenz.gridpool.cli._dump_config import dump_map
from frequenz.gridpool.cli._patch_config import patch_file
from frequenz.gridpool.cli._render_graph import ComponentGraphRenderer, RenderOptions
Expand All @@ -22,6 +27,17 @@ async def cli() -> None:
"""CLI tool for gridpool functionality."""


def _graph_config(prefer_meters: bool) -> ComponentGraphConfig | None:
"""Build the graph config for `--prefer-meters-in-component-formulas`.

Returns `None` when the flag is not set, so the component graph library's
own defaults apply.
"""
if not prefer_meters:
return None
return ComponentGraphConfig(prefer_meters_in_component_formulas=True)


@cli.command()
@click.argument("microgrid_id", type=int)
@click.option(
Expand All @@ -30,9 +46,17 @@ async def cli() -> None:
default="{component}",
help="Prefix format for the output (Supports {microgrid_id} and {component} placeholders).",
)
@click.option(
"--prefer-meters-in-component-formulas",
is_flag=True,
default=False,
help="Read the meter before the component in the per-category formulas. "
"This is the order used before component graph v0.5.0.",
)
async def print_formulas(
microgrid_id: int,
prefix: str,
prefer_meters_in_component_formulas: bool,
) -> None:
"""Fetch and print component graph formulas for a microgrid."""
url = os.environ.get("ASSETS_API_URL")
Expand All @@ -48,7 +72,9 @@ async def print_formulas(
auth_key=key,
sign_secret=secret,
) as client:
cgg = ComponentGraphGenerator(client)
cgg = ComponentGraphGenerator(
client, config=_graph_config(prefer_meters_in_component_formulas)
)

graph = await cgg.get_component_graph(MicrogridId(microgrid_id))
power_formulas = {
Expand Down Expand Up @@ -133,11 +159,19 @@ async def render_graph(microgrid_id: int, output: str, show: bool) -> None:
"existing comments, ordering and formatting in that file; only fills in "
"values it is missing. Requires --default.",
)
@click.option(
"--prefer-meters-in-component-formulas",
is_flag=True,
default=False,
help="Read the meter before the component in the per-category formulas. "
"This is the order used before component graph v0.5.0.",
)
async def generate_config(
microgrid_ids: tuple[int, ...],
default_file: Path | None,
override_file: Path | None,
inplace: bool,
prefer_meters_in_component_formulas: bool,
) -> None:
"""Generate microgrid config from the Assets API as TOML.

Expand All @@ -149,6 +183,10 @@ async def generate_config(
IDs are given, they are taken from the supplied files. Files are only read;
redirect stdout to save the result.

With `--prefer-meters-in-component-formulas`, the per-category formulas
read the meter before the component, which is the order used before
component graph v0.5.0.

With `--inplace`, `--default` is patched directly instead: candidate values
come from the Assets API (with `--override` layered on top), and only
leaves `--default` is missing are added, preserving its existing comments,
Expand Down Expand Up @@ -178,13 +216,19 @@ async def generate_config(
assets_client=client,
override_files=override_file,
microgrid_ids=ids,
component_graph_config=_graph_config(
prefer_meters_in_component_formulas
),
)
else:
configs = await load_configs(
default_files=default_file,
assets_client=client,
override_files=override_file,
microgrid_ids=ids,
component_graph_config=_graph_config(
prefer_meters_in_component_formulas
),
)

if not configs:
Expand Down
4 changes: 4 additions & 0 deletions src/frequenz/gridpool/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""Microgrid configuration data model and loading."""

from frequenz.microgrid_component_graph import ComponentGraphConfig, FormulaOverrides

from .load import (
load_configs,
load_configs_from_api,
Expand All @@ -24,8 +26,10 @@
__all__ = [
"BatteryConfig",
"ComponentCategory",
"ComponentGraphConfig",
"ComponentType",
"ComponentTypeConfig",
"FormulaOverrides",
"Metadata",
"MicrogridConfig",
"PVConfig",
Expand Down
22 changes: 18 additions & 4 deletions src/frequenz/gridpool/config/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from frequenz.client.assets import AssetsApiClient
from frequenz.client.common.microgrid import MicrogridId
from frequenz.microgrid_component_graph import ComponentGraphConfig

from .._graph_generator import (
ComponentGraphGenerator,
Expand Down Expand Up @@ -38,6 +39,7 @@ async def load_configs(
assets_client: AssetsApiClient | None = None,
override_files: str | Path | list[str | Path] | None = None,
microgrid_ids: list[int] | None = None,
component_graph_config: ComponentGraphConfig | None = None,
) -> dict[str, "MicrogridConfig"]:
"""Load configs from up to three sources and merge them in layers.

Expand Down Expand Up @@ -72,6 +74,10 @@ async def load_configs(
Optional explicit microgrid IDs to fetch from the Assets API.
When given, these replace the IDs derived from the files, so the
Assets API layer can be used without any files.
component_graph_config:
How to build the component graph and generate its formulas. See
`ComponentGraphConfig`. Defaults to that class's own defaults.
Requires an `assets_client`.

Returns:
dict[str, MicrogridConfig]:
Expand All @@ -80,14 +86,18 @@ async def load_configs(

Raises:
ValueError: If none of the three sources is provided, or if
`microgrid_ids` is given without an `assets_client`.
`microgrid_ids` or `component_graph_config` is given without an
`assets_client`.
"""
if default_files is None and assets_client is None and override_files is None:
raise ValueError("At least one config source must be provided.")

if microgrid_ids is not None and assets_client is None:
raise ValueError("microgrid_ids requires an assets_client.")

if component_graph_config is not None and assets_client is None:
raise ValueError("component_graph_config requires an assets_client.")

configs: dict[str, MicrogridConfig] = {}
if default_files is not None:
configs = load_configs_from_files(
Expand All @@ -106,6 +116,7 @@ async def load_configs(
assets_configs = await load_configs_from_api(
assets_client=assets_client,
microgrid_ids=microgrid_ids,
component_graph_config=component_graph_config,
)
configs = merge_config_maps(base=configs, override=assets_configs)

Expand Down Expand Up @@ -166,6 +177,7 @@ def load_configs_from_files(
async def load_configs_from_api(
assets_client: AssetsApiClient,
microgrid_ids: list[int],
component_graph_config: ComponentGraphConfig | None = None,
) -> dict[str, "MicrogridConfig"]:
"""Load microgrid configs from the Assets API.

Expand All @@ -184,6 +196,9 @@ async def load_configs_from_api(
component graph.
microgrid_ids:
List of microgrid IDs to load configurations for.
component_graph_config:
How to build the component graph and generate its formulas. See
`ComponentGraphConfig`. Defaults to that class's own defaults.

Returns:
dict[str, MicrogridConfig]:
Expand All @@ -192,6 +207,7 @@ async def load_configs_from_api(
loaded are omitted, so the returned mapping may cover fewer
microgrids than were requested.
"""
generator = ComponentGraphGenerator(assets_client, config=component_graph_config)
configs: dict[str, MicrogridConfig] = {}
for microgrid_id in microgrid_ids:
try:
Expand All @@ -205,9 +221,7 @@ async def load_configs_from_api(
continue

try:
graph = await ComponentGraphGenerator(assets_client).get_component_graph(
MicrogridId(microgrid_id)
)
graph = await generator.get_component_graph(MicrogridId(microgrid_id))
cfg.ctype = _derive_component_configs(graph)
except Exception as exc: # pylint: disable=broad-except
_logger.warning(
Expand Down
Loading
Loading