boto3-assist 0.32.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.
Files changed (67) hide show
  1. boto3_assist/__init__.py +0 -0
  2. boto3_assist/aws_config.py +199 -0
  3. boto3_assist/aws_lambda/event_info.py +414 -0
  4. boto3_assist/aws_lambda/mock_context.py +5 -0
  5. boto3_assist/boto3session.py +87 -0
  6. boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
  7. boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
  8. boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
  9. boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
  10. boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
  11. boto3_assist/cognito/cognito_authorizer.py +169 -0
  12. boto3_assist/cognito/cognito_connection.py +59 -0
  13. boto3_assist/cognito/cognito_utility.py +514 -0
  14. boto3_assist/cognito/jwks_cache.py +21 -0
  15. boto3_assist/cognito/user.py +27 -0
  16. boto3_assist/connection.py +146 -0
  17. boto3_assist/connection_tracker.py +120 -0
  18. boto3_assist/dynamodb/dynamodb.py +1206 -0
  19. boto3_assist/dynamodb/dynamodb_connection.py +113 -0
  20. boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
  21. boto3_assist/dynamodb/dynamodb_importer.py +102 -0
  22. boto3_assist/dynamodb/dynamodb_index.py +507 -0
  23. boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
  24. boto3_assist/dynamodb/dynamodb_key.py +130 -0
  25. boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
  26. boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
  27. boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
  28. boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
  29. boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
  30. boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
  31. boto3_assist/dynamodb/readme.md +68 -0
  32. boto3_assist/dynamodb/troubleshooting.md +7 -0
  33. boto3_assist/ec2/ec2_connection.py +57 -0
  34. boto3_assist/environment_services/__init__.py +0 -0
  35. boto3_assist/environment_services/environment_loader.py +128 -0
  36. boto3_assist/environment_services/environment_variables.py +219 -0
  37. boto3_assist/erc/__init__.py +64 -0
  38. boto3_assist/erc/ecr_connection.py +57 -0
  39. boto3_assist/errors/custom_exceptions.py +46 -0
  40. boto3_assist/http_status_codes.py +80 -0
  41. boto3_assist/models/serializable_model.py +9 -0
  42. boto3_assist/role_assumption_mixin.py +38 -0
  43. boto3_assist/s3/s3.py +64 -0
  44. boto3_assist/s3/s3_bucket.py +67 -0
  45. boto3_assist/s3/s3_connection.py +76 -0
  46. boto3_assist/s3/s3_event_data.py +168 -0
  47. boto3_assist/s3/s3_object.py +695 -0
  48. boto3_assist/securityhub/securityhub.py +150 -0
  49. boto3_assist/securityhub/securityhub_connection.py +57 -0
  50. boto3_assist/session_setup_mixin.py +70 -0
  51. boto3_assist/ssm/connection.py +57 -0
  52. boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
  53. boto3_assist/utilities/datetime_utility.py +349 -0
  54. boto3_assist/utilities/decimal_conversion_utility.py +140 -0
  55. boto3_assist/utilities/dictionary_utility.py +32 -0
  56. boto3_assist/utilities/file_operations.py +135 -0
  57. boto3_assist/utilities/http_utility.py +48 -0
  58. boto3_assist/utilities/logging_utility.py +0 -0
  59. boto3_assist/utilities/numbers_utility.py +329 -0
  60. boto3_assist/utilities/serialization_utility.py +664 -0
  61. boto3_assist/utilities/string_utility.py +337 -0
  62. boto3_assist/version.py +1 -0
  63. boto3_assist-0.32.0.dist-info/METADATA +76 -0
  64. boto3_assist-0.32.0.dist-info/RECORD +67 -0
  65. boto3_assist-0.32.0.dist-info/WHEEL +4 -0
  66. boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
  67. boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
File without changes
@@ -0,0 +1,199 @@
1
+ import os
2
+ from pathlib import Path
3
+ import configparser
4
+ from typing import Literal, Optional
5
+ from boto3_assist.utilities.serialization_utility import SerializableModel
6
+
7
+
8
+ class AWSConfigProfile(SerializableModel):
9
+ def __init__(
10
+ self, region: Optional[str] = "us-east-1", output: Optional[str] = "json"
11
+ ):
12
+
13
+ self.region: Optional[str] = region
14
+ self.output: Optional[str] = output
15
+
16
+ self.aws_access_key_id: Optional[str] = None
17
+ self.aws_secret_access_key: Optional[str] = None
18
+ self.aws_session_token: Optional[str] = None
19
+
20
+ self.sso_session: Optional[str] = None
21
+ self.sso_account_id: Optional[str] = None
22
+ self.sso_role_name: Optional[str] = None
23
+
24
+ self.credential_process: Optional[str] = None
25
+ self.credential_source: Optional[str] = None
26
+ self.role_arn: Optional[str] = None
27
+ self.source_profile: Optional[str] = None
28
+ self.external_id: Optional[str] = None
29
+ self.role_session_name: Optional[str] = None
30
+ self.duration_seconds: Optional[str] = None
31
+
32
+
33
+ class AWSConfigSSOSession(SerializableModel):
34
+ def __init__(
35
+ self,
36
+ sso_start_url: Optional[str] = None,
37
+ sso_region: Optional[str] = None,
38
+ sso_registration_scopes: Optional[str] = None,
39
+ ):
40
+ self.sso_start_url: Optional[str] = sso_start_url
41
+ self.sso_region: Optional[str] = sso_region
42
+ self.sso_registration_scopes: Optional[str] = sso_registration_scopes
43
+
44
+
45
+ class AWSConfig:
46
+ """
47
+ Performs Operations on an AWS Config
48
+ """
49
+
50
+ def __init__(self):
51
+ pass
52
+
53
+ def get_path(self) -> Path:
54
+ r"""
55
+ Returns the path to the AWS config file, honoring AWS_CONFIG_FILE
56
+ and falling back to ~/.aws/config (or %USERPROFILE%\.aws\config on Windows).
57
+ """
58
+ # 1) Check for explicit override
59
+ env_path = os.environ.get("AWS_CONFIG_FILE")
60
+ if env_path:
61
+ return Path(env_path).expanduser()
62
+
63
+ # 2) Default location
64
+ return os.path.join(Path.home(), ".aws", "config")
65
+
66
+ def path_exists(self) -> bool:
67
+ path = self.get_path()
68
+
69
+ return os.path.isfile(path)
70
+
71
+ def has_profile(self, profile_name: str) -> bool:
72
+ config = configparser.ConfigParser()
73
+ self.read_section(profile_name, config)
74
+ return profile_name in config.sections()
75
+
76
+ def upsert_profile(
77
+ self,
78
+ name: str,
79
+ profile: AWSConfigProfile,
80
+ config_path: Optional[str] = None,
81
+ ):
82
+ self.write_section(name, profile, config_path)
83
+
84
+ def upsert_sso_session(
85
+ self,
86
+ profile_name: str,
87
+ sso_session: AWSConfigSSOSession,
88
+ profile: AWSConfigProfile | None = None,
89
+ config_path: Optional[str] = None,
90
+ ):
91
+ """
92
+ Insert / Update the SSO Session block in the aws config
93
+ Args:
94
+ Name (str): Required. Specifies the profile name. This is the init key
95
+ which is added to the section for both sso-session and profile
96
+ e.g. [profile {profile_name}] or [sso-session {profile_name}]
97
+ sso_session (AWSConfigSSOSession): Defines the values written to this session block
98
+ profile (AWSConfigProfile): Defines the values written to the profile block. Typically
99
+ you will need a profile block along with a session block when using sso-session
100
+
101
+
102
+ As a general rule you will typically need to build the following:
103
+ [profile {profile-name}]
104
+ sso_session = {optionally-use-profile-name}
105
+ sso_account_id = {aws-acount-id}
106
+ sso_role_name = {sso-role}
107
+ region = {region}
108
+ output = {output}
109
+
110
+ [sso-session {profile-name}]
111
+ sso_start_url = {sso_start_url} e.g. https://account-alias.awsapps.com/start
112
+ sso_region = {region}
113
+ sso_registration_scopes = {scopes}
114
+
115
+ """
116
+
117
+ if not profile_name:
118
+ raise ValueError("Name is required")
119
+
120
+ self.write_section(profile_name, sso_session, config_path)
121
+
122
+ if profile:
123
+ self.write_section(profile_name, profile, config_path)
124
+
125
+ def write_section(
126
+ self,
127
+ profile_name: str | None,
128
+ section: AWSConfigProfile | AWSConfigSSOSession,
129
+ config_path: Optional[str] = None,
130
+ ):
131
+ config = configparser.ConfigParser()
132
+ path = config_path or self.get_path()
133
+
134
+ if self.path_exists():
135
+ config.read(path) # or any INI file path
136
+
137
+ section_key = ""
138
+
139
+ if profile_name.startswith("sso-session "):
140
+ profile_name = profile_name.replace("sso-session ", "")
141
+ if profile_name.startswith("profile "):
142
+ profile_name = profile_name.replace("profile ", "")
143
+
144
+ if isinstance(section, AWSConfigProfile):
145
+ if profile_name:
146
+ section_key = f"profile {profile_name}"
147
+ else:
148
+ section_key = "default"
149
+ elif isinstance(section, AWSConfigSSOSession):
150
+ section_key = f"sso-session {profile_name}"
151
+ else:
152
+ raise ValueError("Invalid section type")
153
+
154
+ config = self._write_section(section_key, section, config)
155
+
156
+ with open(path, "w", encoding="utf-8") as cfg_file:
157
+ config.write(cfg_file)
158
+
159
+ def _write_section(
160
+ self,
161
+ section_key: str,
162
+ section: AWSConfigProfile | AWSConfigSSOSession,
163
+ config: configparser.ConfigParser,
164
+ ) -> configparser.ConfigParser:
165
+
166
+ # always start with a "fresh" section
167
+ config[section_key] = {}
168
+
169
+ section_dictionary = section.to_dictionary()
170
+ for key, value in section_dictionary.items():
171
+ if value is not None:
172
+ config[section_key][key] = value
173
+
174
+ return config
175
+
176
+ def read_section(
177
+ self,
178
+ profile_name: Optional[str] = None,
179
+ config_path: Optional[str] = None,
180
+ section_type: Literal["profile", "sso-session"] = "profile",
181
+ ) -> configparser.SectionProxy:
182
+ config = configparser.ConfigParser()
183
+ if not config_path:
184
+ config_path = self.get_path()
185
+
186
+ if not os.path.isfile(config_path):
187
+ return config
188
+
189
+ config.read(config_path)
190
+ profile_ini = f"{section_type} {profile_name}"
191
+ if profile_ini in config:
192
+ profile = config[profile_ini]
193
+ return profile
194
+
195
+ if profile_name in config:
196
+ profile = config[profile_name]
197
+ return profile
198
+
199
+ return {}
@@ -0,0 +1,414 @@
1
+ import json
2
+ import re
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from aws_lambda_powertools import Logger
6
+
7
+ from boto3_assist.cognito.cognito_authorizer import CognitoCustomAuthorizer
8
+ from boto3_assist.errors.custom_exceptions import Error
9
+ from boto3_assist.http_status_codes import HttpStatusCodes
10
+
11
+ logger = Logger()
12
+
13
+
14
+ class LambdaEventInfo:
15
+ """
16
+ Utility class for parsing and interacting with AWS Lambda event payloads.
17
+ Contains methods to extract data from API Gateway payloads, path parameters,
18
+ and headers.
19
+ """
20
+
21
+ class ApiGatewayPayload:
22
+ """
23
+ Handles API Gateway-specific event payloads.
24
+ Provides methods to extract HTTP method types, resource paths, and
25
+ authorizer claims.
26
+ """
27
+
28
+ @staticmethod
29
+ def get_http_method_type(event: Dict[str, Any]) -> Optional[str]:
30
+ """
31
+ Extracts the HTTP method type (e.g., GET, POST) from the event.
32
+
33
+ Args:
34
+ event: The Lambda event payload.
35
+
36
+ Returns:
37
+ The HTTP method type as a string, or None if not found.
38
+ """
39
+ return LambdaEventInfo._get_value(event, "method_type", str) # pylint: disable=w0212
40
+
41
+ @staticmethod
42
+ def get_resource_path(event: Dict[str, Any]) -> Optional[str]:
43
+ """
44
+ Extracts the resource path from the event.
45
+
46
+ Args:
47
+ event: The Lambda event payload.
48
+
49
+ Returns:
50
+ The resource path as a string, or None if not found.
51
+ """
52
+ return LambdaEventInfo._get_value_ex(event, "path", str) # pylint: disable=w0212
53
+
54
+ @staticmethod
55
+ def get_resource_pattern(event: Dict[str, Any]) -> Optional[str]:
56
+ """
57
+ Extracts the resource pattern from the event, replacing path variables
58
+ with placeholders (e.g., /users/{user-id}).
59
+
60
+ Args:
61
+ event: The Lambda event payload.
62
+
63
+ Returns:
64
+ The resource pattern as a string, or None if not found.
65
+ """
66
+ return LambdaEventInfo._get_value_ex(event, "resourcePath", str) # pylint: disable=w0212
67
+
68
+ class AuthorizerPayload:
69
+ """
70
+ Handles claims and tokens in API Gateway authorizer payloads.
71
+ """
72
+
73
+ @staticmethod
74
+ def get_authenticated_email(event: Dict[str, Any]) -> Optional[str]:
75
+ """
76
+ Extracts the authenticated email or client ID from the event based
77
+ on the token use.
78
+
79
+ Args:
80
+ event: The Lambda event payload.
81
+
82
+ Returns:
83
+ The email or client ID as a string, or None if not found.
84
+ """
85
+ token_use = (
86
+ LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_token_use(
87
+ event
88
+ )
89
+ ) # pylint: disable=w0212
90
+ key = "email" if token_use == "access" else "client_id"
91
+ return (
92
+ LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_claims_data(
93
+ event, key
94
+ )
95
+ ) # pylint: disable=w0212
96
+
97
+ @staticmethod
98
+ def get_token_use(event: Dict[str, Any]) -> Optional[str]:
99
+ """
100
+ Extracts the token use (e.g., "access" or "id") from the event.
101
+
102
+ Args:
103
+ event: The Lambda event payload.
104
+
105
+ Returns:
106
+ The token use as a string, or None if not found.
107
+ """
108
+ return LambdaEventInfo._get_value( # pylint: disable=w0212
109
+ event, "requestContext/authorizer/claims/token_use", str
110
+ ) # pylint: disable=w0212
111
+
112
+ @staticmethod
113
+ def get_claims_data(event: Dict[str, Any], key: str) -> Optional[str]:
114
+ """
115
+ Extracts a specific claim from the authorizer payload.
116
+
117
+ Args:
118
+ event: The Lambda event payload.
119
+ key: The claim key to extract.
120
+
121
+ Returns:
122
+ The claim value as a string, or None if not found.
123
+ """
124
+ try:
125
+ value = LambdaEventInfo._get_value( # pylint: disable=w0212
126
+ event, f"requestContext/authorizer/claims/{key}", str
127
+ ) # pylint: disable=w0212
128
+ if not value:
129
+ value = LambdaEventInfo.ApiGatewayPayload.AuthorizerPayload.get_value_from_token(
130
+ event, key
131
+ ) # pylint: disable=w0212
132
+
133
+ if value is None:
134
+ raise Error(
135
+ {
136
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
137
+ "message": f"Failed to locate {key} info in JWT Token",
138
+ }
139
+ )
140
+ return value
141
+
142
+ except Exception as e:
143
+ raise Error(
144
+ {
145
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
146
+ "message": f"Failed to locate {key} info in JWT Token",
147
+ "exception": str(e),
148
+ }
149
+ ) from e
150
+
151
+ @staticmethod
152
+ def get_value_from_token(event: Dict[str, Any], key: str) -> Optional[str]:
153
+ """
154
+ Extracts a value from the JWT token in the event.
155
+
156
+ Args:
157
+ event: The Lambda event payload.
158
+ key: The key to extract from the token.
159
+
160
+ Returns:
161
+ The extracted value as a string, or None if not found.
162
+ """
163
+ try:
164
+ jwt_token = LambdaEventInfo._get_value( # pylint: disable=w0212
165
+ event, "headers/Authorization", str
166
+ ) # pylint: disable=w0212
167
+ if jwt_token:
168
+ ccas = CognitoCustomAuthorizer()
169
+ decoded_token = ccas.parse_jwt(token=jwt_token)
170
+ return decoded_token.get(key)
171
+
172
+ raise Error(
173
+ {
174
+ "status_code": HttpStatusCodes.HTTP_404_NOT_FOUND.value,
175
+ "message": f"Failed to locate {key} info in JWT Token",
176
+ }
177
+ )
178
+ except Exception as e:
179
+ raise Error(
180
+ {
181
+ "status_code": HttpStatusCodes.HTTP_401_UNAUTHENTICATED.value,
182
+ "message": f"Failed to locate {key} info in JWT Token",
183
+ "exception": str(e),
184
+ }
185
+ ) from e
186
+
187
+ class HttpPathParameters:
188
+ """
189
+ Handles path parameters in API Gateway events.
190
+ """
191
+
192
+ @staticmethod
193
+ def get_target_user_id(
194
+ event: Dict[str, Any], key: str = "user-id"
195
+ ) -> Optional[str]:
196
+ """
197
+ Extracts the target user ID from the path parameters.
198
+
199
+ Args:
200
+ event: The Lambda event payload.
201
+ key: The key representing the user ID.
202
+
203
+ Returns:
204
+ The user ID as a string, or None if not found.
205
+ """
206
+ return LambdaEventInfo._get_value_from_path_parameters(event, key) # pylint: disable=w0212
207
+
208
+ @staticmethod
209
+ def get_target_tenant_id(
210
+ event: Dict[str, Any], key: str = "tenant-id"
211
+ ) -> Optional[str]:
212
+ """
213
+ Extracts the target tenant ID from the path parameters.
214
+
215
+ Args:
216
+ event: The Lambda event payload.
217
+ key: The key representing the tenant ID.
218
+
219
+ Returns:
220
+ The tenant ID as a string, or None if not found.
221
+ """
222
+ return LambdaEventInfo._get_value_from_path_parameters(event, key) # pylint: disable=w0212
223
+
224
+ @staticmethod
225
+ def get_message_id(event: Dict[str, Any], index: int = 0) -> Optional[str]:
226
+ """
227
+ Extracts the message ID from an event record.
228
+
229
+ Args:
230
+ event: The Lambda event payload.
231
+ index: The index of the record to extract the message ID from.
232
+
233
+ Returns:
234
+ The message ID as a string, or None if not found.
235
+ """
236
+ records: List[Dict[str, Any]] = event.get("Records", [])
237
+ if records and len(records) > index:
238
+ return records[index].get("messageId")
239
+ return None
240
+
241
+ @staticmethod
242
+ def _get_value_ex(
243
+ event: Dict[str, Any], key: str, expected_type: type
244
+ ) -> Optional[Any]:
245
+ """
246
+ Extracts a value from the event, checking additional paths if necessary.
247
+
248
+ Args:
249
+ event: The Lambda event payload.
250
+ key: The key to extract.
251
+ expected_type: The expected type of the value.
252
+
253
+ Returns:
254
+ The extracted value, or None if not found.
255
+ """
256
+ value = LambdaEventInfo._get_value(event, key, expected_type) # pylint: disable=w0212
257
+ if value is None:
258
+ value = LambdaEventInfo._get_value(
259
+ event, f"requestContext/{key}", expected_type
260
+ ) # pylint: disable=w0212
261
+ return value
262
+
263
+ @staticmethod
264
+ def _get_value(
265
+ event: Dict[str, Any], key: Union[str, List[str]], expected_type: type
266
+ ) -> Optional[Any]:
267
+ """
268
+ Extracts a value from the event based on the key.
269
+
270
+ Args:
271
+ event: The Lambda event payload.
272
+ key: The key to extract, which can be a string or a list of strings for nested keys.
273
+ expected_type: The expected type of the value.
274
+
275
+ Returns:
276
+ The extracted value, or None if not found.
277
+ """
278
+ logger.debug({"source": "_get_value", "event": event, "key": key})
279
+ if not event:
280
+ return None
281
+
282
+ if isinstance(key, str):
283
+ key = re.split(r"[./]", key)
284
+
285
+ value = event
286
+ for k in key:
287
+ if isinstance(value, dict):
288
+ value = value.get(k)
289
+ else:
290
+ return None
291
+
292
+ if isinstance(value, expected_type):
293
+ return value
294
+ return None
295
+
296
+ @staticmethod
297
+ def _get_value_from_path_parameters(
298
+ event: Dict[str, Any], key: str, default: Optional[Any] = None
299
+ ) -> Optional[str]:
300
+ """
301
+ Extracts a value from the path parameters in the event.
302
+
303
+ Args:
304
+ event: The Lambda event payload.
305
+ key: The key to extract from the path parameters.
306
+ default: The default value to return if the key is not found.
307
+
308
+ Returns:
309
+ The extracted value, or None if not found.
310
+ """
311
+ value = LambdaEventInfo._search_key(event, "pathParameters", key, default) # pylint: disable=w0212
312
+ if value is None:
313
+ path = LambdaEventInfo.ApiGatewayPayload.get_resource_path(event) # pylint: disable=w0212
314
+ pattern = LambdaEventInfo.ApiGatewayPayload.get_resource_pattern(event) # pylint: disable=w0212
315
+ if path and pattern:
316
+ value = LambdaEventInfo._extract_value_from_path(path, pattern, key) # pylint: disable=w0212
317
+ return value
318
+
319
+ @staticmethod
320
+ def _extract_value_from_path(
321
+ path: str, pattern: str, variable_name: str
322
+ ) -> Optional[str]:
323
+ """
324
+ Extracts a value from a path using a regex pattern.
325
+
326
+ Args:
327
+ path: The actual path from the event.
328
+ pattern: The pattern with placeholders (e.g., /users/{user-id}).
329
+ variable_name: The variable name to extract.
330
+
331
+ Returns:
332
+ The extracted value, or None if not found.
333
+ """
334
+ regex_pattern = re.sub(
335
+ r"\{([^}]+)\}",
336
+ lambda m: f"(?P<{m.group(1).replace('-', '_')}>[^/]+)",
337
+ pattern,
338
+ )
339
+ variable_name = variable_name.replace("-", "_")
340
+ match = re.match(regex_pattern, path)
341
+ if match:
342
+ return match.group(variable_name)
343
+ return None
344
+
345
+ @staticmethod
346
+ def _search_key(
347
+ event: Dict[str, Any], container: str, key: str, default: Optional[Any] = None
348
+ ) -> Optional[Any]:
349
+ """
350
+ Searches for a key within a specified container in the event.
351
+
352
+ Args:
353
+ event: The Lambda event payload.
354
+ container: The container key to search within (e.g., "headers").
355
+ key: The key to search for within the container.
356
+ default: The default value to return if the key is not found.
357
+
358
+ Returns:
359
+ The extracted value, or the default value if not found.
360
+ """
361
+ events = [event]
362
+
363
+ if "Records" in event:
364
+ record = event["Records"][0]
365
+ if "body" in record:
366
+ body = json.loads(record["body"])
367
+ events.append(body)
368
+ if "requestContext" in body:
369
+ events.append(body["requestContext"])
370
+
371
+ for e in events:
372
+ container_data = e.get(container)
373
+ if container_data and key in container_data:
374
+ return container_data[key]
375
+
376
+ return default
377
+
378
+ @staticmethod
379
+ def get_body(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
380
+ """
381
+ Extracts the body of the event payload.
382
+
383
+ Args:
384
+ event: The Lambda event payload.
385
+
386
+ Returns:
387
+ The body as a dictionary, or None if not found.
388
+ """
389
+ tmp = event.get("Records", [{}])[0].get("body", event)
390
+ if isinstance(tmp, str):
391
+ try:
392
+ return json.loads(tmp)
393
+ except json.JSONDecodeError as e:
394
+ raise ValueError("Invalid JSON body in the payload") from e
395
+ return tmp if isinstance(tmp, dict) else None
396
+
397
+ @staticmethod
398
+ def override_event_info(
399
+ event: Dict[str, Any], key: str, value: Any
400
+ ) -> Dict[str, Any]:
401
+ """
402
+ Overrides a value in the event payload.
403
+
404
+ Args:
405
+ event: The Lambda event payload.
406
+ key: The key to override.
407
+ value: The value to set.
408
+
409
+ Returns:
410
+ The updated event payload.
411
+ """
412
+ body = LambdaEventInfo.get_body(event) or {}
413
+ body[key] = value
414
+ return body
@@ -0,0 +1,5 @@
1
+ from aws_lambda_powertools.utilities.typing import LambdaContext
2
+
3
+
4
+ class MockLambdaContext(LambdaContext):
5
+ pass