44"""Types for the Reporting API client."""
55
66import math
7- from collections .abc import Iterable , Iterator
7+ from collections .abc import Iterator , MutableSequence
88from dataclasses import dataclass
99from datetime import datetime
10- from typing import Any , NamedTuple
10+ from typing import Any , Callable , Generic , NamedTuple , Protocol , TypeVar , cast
1111
1212# pylint: disable=no-name-in-module
13+ from frequenz .api .common .v1alpha8 .metrics .metrics_pb2 import (
14+ MetricSample as PbMetricSample ,
15+ )
16+ from frequenz .api .common .v1alpha8 .microgrid .electrical_components .electrical_components_pb2 import (
17+ ElectricalComponentDiagnostic as PbElectricalComponentDiagnostic ,
18+ )
19+ from frequenz .api .common .v1alpha8 .microgrid .electrical_components .electrical_components_pb2 import (
20+ ElectricalComponentStateCode as PbElectricalComponentStateCode ,
21+ )
22+ from frequenz .api .common .v1alpha8 .microgrid .electrical_components .electrical_components_pb2 import (
23+ ElectricalComponentStateSnapshot as PbElectricalComponentStateSnapshot ,
24+ )
25+ from frequenz .api .common .v1alpha8 .microgrid .electrical_components .electrical_components_pb2 import (
26+ ElectricalComponentTelemetry as PbElectricalComponentTelemetry ,
27+ )
28+ from frequenz .api .common .v1alpha8 .microgrid .sensors .sensors_pb2 import (
29+ SensorDiagnostic as PbSensorDiagnostic ,
30+ )
31+ from frequenz .api .common .v1alpha8 .microgrid .sensors .sensors_pb2 import (
32+ SensorStateCode as PbSensorStateCode ,
33+ )
34+ from frequenz .api .common .v1alpha8 .microgrid .sensors .sensors_pb2 import (
35+ SensorStateSnapshot as PbSensorStateSnapshot ,
36+ )
37+ from frequenz .api .common .v1alpha8 .microgrid .sensors .sensors_pb2 import (
38+ SensorTelemetry as PbSensorTelemetry ,
39+ )
1340from frequenz .api .reporting .v1alpha10 .reporting_pb2 import (
1441 ReceiveAggregatedMicrogridComponentsDataStreamResponse as PBAggregatedStreamResponse ,
1542)
@@ -35,13 +62,46 @@ class MetricSample(NamedTuple):
3562
3663 timestamp : datetime
3764 microgrid_id : int
38- component_id : str
65+ component_id : int | str
3966 metric : str
40- value : float
67+ value : (
68+ float
69+ | PbElectricalComponentStateCode .ValueType
70+ | PbSensorStateCode .ValueType
71+ | PbElectricalComponentDiagnostic
72+ | PbSensorDiagnostic
73+ )
74+
75+
76+ class _PbMicrogridTelem (Protocol ):
77+ """Protocol for microgrid telemetry from the Reporting API client."""
78+
79+ @property
80+ def microgrid_id (self ) -> int :
81+ """Return the microgrid ID of the telemetry batch."""
82+
83+
84+ class _PbComponentTelem (Protocol ):
85+ """Protocol for telemetry items in the Reporting API client."""
86+
87+ @property
88+ def metric_samples (self ) -> MutableSequence [PbMetricSample ]:
89+ """Return the metric samples of the telemetry item."""
90+
91+ @property
92+ def state_snapshots (self ) -> MutableSequence [Any ]:
93+ """List of state snapshots associated with this telemetry item."""
94+
95+
96+ _MicrogridTelemT = TypeVar ("_MicrogridTelemT" , bound = _PbMicrogridTelem )
97+ _ComponentTelemT = TypeVar ("_ComponentTelemT" , bound = _PbComponentTelem )
98+ _StateSnapshotT = TypeVar (
99+ "_StateSnapshotT" , bound = PbElectricalComponentStateSnapshot | PbSensorStateSnapshot
100+ )
41101
42102
43103@dataclass (frozen = True )
44- class GenericDataBatch :
104+ class GenericDataBatch ( Generic [ _MicrogridTelemT , _ComponentTelemT , _StateSnapshotT ]) :
45105 """Base class for batches of microgrid data (components or sensors).
46106
47107 This class serves as a base for handling batches of data related to microgrid
@@ -50,9 +110,9 @@ class GenericDataBatch:
50110 functionality to work with bounds if applicable.
51111 """
52112
53- _data_pb : Any
54- id_attr : str
55- items_attr : str
113+ _data_pb : _MicrogridTelemT
114+ id_fetcher : Callable [[ _ComponentTelemT ], int ]
115+ items_fetcher : Callable [[ _MicrogridTelemT ], MutableSequence [ _ComponentTelemT ]]
56116 has_bounds : bool = False
57117
58118 def is_empty (self ) -> bool :
@@ -61,15 +121,13 @@ def is_empty(self) -> bool:
61121 Returns:
62122 True if the batch contains no valid data.
63123 """
64- items = getattr ( self ._data_pb , self .items_attr , [] )
124+ items = self .items_fetcher ( self ._data_pb )
65125 if not items :
66126 return True
67127 for item in items :
68- if not getattr (item , "metric_samples" , []) and not getattr (
69- item , "states" , []
70- ):
71- return True
72- return False
128+ if item .metric_samples or item .state_snapshots :
129+ return False
130+ return True
73131
74132 # pylint: disable=too-many-locals
75133 # pylint: disable=too-many-branches
@@ -89,11 +147,11 @@ def __iter__(self) -> Iterator[MetricSample]:
89147 * value: The metric value.
90148 """
91149 mid = self ._data_pb .microgrid_id
92- items = getattr ( self ._data_pb , self .items_attr )
150+ items = self .items_fetcher ( self ._data_pb )
93151
94152 for item in items :
95- cid = getattr ( item , self .id_attr )
96- for sample in getattr ( item , " metric_samples" , []) :
153+ cid = self .id_fetcher ( item )
154+ for sample in item . metric_samples :
97155 ts = datetime_from_proto (sample .sample_time )
98156 met = enum_from_proto (sample .metric , Metric , allow_invalid = False ).name
99157
@@ -128,21 +186,26 @@ def __iter__(self) -> Iterator[MetricSample]:
128186 ts , mid , cid , f"{ met } _bound_{ i } _upper" , upper
129187 )
130188
131- for state in getattr (item , "state_snapshots" , []):
189+ for state in item .state_snapshots :
190+ state = cast (_StateSnapshotT , state )
132191 ts = datetime_from_proto (state .origin_time )
133192 for category , category_items in {
134- "state" : getattr ( state , " states" , []) ,
135- "warning" : getattr ( state , " warnings" , []) ,
136- "error" : getattr ( state , " errors" , []) ,
193+ "state" : state . states ,
194+ "warning" : state . warnings ,
195+ "error" : state . errors ,
137196 }.items ():
138- if not isinstance (category_items , Iterable ):
139- continue
140197 for s in category_items :
141198 yield MetricSample (ts , mid , cid , category , s )
142199
143200
144201@dataclass (frozen = True )
145- class ComponentsDataBatch (GenericDataBatch ):
202+ class ComponentsDataBatch (
203+ GenericDataBatch [
204+ PBReceiveMicrogridComponentsDataStreamResponse ,
205+ PbElectricalComponentTelemetry ,
206+ PbElectricalComponentStateSnapshot ,
207+ ]
208+ ):
146209 """Batch of microgrid components data."""
147210
148211 def __init__ (self , data_pb : PBReceiveMicrogridComponentsDataStreamResponse ):
@@ -153,14 +216,20 @@ def __init__(self, data_pb: PBReceiveMicrogridComponentsDataStreamResponse):
153216 """
154217 super ().__init__ (
155218 data_pb ,
156- id_attr = " electrical_component_id" ,
157- items_attr = " components" ,
219+ id_fetcher = lambda item : item . electrical_component_id ,
220+ items_fetcher = lambda pb : pb . components ,
158221 has_bounds = True ,
159222 )
160223
161224
162225@dataclass (frozen = True )
163- class SensorsDataBatch (GenericDataBatch ):
226+ class SensorsDataBatch (
227+ GenericDataBatch [
228+ PBReceiveMicrogridSensorsDataStreamResponse ,
229+ PbSensorTelemetry ,
230+ PbSensorStateSnapshot ,
231+ ]
232+ ):
164233 """Batch of microgrid sensors data."""
165234
166235 def __init__ (self , data_pb : PBReceiveMicrogridSensorsDataStreamResponse ):
@@ -169,7 +238,11 @@ def __init__(self, data_pb: PBReceiveMicrogridSensorsDataStreamResponse):
169238 Args:
170239 data_pb: The underlying protobuf message.
171240 """
172- super ().__init__ (data_pb , id_attr = "sensor_id" , items_attr = "sensors" )
241+ super ().__init__ (
242+ data_pb ,
243+ id_fetcher = lambda item : item .sensor_id ,
244+ items_fetcher = lambda pb : pb .sensors ,
245+ )
173246
174247
175248@dataclass (frozen = True )
0 commit comments