forked from aws-powertools/powertools-lambda-python
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.py
159 lines (126 loc) · 5.17 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
import logging
from typing import Any, Callable, Dict, Optional
from pydantic import BaseModel, ValidationError
from ...middleware_factory import lambda_handler_decorator
from ..typing import LambdaContext
from .envelopes.base import BaseEnvelope
from .exceptions import InvalidEnvelopeError, InvalidModelTypeError, ModelValidationError
logger = logging.getLogger(__name__)
@lambda_handler_decorator
def event_parser(
handler: Callable[[Dict, Any], Any],
event: Dict[str, Any],
context: LambdaContext,
model: BaseModel,
envelope: Optional[BaseEnvelope] = None,
) -> Any:
"""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: BaseModel
Your data model that will replace the event.
envelope: BaseEnvelope
Optional envelope to extract the model from
Raises
------
ModelValidationError
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
"""
parsed_event = parse(event=event, model=model, envelope=envelope)
logger.debug(f"Calling handler {handler.__name__}")
return handler(parsed_event, context)
def parse(event: Dict[str, Any], model: BaseModel, envelope: Optional[BaseEnvelope] = None) -> Any:
"""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.exceptions import ModelValidationError
class Order(BaseModel):
id: int
description: str
...
def handler(event: Order, context: LambdaContext):
try:
parse(model=Order)
except ModelValidationError:
...
**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 ModelValidationError:
...
Parameters
----------
event: Dict
Lambda event to be parsed & validated
model: BaseModel
Your data model that will replace the event
envelope: BaseEnvelope
Optional envelope to extract the model from
Raises
------
ModelValidationError
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}")
except (ValidationError, TypeError) as e:
raise ModelValidationError(f"Input event does not conform with model, envelope={envelope}") from e
try:
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 (ValidationError, TypeError) as e:
raise ModelValidationError("Input event does not conform with model") from e
except AttributeError:
raise InvalidModelTypeError("Input model must implement BaseModel")