Skip to content

Commit 90c9336

Browse files
committed
Refactor inject_args for Direct UUID Support
- Allow passing UUID, list[UUID], and Optional[UUID] directly. - Refactor conversion logic into helper functions. - Add tests for UUID, list, and optional UUID handling.
1 parent 712df12 commit 90c9336

File tree

2 files changed

+209
-2
lines changed

2 files changed

+209
-2
lines changed

orchestrator/utils/state.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,14 @@ def _build_arguments(func: StepFunc | InputStepFunc, state: State) -> list: # n
150150
151151
Raises:
152152
KeyError: if requested argument is not in the state, or cannot be reconstructed as an initial domain model.
153+
ValueError: if requested argument cannot be converted to the expected type.
153154
154155
"""
156+
157+
def _convert_to_uuid(v: Any) -> UUID:
158+
"""Converts the value to a UUID instance if it is not already one."""
159+
return v if isinstance(v, UUID) else UUID(v)
160+
155161
sig = inspect.signature(func)
156162
arguments: list[Any] = []
157163
if sig.parameters:
@@ -203,12 +209,24 @@ def _build_arguments(func: StepFunc | InputStepFunc, state: State) -> list: # n
203209
arguments.append(state.get(name, param.default))
204210
else:
205211
try:
206-
arguments.append(state[name])
212+
value = state[name]
213+
if param.annotation == UUID:
214+
arguments.append(_convert_to_uuid(value))
215+
elif is_list_type(param.annotation, UUID):
216+
arguments.append([_convert_to_uuid(item) for item in value])
217+
elif is_optional_type(param.annotation, UUID):
218+
arguments.append(None if value is None else _convert_to_uuid(value))
219+
else:
220+
arguments.append(value)
207221
except KeyError as key_error:
208222
logger.error("Could not find key in state.", key=name, state=state)
209223
raise KeyError(
210224
f"Could not find key '{name}' in state. for function {func.__module__}.{func.__qualname__}"
211225
) from key_error
226+
except ValueError as value_error:
227+
logger.error("Could not convert value to expected type.", key=name, state=state, value=state[name])
228+
raise ValueError(f"Could not convert value '{state[name]}' to {param.annotation}") from value_error
229+
212230
return arguments
213231

214232

test/unit_tests/utils/test_state.py

Lines changed: 190 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# type: ignore
22

3-
from uuid import uuid4
3+
from uuid import UUID, uuid4
44

55
import pytest
66

@@ -299,3 +299,192 @@ def form_function(generic_sub: GenericProduct) -> State:
299299
# Test `rt_1` has been persisted to the database with the modifications from the step function.`
300300
fresh_generic_sub = GenericProduct.from_subscription(state_amended["generic_sub"].subscription_id)
301301
assert fresh_generic_sub.pb_1.rt_1 is not None
302+
303+
304+
def test_uuid_parameter_with_string_uuid():
305+
# Test plain UUID parameter: providing a string should result in conversion to a UUID instance.
306+
307+
@inject_args
308+
def step(uuid_param: UUID) -> State:
309+
# Return the UUID so we can inspect it
310+
return {"result": uuid_param}
311+
312+
valid_uuid = uuid4()
313+
state = {"uuid_param": str(valid_uuid)}
314+
new_state = step(state)
315+
assert isinstance(new_state["result"], UUID)
316+
assert new_state["result"] == valid_uuid
317+
318+
319+
def test_uuid_parameter_with_uuid_instance():
320+
# Test plain UUID parameter: providing a UUID instance should pass it through unchanged.
321+
322+
@inject_args
323+
def step(uuid_param: UUID) -> State:
324+
return {"result": uuid_param}
325+
326+
valid_uuid = uuid4()
327+
state = {"uuid_param": valid_uuid}
328+
new_state = step(state)
329+
assert isinstance(new_state["result"], UUID)
330+
assert new_state["result"] == valid_uuid
331+
332+
333+
def test_uuid_parameter_missing_key():
334+
# Test that if the key for a UUID parameter is missing, a KeyError is raised.
335+
336+
@inject_args
337+
def step(uuid_param: UUID) -> State:
338+
return {"result": uuid_param}
339+
340+
state = {} # key missing
341+
with pytest.raises(KeyError) as excinfo:
342+
step(state)
343+
344+
assert "Could not find key 'uuid_param' in state. for function" in str(excinfo.value)
345+
346+
347+
def test_uuid_parameter_invalid_value():
348+
# Test that an invalid UUID string raises a ValueError.
349+
350+
@inject_args
351+
def step(uuid_param: UUID) -> State:
352+
return {"result": uuid_param}
353+
354+
state = {"uuid_param": "not-a-valid-uuid"}
355+
with pytest.raises(ValueError) as excinfo:
356+
step(state)
357+
358+
assert f"Could not convert value 'not-a-valid-uuid' to {UUID}" in str(excinfo.value)
359+
360+
361+
def test_list_of_uuid_parameter_with_string():
362+
# Test list[UUID] parameter: providing a list of valid UUID strings.
363+
364+
@inject_args
365+
def step(uuid_list: list[UUID]) -> State:
366+
return {"result": uuid_list}
367+
368+
valid_uuid = uuid4()
369+
state = {"uuid_list": [str(valid_uuid)]}
370+
new_state = step(state)
371+
result = new_state["result"]
372+
assert isinstance(result, list)
373+
assert len(result) == 1
374+
assert isinstance(result[0], UUID)
375+
assert result[0] == valid_uuid
376+
377+
378+
def test_list_of_uuid_parameter_with_uuid_instance():
379+
# Test list[UUID] parameter: providing a list with a UUID instance.
380+
381+
@inject_args
382+
def step(uuid_list: list[UUID]) -> State:
383+
return {"result": uuid_list}
384+
385+
valid_uuid = uuid4()
386+
state = {"uuid_list": [valid_uuid]}
387+
new_state = step(state)
388+
result = new_state["result"]
389+
assert isinstance(result, list)
390+
assert len(result) == 1
391+
assert isinstance(result[0], UUID)
392+
assert result[0] == valid_uuid
393+
394+
395+
def test_list_of_uuid_parameter_invalid_value():
396+
# Test list[UUID] parameter: an invalid element in the list should cause a ValueError.
397+
398+
@inject_args
399+
def step(uuid_list: list[UUID]) -> State:
400+
return {"result": uuid_list}
401+
402+
state = {"uuid_list": ["invalid-uuid"]}
403+
with pytest.raises(ValueError) as excinfo:
404+
step(state)
405+
406+
assert f"Could not convert value '['invalid-uuid']' to {list[UUID]}" in str(excinfo.value)
407+
408+
409+
def test_optional_uuid_parameter_with_string():
410+
# Test Optional[UUID] parameter: providing a valid UUID string.
411+
412+
@inject_args
413+
def step(optional_uuid: UUID | None) -> State:
414+
return {"result": optional_uuid}
415+
416+
valid_uuid = uuid4()
417+
state = {"optional_uuid": str(valid_uuid)}
418+
new_state = step(state)
419+
result = new_state["result"]
420+
assert isinstance(result, UUID)
421+
assert result == valid_uuid
422+
423+
424+
def test_optional_uuid_parameter_with_uuid_instance():
425+
# Test Optional[UUID] parameter: providing a UUID instance.
426+
427+
@inject_args
428+
def step(optional_uuid: UUID | None) -> State:
429+
return {"result": optional_uuid}
430+
431+
valid_uuid = uuid4()
432+
state = {"optional_uuid": valid_uuid}
433+
new_state = step(state)
434+
result = new_state["result"]
435+
assert isinstance(result, UUID)
436+
assert result == valid_uuid
437+
438+
439+
def test_optional_uuid_parameter_with_none():
440+
# Test Optional[UUID] parameter: providing None explicitly.
441+
442+
@inject_args
443+
def step(optional_uuid: UUID | None) -> State:
444+
return {"result": optional_uuid}
445+
446+
state = {"optional_uuid": None}
447+
new_state = step(state)
448+
assert new_state["result"] is None
449+
450+
451+
def test_optional_uuid_parameter_missing_key():
452+
# Test Optional[UUID] parameter: when the key is missing.
453+
454+
@inject_args
455+
def step(optional_uuid: UUID | None) -> State:
456+
return {"result": optional_uuid}
457+
458+
state = {} # key missing; no default is provided, so expect a KeyError
459+
with pytest.raises(KeyError) as excinfo:
460+
step(state)
461+
462+
assert "Could not find key 'optional_uuid' in state. for function" in str(excinfo.value)
463+
464+
465+
def test_optional_uuid_parameter_invalid_value():
466+
# Test Optional[UUID] parameter: providing an invalid UUID value should raise ValueError.
467+
468+
@inject_args
469+
def step(optional_uuid: UUID | None) -> State:
470+
return {"result": optional_uuid}
471+
472+
state = {"optional_uuid": "invalid-uuid"}
473+
with pytest.raises(ValueError) as excinfo:
474+
step(state)
475+
476+
assert f"Could not convert value 'invalid-uuid' to {UUID | None}" in str(excinfo.value)
477+
478+
479+
def test_uuid_parameter_with_default_value():
480+
# Test that a default value is used if the key is missing.
481+
482+
default_uuid = uuid4()
483+
484+
@inject_args
485+
def step(uuid_param: UUID = default_uuid) -> State:
486+
return {"result": uuid_param}
487+
488+
state = {} # missing key, so the default should be used
489+
new_state = step(state)
490+
assert new_state["result"] == default_uuid

0 commit comments

Comments
 (0)