Skip to content

Commit 94f030e

Browse files
committed
Revert "Migrate the client code to use betterproto"
This partially reverts commits: * Migrate the client code to use betterproto (e159417) * Fix component tests to use betterproto (b5b52d5) * Fix client test to use betterproto (b3efe71) It preserves the changes to tests to use mocks instead of a fake but full gRPC server, and adapts the new code to use the grpcio library and the generated code from the traditional google protobuf compiler. Since the `client-base` library currently being used doesn't support grpcio, we also need to update that library too, but we do that in a separate commit. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent a03b446 commit 94f030e

File tree

8 files changed

+527
-438
lines changed

8 files changed

+527
-438
lines changed

src/frequenz/client/microgrid/_client.py

Lines changed: 84 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,34 @@
55

66
import asyncio
77
import logging
8-
from collections.abc import Callable, Iterable, Set
9-
from typing import Any, TypeVar
8+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Set
9+
from typing import Any, TypeVar, cast
10+
11+
import grpc.aio
12+
13+
# pylint: disable=no-name-in-module
14+
from frequenz.api.common.components_pb2 import ComponentCategory as PbComponentCategory
15+
from frequenz.api.common.metrics_pb2 import Bounds as PbBounds
16+
from frequenz.api.microgrid.microgrid_pb2 import ComponentData as PbComponentData
17+
from frequenz.api.microgrid.microgrid_pb2 import ComponentFilter as PbComponentFilter
18+
from frequenz.api.microgrid.microgrid_pb2 import ComponentIdParam as PbComponentIdParam
19+
from frequenz.api.microgrid.microgrid_pb2 import ComponentList as PbComponentList
20+
from frequenz.api.microgrid.microgrid_pb2 import ConnectionFilter as PbConnectionFilter
21+
from frequenz.api.microgrid.microgrid_pb2 import ConnectionList as PbConnectionList
22+
from frequenz.api.microgrid.microgrid_pb2 import (
23+
MicrogridMetadata as PbMicrogridMetadata,
24+
)
25+
from frequenz.api.microgrid.microgrid_pb2 import SetBoundsParam as PbSetBoundsParam
26+
from frequenz.api.microgrid.microgrid_pb2 import (
27+
SetPowerActiveParam as PbSetPowerActiveParam,
28+
)
29+
from frequenz.api.microgrid.microgrid_pb2_grpc import MicrogridStub
1030

11-
import grpclib
12-
import grpclib.client
13-
from betterproto.lib.google import protobuf as pb_google
31+
# pylint: enable=no-name-in-module
1432
from frequenz.channels import Receiver
1533
from frequenz.client.base import channel, retry, streaming
16-
from frequenz.microgrid.betterproto.frequenz.api import microgrid as pb_microgrid
17-
from frequenz.microgrid.betterproto.frequenz.api.common import (
18-
components as pb_components,
19-
)
20-
from frequenz.microgrid.betterproto.frequenz.api.common import metrics as pb_metrics
34+
from google.protobuf.empty_pb2 import Empty # pylint: disable=no-name-in-module
35+
from google.protobuf.timestamp_pb2 import Timestamp # pylint: disable=no-name-in-module
2136

2237
from ._component import (
2338
Component,
@@ -72,7 +87,7 @@ def __init__(
7287
self._server_url = server_url
7388
"""The location of the microgrid API server as a URL."""
7489

75-
self.api = pb_microgrid.MicrogridStub(channel.parse_grpc_uri(server_url))
90+
self.api = MicrogridStub(channel.parse_grpc_uri(server_url))
7691
"""The gRPC stub for the microgrid API."""
7792

7893
self._broadcasters: dict[int, streaming.GrpcStreamBroadcaster[Any, Any]] = {}
@@ -95,20 +110,24 @@ async def components(self) -> Iterable[Component]:
95110
[GrpcError][frequenz.client.microgrid.GrpcError].
96111
"""
97112
try:
98-
component_list = await self.api.list_components(
99-
pb_microgrid.ComponentFilter(),
100-
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
113+
# grpc.aio is missing types and mypy thinks this is not awaitable,
114+
# but it is
115+
component_list = await cast(
116+
Awaitable[PbComponentList],
117+
self.api.ListComponents(
118+
PbComponentFilter(),
119+
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
120+
),
101121
)
102-
except grpclib.GRPCError as grpc_error:
122+
except grpc.aio.AioRpcError as grpc_error:
103123
raise ClientError.from_grpc_error(
104124
server_url=self._server_url,
105-
operation="list_components",
125+
operation="ListComponents",
106126
grpc_error=grpc_error,
107127
) from grpc_error
108128

109129
components_only = filter(
110-
lambda c: c.category
111-
is not pb_components.ComponentCategory.COMPONENT_CATEGORY_SENSOR,
130+
lambda c: c.category is not PbComponentCategory.COMPONENT_CATEGORY_SENSOR,
112131
component_list.components,
113132
)
114133
result: Iterable[Component] = map(
@@ -132,13 +151,16 @@ async def metadata(self) -> Metadata:
132151
Returns:
133152
the microgrid metadata.
134153
"""
135-
microgrid_metadata: pb_microgrid.MicrogridMetadata | None = None
154+
microgrid_metadata: PbMicrogridMetadata | None = None
136155
try:
137-
microgrid_metadata = await self.api.get_microgrid_metadata(
138-
pb_google.Empty(),
139-
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
156+
microgrid_metadata = await cast(
157+
Awaitable[PbMicrogridMetadata],
158+
self.api.GetMicrogridMetadata(
159+
Empty(),
160+
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
161+
),
140162
)
141-
except grpclib.GRPCError:
163+
except grpc.aio.AioRpcError:
142164
_logger.exception("The microgrid metadata is not available.")
143165

144166
if not microgrid_metadata:
@@ -174,21 +196,24 @@ async def connections(
174196
most likely a subclass of
175197
[GrpcError][frequenz.client.microgrid.GrpcError].
176198
"""
177-
connection_filter = pb_microgrid.ConnectionFilter(
178-
starts=list(starts), ends=list(ends)
179-
)
199+
connection_filter = PbConnectionFilter(starts=starts, ends=ends)
180200
try:
181201
valid_components, all_connections = await asyncio.gather(
182202
self.components(),
183-
self.api.list_connections(
184-
connection_filter,
185-
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
203+
# grpc.aio is missing types and mypy thinks this is not
204+
# awaitable, but it is
205+
cast(
206+
Awaitable[PbConnectionList],
207+
self.api.ListConnections(
208+
connection_filter,
209+
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
210+
),
186211
),
187212
)
188-
except grpclib.GRPCError as grpc_error:
213+
except grpc.aio.AioRpcError as grpc_error:
189214
raise ClientError.from_grpc_error(
190215
server_url=self._server_url,
191-
operation="list_connections",
216+
operation="ListConnections",
192217
grpc_error=grpc_error,
193218
) from grpc_error
194219
# Filter out the components filtered in `components` method.
@@ -212,7 +237,7 @@ async def _new_component_data_receiver(
212237
*,
213238
component_id: int,
214239
expected_category: ComponentCategory,
215-
transform: Callable[[pb_microgrid.ComponentData], _ComponentDataT],
240+
transform: Callable[[PbComponentData], _ComponentDataT],
216241
maxsize: int,
217242
) -> Receiver[_ComponentDataT]:
218243
"""Return a new broadcaster receiver for a given `component_id`.
@@ -239,8 +264,13 @@ async def _new_component_data_receiver(
239264
if broadcaster is None:
240265
broadcaster = streaming.GrpcStreamBroadcaster(
241266
f"raw-component-data-{component_id}",
242-
lambda: self.api.stream_component_data(
243-
pb_microgrid.ComponentIdParam(id=component_id)
267+
# We need to cast here because grpc says StreamComponentData is
268+
# a grpc.CallIterator[PbComponentData] which is not an AsyncIterator,
269+
# but it is a grpc.aio.UnaryStreamCall[..., PbComponentData], which it
270+
# is.
271+
lambda: cast(
272+
AsyncIterator[PbComponentData],
273+
self.api.StreamComponentData(PbComponentIdParam(id=component_id)),
244274
),
245275
transform,
246276
retry_strategy=self._retry_strategy,
@@ -394,16 +424,17 @@ async def set_power(self, component_id: int, power_w: float) -> None:
394424
[GrpcError][frequenz.client.microgrid.GrpcError].
395425
"""
396426
try:
397-
await self.api.set_power_active(
398-
pb_microgrid.SetPowerActiveParam(
399-
component_id=component_id, power=power_w
427+
await cast(
428+
Awaitable[Empty],
429+
self.api.SetPowerActive(
430+
PbSetPowerActiveParam(component_id=component_id, power=power_w),
431+
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
400432
),
401-
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
402433
)
403-
except grpclib.GRPCError as grpc_error:
434+
except grpc.aio.AioRpcError as grpc_error:
404435
raise ClientError.from_grpc_error(
405436
server_url=self._server_url,
406-
operation="set_power_active",
437+
operation="SetPowerActive",
407438
grpc_error=grpc_error,
408439
) from grpc_error
409440

@@ -413,7 +444,7 @@ async def set_bounds(
413444
lower: float,
414445
upper: float,
415446
) -> None:
416-
"""Send `SetBoundsParam`s received from a channel to the Microgrid service.
447+
"""Send `PbSetBoundsParam`s received from a channel to the Microgrid service.
417448
418449
Args:
419450
component_id: ID of the component to set bounds for.
@@ -432,21 +463,22 @@ async def set_bounds(
432463
if lower > 0:
433464
raise ValueError(f"Lower bound {lower} must be less than or equal to 0.")
434465

435-
target_metric = (
436-
pb_microgrid.SetBoundsParamTargetMetric.TARGET_METRIC_POWER_ACTIVE
437-
)
466+
target_metric = PbSetBoundsParam.TargetMetric.TARGET_METRIC_POWER_ACTIVE
438467
try:
439-
await self.api.add_inclusion_bounds(
440-
pb_microgrid.SetBoundsParam(
441-
component_id=component_id,
442-
target_metric=target_metric,
443-
bounds=pb_metrics.Bounds(lower=lower, upper=upper),
468+
await cast(
469+
Awaitable[Timestamp],
470+
self.api.AddInclusionBounds(
471+
PbSetBoundsParam(
472+
component_id=component_id,
473+
target_metric=target_metric,
474+
bounds=PbBounds(lower=lower, upper=upper),
475+
),
476+
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
444477
),
445-
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
446478
)
447-
except grpclib.GRPCError as grpc_error:
479+
except grpc.aio.AioRpcError as grpc_error:
448480
raise ClientError.from_grpc_error(
449481
server_url=self._server_url,
450-
operation="add_inclusion_bounds",
482+
operation="AddInclusionBounds",
451483
grpc_error=grpc_error,
452484
) from grpc_error

src/frequenz/client/microgrid/_component.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
from dataclasses import dataclass
77
from enum import Enum
88

9-
from frequenz.microgrid.betterproto.frequenz.api.common import components
10-
from frequenz.microgrid.betterproto.frequenz.api.microgrid import grid, inverter
9+
# pylint: disable=no-name-in-module
10+
from frequenz.api.common.components_pb2 import ComponentCategory as PbComponentCategory
11+
from frequenz.api.microgrid.grid_pb2 import Metadata as PbGridMetadata
12+
from frequenz.api.microgrid.inverter_pb2 import Metadata as PbInverterMetadata
13+
from frequenz.api.microgrid.inverter_pb2 import Type as PbInverterType
14+
15+
# pylint: enable=no-name-in-module
1116

1217

1318
class ComponentType(Enum):
@@ -17,22 +22,22 @@ class ComponentType(Enum):
1722
class InverterType(ComponentType):
1823
"""Enum representing inverter types."""
1924

20-
NONE = inverter.Type.TYPE_UNSPECIFIED
25+
NONE = PbInverterType.TYPE_UNSPECIFIED
2126
"""Unspecified inverter type."""
2227

23-
BATTERY = inverter.Type.TYPE_BATTERY
28+
BATTERY = PbInverterType.TYPE_BATTERY
2429
"""Battery inverter."""
2530

26-
SOLAR = inverter.Type.TYPE_SOLAR
31+
SOLAR = PbInverterType.TYPE_SOLAR
2732
"""Solar inverter."""
2833

29-
HYBRID = inverter.Type.TYPE_HYBRID
34+
HYBRID = PbInverterType.TYPE_HYBRID
3035
"""Hybrid inverter."""
3136

3237

3338
def component_type_from_protobuf(
34-
component_category: components.ComponentCategory,
35-
component_metadata: inverter.Metadata,
39+
component_category: PbComponentCategory.ValueType,
40+
component_metadata: PbInverterMetadata,
3641
) -> ComponentType | None:
3742
"""Convert a protobuf InverterType message to Component enum.
3843
@@ -48,7 +53,7 @@ def component_type_from_protobuf(
4853
# ComponentType values in the protobuf definition are not unique across categories
4954
# as of v0.11.0, so we need to check the component category first, before doing any
5055
# component type checks.
51-
if component_category == components.ComponentCategory.COMPONENT_CATEGORY_INVERTER:
56+
if component_category == PbComponentCategory.COMPONENT_CATEGORY_INVERTER:
5257
if not any(int(t.value) == int(component_metadata.type) for t in InverterType):
5358
return None
5459

@@ -60,30 +65,30 @@ def component_type_from_protobuf(
6065
class ComponentCategory(Enum):
6166
"""Possible types of microgrid component."""
6267

63-
NONE = components.ComponentCategory.COMPONENT_CATEGORY_UNSPECIFIED
68+
NONE = PbComponentCategory.COMPONENT_CATEGORY_UNSPECIFIED
6469
"""Unspecified component category."""
6570

66-
GRID = components.ComponentCategory.COMPONENT_CATEGORY_GRID
71+
GRID = PbComponentCategory.COMPONENT_CATEGORY_GRID
6772
"""Grid component."""
6873

69-
METER = components.ComponentCategory.COMPONENT_CATEGORY_METER
74+
METER = PbComponentCategory.COMPONENT_CATEGORY_METER
7075
"""Meter component."""
7176

72-
INVERTER = components.ComponentCategory.COMPONENT_CATEGORY_INVERTER
77+
INVERTER = PbComponentCategory.COMPONENT_CATEGORY_INVERTER
7378
"""Inverter component."""
7479

75-
BATTERY = components.ComponentCategory.COMPONENT_CATEGORY_BATTERY
80+
BATTERY = PbComponentCategory.COMPONENT_CATEGORY_BATTERY
7681
"""Battery component."""
7782

78-
EV_CHARGER = components.ComponentCategory.COMPONENT_CATEGORY_EV_CHARGER
83+
EV_CHARGER = PbComponentCategory.COMPONENT_CATEGORY_EV_CHARGER
7984
"""EV charger component."""
8085

81-
CHP = components.ComponentCategory.COMPONENT_CATEGORY_CHP
86+
CHP = PbComponentCategory.COMPONENT_CATEGORY_CHP
8287
"""CHP component."""
8388

8489

8590
def component_category_from_protobuf(
86-
component_category: components.ComponentCategory,
91+
component_category: PbComponentCategory.ValueType,
8792
) -> ComponentCategory:
8893
"""Convert a protobuf ComponentCategory message to ComponentCategory enum.
8994
@@ -100,7 +105,7 @@ def component_category_from_protobuf(
100105
a valid component category as it does not form part of the
101106
microgrid itself)
102107
"""
103-
if component_category == components.ComponentCategory.COMPONENT_CATEGORY_SENSOR:
108+
if component_category == PbComponentCategory.COMPONENT_CATEGORY_SENSOR:
104109
raise ValueError("Cannot create a component from a sensor!")
105110

106111
if not any(t.value == component_category for t in ComponentCategory):
@@ -131,8 +136,8 @@ class GridMetadata(ComponentMetadata):
131136

132137

133138
def component_metadata_from_protobuf(
134-
component_category: components.ComponentCategory,
135-
component_metadata: grid.Metadata,
139+
component_category: PbComponentCategory.ValueType,
140+
component_metadata: PbGridMetadata,
136141
) -> GridMetadata | None:
137142
"""Convert a protobuf GridMetadata message to GridMetadata class.
138143
@@ -145,7 +150,7 @@ def component_metadata_from_protobuf(
145150
Returns:
146151
GridMetadata instance corresponding to the protobuf message.
147152
"""
148-
if component_category == components.ComponentCategory.COMPONENT_CATEGORY_GRID:
153+
if component_category == PbComponentCategory.COMPONENT_CATEGORY_GRID:
149154
max_current = component_metadata.rated_fuse_current
150155
fuse = Fuse(max_current)
151156
return GridMetadata(fuse)

0 commit comments

Comments
 (0)