-
Notifications
You must be signed in to change notification settings - Fork 421
/
Copy pathparser.py
180 lines (141 loc) · 5.92 KB
/
parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import logging
import typing
from typing import Any, Callable, Dict, Optional, Type, overload
from aws_lambda_powertools.utilities.parser.compat import disable_pydantic_v2_warning
from aws_lambda_powertools.utilities.parser.types import EventParserReturnType, Model
from ...middleware_factory import lambda_handler_decorator
from ..typing import LambdaContext
from .envelopes.base import Envelope
from .exceptions import InvalidEnvelopeError, InvalidModelTypeError
logger = logging.getLogger(__name__)
@lambda_handler_decorator
def event_parser(
handler: Callable[[Any, LambdaContext], EventParserReturnType],
event: Dict[str, Any],
context: LambdaContext,
model: Optional[Type[Model]] = None,
envelope: Optional[Type[Envelope]] = None,
) -> EventParserReturnType:
"""Lambda handler decorator to parse & validate events using Pydantic models
It requires a model that implements Pydantic BaseModel to parse & validate the event.
When an envelope is given, it'll use the following logic:
1. Parse the event against the envelope model first e.g. EnvelopeModel(**event)
2. Envelope will extract a given key to be parsed against the model e.g. event.detail
This is useful when you need to confirm event wrapper structure, and
b) selectively extract a portion of your payload for parsing & validation.
NOTE: If envelope is omitted, the complete event is parsed to match the model parameter BaseModel definition.
Example
-------
**Lambda handler decorator to parse & validate event**
class Order(BaseModel):
id: int
description: str
...
@event_parser(model=Order)
def handler(event: Order, context: LambdaContext):
...
**Lambda handler decorator to parse & validate event - using built-in envelope**
class Order(BaseModel):
id: int
description: str
...
@event_parser(model=Order, envelope=envelopes.EVENTBRIDGE)
def handler(event: Order, context: LambdaContext):
...
Parameters
----------
handler: Callable
Method to annotate on
event: Dict
Lambda event to be parsed & validated
context: LambdaContext
Lambda context object
model: Model
Your data model that will replace the event.
envelope: Envelope
Optional envelope to extract the model from
Raises
------
ValidationError
When input event does not conform with model provided
InvalidModelTypeError
When model given does not implement BaseModel or is not provided
InvalidEnvelopeError
When envelope given does not implement BaseEnvelope
"""
# The first parameter of a Lambda function is always the event
# This line get the model informed in the event_parser function
# or the first parameter of the function by using typing.get_type_hints
type_hints = typing.get_type_hints(handler)
model = model or (list(type_hints.values())[0] if type_hints else None)
if model is None:
raise InvalidModelTypeError(
"The model must be provided either as the `model` argument to `event_parser`"
"or as the type hint of `event` in the handler that it wraps",
)
parsed_event = parse(event=event, model=model, envelope=envelope) if envelope else parse(event=event, model=model)
logger.debug(f"Calling handler {handler.__name__}")
return handler(parsed_event, context)
@overload
def parse(event: Dict[str, Any], model: Type[Model]) -> Model:
... # pragma: no cover
@overload
def parse(event: Dict[str, Any], model: Type[Model], envelope: Type[Envelope]):
... # pragma: no cover
def parse(event: Dict[str, Any], model: Type[Model], envelope: Optional[Type[Envelope]] = None):
"""Standalone function to parse & validate events using Pydantic models
Typically used when you need fine-grained control over error handling compared to event_parser decorator.
Example
-------
**Lambda handler decorator to parse & validate event**
from aws_lambda_powertools.utilities.parser import ValidationError
class Order(BaseModel):
id: int
description: str
...
def handler(event: Order, context: LambdaContext):
try:
parse(model=Order)
except ValidationError:
...
**Lambda handler decorator to parse & validate event - using built-in envelope**
class Order(BaseModel):
id: int
description: str
...
def handler(event: Order, context: LambdaContext):
try:
parse(model=Order, envelope=envelopes.EVENTBRIDGE)
except ValidationError:
...
Parameters
----------
event: Dict
Lambda event to be parsed & validated
model: Model
Your data model that will replace the event
envelope: Envelope
Optional envelope to extract the model from
Raises
------
ValidationError
When input event does not conform with model provided
InvalidModelTypeError
When model given does not implement BaseModel
InvalidEnvelopeError
When envelope given does not implement BaseEnvelope
"""
if envelope and callable(envelope):
try:
logger.debug(f"Parsing and validating event model with envelope={envelope}")
return envelope().parse(data=event, model=model)
except AttributeError:
raise InvalidEnvelopeError(f"Envelope must implement BaseEnvelope, envelope={envelope}")
try:
disable_pydantic_v2_warning()
logger.debug("Parsing and validating event model; no envelope used")
if isinstance(event, str):
return model.parse_raw(event)
return model.parse_obj(event)
except AttributeError:
raise InvalidModelTypeError(f"Input model must implement BaseModel, model={model}")