clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.2__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.
- {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/METADATA +36 -35
- clear_skies_aws-2.0.2.dist-info/RECORD +63 -0
- {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/WHEEL +1 -1
- clear_skies_aws-2.0.2.dist-info/licenses/LICENSE +21 -0
- clearskies_aws/__init__.py +15 -2
- clearskies_aws/actions/__init__.py +13 -106
- clearskies_aws/actions/action_aws.py +74 -57
- clearskies_aws/actions/assume_role.py +43 -30
- clearskies_aws/actions/ses.py +82 -73
- clearskies_aws/actions/sns.py +27 -30
- clearskies_aws/actions/sqs.py +32 -33
- clearskies_aws/actions/step_function.py +38 -31
- clearskies_aws/backends/__init__.py +11 -4
- clearskies_aws/backends/backend.py +106 -0
- clearskies_aws/backends/dynamo_db_backend.py +150 -155
- clearskies_aws/backends/dynamo_db_condition_parser.py +40 -80
- clearskies_aws/backends/dynamo_db_parti_ql_backend.py +179 -337
- clearskies_aws/backends/sqs_backend.py +32 -51
- clearskies_aws/configs/__init__.py +0 -0
- clearskies_aws/contexts/__init__.py +23 -10
- clearskies_aws/contexts/cli_web_socket_mock.py +19 -0
- clearskies_aws/contexts/lambda_alb.py +76 -0
- clearskies_aws/contexts/lambda_api_gateway.py +75 -28
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py +56 -29
- clearskies_aws/contexts/lambda_invocation.py +15 -44
- clearskies_aws/contexts/lambda_sns.py +8 -33
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +14 -36
- clearskies_aws/di/__init__.py +6 -1
- clearskies_aws/di/aws_additional_config_auto_import.py +37 -0
- clearskies_aws/di/inject/__init__.py +6 -0
- clearskies_aws/di/inject/boto3.py +15 -0
- clearskies_aws/di/inject/boto3_session.py +13 -0
- clearskies_aws/di/inject/parameter_store.py +15 -0
- clearskies_aws/{handlers → endpoints}/secrets_manager_rotation.py +76 -55
- clearskies_aws/endpoints/simple_body_routing.py +41 -0
- clearskies_aws/input_outputs/__init__.py +21 -8
- clearskies_aws/input_outputs/{cli_websocket_mock.py → cli_web_socket_mock.py} +9 -3
- clearskies_aws/input_outputs/lambda_alb.py +53 -0
- clearskies_aws/input_outputs/lambda_api_gateway.py +106 -88
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +69 -6
- clearskies_aws/input_outputs/lambda_input_output.py +87 -0
- clearskies_aws/input_outputs/lambda_invocation.py +77 -26
- clearskies_aws/input_outputs/lambda_sns.py +66 -39
- clearskies_aws/input_outputs/lambda_sqs_standard.py +70 -40
- clearskies_aws/mocks/actions/ses.py +25 -19
- clearskies_aws/mocks/actions/sns.py +18 -12
- clearskies_aws/mocks/actions/sqs.py +18 -12
- clearskies_aws/mocks/actions/step_function.py +19 -13
- clearskies_aws/models/__init__.py +0 -0
- clearskies_aws/models/web_socket_connection_model.py +182 -0
- clearskies_aws/secrets/__init__.py +13 -7
- clearskies_aws/secrets/additional_configs/__init__.py +10 -2
- clearskies_aws/secrets/additional_configs/iam_db_auth.py +26 -16
- clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +43 -39
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +30 -31
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +70 -49
- clearskies_aws/secrets/akeyless_with_ssm_cache.py +32 -18
- clearskies_aws/secrets/parameter_store.py +34 -32
- clearskies_aws/secrets/secrets.py +16 -0
- clearskies_aws/secrets/secrets_manager.py +78 -57
- clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
- clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
- clearskies_aws/actions/assume_role_test.py +0 -72
- clearskies_aws/actions/ses_test.py +0 -89
- clearskies_aws/actions/sns_test.py +0 -77
- clearskies_aws/actions/sqs_test.py +0 -127
- clearskies_aws/actions/step_function_test.py +0 -103
- clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
- clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
- clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
- clearskies_aws/backends/sqs_backend_test.py +0 -31
- clearskies_aws/contexts/cli.py +0 -19
- clearskies_aws/contexts/cli_websocket_mock.py +0 -33
- clearskies_aws/contexts/lambda_elb.py +0 -30
- clearskies_aws/contexts/lambda_http_gateway.py +0 -30
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
- clearskies_aws/contexts/wsgi.py +0 -19
- clearskies_aws/di/standard_dependencies.py +0 -60
- clearskies_aws/handlers/simple_body_routing.py +0 -39
- clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
- clearskies_aws/input_outputs/lambda_elb.py +0 -21
- clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
- clearskies_aws/secrets/parameter_store_test.py +0 -18
- clearskies_aws/secrets/secrets_manager_test.py +0 -18
- clearskies_aws/web_socket_connection_model.py +0 -43
- clearskies_aws/{handlers → endpoints}/__init__.py +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import base64
|
|
2
4
|
import json
|
|
3
5
|
import logging
|
|
4
6
|
from decimal import Decimal, DecimalException
|
|
5
|
-
from typing import Any
|
|
7
|
+
from typing import Any
|
|
6
8
|
|
|
7
|
-
from clearskies import
|
|
9
|
+
from clearskies import Validator
|
|
8
10
|
|
|
9
11
|
# Ensure AttributeValueTypeDef is imported from the correct boto3 types package
|
|
10
12
|
# This is crucial for the "ideal fix".
|
|
@@ -13,7 +15,7 @@ from types_boto3_dynamodb.type_defs import AttributeValueTypeDef
|
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
class DynamoDBConditionParser(
|
|
18
|
+
class DynamoDBConditionParser(Validator):
|
|
17
19
|
"""
|
|
18
20
|
Parses string conditions into a structured format suitable for DynamoDB PartiQL queries.
|
|
19
21
|
|
|
@@ -22,7 +24,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
22
24
|
to convert Python values into the DynamoDB AttributeValue format.
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
|
-
operator_lengths:
|
|
27
|
+
operator_lengths: dict[str, int] = {
|
|
26
28
|
"<>": 2,
|
|
27
29
|
"<=": 2,
|
|
28
30
|
">=": 2,
|
|
@@ -41,7 +43,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
41
43
|
"contains": 9,
|
|
42
44
|
"begins_with": 12,
|
|
43
45
|
}
|
|
44
|
-
operators:
|
|
46
|
+
operators: list[str] = [
|
|
45
47
|
# Longer operators first to help with matching
|
|
46
48
|
"is not null",
|
|
47
49
|
"is not missing",
|
|
@@ -61,7 +63,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
61
63
|
"=",
|
|
62
64
|
"in",
|
|
63
65
|
]
|
|
64
|
-
operators_for_matching:
|
|
66
|
+
operators_for_matching: dict[str, str] = {
|
|
65
67
|
"like": " like ",
|
|
66
68
|
"in": " in ",
|
|
67
69
|
"is not missing": " is not missing",
|
|
@@ -73,7 +75,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
73
75
|
"begins_with": " begins_with",
|
|
74
76
|
"contains": " contains",
|
|
75
77
|
}
|
|
76
|
-
operators_with_simple_placeholders:
|
|
78
|
+
operators_with_simple_placeholders: dict[str, bool] = {
|
|
77
79
|
"<>": True,
|
|
78
80
|
"<=": True,
|
|
79
81
|
">=": True,
|
|
@@ -88,17 +90,17 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
88
90
|
"is not missing",
|
|
89
91
|
"is missing",
|
|
90
92
|
}
|
|
91
|
-
operator_needs_remap:
|
|
93
|
+
operator_needs_remap: dict[str, str] = {
|
|
92
94
|
"is not null": "is not missing",
|
|
93
95
|
"is null": "is missing",
|
|
94
96
|
}
|
|
95
97
|
operators_with_special_placeholders: set[str] = {"begins_with", "contains"}
|
|
96
98
|
|
|
97
|
-
def parse_condition(self, condition: str) ->
|
|
99
|
+
def parse_condition(self, condition: str) -> dict[str, Any]:
|
|
98
100
|
"""
|
|
99
|
-
|
|
101
|
+
Parse a string condition into a structured dictionary.
|
|
100
102
|
|
|
101
|
-
The "values" key in the returned dictionary will contain
|
|
103
|
+
The "values" key in the returned dictionary will contain list[AttributeValueTypeDef].
|
|
102
104
|
|
|
103
105
|
Args:
|
|
104
106
|
condition: The condition string to parse.
|
|
@@ -114,9 +116,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
114
116
|
|
|
115
117
|
for operator in self.operators:
|
|
116
118
|
try:
|
|
117
|
-
operator_for_match: str = self.operators_for_matching.get(
|
|
118
|
-
operator, operator
|
|
119
|
-
)
|
|
119
|
+
operator_for_match: str = self.operators_for_matching.get(operator, operator)
|
|
120
120
|
index: int = lowercase_condition.index(operator_for_match)
|
|
121
121
|
|
|
122
122
|
if matching_index == -1 or index < matching_index:
|
|
@@ -134,26 +134,21 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
134
134
|
raise ValueError(f"No supported operators found in condition {condition}")
|
|
135
135
|
|
|
136
136
|
column: str = condition[:matching_index].strip()
|
|
137
|
-
value: str = condition[
|
|
138
|
-
matching_index + self.operator_lengths[matching_operator] :
|
|
139
|
-
].strip()
|
|
137
|
+
value: str = condition[matching_index + self.operator_lengths[matching_operator] :].strip()
|
|
140
138
|
|
|
141
139
|
if len(value) >= 2:
|
|
142
140
|
first_char = value[0]
|
|
143
141
|
last_char = value[-1]
|
|
144
|
-
if (first_char == "'" and last_char == "'") or (
|
|
145
|
-
first_char == '"' and last_char == '"'
|
|
146
|
-
):
|
|
142
|
+
if (first_char == "'" and last_char == "'") or (first_char == '"' and last_char == '"'):
|
|
147
143
|
value = value[1:-1]
|
|
148
144
|
|
|
149
|
-
raw_values:
|
|
145
|
+
raw_values: list[str] = []
|
|
150
146
|
|
|
151
147
|
if matching_operator == "in":
|
|
152
148
|
raw_values = self._parse_condition_list(value) if value else []
|
|
153
149
|
elif matching_operator not in self.operators_without_placeholders and not (
|
|
154
150
|
matching_operator in self.operator_needs_remap
|
|
155
|
-
and self.operator_needs_remap[matching_operator]
|
|
156
|
-
in self.operators_without_placeholders
|
|
151
|
+
and self.operator_needs_remap[matching_operator] in self.operators_without_placeholders
|
|
157
152
|
):
|
|
158
153
|
raw_values = [value]
|
|
159
154
|
|
|
@@ -165,37 +160,29 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
165
160
|
matching_operator = "begins_with"
|
|
166
161
|
raw_values = [value[:-1]]
|
|
167
162
|
elif value.startswith("%") and not value.endswith("%"):
|
|
168
|
-
raise ValueError(
|
|
169
|
-
"DynamoDB PartiQL does not directly support 'ends_with'"
|
|
170
|
-
)
|
|
163
|
+
raise ValueError("DynamoDB PartiQL does not directly support 'ends_with'")
|
|
171
164
|
else:
|
|
172
165
|
matching_operator = "="
|
|
173
166
|
raw_values = [value]
|
|
174
167
|
|
|
175
|
-
matching_operator = self.operator_needs_remap.get(
|
|
176
|
-
matching_operator.lower(), matching_operator
|
|
177
|
-
)
|
|
168
|
+
matching_operator = self.operator_needs_remap.get(matching_operator.lower(), matching_operator)
|
|
178
169
|
|
|
179
170
|
table_name: str = ""
|
|
180
171
|
final_column_name: str = column
|
|
181
172
|
if "." in column:
|
|
182
173
|
table_prefix, column_name_part = column.split(".", 1)
|
|
183
174
|
table_name = table_prefix.strip().replace('"', "").replace("`", "")
|
|
184
|
-
final_column_name = (
|
|
185
|
-
column_name_part.strip().replace('"', "").replace("`", "")
|
|
186
|
-
)
|
|
175
|
+
final_column_name = column_name_part.strip().replace('"', "").replace("`", "")
|
|
187
176
|
else:
|
|
188
177
|
final_column_name = column.replace('"', "").replace("`", "")
|
|
189
178
|
|
|
190
|
-
# This list will now correctly be
|
|
191
|
-
parameters:
|
|
179
|
+
# This list will now correctly be list[AttributeValueTypeDef]
|
|
180
|
+
parameters: list[AttributeValueTypeDef] = []
|
|
192
181
|
if matching_operator.lower() not in self.operators_without_placeholders:
|
|
193
182
|
for val_item in raw_values:
|
|
194
183
|
parameters.append(self.to_dynamodb_attribute_value(val_item))
|
|
195
184
|
|
|
196
|
-
column_for_parsed: str =
|
|
197
|
-
f"{table_name}.{final_column_name}" if table_name else final_column_name
|
|
198
|
-
)
|
|
185
|
+
column_for_parsed: str = f"{table_name}.{final_column_name}" if table_name else final_column_name
|
|
199
186
|
|
|
200
187
|
return {
|
|
201
188
|
"table": table_name,
|
|
@@ -213,19 +200,15 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
213
200
|
self,
|
|
214
201
|
column: str,
|
|
215
202
|
operator: str,
|
|
216
|
-
values:
|
|
217
|
-
AttributeValueTypeDef
|
|
218
|
-
], # Parameter 'values' is List[AttributeValueTypeDef]
|
|
203
|
+
values: list[AttributeValueTypeDef], # Parameter 'values' is list[AttributeValueTypeDef]
|
|
219
204
|
escape: bool = True,
|
|
220
205
|
escape_character: str = '"',
|
|
221
206
|
) -> str:
|
|
222
|
-
"""
|
|
223
|
-
Formats a SQL fragment with placeholders for a given column, operator, and parameters.
|
|
224
|
-
"""
|
|
207
|
+
"""Format a SQL fragment with placeholders for a given column, operator, and parameters."""
|
|
225
208
|
quoted_column = column
|
|
226
209
|
if escape:
|
|
227
|
-
parts:
|
|
228
|
-
cleaned_parts:
|
|
210
|
+
parts: list[str] = column.split(".", 1)
|
|
211
|
+
cleaned_parts: list[str] = [part.strip('"`') for part in parts]
|
|
229
212
|
if len(cleaned_parts) == 2:
|
|
230
213
|
quoted_column = (
|
|
231
214
|
f"{escape_character}{cleaned_parts[0]}{escape_character}"
|
|
@@ -233,9 +216,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
233
216
|
f"{escape_character}{cleaned_parts[1]}{escape_character}"
|
|
234
217
|
)
|
|
235
218
|
else:
|
|
236
|
-
quoted_column =
|
|
237
|
-
f"{escape_character}{cleaned_parts[0]}{escape_character}"
|
|
238
|
-
)
|
|
219
|
+
quoted_column = f"{escape_character}{cleaned_parts[0]}{escape_character}"
|
|
239
220
|
|
|
240
221
|
upper_case_operator: str = operator.upper()
|
|
241
222
|
lower_case_operator: str = operator.lower()
|
|
@@ -253,12 +234,8 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
253
234
|
|
|
254
235
|
raise ValueError(f"Unsupported operator for placeholder generation: {operator}")
|
|
255
236
|
|
|
256
|
-
def to_dynamodb_attribute_value(
|
|
257
|
-
|
|
258
|
-
) -> AttributeValueTypeDef: # Return type changed
|
|
259
|
-
"""
|
|
260
|
-
Converts a Python variable into a DynamoDB-formatted attribute value dictionary.
|
|
261
|
-
"""
|
|
237
|
+
def to_dynamodb_attribute_value(self, value: Any) -> AttributeValueTypeDef: # Return type changed
|
|
238
|
+
"""Convert a Python variable into a DynamoDB-formatted attribute value dictionary."""
|
|
262
239
|
if isinstance(value, str):
|
|
263
240
|
if value.lower() == "true":
|
|
264
241
|
return {"BOOL": True}
|
|
@@ -281,43 +258,26 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
281
258
|
elif isinstance(value, bytes):
|
|
282
259
|
return {"B": base64.b64encode(value).decode("utf-8")}
|
|
283
260
|
elif isinstance(value, list):
|
|
284
|
-
# Each item will be AttributeValueTypeDef, so the list is
|
|
261
|
+
# Each item will be AttributeValueTypeDef, so the list is list[AttributeValueTypeDef]
|
|
285
262
|
return {"L": [self.to_dynamodb_attribute_value(item) for item in value]}
|
|
286
263
|
elif isinstance(value, dict):
|
|
287
264
|
# Each value in the map will be AttributeValueTypeDef
|
|
288
|
-
return {
|
|
289
|
-
"M": {
|
|
290
|
-
str(k): self.to_dynamodb_attribute_value(v)
|
|
291
|
-
for k, v in value.items()
|
|
292
|
-
}
|
|
293
|
-
}
|
|
265
|
+
return {"M": {str(k): self.to_dynamodb_attribute_value(v) for k, v in value.items()}}
|
|
294
266
|
elif isinstance(value, set):
|
|
295
267
|
if not value:
|
|
296
|
-
raise ValueError(
|
|
297
|
-
"Cannot determine DynamoDB Set type from an empty Python set."
|
|
298
|
-
)
|
|
268
|
+
raise ValueError("Cannot determine DynamoDB Set type from an empty Python set.")
|
|
299
269
|
if all(isinstance(item, str) for item in value):
|
|
300
270
|
return {"SS": sorted(list(value))}
|
|
301
271
|
elif all(isinstance(item, (int, float, Decimal)) for item in value):
|
|
302
272
|
return {"NS": sorted([str(item) for item in value])}
|
|
303
273
|
elif all(isinstance(item, bytes) for item in value):
|
|
304
|
-
return {
|
|
305
|
-
|
|
306
|
-
[base64.b64encode(item).decode("utf-8") for item in value]
|
|
307
|
-
)
|
|
308
|
-
}
|
|
309
|
-
raise ValueError(
|
|
310
|
-
"Set contains mixed types or unsupported types for DynamoDB Sets."
|
|
311
|
-
)
|
|
274
|
+
return {"BS": sorted([base64.b64encode(item).decode("utf-8") for item in value])}
|
|
275
|
+
raise ValueError("Set contains mixed types or unsupported types for DynamoDB Sets.")
|
|
312
276
|
else:
|
|
313
|
-
raise TypeError(
|
|
314
|
-
f"Unsupported Python type for DynamoDB conversion: {type(value)}"
|
|
315
|
-
)
|
|
277
|
+
raise TypeError(f"Unsupported Python type for DynamoDB conversion: {type(value)}")
|
|
316
278
|
|
|
317
|
-
def _parse_condition_list(self, list_string: str) ->
|
|
318
|
-
"""
|
|
319
|
-
Parses a string representation of a list into a list of strings.
|
|
320
|
-
"""
|
|
279
|
+
def _parse_condition_list(self, list_string: str) -> list[str]:
|
|
280
|
+
"""Parse a string representation of a list into a list of strings."""
|
|
321
281
|
if not list_string.strip():
|
|
322
282
|
return []
|
|
323
283
|
|
|
@@ -326,7 +286,7 @@ class DynamoDBConditionParser(ConditionParser):
|
|
|
326
286
|
if not list_string.strip():
|
|
327
287
|
return []
|
|
328
288
|
|
|
329
|
-
items:
|
|
289
|
+
items: list[str] = []
|
|
330
290
|
current_item: str = ""
|
|
331
291
|
in_quotes: bool = False
|
|
332
292
|
quote_char: str = ""
|