55
66import asyncio
77import 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
1432from frequenz .channels import Receiver
1533from 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
2237from ._component import (
2338 Component ,
3550)
3651from ._connection import Connection
3752from ._constants import RECEIVER_MAX_SIZE
38- from ._exception import ClientError
53+ from ._exception import ApiClientError
3954from ._metadata import Location , Metadata
4055
4156DEFAULT_GRPC_CALL_TIMEOUT = 60.0
@@ -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 ]] = {}
@@ -90,25 +105,29 @@ async def components(self) -> Iterable[Component]:
90105 Iterator whose elements are all the components in the microgrid.
91106
92107 Raises:
93- ClientError : If the are any errors communicating with the Microgrid API,
108+ ApiClientError : If the are any errors communicating with the Microgrid API,
94109 most likely a subclass of
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 :
103- raise ClientError .from_grpc_error (
122+ except grpc . aio . AioRpcError as grpc_error :
123+ raise ApiClientError .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 :
@@ -170,25 +192,28 @@ async def connections(
170192 Microgrid connections matching the provided start and end filters.
171193
172194 Raises:
173- ClientError : If the are any errors communicating with the Microgrid API,
195+ ApiClientError : If the are any errors communicating with the Microgrid API,
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 :
189- raise ClientError .from_grpc_error (
213+ except grpc . aio . AioRpcError as grpc_error :
214+ raise ApiClientError .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 ,
@@ -389,21 +419,22 @@ async def set_power(self, component_id: int, power_w: float) -> None:
389419 power_w: power to set for the component.
390420
391421 Raises:
392- ClientError : If the are any errors communicating with the Microgrid API,
422+ ApiClientError : If the are any errors communicating with the Microgrid API,
393423 most likely a subclass of
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 :
404- raise ClientError .from_grpc_error (
434+ except grpc . aio . AioRpcError as grpc_error :
435+ raise ApiClientError .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.
@@ -423,7 +454,7 @@ async def set_bounds(
423454 Raises:
424455 ValueError: when upper bound is less than 0, or when lower bound is
425456 greater than 0.
426- ClientError : If the are any errors communicating with the Microgrid API,
457+ ApiClientError : If the are any errors communicating with the Microgrid API,
427458 most likely a subclass of
428459 [GrpcError][frequenz.client.microgrid.GrpcError].
429460 """
@@ -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 :
448- raise ClientError .from_grpc_error (
479+ except grpc . aio . AioRpcError as grpc_error :
480+ raise ApiClientError .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
0 commit comments