Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## 2025-05-15 - [EventBridge] High-Performance JSON Parsing with model_validate_json
**Learning:** For external API responses, Pydantic's `model_validate_json(response.content)` is significantly faster than `model_validate(response.json())`. This is because `model_validate_json` leverages Pydantic's high-performance Rust-based JSON parser directly on raw bytes, bypassing the intermediate Python dictionary creation by `response.json()`.
**Action:** Use `model_validate_json` when processing raw JSON payloads from external HTTP calls or events to minimize Lambda execution time.

## 2025-05-15 - [EventBridge] Connection Pooling with requests.Session
**Learning:** Reusing a `requests.Session` in AWS Lambda allows for TCP/TLS connection pooling across warm starts. This can reduce latency by 50-200ms per request by avoiding the overhead of establishing a new connection for every invocation.
**Action:** Always instantiate `requests.Session()` at the class level or module level for persistent outbound HTTP connections.

## 2025-05-15 - [GraphQL] Rejected: TypeAdapter for List Validation
**Learning:** While `pydantic.TypeAdapter(list[Model])` provides a theoretical ~65% performance improvement over list comprehensions by leveraging Rust-based batch processing, it may be rejected if the perceived value is low relative to the original implementation's simplicity, especially in template code.
**Action:** Prioritize optimizations that have a dramatic and undeniable impact on core latency or resource consumption.
7 changes: 3 additions & 4 deletions templates/eventbridge/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aws_lambda_powertools.utilities.parser import event_parser
from aws_lambda_powertools.utilities.parser.models import EventBridgeModel
from aws_lambda_powertools.utilities.typing import LambdaContext
from requests import get
from requests import Session

from templates.eventbridge.models import ApiResponse
from templates.eventbridge.settings import Settings
Expand All @@ -16,6 +16,7 @@
metrics = Metrics(namespace=settings.metrics_namespace, service=settings.service_name)
secrets_provider = SecretsProvider()
repository = Repository(settings.table_name)
session = Session() # Use a single session for connection pooling and performance


class Handler:
Expand All @@ -27,14 +28,12 @@ def __init__(self, secrets_provider: SecretsProvider, repository: Repository) ->
def handle(self, event: EventBridgeModel) -> ApiResponse:
try:
token = self._secrets_provider.get(settings.secret_name)
response = get(
response = session.get(
settings.api_url,
headers={"Authorization": f"Bearer {token}"},
timeout=settings.api_timeout_seconds,
)
response.raise_for_status()
# Optimize by using Pydantic's Rust-based JSON parser directly on the raw bytes.
# This avoids redundant dictionary creation and improves performance.
api_response = ApiResponse.model_validate_json(response.content)
self._repository.put_item(api_response.model_dump(by_alias=True, exclude_none=True))
metrics.add_metric(name="ApiCallSuccess", unit=MetricUnit.Count, value=1)
Expand Down
12 changes: 6 additions & 6 deletions tests/eventbridge/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_successful_invocation(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")
mock_metrics = mocker.patch.object(handler_module, "metrics")

Expand All @@ -91,7 +91,7 @@ def test_secret_loading_failure(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mocker.patch.object(handler_module, "get")
mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")
mock_metrics = mocker.patch.object(handler_module, "metrics")

Expand All @@ -111,7 +111,7 @@ def test_api_non_2xx_response(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")
mock_metrics = mocker.patch.object(handler_module, "metrics")

Expand All @@ -134,7 +134,7 @@ def test_api_network_exception(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")
mock_metrics = mocker.patch.object(handler_module, "metrics")

Expand All @@ -155,7 +155,7 @@ def test_invalid_eventbridge_event(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand All @@ -174,7 +174,7 @@ def test_dynamodb_write_failure(mocker, lambda_context) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")
mock_metrics = mocker.patch.object(handler_module, "metrics")

Expand Down
14 changes: 7 additions & 7 deletions tests/eventbridge/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def test_valid_event_shapes(mocker, source, detail_type, detail) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -102,7 +102,7 @@ def test_invalid_event_prevents_api_call(mocker, missing_key) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -140,7 +140,7 @@ def test_secret_exception_propagates(mocker, exc) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -180,7 +180,7 @@ def test_bearer_token_header(mocker, token) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_api_failure_propagates(mocker, status_code) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -346,7 +346,7 @@ def test_successful_response_persisted(mocker, status) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down Expand Up @@ -390,7 +390,7 @@ def test_dynamodb_write_failure_propagates(mocker, exc) -> None:
import templates.eventbridge.handler as handler_module

mock_secrets = mocker.patch.object(handler_module, "secrets_provider")
mock_get = mocker.patch.object(handler_module, "get")
mock_get = mocker.patch.object(handler_module.session, "get")
mock_repo = mocker.patch.object(handler_module, "repository")

handler_module.handler._secrets_provider = mock_secrets
Expand Down
Loading