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
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
|
|
11
|
+
logger = Logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NumberUtility:
|
|
15
|
+
"""
|
|
16
|
+
Number Utility.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def is_numeric(value: str | None | int | float) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Determines if a value is a number or not. This will remove any dashes, periods "-", "."
|
|
23
|
+
and then determine if the value is numeric
|
|
24
|
+
Args:
|
|
25
|
+
value (str): string value of a number
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
bool: _description_
|
|
29
|
+
"""
|
|
30
|
+
if value is None:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
if value is None:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
if str(value).isnumeric():
|
|
37
|
+
return True
|
|
38
|
+
try:
|
|
39
|
+
float(value)
|
|
40
|
+
return True
|
|
41
|
+
except: # noqa: E722, pylint: disable=w0702
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def are_numeric(values: List[int] | List[str] | List[float]) -> bool:
|
|
46
|
+
"""determines if a set of values are numeric or not"""
|
|
47
|
+
for value in values:
|
|
48
|
+
if not NumberUtility.is_numeric(value):
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def to_number_or_none(value: str) -> float | int | None:
|
|
55
|
+
"""Converts a string to a number."""
|
|
56
|
+
if value is None:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
if str(value).lower() == "nan":
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
numeric_value = float(value)
|
|
64
|
+
# Check if the number is an integer (e.g., 7.0) and return as int
|
|
65
|
+
if numeric_value.is_integer():
|
|
66
|
+
return int(numeric_value)
|
|
67
|
+
return numeric_value
|
|
68
|
+
except (ValueError, TypeError):
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def to_float(
|
|
73
|
+
value: str | float | int, raise_errors: Optional[bool] = False
|
|
74
|
+
) -> float:
|
|
75
|
+
"""_summary_
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
value (str | float | int): _description_
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
float: returns a float of zero.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
return float(value)
|
|
87
|
+
except: # noqa: E722, pylint: disable=w0702
|
|
88
|
+
logger.error(f"Unable to convert {value} to float")
|
|
89
|
+
if raise_errors:
|
|
90
|
+
raise
|
|
91
|
+
return 0.0
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def get_max_length(items: List[str] | List[int] | List[float]) -> int:
|
|
95
|
+
"""Returns the max length of an item in a list."""
|
|
96
|
+
length = 0
|
|
97
|
+
for item in items:
|
|
98
|
+
if len(str(item)) > length:
|
|
99
|
+
length = len(str(item))
|
|
100
|
+
|
|
101
|
+
return length
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def to_significant_digits_rounded(value, significant_digits=10):
|
|
105
|
+
"""
|
|
106
|
+
converts to significant digits
|
|
107
|
+
"""
|
|
108
|
+
result = math.floor(value * 10**significant_digits) / 10**significant_digits
|
|
109
|
+
to_the_right = len(f"{int(value)}")
|
|
110
|
+
f_string_number = (
|
|
111
|
+
f"{value:.{significant_digits + to_the_right}g}" # Using f-string
|
|
112
|
+
)
|
|
113
|
+
result = float(f_string_number)
|
|
114
|
+
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def get_number_of_decimal_places(number: float) -> int:
|
|
119
|
+
"""
|
|
120
|
+
Gets the number decimal places
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
number (float): the number to inspect
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
int: number of decimal places
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
number_str = f"{number}"
|
|
130
|
+
if "." in number_str:
|
|
131
|
+
to_the_right = number_str.split(".")[1]
|
|
132
|
+
return len(to_the_right)
|
|
133
|
+
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def to_significant_digits(number: float, significant_digits: int = 10):
|
|
138
|
+
"""To Significat Digits"""
|
|
139
|
+
# make sure we're dealing with a number
|
|
140
|
+
number = float(number)
|
|
141
|
+
if significant_digits < 0:
|
|
142
|
+
raise ValueError("Decimal places must be non-negative")
|
|
143
|
+
else:
|
|
144
|
+
number_str = f"{number}"
|
|
145
|
+
if "." in number_str:
|
|
146
|
+
decimal_point_index = number_str.index(".")
|
|
147
|
+
number_of_decimal_places = NumberUtility.get_number_of_decimal_places(
|
|
148
|
+
number
|
|
149
|
+
)
|
|
150
|
+
if number_of_decimal_places > significant_digits:
|
|
151
|
+
return NumberUtility.to_significant_digits_rounded(
|
|
152
|
+
number, significant_digits
|
|
153
|
+
)
|
|
154
|
+
else:
|
|
155
|
+
cutoff_index = decimal_point_index + significant_digits + 1
|
|
156
|
+
truncated_str = number_str[:cutoff_index]
|
|
157
|
+
return float(truncated_str)
|
|
158
|
+
else:
|
|
159
|
+
return number
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def is_decimal(value):
|
|
163
|
+
is_numeric = NumberUtility.is_numeric(value)
|
|
164
|
+
contains_decimal = "." in str(value)
|
|
165
|
+
|
|
166
|
+
return is_numeric and contains_decimal
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def percent_difference(number1, number2):
|
|
170
|
+
"""
|
|
171
|
+
Calculate the percent difference between two numbers.
|
|
172
|
+
|
|
173
|
+
Parameters:
|
|
174
|
+
- number1: The first number.
|
|
175
|
+
- number2: The second number.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
- The percent difference between the two numbers.
|
|
179
|
+
"""
|
|
180
|
+
number1 = float(number1)
|
|
181
|
+
number2 = float(number2)
|
|
182
|
+
# Calculate the absolute difference between the two numbers
|
|
183
|
+
difference = abs(number1 - number2)
|
|
184
|
+
|
|
185
|
+
# Calculate the average of the two numbers
|
|
186
|
+
average = (number1 + number2) / 2
|
|
187
|
+
|
|
188
|
+
if difference > 0:
|
|
189
|
+
# Calculate the percent difference
|
|
190
|
+
percent_diff = (difference / average) * 100
|
|
191
|
+
|
|
192
|
+
return percent_diff
|
|
193
|
+
|
|
194
|
+
return 0.0
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def to_number(
|
|
198
|
+
value: str | float | int,
|
|
199
|
+
raise_errors: Optional[bool] = False,
|
|
200
|
+
error_message: Optional[str] = None,
|
|
201
|
+
) -> int | float:
|
|
202
|
+
"""Converts a string to a number."""
|
|
203
|
+
try:
|
|
204
|
+
numeric_value = float(value)
|
|
205
|
+
# Check if the number is an integer (e.g., 7.0) and return as int
|
|
206
|
+
if numeric_value.is_integer():
|
|
207
|
+
return int(numeric_value)
|
|
208
|
+
return numeric_value
|
|
209
|
+
except Exception as e: # noqa: E722, pylint: disable=w0718
|
|
210
|
+
logger.error(f"Unable to convert {value} to number")
|
|
211
|
+
if raise_errors:
|
|
212
|
+
if error_message:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
f"Unable to convert {value} to number, {error_message}"
|
|
215
|
+
) from e
|
|
216
|
+
else:
|
|
217
|
+
raise ValueError(f"Unable to convert {value} to number") from e
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
@staticmethod
|
|
221
|
+
def to_significant_figure(
|
|
222
|
+
number: int | float | str, sig_figs: int
|
|
223
|
+
) -> int | float | str:
|
|
224
|
+
"""
|
|
225
|
+
Formats a number to it's significant figures.
|
|
226
|
+
Examples
|
|
227
|
+
12345.6789, 4 = 12350
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
number (int | float | str): a valid number
|
|
231
|
+
sig_figs (int): the number of signigicant figures
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
int | float: the value after applying a significant figure
|
|
235
|
+
"""
|
|
236
|
+
# just used for tracking
|
|
237
|
+
original_value = number
|
|
238
|
+
value: int | float | str = 0
|
|
239
|
+
if str(number).lower() == "nan":
|
|
240
|
+
return number
|
|
241
|
+
|
|
242
|
+
if NumberUtility.is_numeric(number) and isinstance(number, str):
|
|
243
|
+
number = NumberUtility.to_number(
|
|
244
|
+
number,
|
|
245
|
+
raise_errors=True,
|
|
246
|
+
error_message=(
|
|
247
|
+
f"Error attempting to set significant figure for value {number}"
|
|
248
|
+
f", sigfig {sig_figs}"
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if number == 0:
|
|
253
|
+
if sig_figs > 1:
|
|
254
|
+
value = "0." + "0" * (sig_figs - 1)
|
|
255
|
+
else:
|
|
256
|
+
value = 0
|
|
257
|
+
|
|
258
|
+
else:
|
|
259
|
+
scale = int(math.floor(math.log10(abs(float(number)))))
|
|
260
|
+
pre_power = int(scale - sig_figs + 1)
|
|
261
|
+
|
|
262
|
+
factor = 10 ** (pre_power)
|
|
263
|
+
rounded = round(number / factor) * factor
|
|
264
|
+
|
|
265
|
+
if not isinstance(rounded, int):
|
|
266
|
+
value = f"{rounded:.{sig_figs}g}"
|
|
267
|
+
else:
|
|
268
|
+
value = f"{rounded}"
|
|
269
|
+
|
|
270
|
+
if "." in f"{value}" and len(f"{value}") >= (sig_figs):
|
|
271
|
+
value = float(value)
|
|
272
|
+
elif "." in f"{value}" and len(f"{value}") <= (sig_figs + 1):
|
|
273
|
+
# there are more sig figures in the length of the figures
|
|
274
|
+
# adding 1 to account for the decimal place
|
|
275
|
+
value = float(value)
|
|
276
|
+
else:
|
|
277
|
+
# due to the scientific express we need to float it first
|
|
278
|
+
value = float(value)
|
|
279
|
+
value = int(value)
|
|
280
|
+
|
|
281
|
+
logger.debug(
|
|
282
|
+
{
|
|
283
|
+
"source": "",
|
|
284
|
+
"sig": sig_figs,
|
|
285
|
+
"value": {"original": original_value, "converted": value},
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return value
|
|
290
|
+
|
|
291
|
+
@staticmethod
|
|
292
|
+
def get_significant_figure(value) -> int:
|
|
293
|
+
"""
|
|
294
|
+
Calculate the number of significant figures in a number.
|
|
295
|
+
Removes leading and trailing zeros for float numbers.
|
|
296
|
+
"""
|
|
297
|
+
if not value:
|
|
298
|
+
return 0
|
|
299
|
+
if isinstance(value, int) or isinstance(value, int):
|
|
300
|
+
return len(str(value).strip("0"))
|
|
301
|
+
elif isinstance(value, float):
|
|
302
|
+
if value == 0:
|
|
303
|
+
return 0
|
|
304
|
+
else:
|
|
305
|
+
length: int = 0
|
|
306
|
+
if value > 1:
|
|
307
|
+
value_str = f"{value}"
|
|
308
|
+
digits = value_str
|
|
309
|
+
number = digits.split(".")
|
|
310
|
+
left = number[0].lstrip("0")
|
|
311
|
+
right = ""
|
|
312
|
+
if len(number) > 1:
|
|
313
|
+
right = f"{number[1].rstrip('0')}"
|
|
314
|
+
|
|
315
|
+
digits_stripped = f"{left}{right}"
|
|
316
|
+
# Remove decimal point and trailing zeros
|
|
317
|
+
length = len(digits_stripped)
|
|
318
|
+
|
|
319
|
+
else:
|
|
320
|
+
value_str = f"{value}"
|
|
321
|
+
# remove the "." remove all left and right decimals
|
|
322
|
+
# example: 0.0012 becomes 12 with sig digit of 2
|
|
323
|
+
digits_stripped = value_str.replace(".", "").rstrip("0").lstrip("0")
|
|
324
|
+
length = len(digits_stripped)
|
|
325
|
+
|
|
326
|
+
return length
|
|
327
|
+
|
|
328
|
+
else:
|
|
329
|
+
return 0
|