|
12 | 12 | from dataclasses import dataclass |
13 | 13 | from datetime import datetime, timedelta, timezone |
14 | 14 | from decimal import Decimal |
15 | | -from typing import Self |
| 15 | +from functools import wraps |
| 16 | +from typing import Callable, Concatenate, ParamSpec, Self, TypeVar |
16 | 17 |
|
17 | 18 | # pylint: disable=no-member |
18 | 19 | from frequenz.api.common.v1.grid import delivery_area_pb2, delivery_duration_pb2 |
|
24 | 25 | _logger = logging.getLogger(__name__) |
25 | 26 |
|
26 | 27 |
|
| 28 | +T = TypeVar("T") # Generic type variable for class methods |
| 29 | +P = ParamSpec("P") |
| 30 | + |
| 31 | + |
| 32 | +def from_pb( |
| 33 | + func: Callable[Concatenate[type[T], P], T] |
| 34 | +) -> Callable[Concatenate[type[T], P], T]: |
| 35 | + """Standardize from_pb methods like error handling with this decorator. |
| 36 | +
|
| 37 | + Args: |
| 38 | + func: A class method that converts a protobuf message into an object. |
| 39 | +
|
| 40 | + Returns: |
| 41 | + The wrapped function with standardized error handling. |
| 42 | + """ |
| 43 | + |
| 44 | + @wraps(func) |
| 45 | + def wrapper(cls: type[T], /, *args: P.args, **kwargs: P.kwargs) -> T: |
| 46 | + try: |
| 47 | + return func(cls, *args, **kwargs) |
| 48 | + except Exception as e: |
| 49 | + _logger.error( |
| 50 | + "Error converting %s from protobuf (`%s`): %s", cls.__name__, *args, e |
| 51 | + ) |
| 52 | + raise |
| 53 | + |
| 54 | + return wrapper |
| 55 | + |
| 56 | + |
27 | 57 | # From frequanz.api.common |
28 | 58 | class Currency(enum.Enum): |
29 | 59 | """ |
@@ -56,6 +86,7 @@ class Currency(enum.Enum): |
56 | 86 | SGD = price_pb2.Price.Currency.CURRENCY_SGD |
57 | 87 |
|
58 | 88 | @classmethod |
| 89 | + @from_pb |
59 | 90 | def from_pb(cls, currency: price_pb2.Price.Currency.ValueType) -> "Currency": |
60 | 91 | """Convert a protobuf Currency value to Currency enum. |
61 | 92 |
|
@@ -91,6 +122,7 @@ class Price: |
91 | 122 | """Currency of the price.""" |
92 | 123 |
|
93 | 124 | @classmethod |
| 125 | + @from_pb |
94 | 126 | def from_pb(cls, price: price_pb2.Price) -> Self: |
95 | 127 | """Convert a protobuf Price to Price object. |
96 | 128 |
|
@@ -131,6 +163,7 @@ class Power: |
131 | 163 | mw: Decimal |
132 | 164 |
|
133 | 165 | @classmethod |
| 166 | + @from_pb |
134 | 167 | def from_pb(cls, power: power_pb2.Power) -> Self: |
135 | 168 | """Convert a protobuf Power to Power object. |
136 | 169 |
|
@@ -184,6 +217,7 @@ class EnergyMarketCodeType(enum.Enum): |
184 | 217 | """North American Electric Reliability Corporation identifiers.""" |
185 | 218 |
|
186 | 219 | @classmethod |
| 220 | + @from_pb |
187 | 221 | def from_pb( |
188 | 222 | cls, energy_market_code_type: delivery_area_pb2.EnergyMarketCodeType.ValueType |
189 | 223 | ) -> "EnergyMarketCodeType": |
@@ -229,6 +263,7 @@ class DeliveryArea: |
229 | 263 | """Type of code used for identifying the delivery area itself.""" |
230 | 264 |
|
231 | 265 | @classmethod |
| 266 | + @from_pb |
232 | 267 | def from_pb(cls, delivery_area: delivery_area_pb2.DeliveryArea) -> Self: |
233 | 268 | """Convert a protobuf DeliveryArea to DeliveryArea object. |
234 | 269 |
|
@@ -278,6 +313,7 @@ class DeliveryDuration(enum.Enum): |
278 | 313 | """1-hour contract duration.""" |
279 | 314 |
|
280 | 315 | @classmethod |
| 316 | + @from_pb |
281 | 317 | def from_pb( |
282 | 318 | cls, delivery_duration: delivery_duration_pb2.DeliveryDuration.ValueType |
283 | 319 | ) -> "DeliveryDuration": |
@@ -408,6 +444,7 @@ def __repr__(self) -> str: |
408 | 444 | return self.__str__() |
409 | 445 |
|
410 | 446 | @classmethod |
| 447 | + @from_pb |
411 | 448 | def from_pb(cls, delivery_period: delivery_duration_pb2.DeliveryPeriod) -> Self: |
412 | 449 | """Convert a protobuf DeliveryPeriod to DeliveryPeriod object. |
413 | 450 |
|
@@ -482,6 +519,7 @@ class OrderExecutionOption(enum.Enum): |
482 | 519 | immediately will be cancelled.""" |
483 | 520 |
|
484 | 521 | @classmethod |
| 522 | + @from_pb |
485 | 523 | def from_pb( |
486 | 524 | cls, |
487 | 525 | order_execution_option: electricity_trading_pb2.OrderExecutionOption.ValueType, |
@@ -547,6 +585,7 @@ class OrderType(enum.Enum): |
547 | 585 | order book and has no market impact. (Not yet supported).""" |
548 | 586 |
|
549 | 587 | @classmethod |
| 588 | + @from_pb |
550 | 589 | def from_pb( |
551 | 590 | cls, order_type: electricity_trading_pb2.OrderType.ValueType |
552 | 591 | ) -> "OrderType": |
@@ -586,6 +625,7 @@ class MarketSide(enum.Enum): |
586 | 625 | """Order to sell electricity, referred to as an 'ask' or 'offer' in the order book.""" |
587 | 626 |
|
588 | 627 | @classmethod |
| 628 | + @from_pb |
589 | 629 | def from_pb( |
590 | 630 | cls, market_side: electricity_trading_pb2.MarketSide.ValueType |
591 | 631 | ) -> "MarketSide": |
@@ -648,6 +688,7 @@ class OrderState(enum.Enum): |
648 | 688 | could be due to certain conditions not yet being met.""" |
649 | 689 |
|
650 | 690 | @classmethod |
| 691 | + @from_pb |
651 | 692 | def from_pb( |
652 | 693 | cls, order_state: electricity_trading_pb2.OrderState.ValueType |
653 | 694 | ) -> "OrderState": |
@@ -711,6 +752,7 @@ class TradeState(enum.Enum): |
711 | 752 | """An approval has been requested.""" |
712 | 753 |
|
713 | 754 | @classmethod |
| 755 | + @from_pb |
714 | 756 | def from_pb( |
715 | 757 | cls, trade_state: electricity_trading_pb2.TradeState.ValueType |
716 | 758 | ) -> "TradeState": |
@@ -811,6 +853,7 @@ class StateReason(enum.Enum): |
811 | 853 | """A quote was partially executed.""" |
812 | 854 |
|
813 | 855 | @classmethod |
| 856 | + @from_pb |
814 | 857 | def from_pb( |
815 | 858 | cls, |
816 | 859 | state_reason: electricity_trading_pb2.OrderDetail.StateDetail.StateReason.ValueType, |
@@ -864,6 +907,7 @@ class MarketActor(enum.Enum): |
864 | 907 | """The system was the actor.""" |
865 | 908 |
|
866 | 909 | @classmethod |
| 910 | + @from_pb |
867 | 911 | def from_pb( |
868 | 912 | cls, |
869 | 913 | market_actor: electricity_trading_pb2.OrderDetail.StateDetail.MarketActor.ValueType, |
@@ -951,6 +995,7 @@ def __post_init__(self) -> None: |
951 | 995 | self.valid_until = self.valid_until.astimezone(timezone.utc) |
952 | 996 |
|
953 | 997 | @classmethod |
| 998 | + @from_pb |
954 | 999 | def from_pb(cls, order: electricity_trading_pb2.Order) -> Self: |
955 | 1000 | """Convert a protobuf Order to Order object. |
956 | 1001 |
|
@@ -1115,6 +1160,7 @@ def __post_init__(self) -> None: |
1115 | 1160 | self.execution_time = self.execution_time.astimezone(timezone.utc) |
1116 | 1161 |
|
1117 | 1162 | @classmethod |
| 1163 | + @from_pb |
1118 | 1164 | def from_pb(cls, trade: electricity_trading_pb2.Trade) -> Self: |
1119 | 1165 | """Convert a protobuf Trade to Trade object. |
1120 | 1166 |
|
@@ -1172,6 +1218,7 @@ class StateDetail: |
1172 | 1218 | """Actor responsible for the current state.""" |
1173 | 1219 |
|
1174 | 1220 | @classmethod |
| 1221 | + @from_pb |
1175 | 1222 | def from_pb( |
1176 | 1223 | cls, state_detail: electricity_trading_pb2.OrderDetail.StateDetail |
1177 | 1224 | ) -> Self: |
@@ -1252,6 +1299,7 @@ def __post_init__(self) -> None: |
1252 | 1299 | self.modification_time = self.modification_time.astimezone(timezone.utc) |
1253 | 1300 |
|
1254 | 1301 | @classmethod |
| 1302 | + @from_pb |
1255 | 1303 | def from_pb(cls, order_detail: electricity_trading_pb2.OrderDetail) -> Self: |
1256 | 1304 | """Convert a protobuf OrderDetail to OrderDetail object. |
1257 | 1305 |
|
@@ -1332,6 +1380,7 @@ def __post_init__(self) -> None: |
1332 | 1380 | self.execution_time = self.execution_time.astimezone(timezone.utc) |
1333 | 1381 |
|
1334 | 1382 | @classmethod |
| 1383 | + @from_pb |
1335 | 1384 | def from_pb(cls, public_trade: electricity_trading_pb2.PublicTrade) -> Self: |
1336 | 1385 | """Convert a protobuf PublicTrade to PublicTrade object. |
1337 | 1386 |
|
@@ -1430,6 +1479,7 @@ def __hash__(self) -> int: |
1430 | 1479 | ) |
1431 | 1480 |
|
1432 | 1481 | @classmethod |
| 1482 | + @from_pb |
1433 | 1483 | def from_pb( |
1434 | 1484 | cls, gridpool_order_filter: electricity_trading_pb2.GridpoolOrderFilter |
1435 | 1485 | ) -> Self: |
@@ -1554,6 +1604,7 @@ def __hash__(self) -> int: |
1554 | 1604 | ) |
1555 | 1605 |
|
1556 | 1606 | @classmethod |
| 1607 | + @from_pb |
1557 | 1608 | def from_pb( |
1558 | 1609 | cls, gridpool_trade_filter: electricity_trading_pb2.GridpoolTradeFilter |
1559 | 1610 | ) -> "GridpoolTradeFilter": |
@@ -1667,6 +1718,7 @@ def __hash__(self) -> int: |
1667 | 1718 | ) |
1668 | 1719 |
|
1669 | 1720 | @classmethod |
| 1721 | + @from_pb |
1670 | 1722 | def from_pb( |
1671 | 1723 | cls, public_trade_filter: electricity_trading_pb2.PublicTradeFilter |
1672 | 1724 | ) -> Self: |
@@ -1779,6 +1831,7 @@ def __post_init__(self) -> None: |
1779 | 1831 | self.valid_until = self.valid_until.astimezone(timezone.utc) |
1780 | 1832 |
|
1781 | 1833 | @classmethod |
| 1834 | + @from_pb |
1782 | 1835 | def from_pb( |
1783 | 1836 | cls, |
1784 | 1837 | update_order: electricity_trading_pb2.UpdateGridpoolOrderRequest.UpdateOrder, |
|
0 commit comments