Skip to content

Commit f32a86d

Browse files
authored
Add call unique id to handler kwargs (#545)
Issue Link: #544 The `call_unique_id` is passed to the `on` and `after` handlers only if it is explicitly set in the handler signature.
1 parent 6a6c7ae commit f32a86d

File tree

3 files changed

+126
-9
lines changed

3 files changed

+126
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Change log
22

33

4+
- [#544](https://github.com/mobilityhouse/ocpp/issues/544) Pass `Call.unique_id` to the `on` and `after` routing handlers.
45
- [#559](https://github.com/mobilityhouse/ocpp/issues/559) Update project dependencies as of 22-12-2023
56
- [#447](https://github.com/mobilityhouse/ocpp/issues/447) Make formatting of enums in py3.11 consistent with earlier Python versions
67
- [#421](https://github.com/mobilityhouse/ocpp/issues/421) Type of v16.datatypes.SampledValue.context is incorrect

ocpp/charge_point.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,15 @@ async def _handle_call(self, msg):
225225
handler = handlers["_on_action"]
226226
except KeyError:
227227
_raise_key_error(msg.action, self._ocpp_version)
228-
228+
handler_signature = inspect.signature(handler)
229+
call_unique_id_required = "call_unique_id" in handler_signature.parameters
229230
try:
230-
response = handler(**snake_case_payload)
231+
# call_unique_id should be passed as kwarg only if is defined explicitly
232+
# in the handler signature
233+
if call_unique_id_required:
234+
response = handler(**snake_case_payload, call_unique_id=msg.unique_id)
235+
else:
236+
response = handler(**snake_case_payload)
231237
if inspect.isawaitable(response):
232238
response = await response
233239
except Exception as e:
@@ -259,9 +265,16 @@ async def _handle_call(self, msg):
259265

260266
try:
261267
handler = handlers["_after_action"]
268+
handler_signature = inspect.signature(handler)
269+
call_unique_id_required = "call_unique_id" in handler_signature.parameters
270+
# call_unique_id should be passed as kwarg only if is defined explicitly
271+
# in the handler signature
272+
if call_unique_id_required:
273+
response = handler(**snake_case_payload, call_unique_id=msg.unique_id)
274+
else:
275+
response = handler(**snake_case_payload)
262276
# Create task to avoid blocking when making a call inside the
263277
# after handler
264-
response = handler(**snake_case_payload)
265278
if inspect.isawaitable(response):
266279
asyncio.ensure_future(response)
267280
except KeyError:

tests/test_charge_point.py

+109-6
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@
33
import pytest
44

55
from ocpp.charge_point import camel_to_snake_case, remove_nones, snake_to_camel_case
6-
from ocpp.routing import create_route_map, on
6+
from ocpp.messages import Call
7+
from ocpp.routing import after, create_route_map, on
8+
from ocpp.v16 import ChargePoint as cp_16
79
from ocpp.v16.call import (
810
BootNotificationPayload,
911
GetConfigurationPayload,
1012
MeterValuesPayload,
1113
)
14+
from ocpp.v16.call_result import (
15+
BootNotificationPayload as BootNotificationResultPayload,
16+
)
1217
from ocpp.v16.datatypes import MeterValue, SampledValue
13-
from ocpp.v16.enums import Action
14-
from ocpp.v20 import ChargePoint as cp
18+
from ocpp.v16.enums import Action, RegistrationStatus
19+
from ocpp.v20 import ChargePoint as cp_20
1520
from ocpp.v201.call import SetNetworkProfilePayload
1621
from ocpp.v201.datatypes import NetworkConnectionProfileType
1722
from ocpp.v201.enums import OCPPInterfaceType, OCPPTransportType, OCPPVersionType
1823

1924

2025
def test_getters_should_not_be_called_during_routemap_setup():
21-
class ChargePoint(cp):
26+
class ChargePoint(cp_20):
2227
@property
2328
def foo(self):
2429
raise RuntimeError("this will be raised")
@@ -31,12 +36,12 @@ def foo(self):
3136

3237

3338
def test_multiple_classes_with_same_name_for_handler():
34-
class ChargerA(cp):
39+
class ChargerA(cp_20):
3540
@on(Action.Heartbeat)
3641
def heartbeat(self, **kwargs):
3742
pass
3843

39-
class ChargerB(cp):
44+
class ChargerB(cp_20):
4045
@on(Action.Heartbeat)
4146
def heartbeat(self, **kwargs):
4247
pass
@@ -232,3 +237,101 @@ def test_remove_nones_with_list_of_strings():
232237
assert remove_nones(payload) == {
233238
"key": ["ClockAlignedDataInterval", "ConnectionTimeOut"]
234239
}
240+
241+
242+
@pytest.mark.asyncio
243+
async def test_call_unique_id_added_to_handler_args_correctly(connection):
244+
"""
245+
This test ensures that the `call_unique_id` is getting passed to the
246+
`on` and `after` handlers only if it is explicitly set in the handler signature.
247+
248+
To cover all possible cases, we define two chargers:
249+
250+
ChargerA:
251+
`call_unique_id` not required on `on` handler but required on `after` handler.
252+
253+
ChargerB:
254+
`call_unique_id` required on `on` handler but not required on `after` handler.
255+
256+
Each handler verifies a set of asserts to verify that the `call_unique_id`
257+
is passed correctly.
258+
To confirm that the handlers are actually being called and hence the asserts
259+
are being ran, we introduce a set of counters that increase each time a specific
260+
handler runs.
261+
"""
262+
charger_a_test_call_unique_id = "charger_a_1234"
263+
charger_b_test_call_unique_id = "charger_b_5678"
264+
payload_a = {"chargePointVendor": "foo_a", "chargePointModel": "bar_a"}
265+
payload_b = {"chargePointVendor": "foo_b", "chargePointModel": "bar_b"}
266+
267+
class ChargerA(cp_16):
268+
on_boot_notification_call_count = 0
269+
after_boot_notification_call_count = 0
270+
271+
@on(Action.BootNotification)
272+
def on_boot_notification(self, *args, **kwargs):
273+
# call_unique_id should not be passed as arg nor kwarg
274+
assert kwargs == camel_to_snake_case(payload_a)
275+
assert args == ()
276+
ChargerA.on_boot_notification_call_count += 1
277+
return BootNotificationResultPayload(
278+
current_time="foo", interval=1, status=RegistrationStatus.accepted
279+
)
280+
281+
@after(Action.BootNotification)
282+
def after_boot_notification(self, call_unique_id, *args, **kwargs):
283+
assert call_unique_id == charger_a_test_call_unique_id
284+
assert kwargs == camel_to_snake_case(payload_a)
285+
# call_unique_id should not be passed as arg
286+
assert args == ()
287+
ChargerA.after_boot_notification_call_count += 1
288+
return BootNotificationResultPayload(
289+
current_time="foo", interval=1, status=RegistrationStatus.accepted
290+
)
291+
292+
class ChargerB(cp_16):
293+
on_boot_notification_call_count = 0
294+
after_boot_notification_call_count = 0
295+
296+
@on(Action.BootNotification)
297+
def on_boot_notification(self, call_unique_id, *args, **kwargs):
298+
assert call_unique_id == charger_b_test_call_unique_id
299+
assert kwargs == camel_to_snake_case(payload_b)
300+
# call_unique_id should not be passed as arg
301+
assert args == ()
302+
ChargerB.on_boot_notification_call_count += 1
303+
return BootNotificationResultPayload(
304+
current_time="foo", interval=1, status=RegistrationStatus.accepted
305+
)
306+
307+
@after(Action.BootNotification)
308+
def after_boot_notification(self, *args, **kwargs):
309+
# call_unique_id should not be passed as arg nor kwarg
310+
assert kwargs == camel_to_snake_case(payload_b)
311+
assert args == ()
312+
ChargerB.after_boot_notification_call_count += 1
313+
return BootNotificationResultPayload(
314+
current_time="foo", interval=1, status=RegistrationStatus.accepted
315+
)
316+
317+
charger_a = ChargerA("charger_a_id", connection)
318+
charger_b = ChargerB("charger_b_id", connection)
319+
320+
msg_a = Call(
321+
unique_id=charger_a_test_call_unique_id,
322+
action=Action.BootNotification.value,
323+
payload=payload_a,
324+
)
325+
await charger_a._handle_call(msg_a)
326+
327+
msg_b = Call(
328+
unique_id=charger_b_test_call_unique_id,
329+
action=Action.BootNotification.value,
330+
payload=payload_b,
331+
)
332+
await charger_b._handle_call(msg_b)
333+
334+
assert ChargerA.on_boot_notification_call_count == 1
335+
assert ChargerA.after_boot_notification_call_count == 1
336+
assert ChargerB.on_boot_notification_call_count == 1
337+
assert ChargerB.after_boot_notification_call_count == 1

0 commit comments

Comments
 (0)