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,349 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import uuid
8
+ from datetime import UTC, datetime, timedelta, timezone
9
+ from typing import Any
10
+ import pytz # type: ignore
11
+ from aws_lambda_powertools import Logger
12
+ from dateutil.relativedelta import relativedelta
13
+
14
+
15
+ logger = Logger()
16
+
17
+ _last_timestamp = None
18
+
19
+
20
+ class DatetimeUtility:
21
+ @staticmethod
22
+ def get_elapsed_time(start: datetime, end=None) -> str:
23
+ end = end or DatetimeUtility.get_utc_now()
24
+ delta: timedelta = end - start
25
+
26
+ total_seconds = delta.total_seconds()
27
+ days = int(total_seconds // (3600 * 24))
28
+ total_seconds %= 3600 * 24
29
+ hours = int(total_seconds // 3600)
30
+ total_seconds %= 3600
31
+ minutes = int(total_seconds // 60)
32
+ seconds = int(total_seconds % 60)
33
+ milliseconds = int(delta.microseconds / 1000)
34
+ timespan = f"{days} days, {hours} hours, {minutes} minutes, {seconds} seconds, {milliseconds} milliseconds"
35
+
36
+ return timespan
37
+
38
+ @staticmethod
39
+ def get_start_time() -> datetime:
40
+ return DatetimeUtility.get_utc_now()
41
+
42
+ @staticmethod
43
+ def get_utc_now() -> datetime:
44
+ # datetime.utcnow()
45
+ # below is the preferred over datetime.utcnow()
46
+ return datetime.now(timezone.utc)
47
+
48
+ @staticmethod
49
+ def string_to_date(string_date: str | datetime) -> datetime | None:
50
+ """
51
+ Description: takes a string value and returns it as a datetime.
52
+ If the value is already a datetime type, it will return it as is, otherwise
53
+ the returned value is None
54
+ string_date: str must be in format of %Y-%m-%dT%H:%M:%S.%f
55
+ """
56
+
57
+ if not string_date or str(string_date) == "None":
58
+ return None
59
+
60
+ if isinstance(string_date, datetime):
61
+ return string_date
62
+
63
+ if "Z" in str(string_date):
64
+ string_date = str(string_date).replace("Z", "+00:00")
65
+ string_date = str(string_date)
66
+ string_date = string_date.replace(" ", "T")
67
+ string_date = string_date.replace("Z", "")
68
+ string_date = string_date.replace("+00:00", "")
69
+ # todo determine the format
70
+ date_formats = [
71
+ "%Y-%m-%dT%H:%M:%S.%f",
72
+ "%Y-%m-%dT%H:%M:%S",
73
+ "%Y-%m-%d",
74
+ "%m-%d-%Y",
75
+ "%m-%d-%y",
76
+ "%Y/%m/%d",
77
+ "%m/%d/%Y",
78
+ "%m/%d/%y",
79
+ ]
80
+
81
+ result: datetime | None = None
82
+ try:
83
+ if isinstance(string_date, str):
84
+ for date_format in date_formats:
85
+ try:
86
+ result = datetime.strptime(string_date, date_format)
87
+ break
88
+ except ValueError:
89
+ pass
90
+ # if nothing the we need to raise an error
91
+ if result is None:
92
+ raise ValueError(f"Unable to parse date: {string_date}")
93
+
94
+ elif isinstance(string_date, datetime):
95
+ result = string_date
96
+ else:
97
+ logger.warning(
98
+ {
99
+ "metric_filter": "string_to_date_warning",
100
+ "datetime": string_date,
101
+ "action": "returning none",
102
+ }
103
+ )
104
+ except Exception as e: # noqa: E722, pylint: disable=W0718
105
+ msg = {
106
+ "metric_filter": "string_to_date_error",
107
+ "datetime": string_date,
108
+ "error": str(e),
109
+ "action": "returning none",
110
+ "type": type(string_date).__name__,
111
+ "accepted_formats": date_formats,
112
+ }
113
+ logger.error(msg=msg)
114
+
115
+ raise RuntimeError(msg) from e
116
+
117
+ return result
118
+
119
+ @staticmethod
120
+ def to_datetime(
121
+ value, default: datetime | None = None, tzinfo=UTC
122
+ ) -> datetime | None:
123
+ """
124
+ Description: takes a value and attempts to turn it into a datetime object
125
+ Returns: datetime or None
126
+ """
127
+
128
+ result = DatetimeUtility.string_to_date(value)
129
+
130
+ if result is None and default is not None:
131
+ if not isinstance(default, datetime):
132
+ default = DatetimeUtility.string_to_date(value)
133
+ result = default
134
+
135
+ if result and isinstance(result, datetime):
136
+ result = result.replace(tzinfo=tzinfo)
137
+
138
+ return result
139
+
140
+ @staticmethod
141
+ def to_datetime_utc(value, default: datetime | None = None) -> datetime | None:
142
+ """
143
+ Description: takes a value and attempts to turn it into a datetime object
144
+ Returns: datetime or None
145
+ """
146
+
147
+ result = DatetimeUtility.to_datetime(value, default, tzinfo=UTC)
148
+ return result
149
+
150
+ @staticmethod
151
+ def to_date_string(value: Any, default: datetime | None | str = None) -> str | None:
152
+ """
153
+ Description: takes a value and attempts to turn it into a datetime object
154
+ Returns: datetime or None
155
+ """
156
+ value = DatetimeUtility.to_datetime(value=value)
157
+ result = DatetimeUtility.to_string(value, date_format="%Y-%m-%d")
158
+ if result is None and default is not None:
159
+ if isinstance(default, datetime):
160
+ result = DatetimeUtility.to_string(default, date_format="%Y-%m-%d")
161
+
162
+ return result
163
+
164
+ @staticmethod
165
+ def to_time_string(value, default: datetime | None = None) -> str | None:
166
+ """
167
+ Description: takes a value and attempts to turn it into a datetime object
168
+ Returns: datetime or None
169
+ """
170
+ value = DatetimeUtility.to_datetime(value=value)
171
+ result = f"{DatetimeUtility.to_string(value, date_format='%H:%M:%S')}+00:00"
172
+ if result is None and default is not None:
173
+ result = default
174
+
175
+ return result
176
+
177
+ @staticmethod
178
+ def to_string(
179
+ value: datetime, date_format: str = "%Y-%m-%d-%H-%M-%S-%f"
180
+ ) -> str | None:
181
+ """
182
+ Description: takes a string value and returns it as a datetime.
183
+ If the value is already a datetime type, it will return it as is, otherwise
184
+ the returned value is None
185
+ """
186
+ # todo determine the format
187
+ if not value:
188
+ return None
189
+
190
+ result = value.strftime(date_format)
191
+
192
+ return result
193
+
194
+ @staticmethod
195
+ def datetime_from_uuid1(uuid1: uuid.UUID) -> datetime:
196
+ """
197
+ Converts a uuid1 to a datetime
198
+ """
199
+ ns = 0x01B21DD213814000
200
+ timestamp = datetime.fromtimestamp(
201
+ (uuid1.time - ns) * 100 / 1e9, tz=timezone.utc
202
+ )
203
+ return timestamp
204
+
205
+ @staticmethod
206
+ def fromtimestamp(value: float, default=None) -> datetime:
207
+ result = default
208
+ try:
209
+ if "-" in str(value):
210
+ value = float(str(value).replace("-", "."))
211
+ result = datetime.fromtimestamp(float(value))
212
+ except Exception as e:
213
+ logger.error(str(e))
214
+ pass
215
+
216
+ return result
217
+
218
+ @staticmethod
219
+ def uuid1_utc(node=0, clock_seq=0, timestamp=None):
220
+ global _last_timestamp # pylint: disable=w0603
221
+
222
+ if not timestamp:
223
+ timestamp = float(DatetimeUtility.get_utc_now().timestamp())
224
+ if isinstance(timestamp, datetime):
225
+ timestamp = timestamp.timestamp()
226
+
227
+ nanoseconds = int(timestamp * 1e9)
228
+ # import time
229
+ # t = time.time_ns()
230
+ # ns = int(t * 1e9)
231
+
232
+ # 0x01b21dd213814000 is the number of 100-ns intervals between the
233
+ # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
234
+ timestamp = nanoseconds // 100 + 0x01B21DD213814000
235
+ if _last_timestamp is not None and timestamp <= _last_timestamp:
236
+ timestamp = _last_timestamp + 1
237
+ _last_timestamp = timestamp
238
+ if clock_seq is None:
239
+ import random
240
+
241
+ clock_seq = random.getrandbits(14) # instead of stable storage
242
+ time_low = timestamp & 0xFFFFFFFF
243
+ time_mid = (timestamp >> 32) & 0xFFFF
244
+ time_hi_version = (timestamp >> 48) & 0x0FFF
245
+ clock_seq_low = clock_seq & 0xFF
246
+ clock_seq_hi_variant = (clock_seq >> 8) & 0x3F
247
+ if node is None:
248
+ node = uuid.getnode()
249
+ return uuid.UUID(
250
+ fields=(
251
+ time_low,
252
+ time_mid,
253
+ time_hi_version,
254
+ clock_seq_hi_variant,
255
+ clock_seq_low,
256
+ node,
257
+ ),
258
+ version=1,
259
+ )
260
+
261
+ @staticmethod
262
+ def add_month(dt: datetime, months: int = 1) -> datetime:
263
+ """Add a month to the current date
264
+
265
+ Args:
266
+ dt (datetime): datetime
267
+ months (int): the number of months
268
+
269
+ Returns:
270
+ datetime: X Month(s) added to the input dt
271
+ """
272
+ new_date = dt + relativedelta(months=+months)
273
+ new_date = new_date + relativedelta(microseconds=-1)
274
+
275
+ return new_date
276
+
277
+ @staticmethod
278
+ def add_days(dt: datetime, days: int = 1) -> datetime:
279
+ """Add a day to the current date
280
+
281
+ Args:
282
+ dt (datetime): datetime
283
+ days (int): the number of days, use a negative number to subtract
284
+
285
+ Returns:
286
+ datetime: X days added to the input dt
287
+ """
288
+ new_date = dt + relativedelta(days=+days)
289
+ new_date = new_date + relativedelta(microseconds=-1)
290
+
291
+ return new_date
292
+
293
+ @staticmethod
294
+ def add_minutes(dt: datetime, minutes: int = 1) -> datetime:
295
+ """Add a month to the current date
296
+
297
+ Args:
298
+ dt (datetime): datetime
299
+ months (int): the number of months
300
+
301
+ Returns:
302
+ datetime: One Month added to the input dt
303
+ """
304
+ new_date = dt + relativedelta(minutes=+minutes)
305
+ new_date = new_date + relativedelta(microseconds=-1)
306
+
307
+ return new_date
308
+
309
+ @staticmethod
310
+ def to_timezone(utc_datetime: datetime, timezone_name: str) -> datetime:
311
+ """_summary_
312
+
313
+ Args:
314
+ utc_datetime (datetime): datetime in utc
315
+ timezone (str): 'US/Eastern', 'US/Mountain', etc
316
+
317
+ Returns:
318
+ datetime: in the correct timezone
319
+ """
320
+
321
+ tz = pytz.timezone(timezone_name)
322
+ result = utc_datetime.astimezone(tz)
323
+ return result
324
+
325
+ @staticmethod
326
+ def get_timestamp(value: datetime | None | str) -> float:
327
+ """Get a timestamp from a date or 0.0"""
328
+ if value is None:
329
+ return 0.0
330
+ if not isinstance(value, datetime):
331
+ value = DatetimeUtility.to_datetime_utc(value=value)
332
+
333
+ if not isinstance(value, datetime):
334
+ return 0.0
335
+ ts = value.timestamp()
336
+ return ts
337
+
338
+ @staticmethod
339
+ def get_timestamp_or_none(value: datetime | None | str) -> float | None:
340
+ """Get a timestamp from a date or None"""
341
+ if value is None:
342
+ return None
343
+ if not isinstance(value, datetime):
344
+ value = DatetimeUtility.to_datetime_utc(value=value)
345
+
346
+ if not isinstance(value, datetime):
347
+ return None
348
+ ts = value.timestamp()
349
+ return ts
@@ -0,0 +1,140 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from decimal import Decimal
8
+ from typing import Any, Dict, List, Union
9
+
10
+
11
+ class DecimalConversionUtility:
12
+ """
13
+ Utility class for handling decimal conversions between Python types and DynamoDB.
14
+
15
+ DynamoDB stores all numbers as Decimal types, but Python applications often
16
+ expect int or float types. This utility provides conversion methods to handle
17
+ the transformation seamlessly.
18
+ """
19
+
20
+ @staticmethod
21
+ def convert_decimals_to_native_types(data: Any) -> Any:
22
+ """
23
+ Recursively converts Decimal objects to native Python types (int or float).
24
+
25
+ This is typically used when deserializing data from DynamoDB, where all
26
+ numbers are stored as Decimal objects but the application expects native
27
+ Python numeric types.
28
+
29
+ Args:
30
+ data: The data structure to convert. Can be dict, list, or any other type.
31
+
32
+ Returns:
33
+ The data structure with Decimal objects converted to int or float.
34
+ """
35
+ if isinstance(data, dict):
36
+ return {
37
+ key: DecimalConversionUtility.convert_decimals_to_native_types(value)
38
+ for key, value in data.items()
39
+ }
40
+ elif isinstance(data, list):
41
+ return [
42
+ DecimalConversionUtility.convert_decimals_to_native_types(item)
43
+ for item in data
44
+ ]
45
+ elif isinstance(data, Decimal):
46
+ # Convert Decimal to int if it's a whole number, otherwise to float
47
+ if data % 1 == 0:
48
+ return int(data)
49
+ else:
50
+ return float(data)
51
+ else:
52
+ return data
53
+
54
+ @staticmethod
55
+ def convert_native_types_to_decimals(data: Any) -> Any:
56
+ """
57
+ Recursively converts native Python numeric types (int, float) to Decimal objects.
58
+
59
+ This is typically used when serializing data for DynamoDB, where all
60
+ numbers should be stored as Decimal objects for precision.
61
+
62
+ Args:
63
+ data: The data structure to convert. Can be dict, list, or any other type.
64
+
65
+ Returns:
66
+ The data structure with int and float objects converted to Decimal.
67
+ """
68
+ if isinstance(data, dict):
69
+ return {
70
+ key: DecimalConversionUtility.convert_native_types_to_decimals(value)
71
+ for key, value in data.items()
72
+ }
73
+ elif isinstance(data, list):
74
+ return [
75
+ DecimalConversionUtility.convert_native_types_to_decimals(item)
76
+ for item in data
77
+ ]
78
+ elif isinstance(data, float):
79
+ # Convert float to Decimal using string representation for precision
80
+ return Decimal(str(data))
81
+ elif isinstance(data, int) and not isinstance(data, bool):
82
+ # Convert int to Decimal, but exclude bool (which is a subclass of int)
83
+ return Decimal(data)
84
+ else:
85
+ return data
86
+
87
+ @staticmethod
88
+ def is_numeric_type(value: Any) -> bool:
89
+ """
90
+ Check if a value is a numeric type (int, float, or Decimal).
91
+
92
+ Args:
93
+ value: The value to check.
94
+
95
+ Returns:
96
+ True if the value is a numeric type, False otherwise.
97
+ """
98
+ return isinstance(value, (int, float, Decimal)) and not isinstance(value, bool)
99
+
100
+ @staticmethod
101
+ def safe_decimal_conversion(value: Any, default: Any = None) -> Union[Decimal, Any]:
102
+ """
103
+ Safely convert a value to Decimal, returning a default if conversion fails.
104
+
105
+ Args:
106
+ value: The value to convert to Decimal.
107
+ default: The default value to return if conversion fails.
108
+
109
+ Returns:
110
+ Decimal representation of the value, or the default if conversion fails.
111
+ """
112
+ try:
113
+ if isinstance(value, Decimal):
114
+ return value
115
+ elif isinstance(value, (int, float)):
116
+ return Decimal(str(value))
117
+ elif isinstance(value, str):
118
+ return Decimal(value)
119
+ else:
120
+ return default
121
+ except (ValueError, TypeError, ArithmeticError):
122
+ return default
123
+
124
+ @staticmethod
125
+ def format_decimal_for_display(value: Decimal, precision: int = 2) -> str:
126
+ """
127
+ Format a Decimal value for display with specified precision.
128
+
129
+ Args:
130
+ value: The Decimal value to format.
131
+ precision: Number of decimal places to display.
132
+
133
+ Returns:
134
+ Formatted string representation of the Decimal.
135
+ """
136
+ if not isinstance(value, Decimal):
137
+ value = DecimalConversionUtility.safe_decimal_conversion(value, Decimal('0'))
138
+
139
+ format_string = f"{{:.{precision}f}}"
140
+ return format_string.format(float(value))
@@ -0,0 +1,32 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from typing import List
8
+
9
+
10
+ class DictionaryUtilitiy:
11
+ """
12
+ A class to provide utility methods for working with dictionaries.
13
+ """
14
+
15
+ @staticmethod
16
+ def find_dict_by_name(
17
+ dict_list: List[dict], key_field: str, name: str
18
+ ) -> List[dict] | dict | str:
19
+ """
20
+ Searches for dictionaries in a list where the key 'name' matches the specified value.
21
+
22
+ Args:
23
+ dict_list (list): A list of dictionaries to search through.
24
+ key_field (str): The key to search for in each dictionary.
25
+ name (str): The value to search for in the 'key_field' key.
26
+
27
+ Returns:
28
+ list: A list of dictionaries where the 'key_field' key matches the specified value.
29
+ """
30
+ # List comprehension to filter dictionaries that have the 'name' key equal to the specified name
31
+
32
+ return [d for d in dict_list if d.get(key_field) == name]
@@ -0,0 +1,135 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ import os
8
+
9
+ import shutil
10
+ import tempfile
11
+
12
+
13
+ from aws_lambda_powertools import Logger
14
+
15
+ logger = Logger()
16
+
17
+
18
+ class FileOperations:
19
+ """
20
+ General File Operations
21
+ """
22
+
23
+ def __init__(self) -> None:
24
+ pass
25
+
26
+ @staticmethod
27
+ def makedirs(path):
28
+ """Create a directory and all sub directories."""
29
+ abs_path = os.path.abspath(path)
30
+ os.makedirs(abs_path, exist_ok=True)
31
+
32
+ @staticmethod
33
+ def clean_directory(path: str):
34
+ """Clean / Delete all files and directories and sub directories"""
35
+ if path is None:
36
+ return
37
+ if path == "/":
38
+ raise ValueError("Cannot delete root directory")
39
+
40
+ abs_path = os.path.abspath(path)
41
+ if os.path.exists(abs_path):
42
+ items = os.listdir(abs_path)
43
+ for item in items:
44
+ path = os.path.join(abs_path, item)
45
+ if os.path.exists(path):
46
+ try:
47
+ if os.path.isdir(path):
48
+ shutil.rmtree(path)
49
+ elif os.path.isfile(path):
50
+ os.remove(path)
51
+
52
+ except Exception as e: # pylint: disable=W0718
53
+ logger.exception(f"clean up error {str(e)}")
54
+
55
+ @staticmethod
56
+ def get_directory_name(path: str):
57
+ """
58
+ Get the directory path from a path that is either a directory
59
+ or a path to a file.
60
+ """
61
+ dirname = os.path.dirname(path)
62
+ return dirname
63
+
64
+ @staticmethod
65
+ def read_file(path: str, encoding: str = "utf-8") -> str:
66
+ """
67
+ Read a file
68
+ """
69
+ logger.debug(f"reading file {path}")
70
+ with open(path, "r", encoding=encoding) as file:
71
+ data = file.read()
72
+ return data
73
+
74
+ @staticmethod
75
+ def write_to_file(path: str, data: str, append: bool = False) -> str:
76
+ """
77
+ Write to a file
78
+
79
+ """
80
+ return FileOperations.write_file(path=path, output=data, append=append)
81
+
82
+ @staticmethod
83
+ def write_file(path: str, output: str, append: bool = False) -> str:
84
+ """
85
+ Writes to a file
86
+ Args:
87
+ path (str): path
88
+ output (str): text to write to the file
89
+ append (bool): if true this operation will append to the file
90
+ otherwise it will overwrite. the default is to overwrite
91
+ Returns:
92
+ str: path to the file
93
+ """
94
+ dirname = FileOperations.get_directory_name(path)
95
+ FileOperations.makedirs(dirname)
96
+ mode = "a" if append else "w"
97
+
98
+ if output is None:
99
+ output = ""
100
+ with open(path, mode=mode, encoding="utf-8") as file:
101
+ file.write(output)
102
+
103
+ return path
104
+
105
+ @staticmethod
106
+ def get_file_extension(file_name: str, include_dot: bool = False):
107
+ """Get the extension of a file"""
108
+ logger.debug(f"getting extension for {file_name}")
109
+ # get the last part of a string after a period .
110
+ extention = os.path.splitext(file_name)[1]
111
+ logger.debug(f"extention is {extention}")
112
+
113
+ if not include_dot:
114
+ if str(extention).startswith("."):
115
+ extention = str(extention).removeprefix(".")
116
+ logger.debug(f"extension after prefix removal: {extention}")
117
+
118
+ return extention
119
+
120
+ @staticmethod
121
+ def get_tmp_directory() -> str:
122
+ """
123
+ Get the temp directory
124
+ """
125
+ # are we in an aws lambda function?
126
+ if os.environ.get("AWS_LAMBDA_FUNCTION_NAME"):
127
+ # we are in a lambda function /tmp is the only place
128
+ # we can write to
129
+ if not os.path.exists("/tmp"):
130
+ raise ValueError("Temp directory does not exist.")
131
+
132
+ tmp_dir = "/tmp"
133
+ return tmp_dir
134
+
135
+ return tempfile.gettempdir()
@@ -0,0 +1,48 @@
1
+ """
2
+ Geek Cafe, LLC
3
+ Maintainers: Eric Wilson
4
+ MIT License. See Project Root for the license information.
5
+ """
6
+
7
+ from urllib.parse import unquote
8
+ from typing import Dict, Any
9
+
10
+
11
+ class HttpUtility:
12
+ """HTTP Utilities"""
13
+
14
+ @staticmethod
15
+ def get_query_params(query_string: str) -> Dict[str, Any]:
16
+ """
17
+ Get the query parameters from a query string
18
+ returns a dictionary of key value pairs
19
+ """
20
+ if not query_string:
21
+ return {}
22
+
23
+ params = {}
24
+ if query_string:
25
+ for param in query_string.split("&"):
26
+ key, value = param.split("=")
27
+ params[key] = unquote(value)
28
+ return params
29
+
30
+ @staticmethod
31
+ def get_query_param(query_string: str | None, key: str) -> str | None:
32
+ """Get a query parameter from a query string"""
33
+
34
+ if not query_string:
35
+ return None
36
+ params = HttpUtility.get_query_params(query_string)
37
+ if key in params:
38
+ return params[key]
39
+ return None
40
+
41
+ @staticmethod
42
+ def decode_url(url: str):
43
+ """Decodes a URL"""
44
+
45
+ # sometimes a paylaod will have a + added instead of the space
46
+ # or the space encoded value of %2B
47
+ url = url.replace("+", " ")
48
+ return unquote(url)
File without changes