Skip to content
Open
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
23 changes: 21 additions & 2 deletions aws_lambda_powertools/utilities/parser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
"""Advanced event_parser utility"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator

from aws_lambda_powertools.utilities.parser import envelopes
from aws_lambda_powertools.utilities.parser.envelopes import BaseEnvelope
from aws_lambda_powertools.utilities.parser.parser import event_parser, parse

if TYPE_CHECKING:
from aws_lambda_powertools.utilities.parser import envelopes as envelopes
from aws_lambda_powertools.utilities.parser.envelopes import BaseEnvelope


def __getattr__(name: str) -> object:
if name == "envelopes":
from aws_lambda_powertools.utilities.parser import envelopes as _envelopes # noqa: PLC0415

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Erez, thank you for profiling this and documenting the cold-start impact. I really like the direction of this change.

I pulled the branch locally and tried the existing public import in a fresh Python process:

from aws_lambda_powertools.utilities.parser import envelopes

It currently ends in a RecursionError. __getattr__("envelopes") imports the same attribute from the same package, which invokes __getattr__ again. from ...parser import * fails for the same reason.

Could we load the submodule with importlib.import_module(f"{__name__}.envelopes") and cache the resolved value in globals()? I tested that approach locally and it preserves both the lazy import and the existing public API.


return _envelopes
if name == "BaseEnvelope":
from aws_lambda_powertools.utilities.parser.envelopes import BaseEnvelope as _BaseEnvelope # noqa: PLC0415

return _BaseEnvelope
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
"event_parser",
"parse",
Expand Down
27 changes: 27 additions & 0 deletions tests/functional/parser/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,30 @@ def handler(event: SqsModel, _: LambdaContext):
assert parsed_event[0].version == "version"

handler(event, LambdaContext())


def test_parser_import_does_not_eagerly_load_envelopes():
"""Importing parse from parser __init__ must not eagerly load all envelope modules.

Envelopes are only needed when envelope= is passed to parse()/event_parser().
Eager loading all 16 envelopes adds ~900ms to Lambda cold start for functions
that only use parse() without an envelope.
"""
import sys

# Remove any previously cached parser modules to simulate a fresh import
parser_modules = [key for key in sys.modules if "aws_lambda_powertools.utilities.parser.envelopes" in key]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One concern with this test: by the time it runs, the conftest and this module have already imported envelope classes. Removing entries from sys.modules can leave references behind, so the result may depend on test order.

Could we run this check in a fresh subprocess instead? I would also cover the existing public imports for envelopes and BaseEnvelope, since those are the compatibility paths affected by this change.

for mod in parser_modules:
del sys.modules[mod]

# Also remove the parser __init__ itself so __getattr__ is exercised
sys.modules.pop("aws_lambda_powertools.utilities.parser", None)

# Re-import — only parse is needed, envelopes should NOT be loaded
from aws_lambda_powertools.utilities.parser import parse # noqa: F401

envelope_modules_loaded = [key for key in sys.modules if "aws_lambda_powertools.utilities.parser.envelopes" in key]
assert not envelope_modules_loaded, (
f"Envelope modules were eagerly loaded on parser import: {envelope_modules_loaded}. "
"This adds significant Lambda cold start latency for functions not using envelopes."
)
Loading