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
@@ -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