Skip to content

Commit beb8d2f

Browse files
refactor: reuse the Lambda client (#251)
* refactor: reuse Lambda client * refactor: reuse Lambda client * refactor: reuse Lambda client * remove ruff lint * remove ruff lint
1 parent dc76c71 commit beb8d2f

2 files changed

Lines changed: 99 additions & 13 deletions

File tree

src/aws_durable_execution_sdk_python/lambda_service.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,19 +1031,32 @@ def get_execution_state(
10311031
class LambdaClient(DurableServiceClient):
10321032
"""Persist durable operations to the Lambda Durable Function APIs."""
10331033

1034+
_cached_boto_client: Any = None
1035+
10341036
def __init__(self, client: Any) -> None:
10351037
self.client = client
10361038

1037-
@staticmethod
1038-
def initialize_client() -> LambdaClient:
1039-
client = boto3.client(
1040-
"lambda",
1041-
config=Config(
1042-
connect_timeout=5,
1043-
read_timeout=50,
1044-
),
1045-
)
1046-
return LambdaClient(client=client)
1039+
@classmethod
1040+
def initialize_client(cls) -> LambdaClient:
1041+
"""Initialize or return cached Lambda client.
1042+
1043+
Implements lazy initialization with class-level caching to optimize
1044+
Lambda warm starts. The boto3 client is created once and reused across
1045+
invocations, avoiding repeated credential resolution and connection
1046+
pool setup.
1047+
1048+
Returns:
1049+
LambdaClient: A new LambdaClient instance wrapping the cached boto3 client.
1050+
"""
1051+
if cls._cached_boto_client is None:
1052+
cls._cached_boto_client = boto3.client(
1053+
"lambda",
1054+
config=Config(
1055+
connect_timeout=5,
1056+
read_timeout=50,
1057+
),
1058+
)
1059+
return cls(client=cls._cached_boto_client)
10471060

10481061
def checkpoint(
10491062
self,

tests/lambda_service_test.py

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@
3939
WaitOptions,
4040
)
4141

42+
# =============================================================================
43+
# Fixtures
44+
# =============================================================================
45+
46+
47+
@pytest.fixture
48+
def reset_lambda_client_cache():
49+
"""Reset the class-level boto3 client cache before and after each test."""
50+
LambdaClient._cached_boto_client = None # noqa: SLF001
51+
yield
52+
LambdaClient._cached_boto_client = None # noqa: SLF001
53+
54+
4255
# =============================================================================
4356
# Tests for Data Classes (ExecutionDetails, ContextDetails, ErrorObject, etc.)
4457
# =============================================================================
@@ -1910,7 +1923,9 @@ def test_lambda_client_constructor():
19101923

19111924
@patch.dict("os.environ", {}, clear=True)
19121925
@patch("boto3.client")
1913-
def test_lambda_client_initialize_client_default(mock_boto_client):
1926+
def test_lambda_client_initialize_client_default(
1927+
mock_boto_client, reset_lambda_client_cache
1928+
):
19141929
"""Test LambdaClient.initialize_client with default endpoint."""
19151930
mock_client = Mock()
19161931
mock_boto_client.return_value = mock_client
@@ -1930,7 +1945,9 @@ def test_lambda_client_initialize_client_default(mock_boto_client):
19301945

19311946
@patch.dict("os.environ", {"AWS_ENDPOINT_URL_LAMBDA": "http://localhost:3000"})
19321947
@patch("boto3.client")
1933-
def test_lambda_client_initialize_client_with_endpoint(mock_boto_client):
1948+
def test_lambda_client_initialize_client_with_endpoint(
1949+
mock_boto_client, reset_lambda_client_cache
1950+
):
19341951
"""Test LambdaClient.initialize_client with custom endpoint (boto3 handles it automatically)."""
19351952
mock_client = Mock()
19361953
mock_boto_client.return_value = mock_client
@@ -2008,7 +2025,9 @@ def test_checkpoint_error_handling():
20082025

20092026
@patch.dict("os.environ", {}, clear=True)
20102027
@patch("boto3.client")
2011-
def test_lambda_client_initialize_client_no_endpoint(mock_boto_client):
2028+
def test_lambda_client_initialize_client_no_endpoint(
2029+
mock_boto_client, reset_lambda_client_cache
2030+
):
20122031
"""Test LambdaClient.initialize_client without AWS_ENDPOINT_URL_LAMBDA."""
20132032
mock_client = Mock()
20142033
mock_boto_client.return_value = mock_client
@@ -2047,6 +2066,60 @@ def test_lambda_client_checkpoint_with_non_none_client_token():
20472066

20482067

20492068
# =============================================================================
2069+
# Tests for LambdaClient caching behavior
2070+
# =============================================================================
2071+
2072+
2073+
@patch("boto3.client")
2074+
def test_lambda_client_cache_reuses_client(mock_boto_client, reset_lambda_client_cache):
2075+
"""Test that initialize_client reuses the same boto3 client on subsequent calls."""
2076+
mock_client = Mock()
2077+
mock_boto_client.return_value = mock_client
2078+
2079+
# First call should create the boto3 client
2080+
client1 = LambdaClient.initialize_client()
2081+
2082+
# Second call should reuse the same boto3 client
2083+
client2 = LambdaClient.initialize_client()
2084+
2085+
# boto3.client should only be called once
2086+
mock_boto_client.assert_called_once()
2087+
2088+
# Both LambdaClient instances should wrap the same boto3 client
2089+
assert client1.client is client2.client
2090+
2091+
2092+
@patch("boto3.client")
2093+
def test_lambda_client_cache_creates_client_only_once(
2094+
mock_boto_client, reset_lambda_client_cache
2095+
):
2096+
"""Test that boto3.client is called only once even with multiple initialize_client calls."""
2097+
mock_client = Mock()
2098+
mock_boto_client.return_value = mock_client
2099+
2100+
# Call initialize_client multiple times
2101+
for _ in range(5):
2102+
LambdaClient.initialize_client()
2103+
2104+
# boto3.client should only be called once
2105+
assert mock_boto_client.call_count == 1
2106+
2107+
2108+
@patch("boto3.client")
2109+
def test_lambda_client_cache_is_class_level(
2110+
mock_boto_client, reset_lambda_client_cache
2111+
):
2112+
"""Test that the boto3 client cache is stored at class level."""
2113+
mock_client = Mock()
2114+
mock_boto_client.return_value = mock_client
2115+
2116+
# Create client
2117+
LambdaClient.initialize_client()
2118+
2119+
# Verify the boto3 client is cached at class level
2120+
assert LambdaClient._cached_boto_client is mock_client # noqa: SLF001
2121+
2122+
20502123
# Tests for Operation JSON Serialization Methods
20512124
# =============================================================================
20522125

0 commit comments

Comments
 (0)