Skip to content

Commit 3d8d9e7

Browse files
Add HMAC capabilities (#152)
This adds HMAC capabilities by allowing to provide a signature secret. Additionally removes the old way of adding the API key to the metadata.
2 parents 3f863dc + 7b9d2a4 commit 3d8d9e7

File tree

4 files changed

+139
-52
lines changed

4 files changed

+139
-52
lines changed

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- Updated to `v0.8.0` of the Electricity Trading API.
1010
- Updated to `v0.11.0` of the base client library.
11+
- Added HMAC capabilities
1112

1213
## Bug Fixes
1314

src/frequenz/client/electricity_trading/_client.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,14 @@ def dt_to_pb_timestamp_utc(dt: datetime) -> Timestamp:
161161
class Client(BaseApiClient[ElectricityTradingServiceStub]):
162162
"""Electricity trading client."""
163163

164-
_instances: dict[tuple[str, str | None], "Client"] = {}
164+
_instances: dict[tuple[str, str | None, str | None], "Client"] = {}
165165

166166
def __new__(
167-
cls, server_url: str, connect: bool = True, auth_key: str | None = None
167+
cls,
168+
server_url: str,
169+
connect: bool = True,
170+
auth_key: str | None = None,
171+
sign_secret: str | None = None,
168172
) -> "Client":
169173
"""
170174
Create a new instance of the client or return an existing one if it already exists.
@@ -173,11 +177,12 @@ def __new__(
173177
server_url: The URL of the Electricity Trading service.
174178
connect: Whether to connect to the server immediately.
175179
auth_key: The API key for the authorization.
180+
sign_secret: The cryptographic secret to use for HMAC generation.
176181
177182
Returns:
178183
The client instance.
179184
"""
180-
key = (server_url, auth_key)
185+
key = (server_url, auth_key, sign_secret)
181186

182187
# Check if an instance already exists for this key
183188
if key not in cls._instances:
@@ -188,14 +193,19 @@ def __new__(
188193
return cls._instances[key]
189194

190195
def __init__(
191-
self, server_url: str, connect: bool = True, auth_key: str | None = None
196+
self,
197+
server_url: str,
198+
connect: bool = True,
199+
auth_key: str | None = None,
200+
sign_secret: str | None = None,
192201
) -> None:
193202
"""Initialize the client.
194203
195204
Args:
196205
server_url: The URL of the Electricity Trading service.
197206
connect: Whether to connect to the server immediately.
198207
auth_key: The API key for the authorization.
208+
sign_secret: The cryptographic secret to use for HMAC generation.
199209
"""
200210
if not hasattr(
201211
self, "_initialized"
@@ -204,6 +214,8 @@ def __init__(
204214
server_url,
205215
connect=connect,
206216
create_stub=ElectricityTradingServiceStub,
217+
auth_key=auth_key,
218+
sign_secret=sign_secret,
207219
)
208220
self._initialized = True
209221

@@ -236,8 +248,6 @@ def __init__(
236248
],
237249
] = {}
238250

239-
self._metadata = (("key", auth_key),) if auth_key else ()
240-
241251
@property
242252
def stub(self) -> electricity_trading_pb2_grpc.ElectricityTradingServiceAsyncStub:
243253
"""
@@ -311,7 +321,6 @@ def gridpool_orders_stream(
311321
gridpool_id=gridpool_id,
312322
filter=gridpool_order_filter.to_pb(),
313323
),
314-
metadata=self._metadata,
315324
),
316325
lambda response: OrderDetail.from_pb(response.order_detail),
317326
)
@@ -376,7 +385,6 @@ def gridpool_trades_stream(
376385
gridpool_id=gridpool_id,
377386
filter=gridpool_trade_filter.to_pb(),
378387
),
379-
metadata=self._metadata,
380388
),
381389
lambda response: Trade.from_pb(response.trade),
382390
)
@@ -549,7 +557,6 @@ async def create_gridpool_order(
549557
electricity_trading_pb2.CreateGridpoolOrderRequest(
550558
gridpool_id=gridpool_id, order=order.to_pb()
551559
),
552-
metadata=self._metadata,
553560
timeout=timeout,
554561
),
555562
)
@@ -666,7 +673,6 @@ async def update_gridpool_order(
666673
update_order_fields=update_order_fields.to_pb(),
667674
update_mask=update_mask,
668675
),
669-
metadata=self._metadata,
670676
timeout=timeout,
671677
),
672678
)
@@ -705,7 +711,6 @@ async def cancel_gridpool_order(
705711
electricity_trading_pb2.CancelGridpoolOrderRequest(
706712
gridpool_id=gridpool_id, order_id=order_id
707713
),
708-
metadata=self._metadata,
709714
timeout=timeout,
710715
),
711716
)
@@ -741,7 +746,6 @@ async def cancel_all_gridpool_orders(
741746
electricity_trading_pb2.CancelAllGridpoolOrdersRequest(
742747
gridpool_id=gridpool_id
743748
),
744-
metadata=self._metadata,
745749
timeout=timeout,
746750
),
747751
)
@@ -782,7 +786,6 @@ async def get_gridpool_order(
782786
electricity_trading_pb2.GetGridpoolOrderRequest(
783787
gridpool_id=gridpool_id, order_id=order_id
784788
),
785-
metadata=self._metadata,
786789
timeout=timeout,
787790
),
788791
)
@@ -848,7 +851,6 @@ async def list_gridpool_orders(
848851
grpc_call_with_timeout(
849852
self.stub.ListGridpoolOrders,
850853
request,
851-
metadata=self._metadata,
852854
timeout=timeout,
853855
),
854856
)
@@ -926,7 +928,6 @@ async def list_gridpool_trades(
926928
grpc_call_with_timeout(
927929
self.stub.ListGridpoolTrades,
928930
request,
929-
metadata=self._metadata,
930931
timeout=timeout,
931932
),
932933
)
@@ -1010,7 +1011,6 @@ def dt_to_pb_timestamp(dt: datetime) -> Timestamp:
10101011
dt_to_pb_timestamp(end_time) if end_time else None
10111012
),
10121013
),
1013-
metadata=self._metadata,
10141014
),
10151015
lambda response: PublicTrade.from_pb(response.public_trade),
10161016
)
@@ -1071,7 +1071,6 @@ def receive_public_order_book(
10711071
start_time=start_time_utc,
10721072
end_time=end_time_utc,
10731073
),
1074-
metadata=self._metadata,
10751074
),
10761075
lambda response: [
10771076
PublicOrder.from_pb(public_order)

src/frequenz/client/electricity_trading/cli/__main__.py

Lines changed: 84 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,59 +46,100 @@ def cli() -> None:
4646

4747
@cli.command()
4848
@click.option("--url", required=True, type=str)
49-
@click.option("--key", required=True, type=str)
49+
@click.option("--auth_key", required=True, type=str)
5050
@click.option("--delivery-start", default=None, type=iso)
5151
@click.option("--start", default=None, type=iso)
5252
@click.option("--end", default=None, type=iso)
53-
def receive_public_trades(
54-
url: str, key: str, *, start: datetime, end: datetime, delivery_start: datetime
53+
@click.option("--sign_secret", default=None, type=str)
54+
def receive_public_trades( # pylint: disable=too-many-arguments
55+
url: str,
56+
auth_key: str,
57+
*,
58+
start: datetime,
59+
end: datetime,
60+
delivery_start: datetime,
61+
sign_secret: str | None = None,
5562
) -> None:
5663
"""List and/or stream public trades."""
5764
asyncio.run(
5865
run_receive_public_trades(
59-
url=url, key=key, delivery_start=delivery_start, start=start, end=end
66+
url=url,
67+
auth_key=auth_key,
68+
delivery_start=delivery_start,
69+
start=start,
70+
end=end,
71+
sign_secret=sign_secret,
6072
)
6173
)
6274

6375

6476
@cli.command()
6577
@click.option("--url", required=True, type=str)
66-
@click.option("--key", required=True, type=str)
78+
@click.option("--auth_key", required=True, type=str)
6779
@click.option("--gid", required=True, type=int)
6880
@click.option("--start", default=None, type=iso)
69-
def receive_gridpool_trades(url: str, key: str, gid: int, *, start: datetime) -> None:
81+
@click.option("--sign_secret", default=None, type=str)
82+
def receive_gridpool_trades(
83+
url: str,
84+
auth_key: str,
85+
gid: int,
86+
*,
87+
start: datetime,
88+
sign_secret: str | None = None,
89+
) -> None:
7090
"""List and/or stream gridpool trades."""
7191
asyncio.run(
72-
run_list_gridpool_trades(url=url, key=key, gid=gid, delivery_start=start)
92+
run_list_gridpool_trades(
93+
url=url,
94+
auth_key=auth_key,
95+
gid=gid,
96+
delivery_start=start,
97+
sign_secret=sign_secret,
98+
)
7399
)
74100

75101

76102
@cli.command()
77103
@click.option("--url", required=True, type=str)
78-
@click.option("--key", required=True, type=str)
104+
@click.option("--auth_key", required=True, type=str)
79105
@click.option("--start", default=None, type=iso)
80106
@click.option("--gid", required=True, type=int)
81-
def receive_gridpool_orders(url: str, key: str, *, start: datetime, gid: int) -> None:
107+
@click.option("--sign_secret", default=None, type=str)
108+
def receive_gridpool_orders(
109+
url: str,
110+
auth_key: str,
111+
*,
112+
start: datetime,
113+
gid: int,
114+
sign_secret: str | None = None,
115+
) -> None:
82116
"""List and/or stream gridpool orders."""
83117
asyncio.run(
84-
run_list_gridpool_orders(url=url, key=key, delivery_start=start, gid=gid)
118+
run_list_gridpool_orders(
119+
url=url,
120+
auth_key=auth_key,
121+
delivery_start=start,
122+
gid=gid,
123+
sign_secret=sign_secret,
124+
)
85125
)
86126

87127

88128
@cli.command()
89129
@click.option("--url", required=True, type=str)
90-
@click.option("--key", required=True, type=str)
130+
@click.option("--auth_key", required=True, type=str)
91131
@click.option("--start", required=True, type=iso)
92132
@click.option("--gid", required=True, type=int)
93133
@click.option("--quantity", required=True, type=str)
94134
@click.option("--price", required=True, type=str)
95135
@click.option("--area", required=True, type=str)
96136
@click.option("--currency", default="EUR", type=str)
97137
@click.option("--duration", default=900, type=int)
138+
@click.option("--sign_secret", default=None, type=str)
98139
def create_order(
99140
# pylint: disable=too-many-arguments
100141
url: str,
101-
key: str,
142+
auth_key: str,
102143
*,
103144
start: datetime,
104145
gid: int,
@@ -107,6 +148,7 @@ def create_order(
107148
area: str,
108149
currency: str,
109150
duration: int,
151+
sign_secret: str | None = None,
110152
) -> None:
111153
"""Create an order.
112154
@@ -118,35 +160,58 @@ def create_order(
118160
asyncio.run(
119161
run_create_order(
120162
url=url,
121-
key=key,
163+
auth_key=auth_key,
122164
delivery_start=start,
123165
gid=gid,
124166
quantity_mw=quantity,
125167
price=price,
126168
delivery_area=area,
127169
currency=currency,
128170
duration=timedelta(seconds=duration),
171+
sign_secret=sign_secret,
129172
)
130173
)
131174

132175

133176
@cli.command()
134177
@click.option("--url", required=True, type=str)
135-
@click.option("--key", required=True, type=str)
178+
@click.option("--auth_key", required=True, type=str)
136179
@click.option("--gid", required=True, type=int)
137180
@click.option("--order", required=True, type=int)
138-
def cancel_order(url: str, key: str, gid: int, order: int) -> None:
181+
@click.option("--sign_secret", default=None, type=str)
182+
def cancel_order(
183+
url: str, auth_key: str, gid: int, order: int, sign_secret: str | None = None
184+
) -> None:
139185
"""Cancel an order."""
140-
asyncio.run(run_cancel_order(url=url, key=key, gridpool_id=gid, order_id=order))
186+
asyncio.run(
187+
run_cancel_order(
188+
url=url,
189+
auth_key=auth_key,
190+
gridpool_id=gid,
191+
order_id=order,
192+
sign_secret=sign_secret,
193+
)
194+
)
141195

142196

143197
@cli.command()
144198
@click.option("--url", required=True, type=str)
145-
@click.option("--key", required=True, type=str)
199+
@click.option("--auth_key", required=True, type=str)
146200
@click.option("--gid", required=True, type=int)
147-
def cancel_all_orders(url: str, key: str, gid: int) -> None:
201+
@click.option("--sign_secret", default=None, type=str)
202+
def cancel_all_orders(
203+
url: str, auth_key: str, gid: int, sign_secret: str | None = None
204+
) -> None:
148205
"""Cancel all orders for a gridpool ID."""
149-
asyncio.run(run_cancel_order(url=url, key=key, gridpool_id=gid, order_id=None))
206+
asyncio.run(
207+
run_cancel_order(
208+
url=url,
209+
auth_key=auth_key,
210+
gridpool_id=gid,
211+
order_id=None,
212+
sign_secret=sign_secret,
213+
)
214+
)
150215

151216

152217
@cli.command()

0 commit comments

Comments
 (0)