Skip to content

Commit 89929b3

Browse files
committed
feat: add standalone parse function
1 parent 7840d6b commit 89929b3

File tree

3 files changed

+94
-59
lines changed

3 files changed

+94
-59
lines changed

aws_lambda_powertools/utilities/parser/envelopes/base.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from pydantic import BaseModel, ValidationError
66

7-
from ..exceptions import InvalidEnvelopeError, SchemaValidationError
7+
from ..exceptions import SchemaValidationError
88

99
logger = logging.getLogger(__name__)
1010

@@ -29,11 +29,3 @@ def _parse(event: Union[Dict[str, Any], str], schema: BaseModel) -> Any:
2929
@abstractmethod
3030
def parse(self, event: Dict[str, Any], schema: BaseModel):
3131
return NotImplemented # pragma: no cover
32-
33-
34-
def parse_envelope(event: Dict[str, Any], envelope: BaseEnvelope, schema: BaseModel):
35-
try:
36-
logger.debug(f"Parsing and validating event schema, envelope={envelope}")
37-
return envelope().parse(event=event, schema=schema)
38-
except (TypeError, AttributeError):
39-
raise InvalidEnvelopeError(f"envelope must be a callable and instance of BaseEnvelope, envelope={envelope}")

aws_lambda_powertools/utilities/parser/parser.py

Lines changed: 90 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from aws_lambda_powertools.middleware_factory import lambda_handler_decorator
77

8-
from .envelopes.base import BaseEnvelope, parse_envelope
9-
from .exceptions import InvalidSchemaTypeError, SchemaValidationError
8+
from .envelopes.base import BaseEnvelope
9+
from .exceptions import InvalidEnvelopeError, InvalidSchemaTypeError, SchemaValidationError
1010

1111
logger = logging.getLogger(__name__)
1212

@@ -19,53 +19,96 @@ def parser(
1919
schema: BaseModel,
2020
envelope: Optional[BaseEnvelope] = None,
2121
) -> Any:
22-
# noinspection SpellCheckingInspection,SpellCheckingInspection
2322
"""Decorator to conduct advanced parsing & validation for lambda handlers events
2423
25-
As Lambda follows (event, context) signature we can remove some of the boilerplate
26-
and also capture any exception any Lambda function throws as metadata.
27-
event will be the parsed and passed as a BaseModel pydantic class of the input type "schema"
28-
to the lambda handler.
29-
event will be extracted from the envelope in case envelope is not None.
30-
In case envelope is None, the complete event is parsed to match the schema parameter BaseModel definition.
31-
In case envelope is not None, first the event is parsed as the envelope's schema definition, and the user
32-
message is extracted and parsed again as the schema parameter's definition.
33-
34-
Example
35-
-------
36-
**Lambda function using validation decorator**
37-
38-
@parser(schema=MyBusiness, envelope=envelopes.EVENTBRIDGE)
39-
def handler(event: MyBusiness , context: LambdaContext):
40-
...
41-
42-
Parameters
43-
----------
44-
handler: input for lambda_handler_decorator, wraps the handler lambda
45-
event: AWS event dictionary
46-
context: AWS lambda context
47-
schema: pydantic BaseModel class. This is the user data schema that will replace the event.
48-
event parameter will be parsed and a new schema object will be created from it.
49-
envelope: what envelope to extract the schema from, can be any AWS service that is currently
50-
supported in the envelopes module. Can be None.
51-
52-
Raises
53-
------
54-
SchemaValidationError
55-
When input event doesn't conform with schema provided
56-
InvalidSchemaTypeError
57-
When schema given does not implement BaseModel
58-
"""
59-
if envelope is None:
60-
try:
61-
logger.debug("Parsing and validating event schema; no envelope used")
62-
parsed_event = schema.parse_obj(event)
63-
except (ValidationError, TypeError) as e:
64-
raise SchemaValidationError("Input event doesn't conform with schema") from e
65-
except AttributeError:
66-
raise InvalidSchemaTypeError("Input schema must implement BaseModel")
67-
else:
68-
parsed_event = parse_envelope(event=event, envelope=envelope, schema=schema)
24+
As Lambda follows (event, context) signature we can remove some of the boilerplate
25+
and also capture any exception any Lambda function throws as metadata.
26+
event will be the parsed and passed as a BaseModel Pydantic class of the input type "schema"
27+
to the lambda handler.
28+
event will be extracted from the envelope in case envelope is not None.
29+
In case envelope is None, the complete event is parsed to match the schema parameter BaseModel definition.
30+
In case envelope is not None, first the event is parsed as the envelope's schema definition, and the user
31+
message is extracted and parsed again as the schema parameter's definition.
32+
33+
Example
34+
-------
35+
**Lambda function using validation decorator**
36+
37+
@parser(schema=MyBusiness, envelope=envelopes.EVENTBRIDGE)
38+
def handler(event: MyBusiness , context: LambdaContext):
39+
...
40+
41+
Parameters
42+
----------
43+
handler: input for lambda_handler_decorator, wraps the handler lambda
44+
event: AWS event dictionary
45+
context: AWS lambda context
46+
schema: pydantic BaseModel class. This is the user data schema that will replace the event.
47+
event parameter will be parsed and a new schema object will be created from it.
48+
envelope: what envelope to extract the schema from, can be any AWS service that is currently
49+
supported in the envelopes module. Can be None.
6950
51+
Raises
52+
------
53+
SchemaValidationError
54+
When input event doesn't conform with schema provided
55+
InvalidSchemaTypeError
56+
When schema given does not implement BaseModel
57+
"""
58+
parsed_event = parse(event=event, schema=schema, envelope=envelope)
7059
logger.debug(f"Calling handler {handler.__name__}")
7160
return handler(parsed_event, context)
61+
62+
63+
def parse(event: Dict[str, Any], schema: BaseModel, envelope: Optional[BaseEnvelope] = None) -> Any:
64+
"""
65+
Standalone parse function to conduct advanced parsing & validation for lambda handlers events
66+
67+
As Lambda follows (event, context) signature we can remove some of the boilerplate
68+
and also capture any exception any Lambda function throws as metadata.
69+
event will be the parsed and passed as a BaseModel Pydantic class of the input type "schema"
70+
to the lambda handler.
71+
event will be extracted from the envelope in case envelope is not None.
72+
In case envelope is None, the complete event is parsed to match the schema parameter BaseModel definition.
73+
In case envelope is not None, first the event is parsed as the envelope's schema definition, and the user
74+
message is extracted and parsed again as the schema parameter's definition.
75+
76+
Example
77+
-------
78+
**Lambda function using standalone parse**
79+
80+
def handler(event: MyBusiness , context: LambdaContext):
81+
parse(event=event, schema=MyBusiness, envelope=envelopes.EVENTBRIDGE)
82+
...
83+
84+
Parameters
85+
----------
86+
event: AWS event dictionary
87+
schema: pydantic BaseModel class. This is the user data schema that will replace the event.
88+
event parameter will be parsed and a new schema object will be created from it.
89+
envelope: what envelope to extract the schema from, can be any AWS service that is currently
90+
supported in the envelopes module. Can be None.
91+
92+
Raises
93+
------
94+
SchemaValidationError
95+
When input event doesn't conform with schema provided
96+
InvalidSchemaTypeError
97+
When schema given does not implement BaseModel
98+
99+
"""
100+
if envelope:
101+
try:
102+
logger.debug(f"Parsing and validating event schema, envelope={envelope}")
103+
# noinspection PyCallingNonCallable
104+
return envelope().parse(event=event, schema=schema)
105+
except (TypeError, AttributeError):
106+
raise InvalidEnvelopeError(f"envelope must be a callable and instance of BaseEnvelope, envelope={envelope}")
107+
108+
try:
109+
logger.debug("Parsing and validating event schema; no envelope used")
110+
return schema.parse_obj(event)
111+
except (ValidationError, TypeError) as e:
112+
raise SchemaValidationError("Input event doesn't conform with schema") from e
113+
except AttributeError:
114+
raise InvalidSchemaTypeError("Input schema must implement BaseModel")

tests/functional/parser/test_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ def handle_no_envelope(event: Dict, _: LambdaContext):
1616
handle_no_envelope(event=invalid_value, context=LambdaContext())
1717

1818

19-
@pytest.mark.parametrize("invalid_envelope", [bool(), [], (), object])
20-
def test_parser_invalid_envelope_type(dummy_schema, invalid_envelope):
19+
@pytest.mark.parametrize("invalid_envelope", [True, ["dummy"], ("dummy"), object])
20+
def test_parser_invalid_envelope_type(dummy_event, dummy_schema, invalid_envelope):
2121
@parser(schema=dummy_schema, envelope=invalid_envelope)
2222
def handle_no_envelope(event: Dict, _: LambdaContext):
2323
return event
2424

2525
with pytest.raises(exceptions.InvalidEnvelopeError):
26-
handle_no_envelope(event={}, context=LambdaContext())
26+
handle_no_envelope(event=dummy_event["payload"], context=LambdaContext())
2727

2828

2929
def test_parser_schema_with_envelope(dummy_event, dummy_schema, dummy_envelope):

0 commit comments

Comments
 (0)