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.
- boto3_assist/__init__.py +0 -0
- boto3_assist/aws_config.py +199 -0
- boto3_assist/aws_lambda/event_info.py +414 -0
- boto3_assist/aws_lambda/mock_context.py +5 -0
- boto3_assist/boto3session.py +87 -0
- boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
- boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
- boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
- boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
- boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
- boto3_assist/cognito/cognito_authorizer.py +169 -0
- boto3_assist/cognito/cognito_connection.py +59 -0
- boto3_assist/cognito/cognito_utility.py +514 -0
- boto3_assist/cognito/jwks_cache.py +21 -0
- boto3_assist/cognito/user.py +27 -0
- boto3_assist/connection.py +146 -0
- boto3_assist/connection_tracker.py +120 -0
- boto3_assist/dynamodb/dynamodb.py +1206 -0
- boto3_assist/dynamodb/dynamodb_connection.py +113 -0
- boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
- boto3_assist/dynamodb/dynamodb_importer.py +102 -0
- boto3_assist/dynamodb/dynamodb_index.py +507 -0
- boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
- boto3_assist/dynamodb/dynamodb_key.py +130 -0
- boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
- boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
- boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
- boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
- boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
- boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
- boto3_assist/dynamodb/readme.md +68 -0
- boto3_assist/dynamodb/troubleshooting.md +7 -0
- boto3_assist/ec2/ec2_connection.py +57 -0
- boto3_assist/environment_services/__init__.py +0 -0
- boto3_assist/environment_services/environment_loader.py +128 -0
- boto3_assist/environment_services/environment_variables.py +219 -0
- boto3_assist/erc/__init__.py +64 -0
- boto3_assist/erc/ecr_connection.py +57 -0
- boto3_assist/errors/custom_exceptions.py +46 -0
- boto3_assist/http_status_codes.py +80 -0
- boto3_assist/models/serializable_model.py +9 -0
- boto3_assist/role_assumption_mixin.py +38 -0
- boto3_assist/s3/s3.py +64 -0
- boto3_assist/s3/s3_bucket.py +67 -0
- boto3_assist/s3/s3_connection.py +76 -0
- boto3_assist/s3/s3_event_data.py +168 -0
- boto3_assist/s3/s3_object.py +695 -0
- boto3_assist/securityhub/securityhub.py +150 -0
- boto3_assist/securityhub/securityhub_connection.py +57 -0
- boto3_assist/session_setup_mixin.py +70 -0
- boto3_assist/ssm/connection.py +57 -0
- boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
- boto3_assist/utilities/datetime_utility.py +349 -0
- boto3_assist/utilities/decimal_conversion_utility.py +140 -0
- boto3_assist/utilities/dictionary_utility.py +32 -0
- boto3_assist/utilities/file_operations.py +135 -0
- boto3_assist/utilities/http_utility.py +48 -0
- boto3_assist/utilities/logging_utility.py +0 -0
- boto3_assist/utilities/numbers_utility.py +329 -0
- boto3_assist/utilities/serialization_utility.py +664 -0
- boto3_assist/utilities/string_utility.py +337 -0
- boto3_assist/version.py +1 -0
- boto3_assist-0.32.0.dist-info/METADATA +76 -0
- boto3_assist-0.32.0.dist-info/RECORD +67 -0
- boto3_assist-0.32.0.dist-info/WHEEL +4 -0
- boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
- boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
boto3_assist/__init__.py
ADDED
|
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
|