diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 80e5f0cb..0cd65e95 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,7 +6,12 @@ ## Upgrading - +- `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 diff --git a/pyproject.toml b/pyproject.toml index d3015749..d10ca3d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/src/frequenz/client/microgrid/__init__.py b/src/frequenz/client/microgrid/__init__.py index 5b4c53de..0ae99d39 100644 --- a/src/frequenz/client/microgrid/__init__.py +++ b/src/frequenz/client/microgrid/__init__.py @@ -10,7 +10,6 @@ from ._client import MicrogridApiClient from ._component import ( Component, - ComponentCategory, ComponentMetadata, ComponentMetricId, ComponentType, @@ -74,7 +73,6 @@ "BatteryRelayState", "ClientNotConnected", "Component", - "ComponentCategory", "ComponentData", "ComponentMetadata", "ComponentMetricId", diff --git a/src/frequenz/client/microgrid/_client.py b/src/frequenz/client/microgrid/_client.py index 4f8cdcb7..3381e529 100644 --- a/src/frequenz/client/microgrid/_client.py +++ b/src/frequenz/client/microgrid/_client.py @@ -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, ) @@ -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), ), @@ -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) diff --git a/src/frequenz/client/microgrid/_component.py b/src/frequenz/client/microgrid/_component.py index 6aa42a20..9b61dbed 100644 --- a/src/frequenz/client/microgrid/_component.py +++ b/src/frequenz/client/microgrid/_component.py @@ -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): @@ -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.""" @@ -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 diff --git a/tests/test_client.py b/tests/test_client.py index d1029839..4b491779 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -18,7 +18,7 @@ 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 @@ -26,7 +26,6 @@ ApiClientError, BatteryData, Component, - ComponentCategory, ComponentData, Connection, EVChargerData, @@ -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, @@ -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) diff --git a/tests/test_component.py b/tests/test_component.py index 0fdc527d..525b6aaf 100644 --- a/tests/test_component.py +++ b/tests/test_component.py @@ -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 @@ -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()