modmex-lambda 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modmex_lambda/__init__.py +62 -0
- modmex_lambda/data_classes/__init__.py +49 -0
- modmex_lambda/data_classes/api_gateway_authorizer_event.py +38 -0
- modmex_lambda/data_classes/api_gateway_proxy_event.py +328 -0
- modmex_lambda/data_classes/api_gateway_websocket_event.py +40 -0
- modmex_lambda/data_classes/cognito_user_pool_event.py +599 -0
- modmex_lambda/data_classes/common.py +441 -0
- modmex_lambda/event_handler/__init__.py +45 -0
- modmex_lambda/event_handler/api_gateway.py +331 -0
- modmex_lambda/event_handler/constants.py +3 -0
- modmex_lambda/event_handler/content_types.py +13 -0
- modmex_lambda/event_handler/cors.py +97 -0
- modmex_lambda/event_handler/dependencies/__init__.py +0 -0
- modmex_lambda/event_handler/dependencies/compat.py +231 -0
- modmex_lambda/event_handler/dependencies/dependant.py +279 -0
- modmex_lambda/event_handler/dependencies/dependency_middleware.py +423 -0
- modmex_lambda/event_handler/dependencies/depends.py +184 -0
- modmex_lambda/event_handler/dependencies/params.py +317 -0
- modmex_lambda/event_handler/dependencies/types.py +14 -0
- modmex_lambda/event_handler/exception_handler.py +70 -0
- modmex_lambda/event_handler/exceptions.py +72 -0
- modmex_lambda/event_handler/gateway_response.py +96 -0
- modmex_lambda/event_handler/middlewares.py +33 -0
- modmex_lambda/event_handler/params.py +44 -0
- modmex_lambda/event_handler/request.py +70 -0
- modmex_lambda/event_handler/response.py +60 -0
- modmex_lambda/event_handler/routing.py +507 -0
- modmex_lambda/event_handler/routing_fallbacks.py +92 -0
- modmex_lambda/event_handler/types.py +31 -0
- modmex_lambda/event_sources.py +53 -0
- modmex_lambda/exceptions.py +3 -0
- modmex_lambda/logging.py +99 -0
- modmex_lambda/params.py +3 -0
- modmex_lambda/parser.py +47 -0
- modmex_lambda/request.py +3 -0
- modmex_lambda/resolver.py +3 -0
- modmex_lambda/response.py +3 -0
- modmex_lambda/routing.py +3 -0
- modmex_lambda/shared/__init__.py +0 -0
- modmex_lambda/shared/cookies.py +84 -0
- modmex_lambda/shared/headers_serializer.py +65 -0
- modmex_lambda/shared/json_encoder.py +53 -0
- modmex_lambda/shared/types.py +4 -0
- modmex_lambda/validation.py +178 -0
- modmex_lambda-0.1.0.dist-info/METADATA +375 -0
- modmex_lambda-0.1.0.dist-info/RECORD +48 -0
- modmex_lambda-0.1.0.dist-info/WHEEL +4 -0
- modmex_lambda-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Public API for modmex-lambda."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .event_handler import (
|
|
10
|
+
ApiGatewayHttpResolver,
|
|
11
|
+
ApiGatewayRestResolver,
|
|
12
|
+
Response,
|
|
13
|
+
)
|
|
14
|
+
from .event_handler.request import Request
|
|
15
|
+
from .event_sources import event_source
|
|
16
|
+
from .event_handler.dependencies.depends import Depends
|
|
17
|
+
from .logging import Logger
|
|
18
|
+
from .parser import event_parser, parse
|
|
19
|
+
from .validation import ModmexValidator, ValidationError
|
|
20
|
+
|
|
21
|
+
_EXPORTS = {
|
|
22
|
+
"ApiGatewayHttpResolver": ("modmex_lambda.event_handler", "ApiGatewayHttpResolver"),
|
|
23
|
+
"ApiGatewayRestResolver": ("modmex_lambda.event_handler", "ApiGatewayRestResolver"),
|
|
24
|
+
"Request": ("modmex_lambda.event_handler.request", "Request"),
|
|
25
|
+
"Response": ("modmex_lambda.event_handler", "Response"),
|
|
26
|
+
"parse": ("modmex_lambda.parser", "parse"),
|
|
27
|
+
"event_parser": ("modmex_lambda.parser", "event_parser"),
|
|
28
|
+
"event_source": ("modmex_lambda.event_sources", "event_source"),
|
|
29
|
+
"Depends": ("modmex_lambda.event_handler.dependencies.depends", "Depends"),
|
|
30
|
+
"Logger": ("modmex_lambda.logging", "Logger"),
|
|
31
|
+
"ModmexValidator": ("modmex_lambda.validation", "ModmexValidator"),
|
|
32
|
+
"ValidationError": ("modmex_lambda.validation", "ValidationError"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"ApiGatewayHttpResolver",
|
|
37
|
+
"ApiGatewayRestResolver",
|
|
38
|
+
"Request",
|
|
39
|
+
"Response",
|
|
40
|
+
"parse",
|
|
41
|
+
"event_parser",
|
|
42
|
+
"event_source",
|
|
43
|
+
"Depends",
|
|
44
|
+
"Logger",
|
|
45
|
+
"ModmexValidator",
|
|
46
|
+
"ValidationError",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def __getattr__(name: str) -> Any:
|
|
51
|
+
target = _EXPORTS.get(name)
|
|
52
|
+
if target is None:
|
|
53
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
54
|
+
|
|
55
|
+
module_name, attr = target
|
|
56
|
+
value = getattr(import_module(module_name), attr)
|
|
57
|
+
globals()[name] = value
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def __dir__() -> list[str]:
|
|
62
|
+
return sorted([*globals().keys(), *__all__])
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from modmex_lambda.data_classes.api_gateway_proxy_event import (
|
|
2
|
+
APIGatewayProxyEvent,
|
|
3
|
+
APIGatewayProxyEventV2,
|
|
4
|
+
)
|
|
5
|
+
from modmex_lambda.data_classes.api_gateway_authorizer_event import APIGatewayAuthorizerEvent
|
|
6
|
+
from modmex_lambda.data_classes.api_gateway_websocket_event import APIGatewayWebSocketEvent
|
|
7
|
+
from modmex_lambda.data_classes.common import DictWrapper
|
|
8
|
+
from modmex_lambda.data_classes.cognito_user_pool_event import (
|
|
9
|
+
CognitoUserPoolEvent,
|
|
10
|
+
CreateAuthChallengeTriggerEvent,
|
|
11
|
+
CustomEmailSenderTriggerEvent,
|
|
12
|
+
CustomMessageTriggerEvent,
|
|
13
|
+
CustomSMSSenderTriggerEvent,
|
|
14
|
+
DefineAuthChallengeTriggerEvent,
|
|
15
|
+
PostAuthenticationTriggerEvent,
|
|
16
|
+
PostConfirmationTriggerEvent,
|
|
17
|
+
PreAuthenticationTriggerEvent,
|
|
18
|
+
PreSignUpTriggerEvent,
|
|
19
|
+
PreTokenGenerationTriggerEvent,
|
|
20
|
+
PreTokenGenerationV2TriggerEvent,
|
|
21
|
+
PreTokenGenerationV3TriggerEvent,
|
|
22
|
+
UserMigrationTriggerEvent,
|
|
23
|
+
VerifyAuthChallengeResponseTriggerEvent,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"DictWrapper",
|
|
28
|
+
"APIGatewayProxyEvent",
|
|
29
|
+
"APIGatewayProxyEventV2",
|
|
30
|
+
"APIGatewayRestEvent",
|
|
31
|
+
"APIGatewayHttpEvent",
|
|
32
|
+
"APIGatewayAuthorizerEvent",
|
|
33
|
+
"APIGatewayWebSocketEvent",
|
|
34
|
+
"CognitoUserPoolEvent",
|
|
35
|
+
"PreSignUpTriggerEvent",
|
|
36
|
+
"PostConfirmationTriggerEvent",
|
|
37
|
+
"UserMigrationTriggerEvent",
|
|
38
|
+
"CustomMessageTriggerEvent",
|
|
39
|
+
"PreAuthenticationTriggerEvent",
|
|
40
|
+
"PostAuthenticationTriggerEvent",
|
|
41
|
+
"PreTokenGenerationTriggerEvent",
|
|
42
|
+
"PreTokenGenerationV2TriggerEvent",
|
|
43
|
+
"PreTokenGenerationV3TriggerEvent",
|
|
44
|
+
"DefineAuthChallengeTriggerEvent",
|
|
45
|
+
"CreateAuthChallengeTriggerEvent",
|
|
46
|
+
"VerifyAuthChallengeResponseTriggerEvent",
|
|
47
|
+
"CustomEmailSenderTriggerEvent",
|
|
48
|
+
"CustomSMSSenderTriggerEvent",
|
|
49
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""API Gateway Lambda authorizer event data class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from modmex_lambda.data_classes.common import DictWrapper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class APIGatewayAuthorizerEvent(DictWrapper):
|
|
11
|
+
@property
|
|
12
|
+
def type(self) -> str:
|
|
13
|
+
return str(self.get("type") or "")
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def method_arn(self) -> str:
|
|
17
|
+
return str(self.get("methodArn") or "")
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def authorization_token(self) -> str | None:
|
|
21
|
+
value = self.get("authorizationToken")
|
|
22
|
+
return None if value is None else str(value)
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def headers(self) -> dict[str, Any]:
|
|
26
|
+
return dict(self.get("headers") or {})
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def query_string_parameters(self) -> dict[str, Any]:
|
|
30
|
+
return dict(self.get("queryStringParameters") or {})
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def path_parameters(self) -> dict[str, Any]:
|
|
34
|
+
return dict(self.get("pathParameters") or {})
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def request_context(self) -> dict[str, Any]:
|
|
38
|
+
return dict(self.get("requestContext") or {})
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""API Gateway event source data classes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
from functools import cached_property
|
|
8
|
+
from modmex_lambda.data_classes.common import (
|
|
9
|
+
DictWrapper,
|
|
10
|
+
BaseProxyEvent,
|
|
11
|
+
CaseInsensitiveDict,
|
|
12
|
+
BaseRequestContext,
|
|
13
|
+
BaseRequestContextV2,
|
|
14
|
+
)
|
|
15
|
+
from modmex_lambda.shared.headers_serializer import HttpApiHeadersSerializer, MultiValueHeadersSerializer, BaseHeadersSerializer
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class APIGatewayEventAuthorizer(DictWrapper):
|
|
19
|
+
@property
|
|
20
|
+
def claims(self) -> dict[str, Any]:
|
|
21
|
+
return self.get("claims") or {} # key might exist but can be `null`
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def scopes(self) -> list[str]:
|
|
25
|
+
return self.get("scopes") or [] # key might exist but can be `null`
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def principal_id(self) -> str:
|
|
29
|
+
"""The principal user identification associated with the token sent by the client and returned from an
|
|
30
|
+
API Gateway Lambda authorizer (formerly known as a custom authorizer)"""
|
|
31
|
+
return self.get("principalId") or "" # key might exist but can be `null`
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def integration_latency(self) -> int | None:
|
|
35
|
+
"""The authorizer latency in ms."""
|
|
36
|
+
return self.get("integrationLatency")
|
|
37
|
+
|
|
38
|
+
def get_context(self) -> dict[str, Any]:
|
|
39
|
+
"""Retrieve the authorization context details injected by a Lambda Authorizer.
|
|
40
|
+
|
|
41
|
+
Example
|
|
42
|
+
--------
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
ctx: dict = request_context.authorizer.get_context()
|
|
46
|
+
|
|
47
|
+
tenant_id = ctx.get("tenant_id")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
--------
|
|
52
|
+
dict[str, Any]
|
|
53
|
+
A dictionary containing Lambda authorization context details.
|
|
54
|
+
"""
|
|
55
|
+
return self._data
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class APIGatewayEventRequestContext(BaseRequestContext):
|
|
59
|
+
@property
|
|
60
|
+
def connected_at(self) -> int | None:
|
|
61
|
+
"""The Epoch-formatted connection time. (WebSocket API)"""
|
|
62
|
+
return self.get("connectedAt")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def connection_id(self) -> str | None:
|
|
66
|
+
"""A unique ID for the connection that can be used to make a callback to the client. (WebSocket API)"""
|
|
67
|
+
return self.get("connectionId")
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def event_type(self) -> str | None:
|
|
71
|
+
"""The event type: `CONNECT`, `MESSAGE`, or `DISCONNECT`. (WebSocket API)"""
|
|
72
|
+
return self.get("eventType")
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def message_direction(self) -> str | None:
|
|
76
|
+
"""Message direction (WebSocket API)"""
|
|
77
|
+
return self.get("messageDirection")
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def message_id(self) -> str | None:
|
|
81
|
+
"""A unique server-side ID for a message. Available only when the `eventType` is `MESSAGE`."""
|
|
82
|
+
return self.get("messageId")
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def operation_name(self) -> str | None:
|
|
86
|
+
"""The name of the operation being performed"""
|
|
87
|
+
return self.get("operationName")
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def route_key(self) -> str | None:
|
|
91
|
+
"""The selected route key."""
|
|
92
|
+
return self.get("routeKey")
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def authorizer(self) -> APIGatewayEventAuthorizer:
|
|
96
|
+
return APIGatewayEventAuthorizer(self.get("authorizer") or {})
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class APIGatewayProxyEvent(BaseProxyEvent):
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def version(self) -> str:
|
|
105
|
+
return self.get("version") or "1.0"
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def resource(self) -> str:
|
|
109
|
+
return self["resource"]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def multi_value_headers(self) -> dict[str, list[str]]:
|
|
113
|
+
return CaseInsensitiveDict(self.get("multiValueHeaders"))
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def resolved_query_string_parameters(self) -> dict[str, list[str]]:
|
|
117
|
+
multi_value = self.multi_value_query_string_parameters
|
|
118
|
+
single_value = super().resolved_query_string_parameters
|
|
119
|
+
|
|
120
|
+
if not multi_value:
|
|
121
|
+
return single_value
|
|
122
|
+
|
|
123
|
+
if not single_value:
|
|
124
|
+
return multi_value
|
|
125
|
+
|
|
126
|
+
# Merge both: multi_value takes precedence, single_value fills missing keys
|
|
127
|
+
return {**single_value, **multi_value}
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def resolved_headers_field(self) -> dict[str, Any]:
|
|
131
|
+
return self.multi_value_headers or self.headers
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def request_context(self) -> APIGatewayEventRequestContext:
|
|
135
|
+
return APIGatewayEventRequestContext(self["requestContext"])
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def path_parameters(self) -> dict[str, str]:
|
|
139
|
+
return self.get("pathParameters") or {}
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def stage_variables(self) -> dict[str, str]:
|
|
143
|
+
return self.get("stageVariables") or {}
|
|
144
|
+
|
|
145
|
+
def header_serializer(self) -> BaseHeadersSerializer:
|
|
146
|
+
return MultiValueHeadersSerializer()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class RequestContextV2AuthorizerIam(DictWrapper):
|
|
151
|
+
@property
|
|
152
|
+
def access_key(self) -> str:
|
|
153
|
+
"""The IAM user access key associated with the request."""
|
|
154
|
+
return self.get("accessKey") or "" # key might exist but can be `null`
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def account_id(self) -> str:
|
|
158
|
+
"""The AWS account ID associated with the request."""
|
|
159
|
+
return self.get("accountId") or "" # key might exist but can be `null`
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def caller_id(self) -> str:
|
|
163
|
+
"""The principal identifier of the caller making the request."""
|
|
164
|
+
return self.get("callerId") or "" # key might exist but can be `null`
|
|
165
|
+
|
|
166
|
+
def _cognito_identity(self) -> dict:
|
|
167
|
+
return self.get("cognitoIdentity") or {} # not available in FunctionURL; key might exist but can be `null`
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def cognito_amr(self) -> list[str]:
|
|
171
|
+
"""This represents how the user was authenticated.
|
|
172
|
+
AMR stands for Authentication Methods References as per the openid spec"""
|
|
173
|
+
return self._cognito_identity().get("amr", [])
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def cognito_identity_id(self) -> str:
|
|
177
|
+
"""The Amazon Cognito identity ID of the caller making the request.
|
|
178
|
+
Available only if the request was signed with Amazon Cognito credentials."""
|
|
179
|
+
return self._cognito_identity().get("identityId", "")
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def cognito_identity_pool_id(self) -> str:
|
|
183
|
+
"""The Amazon Cognito identity pool ID of the caller making the request.
|
|
184
|
+
Available only if the request was signed with Amazon Cognito credentials."""
|
|
185
|
+
return self._cognito_identity().get("identityPoolId") or "" # key might exist but can be `null`
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def principal_org_id(self) -> str:
|
|
189
|
+
"""The AWS organization ID."""
|
|
190
|
+
return self.get("principalOrgId") or "" # key might exist but can be `null`
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def user_arn(self) -> str:
|
|
194
|
+
"""The Amazon Resource Name (ARN) of the effective user identified after authentication."""
|
|
195
|
+
return self.get("userArn") or "" # key might exist but can be `null`
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def user_id(self) -> str:
|
|
199
|
+
"""The IAM user ID of the effective user identified after authentication."""
|
|
200
|
+
return self.get("userId") or "" # key might exist but can be `null`
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class RequestContextV2Authorizer(DictWrapper):
|
|
204
|
+
@property
|
|
205
|
+
def jwt_claim(self) -> dict[str, Any]:
|
|
206
|
+
jwt = self.get("jwt") or {} # not available in FunctionURL; key might exist but can be `null`
|
|
207
|
+
return jwt.get("claims") or {} # key might exist but can be `null`
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def jwt_scopes(self) -> list[str]:
|
|
211
|
+
jwt = self.get("jwt") or {} # not available in FunctionURL; key might exist but can be `null`
|
|
212
|
+
return jwt.get("scopes", [])
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
def get_lambda(self) -> dict[str, Any]:
|
|
216
|
+
"""Lambda authorization context details"""
|
|
217
|
+
return self.get("lambda") or {} # key might exist but can be `null`
|
|
218
|
+
|
|
219
|
+
def get_context(self) -> dict[str, Any]:
|
|
220
|
+
"""Retrieve the authorization context details injected by a Lambda Authorizer.
|
|
221
|
+
|
|
222
|
+
Example
|
|
223
|
+
--------
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
ctx: dict = request_context.authorizer.get_context()
|
|
227
|
+
|
|
228
|
+
tenant_id = ctx.get("tenant_id")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
--------
|
|
233
|
+
dict[str, Any]
|
|
234
|
+
A dictionary containing Lambda authorization context details.
|
|
235
|
+
"""
|
|
236
|
+
return self.get_lambda
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def iam(self) -> RequestContextV2AuthorizerIam:
|
|
240
|
+
"""IAM authorization details used for making the request."""
|
|
241
|
+
iam = self.get("iam") or {} # key might exist but can be `null`
|
|
242
|
+
return RequestContextV2AuthorizerIam(iam)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class RequestContextV2(BaseRequestContextV2):
|
|
246
|
+
@property
|
|
247
|
+
def authorizer(self) -> RequestContextV2Authorizer:
|
|
248
|
+
return RequestContextV2Authorizer(self.get("authorizer") or {})
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class APIGatewayProxyEventV2(BaseProxyEvent):
|
|
252
|
+
"""AWS Lambda proxy V2 event
|
|
253
|
+
|
|
254
|
+
Notes:
|
|
255
|
+
-----
|
|
256
|
+
Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. Duplicate headers
|
|
257
|
+
are combined with commas and included in the headers field. Duplicate query strings are combined with
|
|
258
|
+
commas and included in the queryStringParameters field.
|
|
259
|
+
|
|
260
|
+
Format 2.0 includes a new cookies field. All cookie headers in the request are combined with commas and
|
|
261
|
+
added to the cookies field. In the response to the client, each cookie becomes a set-cookie header.
|
|
262
|
+
|
|
263
|
+
Documentation:
|
|
264
|
+
--------------
|
|
265
|
+
- https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def version(self) -> str:
|
|
270
|
+
return self["version"]
|
|
271
|
+
|
|
272
|
+
@property
|
|
273
|
+
def route_key(self) -> str:
|
|
274
|
+
return self["routeKey"]
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def raw_path(self) -> str:
|
|
278
|
+
return self["rawPath"]
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def raw_query_string(self) -> str:
|
|
282
|
+
return self["rawQueryString"]
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def cookies(self) -> list[str]:
|
|
286
|
+
return self.get("cookies") or []
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def resolved_cookies_field(self) -> dict[str, str]:
|
|
290
|
+
"""
|
|
291
|
+
Parse cookies from the dedicated ``cookies`` field in API Gateway HTTP API v2 format.
|
|
292
|
+
|
|
293
|
+
The ``cookies`` field contains a list of strings like ``["session=abc", "theme=dark"]``.
|
|
294
|
+
"""
|
|
295
|
+
from modmex_lambda.data_classes.common import _parse_cookie_string
|
|
296
|
+
|
|
297
|
+
return _parse_cookie_string("; ".join(self.cookies))
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def request_context(self) -> RequestContextV2:
|
|
301
|
+
return RequestContextV2(self["requestContext"])
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def path_parameters(self) -> dict[str, str]:
|
|
305
|
+
return self.get("pathParameters") or {}
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def stage_variables(self) -> dict[str, str]:
|
|
309
|
+
return self.get("stageVariables") or {}
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def path(self) -> str:
|
|
313
|
+
stage = self.request_context.stage
|
|
314
|
+
if stage != "$default":
|
|
315
|
+
return self.raw_path[len("/" + stage) :]
|
|
316
|
+
return self.raw_path
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def http_method(self) -> str:
|
|
320
|
+
"""The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT."""
|
|
321
|
+
return self.request_context.http.method
|
|
322
|
+
|
|
323
|
+
def header_serializer(self):
|
|
324
|
+
return HttpApiHeadersSerializer()
|
|
325
|
+
|
|
326
|
+
@cached_property
|
|
327
|
+
def resolved_headers_field(self) -> dict[str, Any]:
|
|
328
|
+
return CaseInsensitiveDict((k, v.split(",") if "," in v else v) for k, v in self.headers.items())
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""API Gateway WebSocket event data class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from modmex_lambda.data_classes.common import DictWrapper
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class APIGatewayWebSocketEvent(DictWrapper):
|
|
12
|
+
@property
|
|
13
|
+
def request_context(self) -> dict[str, Any]:
|
|
14
|
+
return dict(self.get("requestContext") or {})
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def route_key(self) -> str:
|
|
18
|
+
return str((self.request_context or {}).get("routeKey") or "")
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def event_type(self) -> str:
|
|
22
|
+
return str((self.request_context or {}).get("eventType") or "")
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def connection_id(self) -> str:
|
|
26
|
+
return str((self.request_context or {}).get("connectionId") or "")
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def body(self) -> Any:
|
|
30
|
+
return self.get("body")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def json_body(self) -> Any:
|
|
34
|
+
body = self.body
|
|
35
|
+
if isinstance(body, str):
|
|
36
|
+
try:
|
|
37
|
+
return json.loads(body)
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
return body
|
|
40
|
+
return body
|