Skip to content
Draft
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
7 changes: 6 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
- `ComponentCategory` is now being imported from `frequenz-client-common` v0.3.3. This means:

* You need to add/update the minimum dependency to `frequenz-client-common`
* `ComponentCategory.NONE` is not named `ComponentCategory.UNSPECIFIED`
* `Component.category` has now the type `ComponentCategory | int`.
* Before if a category that had no corresponding value in `ComponentCategory` was received (the server is probably using a newer version with a new category), `ComponentCategory.NONE` was used. Now we keep the original `int` received from protobuf. This allows to use an old client version with a new server, as long as the user knows how to interpret the `int` value, so it provided more flexibility.

## New Features

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ dependencies = [
"frequenz-api-microgrid >= 0.15.5, < 0.16.0",
"frequenz-channels >= 1.0.0-rc1, < 2.0.0",
"frequenz-client-base >= 0.8.0, < 0.12.0",
"frequenz-client-common >= 0.3.2, < 0.4.0",
#"frequenz-client-common >= 0.3.2, < 0.4.0",
"frequenz-client-common @ git+https://github.com/frequenz-floss/frequenz-client-common-python.git@refs/pull/82/head",
"grpcio >= 1.63.0, < 2",
"protobuf >= 5.26.1, < 7",
"timezonefinder >= 6.2.0, < 7",
Expand Down
2 changes: 0 additions & 2 deletions src/frequenz/client/microgrid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from ._client import MicrogridApiClient
from ._component import (
Component,
ComponentCategory,
ComponentMetadata,
ComponentMetricId,
ComponentType,
Expand Down Expand Up @@ -74,7 +73,6 @@
"BatteryRelayState",
"ClientNotConnected",
"Component",
"ComponentCategory",
"ComponentData",
"ComponentMetadata",
"ComponentMetricId",
Expand Down
15 changes: 9 additions & 6 deletions src/frequenz/client/microgrid/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@
from frequenz.api.microgrid import microgrid_pb2, microgrid_pb2_grpc, sensor_pb2
from frequenz.channels import Receiver
from frequenz.client.base import channel, client, retry, streaming
from frequenz.client.common.enum_proto import enum_from_proto
from frequenz.client.common.microgrid import MicrogridId
from frequenz.client.common.microgrid.components import ComponentId
from frequenz.client.common.microgrid.components import ComponentCategory, ComponentId
from frequenz.client.common.microgrid.sensors import SensorId
from google.protobuf.empty_pb2 import Empty
from typing_extensions import override

from ._component import (
Component,
ComponentCategory,
component_category_from_protobuf,
component_metadata_from_protobuf,
component_type_from_protobuf,
)
Expand Down Expand Up @@ -187,7 +186,7 @@ async def components( # noqa: DOC502 (raises ApiClientError indirectly)
result: Iterable[Component] = map(
lambda c: Component(
ComponentId(c.id),
component_category_from_protobuf(c.category),
enum_from_proto(c.category, ComponentCategory),
component_type_from_protobuf(c.category, c.inverter),
component_metadata_from_protobuf(c.category, c.grid),
),
Expand Down Expand Up @@ -386,9 +385,13 @@ async def _expect_category(
raise ValueError(f"Unable to find {component_id}") from exc

if comp.category != expected_category:
cat = (
comp.category.name
if isinstance(comp.category, ComponentCategory)
else comp.category
)
raise ValueError(
f"{component_id} is a {comp.category.name.lower()}"
f", not a {expected_category.name.lower()}."
f"{component_id} has category {cat}, but {expected_category.name} was expected"
)

async def meter_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category)
Expand Down
56 changes: 2 additions & 54 deletions src/frequenz/client/microgrid/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from frequenz.api.common import components_pb2
from frequenz.api.microgrid import grid_pb2, inverter_pb2
from frequenz.client.common.microgrid.components import ComponentId
from frequenz.client.common.microgrid.components import ComponentCategory, ComponentId


class ComponentType(Enum):
Expand Down Expand Up @@ -61,58 +61,6 @@ def component_type_from_protobuf(
return None


class ComponentCategory(Enum):
"""Possible types of microgrid component."""

NONE = components_pb2.ComponentCategory.COMPONENT_CATEGORY_UNSPECIFIED
"""Unspecified component category."""

GRID = components_pb2.ComponentCategory.COMPONENT_CATEGORY_GRID
"""Grid component."""

METER = components_pb2.ComponentCategory.COMPONENT_CATEGORY_METER
"""Meter component."""

INVERTER = components_pb2.ComponentCategory.COMPONENT_CATEGORY_INVERTER
"""Inverter component."""

BATTERY = components_pb2.ComponentCategory.COMPONENT_CATEGORY_BATTERY
"""Battery component."""

EV_CHARGER = components_pb2.ComponentCategory.COMPONENT_CATEGORY_EV_CHARGER
"""EV charger component."""

CHP = components_pb2.ComponentCategory.COMPONENT_CATEGORY_CHP
"""CHP component."""


def component_category_from_protobuf(
component_category: components_pb2.ComponentCategory.ValueType,
) -> ComponentCategory:
"""Convert a protobuf ComponentCategory message to ComponentCategory enum.

For internal-only use by the `microgrid` package.

Args:
component_category: protobuf enum to convert

Returns:
Enum value corresponding to the protobuf message.

Raises:
ValueError: if `component_category` is a sensor (this is not considered
a valid component category as it does not form part of the
microgrid itself)
"""
if component_category == components_pb2.ComponentCategory.COMPONENT_CATEGORY_SENSOR:
raise ValueError("Cannot create a component from a sensor!")

if not any(t.value == component_category for t in ComponentCategory):
return ComponentCategory.NONE

return ComponentCategory(component_category)


@dataclass(frozen=True)
class Fuse:
"""Fuse data class."""
Expand Down Expand Up @@ -164,7 +112,7 @@ class Component:
component_id: ComponentId
"""The ID of this component."""

category: ComponentCategory
category: ComponentCategory | int
"""The category of this component."""

type: ComponentType | None = None
Expand Down
8 changes: 4 additions & 4 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@
from frequenz.api.microgrid import grid_pb2, inverter_pb2, microgrid_pb2, sensor_pb2
from frequenz.client.base import conversion, retry
from frequenz.client.common.microgrid import MicrogridId
from frequenz.client.common.microgrid.components import ComponentId
from frequenz.client.common.microgrid.components import ComponentCategory, ComponentId
from frequenz.client.common.microgrid.sensors import SensorId
from google.protobuf.empty_pb2 import Empty

from frequenz.client.microgrid import (
ApiClientError,
BatteryData,
Component,
ComponentCategory,
ComponentData,
Connection,
EVChargerData,
Expand Down Expand Up @@ -191,7 +190,7 @@ async def test_components(client: _TestClient) -> None:
grid_fuse = Fuse(123.0)

assert set(await client.components()) == {
Component(ComponentId(100), ComponentCategory.NONE),
Component(ComponentId(100), ComponentCategory.UNSPECIFIED),
Component(
ComponentId(101),
ComponentCategory.GRID,
Expand Down Expand Up @@ -657,7 +656,8 @@ async def test_data_bad_category(

# It should raise a ValueError for a wrong component category
with pytest.raises(
ValueError, match=f"{component_id} is a .*, not a {method[:-5]}"
ValueError,
match=rf"{component_id} has category .*, but {method[:-5].upper()} was expected",
):
await getattr(client, method)(component_id)

Expand Down
65 changes: 4 additions & 61 deletions tests/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,66 +4,9 @@
"""Tests for the microgrid component wrapper."""

import pytest
from frequenz.api.common import components_pb2
from frequenz.client.common.microgrid.components import ComponentId
from frequenz.client.common.microgrid.components import ComponentCategory, ComponentId

from frequenz.client.microgrid import (
Component,
ComponentCategory,
)
from frequenz.client.microgrid._component import component_category_from_protobuf


def test_component_category_from_protobuf() -> None:
"""Test the creating component category from protobuf."""
assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_UNSPECIFIED
)
== ComponentCategory.NONE
)

assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_GRID
)
== ComponentCategory.GRID
)

assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_METER
)
== ComponentCategory.METER
)

assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_INVERTER
)
== ComponentCategory.INVERTER
)

assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_BATTERY
)
== ComponentCategory.BATTERY
)

assert (
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_EV_CHARGER
)
== ComponentCategory.EV_CHARGER
)

assert component_category_from_protobuf(666) == ComponentCategory.NONE # type: ignore

with pytest.raises(ValueError):
component_category_from_protobuf(
components_pb2.ComponentCategory.COMPONENT_CATEGORY_SENSOR
)
from frequenz.client.microgrid import Component


# pylint: disable=invalid-name
Expand Down Expand Up @@ -91,8 +34,8 @@ def test_Component() -> None:
# Should raise error with negative ID
Component(ComponentId(-1), ComponentCategory.GRID)

invalid_type = Component(ComponentId(666), -1) # type: ignore
invalid_type = Component(ComponentId(666), -1)
assert not invalid_type.is_valid()

another_invalid_type = Component(ComponentId(666), 666) # type: ignore
another_invalid_type = Component(ComponentId(666), 666)
assert not another_invalid_type.is_valid()
Loading