Skip to content

Commit 42a0f73

Browse files
committed
Make tenant_id explicit instead of inherited
- Remove UNSET sentinel and automatic tenant_id inheritance - Change InvokeConfig.tenant_id to default to None - Remove with_tenant_id_if_unset() method - Update DurableContext.invoke() to not auto-populate tenant_id - Add tests verifying tenant_id is correctly passed to checkpoints - Add tests for explicit tenant_id, None, and default behavior - Simplify existing tests to match new explicit behavior Users must now explicitly set tenant_id in InvokeConfig if needed.
1 parent ace3608 commit 42a0f73

File tree

6 files changed

+123
-153
lines changed

6 files changed

+123
-153
lines changed

src/aws_durable_execution_sdk_python/config.py

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@
2727
Numeric = int | float # deliberately leaving off complex
2828

2929

30-
class _UnsetType:
31-
def __repr__(self):
32-
return "<UNSET>"
33-
34-
35-
UNSET = _UnsetType()
36-
37-
3830
@dataclass(frozen=True)
3931
class Duration:
4032
"""Represents a duration stored as total seconds."""
@@ -392,30 +384,13 @@ class InvokeConfig(Generic[P, R]):
392384
timeout: Duration = field(default_factory=Duration)
393385
serdes_payload: SerDes[P] | None = None
394386
serdes_result: SerDes[R] | None = None
395-
# we want to distinguish between deliberate use of None
396-
# and simply not setting the value.
397-
# We use the sentinel to indicate that the user has not set
398-
# the field, so that we can fall back to the fallback behaviour.
399-
# The reason we want to accept None as a valid input is that we want to
400-
# allow deliberate invocation without a tenant if the users desire.
401-
# However, we should also, by default, accept a sane input — which is using
402-
# the same tenant id as the current invocation
403-
tenant_id: str | None = field(default=UNSET) # type: ignore
387+
tenant_id: str | None = None
404388

405389
@property
406390
def timeout_seconds(self) -> int:
407391
"""Get timeout in seconds."""
408392
return self.timeout.to_seconds()
409393

410-
def with_tenant_id_if_unset(self, tenant_id: str | None) -> InvokeConfig:
411-
if self.tenant_id is UNSET:
412-
return InvokeConfig(
413-
timeout=self.timeout,
414-
serdes_payload=self.serdes_payload,
415-
serdes_result=self.serdes_result,
416-
tenant_id=tenant_id,
417-
)
418-
return self
419394

420395

421396
@dataclass(frozen=True)

src/aws_durable_execution_sdk_python/context.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -303,29 +303,9 @@ def invoke(
303303
name: Optional name for the operation
304304
config: Optional configuration for the invoke operation
305305
306-
Notes:
307-
config: InvokeConfig contains a tenant_id.
308-
In the event that the tenant is not set, we will default to
309-
using the same tenant as the current invocation.
310-
311-
In the event that the tenant is set for this invocation, we will
312-
respect it, and propagate it.
313-
314-
In the event that the tenant is not set for this invocation, we
315-
will not impose a tenant.
316-
317-
At the same time, we allow setting up tenant to None, or a key,
318-
so that an isolated function can call a non-isolated function,
319-
and vice versa.
320-
321306
Returns:
322307
The result of the invoked function
323308
"""
324-
325-
if config is None:
326-
config = InvokeConfig()
327-
config = config.with_tenant_id_if_unset(self.tenant_id)
328-
329309
return invoke_handler(
330310
function_name=function_name,
331311
payload=payload,

src/aws_durable_execution_sdk_python/operation/invoke.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ def invoke_handler(
4040

4141
if not config:
4242
config = InvokeConfig[P, R]()
43-
config = config.with_tenant_id_if_unset(None)
4443
tenant_id = config.tenant_id
4544

4645
# Check if we have existing step data

tests/config_test.py

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from unittest.mock import Mock
55

66
from aws_durable_execution_sdk_python.config import (
7-
UNSET,
87
BatchedInput,
98
CallbackConfig,
109
CheckpointMode,
@@ -279,16 +278,10 @@ def test_step_future_without_name():
279278
assert result == 42
280279

281280

282-
def test_unset_type():
283-
"""Test UNSET sentinel value."""
284-
assert str(UNSET) == "<UNSET>"
285-
assert repr(UNSET) == "<UNSET>"
286-
287-
288281
def test_invoke_config_defaults():
289282
"""Test InvokeConfig defaults."""
290283
config = InvokeConfig()
291-
assert config.tenant_id is UNSET
284+
assert config.tenant_id is None
292285
assert config.timeout_seconds == 0
293286

294287

@@ -297,30 +290,3 @@ def test_invoke_config_with_tenant_id():
297290
config = InvokeConfig(tenant_id="test-tenant")
298291
assert config.tenant_id == "test-tenant"
299292

300-
301-
def test_invoke_config_with_tenant_id_if_unset_when_unset():
302-
"""Test with_tenant_id_if_unset when tenant_id is UNSET."""
303-
config = InvokeConfig()
304-
new_config = config.with_tenant_id_if_unset("new-tenant")
305-
306-
assert config.tenant_id is UNSET # Original unchanged
307-
assert new_config.tenant_id == "new-tenant"
308-
assert new_config.timeout == config.timeout
309-
310-
311-
def test_invoke_config_with_tenant_id_if_unset_when_set():
312-
"""Test with_tenant_id_if_unset when tenant_id is already set."""
313-
config = InvokeConfig(tenant_id="existing-tenant")
314-
new_config = config.with_tenant_id_if_unset("new-tenant")
315-
316-
assert new_config is config # Same object returned
317-
assert new_config.tenant_id == "existing-tenant"
318-
319-
320-
def test_invoke_config_with_tenant_id_if_unset_with_none():
321-
"""Test with_tenant_id_if_unset with None value."""
322-
config = InvokeConfig()
323-
new_config = config.with_tenant_id_if_unset(None)
324-
325-
assert config.tenant_id is UNSET # Original unchanged
326-
assert new_config.tenant_id is None

tests/context_test.py

Lines changed: 48 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -512,31 +512,16 @@ def test_step_with_original_name(mock_handler):
512512

513513

514514
# region invoke
515-
@pytest.mark.parametrize(
516-
("lambda_tenant_id", "expected_tenant_id"),
517-
[
518-
(None, None),
519-
("test-tenant", "test-tenant"),
520-
],
521-
)
522515
@patch("aws_durable_execution_sdk_python.context.invoke_handler")
523-
def test_invoke_basic(mock_handler, lambda_tenant_id, expected_tenant_id):
524-
"""Test invoke with basic parameters and tenant_id inheritance."""
516+
def test_invoke_basic(mock_handler):
517+
"""Test invoke with basic parameters."""
525518
mock_handler.return_value = "invoke_result"
526519
mock_state = Mock(spec=ExecutionState)
527520
mock_state.durable_execution_arn = (
528521
"arn:aws:durable:us-east-1:123456789012:execution/test"
529522
)
530523

531-
# Create mock lambda context with tenant_id
532-
mock_lambda_context = Mock()
533-
if lambda_tenant_id:
534-
mock_lambda_context.tenant_id = lambda_tenant_id
535-
else:
536-
# Remove tenant_id attribute entirely when None
537-
del mock_lambda_context.tenant_id
538-
539-
context = DurableContext(state=mock_state, lambda_context=mock_lambda_context)
524+
context = DurableContext(state=mock_state)
540525
operation_ids = operation_id_sequence()
541526
expected_operation_id = next(operation_ids)
542527

@@ -549,7 +534,7 @@ def test_invoke_basic(mock_handler, lambda_tenant_id, expected_tenant_id):
549534
payload="test_payload",
550535
state=mock_state,
551536
operation_identifier=OperationIdentifier(expected_operation_id, None, None),
552-
config=InvokeConfig(tenant_id=expected_tenant_id),
537+
config=None,
553538
)
554539

555540

@@ -576,15 +561,12 @@ def test_invoke_with_name_and_config(mock_handler):
576561
expected_id = next(seq) # 6th
577562

578563
assert result == "configured_result"
579-
expected_config = InvokeConfig[str, str](
580-
timeout=Duration.from_seconds(30), tenant_id=None
581-
)
582564
mock_handler.assert_called_once_with(
583565
function_name="test_function",
584566
payload={"key": "value"},
585567
state=mock_state,
586568
operation_identifier=OperationIdentifier(expected_id, None, "named_invoke"),
587-
config=expected_config,
569+
config=config,
588570
)
589571

590572

@@ -611,7 +593,7 @@ def test_invoke_with_parent_id(mock_handler):
611593
payload=None,
612594
state=mock_state,
613595
operation_identifier=OperationIdentifier(expected_id, "parent123", None),
614-
config=InvokeConfig(tenant_id=None),
596+
config=None,
615597
)
616598

617599

@@ -667,7 +649,7 @@ def test_invoke_with_none_payload(mock_handler):
667649
payload=None,
668650
state=mock_state,
669651
operation_identifier=OperationIdentifier(expected_id, None, None),
670-
config=InvokeConfig(tenant_id=None),
652+
config=None,
671653
)
672654

673655

@@ -701,20 +683,14 @@ def test_invoke_with_custom_serdes(mock_handler):
701683
expected_id = next(seq)
702684

703685
assert result == {"transformed": "data"}
704-
expected_config = InvokeConfig[dict, dict](
705-
serdes_payload=payload_serdes,
706-
serdes_result=result_serdes,
707-
timeout=Duration.from_minutes(1),
708-
tenant_id=None,
709-
)
710686
mock_handler.assert_called_once_with(
711687
function_name="test_function",
712688
payload={"original": "data"},
713689
state=mock_state,
714690
operation_identifier=OperationIdentifier(
715691
expected_id, None, "custom_serdes_invoke"
716692
),
717-
config=expected_config,
693+
config=config,
718694
)
719695

720696

@@ -1022,45 +998,6 @@ def test_run_in_child_context_resolves_name_from_callable(mock_handler):
1022998
assert call_args[1]["operation_identifier"].name == "original_function_name"
1023999

10241000

1025-
@pytest.mark.parametrize(
1026-
("parent_tenant_id", "expected_tenant_id"),
1027-
[
1028-
(None, None),
1029-
("parent-tenant", "parent-tenant"),
1030-
],
1031-
)
1032-
def test_run_in_child_context_inherits_tenant_id(parent_tenant_id, expected_tenant_id):
1033-
"""Test that child context inherits tenant_id from parent."""
1034-
mock_state = Mock(spec=ExecutionState)
1035-
mock_state.durable_execution_arn = (
1036-
"arn:aws:durable:us-east-1:123456789012:execution/test"
1037-
)
1038-
1039-
# Create mock lambda context with tenant_id
1040-
mock_lambda_context = Mock()
1041-
if parent_tenant_id:
1042-
mock_lambda_context.tenant_id = parent_tenant_id
1043-
else:
1044-
del mock_lambda_context.tenant_id
1045-
1046-
parent_context = DurableContext(
1047-
state=mock_state, lambda_context=mock_lambda_context
1048-
)
1049-
1050-
def check_child_tenant_id(child_context):
1051-
# Verify child context has same tenant_id as parent
1052-
assert child_context.tenant_id == expected_tenant_id
1053-
return "result"
1054-
1055-
with patch(
1056-
"aws_durable_execution_sdk_python.context.child_handler"
1057-
) as mock_handler:
1058-
# Make child_handler execute the function with a child context
1059-
mock_handler.side_effect = lambda func, **kwargs: func()
1060-
1061-
parent_context.run_in_child_context(check_child_tenant_id, "test_child")
1062-
1063-
10641001
# endregion run_in_child_context
10651002

10661003

@@ -1745,3 +1682,43 @@ def test_operation_id_generation_unique():
17451682

17461683
for i in range(len(ids) - 1):
17471684
assert ids[i] != ids[i + 1]
1685+
1686+
1687+
@patch("aws_durable_execution_sdk_python.context.invoke_handler")
1688+
def test_invoke_with_explicit_tenant_id(mock_handler):
1689+
"""Test invoke with explicit tenant_id in config."""
1690+
mock_handler.return_value = "result"
1691+
mock_state = Mock(spec=ExecutionState)
1692+
mock_state.durable_execution_arn = (
1693+
"arn:aws:durable:us-east-1:123456789012:execution/test"
1694+
)
1695+
1696+
config = InvokeConfig(tenant_id="explicit-tenant")
1697+
context = DurableContext(state=mock_state)
1698+
1699+
result = context.invoke("test_function", "payload", config=config)
1700+
1701+
assert result == "result"
1702+
call_args = mock_handler.call_args[1]
1703+
assert call_args["config"].tenant_id == "explicit-tenant"
1704+
1705+
1706+
@patch("aws_durable_execution_sdk_python.context.invoke_handler")
1707+
def test_invoke_without_tenant_id_defaults_to_none(mock_handler):
1708+
"""Test invoke without tenant_id defaults to None."""
1709+
mock_handler.return_value = "result"
1710+
mock_state = Mock(spec=ExecutionState)
1711+
mock_state.durable_execution_arn = (
1712+
"arn:aws:durable:us-east-1:123456789012:execution/test"
1713+
)
1714+
1715+
context = DurableContext(state=mock_state)
1716+
1717+
result = context.invoke("test_function", "payload")
1718+
1719+
assert result == "result"
1720+
# Config should be None when not provided
1721+
call_args = mock_handler.call_args[1]
1722+
assert call_args["config"] is None
1723+
1724+

0 commit comments

Comments
 (0)