boto3-assist 0.6.0__py3-none-any.whl → 0.7.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.
@@ -1,16 +1,15 @@
1
1
  """Serialization Utility"""
2
2
 
3
- from datetime import datetime
4
- from decimal import Decimal
5
- from typing import Dict, List, TypeVar, Any
6
- import json
7
- import jsons
8
3
  import datetime as dt
9
4
  import decimal
10
5
  import inspect
6
+ import json
11
7
  import uuid
12
- from aws_lambda_powertools import Logger
8
+ from datetime import datetime
9
+ from decimal import Decimal
10
+ from typing import Any, Dict, List, TypeVar
13
11
 
12
+ from aws_lambda_powertools import Logger
14
13
 
15
14
  T = TypeVar("T")
16
15
 
@@ -53,6 +52,15 @@ class SerializableModel:
53
52
  instance=self, serialize_fn=lambda x: x, include_none=True
54
53
  )
55
54
 
55
+ def to_wide_dictionary(self) -> Dict:
56
+ """
57
+ Dumps an object to dictionary structure
58
+ """
59
+
60
+ dump = Serialization.to_wide_dictionary(model=self)
61
+
62
+ return dump
63
+
56
64
 
57
65
  class JsonEncoder(json.JSONEncoder):
58
66
  """
@@ -108,6 +116,32 @@ class Serialization:
108
116
 
109
117
  return dump
110
118
 
119
+ @staticmethod
120
+ def to_wide_dictionary(model: object) -> Dict:
121
+ """
122
+ Dumps an object to dictionary structure
123
+ """
124
+
125
+ dump = Serialization.to_dict(
126
+ instance=model, serialize_fn=lambda x: x, include_none=True
127
+ )
128
+
129
+ # have a dictionary now let's flatten out
130
+ flat_dict = {}
131
+ for key, value in dump.items():
132
+ if isinstance(value, dict):
133
+ for sub_key, sub_value in value.items():
134
+ flat_dict[f"{key}_{sub_key}"] = sub_value
135
+ elif isinstance(value, list):
136
+ for i, sub_value in enumerate(value):
137
+ sub_dict = Serialization.to_wide_dictionary(sub_value)
138
+ for sub_key, sub_value in sub_dict.items():
139
+ flat_dict[f"{key}_{i}_{sub_key}"] = sub_value
140
+ else:
141
+ flat_dict[key] = value
142
+
143
+ return flat_dict
144
+
111
145
  @staticmethod
112
146
  def map(source: object, target: T, coerce: bool = True) -> T | None:
113
147
  """Map an object from one object to another"""
@@ -118,12 +152,92 @@ class Serialization:
118
152
  source_dict = Serialization.convert_object_to_dict(source)
119
153
  if not isinstance(source_dict, dict):
120
154
  return None
121
- return Serialization.load_properties(
155
+ return Serialization._load_properties(
122
156
  source=source_dict, target=target, coerce=coerce
123
157
  )
124
158
 
125
159
  @staticmethod
126
- def load_properties(
160
+ def to_wide_dictionary_list(
161
+ data: Dict[str, Any] | List[Dict[str, Any]],
162
+ remove_collisions: bool = True,
163
+ raise_error_on_collision: bool = False,
164
+ ) -> List[Dict[str, Any]]:
165
+ """
166
+ Converts a dictionary or list of dictionaries to a list of dictionaries.
167
+
168
+ :param data: Dictionary or list of dictionaries to be converted
169
+ :param remove_collisions: If True, removes duplicate keys from the dictionaries
170
+ :return: List of dictionaries
171
+ """
172
+
173
+ collisions = []
174
+
175
+ def recursive_flatten(prefix, obj):
176
+ """
177
+ Recursively flattens a JSON object.
178
+
179
+ :param prefix: Current key prefix
180
+ :param obj: Object to flatten
181
+ :return: List of flattened dictionaries
182
+ """
183
+ if isinstance(obj, list):
184
+ result = []
185
+ for _, item in enumerate(obj):
186
+ x = recursive_flatten("", item)
187
+ result.extend(x)
188
+ return result
189
+ elif isinstance(obj, dict):
190
+ result = [{}]
191
+ for key, value in obj.items():
192
+ sub_result = recursive_flatten(
193
+ f"{prefix}_{key}" if prefix else key, value
194
+ )
195
+ new_result = []
196
+ for entry in result:
197
+ for sub_entry in sub_result:
198
+ # remove any collisions
199
+
200
+ for k in entry:
201
+ if k in sub_entry:
202
+ if k not in collisions:
203
+ logger.debug(f"Collision detected: {k}")
204
+ collisions.append(k)
205
+ merged = entry.copy()
206
+ merged.update(sub_entry)
207
+ new_result.append(merged)
208
+ result = new_result
209
+ return result
210
+ else:
211
+ return [{prefix: obj}] if prefix else []
212
+
213
+ results = recursive_flatten("", data)
214
+ if remove_collisions:
215
+ results = Serialization.remove_collisions(results, collisions)
216
+
217
+ if raise_error_on_collision and len(collisions) > 0:
218
+ raise ValueError(f"Duplicate keys detected: {collisions}")
219
+
220
+ return results
221
+
222
+ @staticmethod
223
+ def remove_collisions(
224
+ data: List[Dict[str, Any]], collisions: List[str]
225
+ ) -> List[Dict[str, Any]]:
226
+ """
227
+ Removes collisions from a list of dictionaries.
228
+
229
+ :param data: List of dictionaries
230
+ :param collisions: List of collision keys
231
+ :return: List of dictionaries with collisions removed
232
+ """
233
+ for c in collisions:
234
+ for r in data:
235
+ if c in r:
236
+ del r[c]
237
+ return data
238
+
239
+ @staticmethod
240
+ def _load_properties(
127
241
  source: dict,
128
242
  target: T,
129
243
  coerce: bool = True,
@@ -183,9 +297,9 @@ class Serialization:
183
297
  attr.clear()
184
298
  attr.extend(value)
185
299
  elif isinstance(attr, dict) and isinstance(value, dict):
186
- Serialization.load_properties(value, attr, coerce=coerce)
300
+ Serialization._load_properties(value, attr, coerce=coerce)
187
301
  elif hasattr(attr, "__dict__") and isinstance(value, dict):
188
- Serialization.load_properties(value, attr, coerce=coerce)
302
+ Serialization._load_properties(value, attr, coerce=coerce)
189
303
  else:
190
304
  setattr(target, key, value)
191
305
  except ValueError as e:
@@ -230,12 +344,18 @@ class Serialization:
230
344
 
231
345
  @staticmethod
232
346
  def to_dict(
233
- instance: SerializableModel,
347
+ instance: SerializableModel | dict,
234
348
  serialize_fn,
235
349
  include_none: bool = True,
236
350
  ) -> Dict[str, Any]:
237
351
  """To Dict / Dictionary"""
238
352
 
353
+ if instance is None:
354
+ return {}
355
+
356
+ if isinstance(instance, dict):
357
+ return instance
358
+
239
359
  def is_primitive(value):
240
360
  """Check if the value is a primitive data type."""
241
361
  return isinstance(value, (str, int, bool, type(None)))
@@ -195,6 +195,36 @@ class StringUtility:
195
195
  hash_object.update(encoded_string)
196
196
  return hash_object.hexdigest()
197
197
 
198
+ @staticmethod
199
+ def generate_idempotent_uuid(
200
+ namespace: uuid.UUID | str, unique_string: str, case_sensitive: bool = False
201
+ ) -> str:
202
+ """
203
+ Generates an idempotnent UUID, which is useful for creates
204
+
205
+ Args:
206
+ namespace (GUID | str): A namespace for your id, it must be a UUID or a string in a UUID format
207
+ unique_string (str): A unique string like an email address, a tenant name.
208
+ Use a combination for more granularity:
209
+ tenant-name:email
210
+ vendor:product-name
211
+ vendor:product-id
212
+ etc
213
+
214
+ Returns:
215
+ str: a string representation of a UUID
216
+ """
217
+ if isinstance(namespace, str):
218
+ namespace = uuid.UUID(namespace)
219
+
220
+ if not unique_string:
221
+ raise ValueError("unique_string cannot be empty")
222
+
223
+ if not case_sensitive:
224
+ unique_string = unique_string.lower()
225
+
226
+ return str(uuid.uuid5(namespace, unique_string))
227
+
198
228
  @staticmethod
199
229
  def get_size_in_kb(input_string: str | dict) -> float:
200
230
  """
boto3_assist/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.6.0'
1
+ __version__ = '0.7.0'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Additional boto3 wrappers to make your life a little easier
5
5
  Author-email: Eric Wilson <boto3-assist@geekcafe.com>
6
6
  License-File: LICENSE-EXPLAINED.txt
@@ -1,9 +1,9 @@
1
1
  boto3_assist/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  boto3_assist/boto3session.py,sha256=Q9sByNC0r_aMQfHnIEnxtTaiCMUqikm8UeSTxV7-Np0,6632
3
3
  boto3_assist/connection.py,sha256=CNGkAIRyfrELoWrV0ziQBA3oHacNFuLL3i8faUPRiO0,3486
4
- boto3_assist/connection_tracker.py,sha256=bfImvNVX-0Lhb-ombOurWUpNLdI0qVDil-kokBdIFkY,4345
4
+ boto3_assist/connection_tracker.py,sha256=UgfR9RlvXf3A4ssMr3gDMpw89ka8mSRvJn4M34SzhbU,4378
5
5
  boto3_assist/http_status_codes.py,sha256=G0zRSWenwavYKETvDF9tNVUXQz3Ae2gXdBETYbjvJe8,3284
6
- boto3_assist/version.py,sha256=CBY3jsC-9HCm7eZ6CKD-sYLCejqOJ1pYWPQM4LGIXcI,22
6
+ boto3_assist/version.py,sha256=09hol5ovL6ArILJIJZ-MiscnOjKa6RjaETmtdgJpW2c,22
7
7
  boto3_assist/aws_lambda/event_info.py,sha256=OkZ4WzuGaHEu_T8sB188KBgShAJhZpWASALKRGBOhMg,14648
8
8
  boto3_assist/cloudwatch/cloudwatch_connection.py,sha256=mnGWaLSQpHh5EeY7Ek_2o9JKHJxOELIYtQVMX1IaHn4,2480
9
9
  boto3_assist/cloudwatch/cloudwatch_connection_tracker.py,sha256=mzRtO1uHrcfJNh1XrGEiXdTqxwEP8d1RqJkraMNkgK0,410
@@ -34,19 +34,21 @@ boto3_assist/environment_services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
34
34
  boto3_assist/environment_services/environment_loader.py,sha256=zCA4mRdVWMLKzjDRvrJbhQfRVP4HAMGpuQFi07zzULk,3396
35
35
  boto3_assist/environment_services/environment_variables.py,sha256=4ccBKdPt6O7hcRT3zBHd8vqu8yQU8udmoD5RLAT3iMs,6801
36
36
  boto3_assist/errors/custom_exceptions.py,sha256=zC2V2Y4PUtKj3uLPn8mB-JessksKWJWvKM9kp1dmvt8,760
37
- boto3_assist/models/serializable_model.py,sha256=nzGdkggAzmrIukPPyumB9EPUNrPfvQrCqbTSndnwT1c,257
38
- boto3_assist/s3/s3.py,sha256=DFCJs5z1mMIT8nZfnqPyr_cvhi9-FePuYH--tzD7b5E,17104
39
- boto3_assist/s3/s3_connection.py,sha256=FI1AhZV4UbTXQRTb4TqL9mv88Gt018rPZVFBvLetVAw,2163
37
+ boto3_assist/models/serializable_model.py,sha256=ZMrRJRvJWLY8PBSKK_nPCgYKv1qUxDPEVdcADKbIHsI,266
38
+ boto3_assist/s3/s3.py,sha256=ESTPXtyDi8mrwHaYNWjQLNGTuTUV4CxKDqw-O_KGzKs,2052
39
+ boto3_assist/s3/s3_bucket.py,sha256=GfyBbuI5BWz_ybwU_nDqUZiC0wt24PNt49GKZmb05OY,2018
40
+ boto3_assist/s3/s3_connection.py,sha256=0JgEDNoDFPQTo5hQe-lS8mWnFBJ2S8MDSl0LPG__lZo,2008
41
+ boto3_assist/s3/s3_object.py,sha256=kH9apiVZ-lWGtcSqcLvaAI-2g011ecrvg8wP7y9u140,19482
40
42
  boto3_assist/utilities/datetime_utility.py,sha256=dgAMB9VqakrYIPXlSoVQiLSsc_yhrJK4gMfJO9mX90w,11112
41
43
  boto3_assist/utilities/dictionaroy_utility.py,sha256=PjUrerEd6uhmw37A-k7xe_DWHvXZGGoMqT6xjUqmWBI,893
42
44
  boto3_assist/utilities/file_operations.py,sha256=Zy8fu8fpuVNf7U9NimrLdy5FRF71XSI159cnRdzmzGY,3411
43
45
  boto3_assist/utilities/http_utility.py,sha256=koFv7Va-8ng-47Nt1K2Sh7Ti95e62IYs9VMLlGh9Kt4,1173
44
46
  boto3_assist/utilities/logging_utility.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
47
  boto3_assist/utilities/numbers_utility.py,sha256=KIiNkBSRbfNWvtXG5SdHp625LTiW12VtADUa4ZlWMFo,8709
46
- boto3_assist/utilities/serialization_utility.py,sha256=fj5FDQjA7pC_mhX22n0N1UwRXl7S__q0VrisQTc0GHg,12415
47
- boto3_assist/utilities/string_utility.py,sha256=sBY80aQO-fTRanlHryZFMQBxdo6OvLRvnZjZrQepHlI,9283
48
- boto3_assist-0.6.0.dist-info/METADATA,sha256=DFeAHlHVbA3lVP2ZKQPfSc9y8pvUWleUq8G649h8iy4,1728
49
- boto3_assist-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
50
- boto3_assist-0.6.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
51
- boto3_assist-0.6.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
52
- boto3_assist-0.6.0.dist-info/RECORD,,
48
+ boto3_assist/utilities/serialization_utility.py,sha256=KJhit0lI1lr8hhndW6UhKQSZD3c2RH555Ni7eP-e5Ms,16557
49
+ boto3_assist/utilities/string_utility.py,sha256=5BpDaqGZI8cSM-3YFQLU1fKcWcqG9r1_GPgDstCWFIs,10318
50
+ boto3_assist-0.7.0.dist-info/METADATA,sha256=0cBmMT5N4of5YzMQhUlK3S_ovOEaxRItYa_JQnjH16I,1728
51
+ boto3_assist-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
+ boto3_assist-0.7.0.dist-info/licenses/LICENSE-EXPLAINED.txt,sha256=WFREvTpfTjPjDHpOLADxJpCKpIla3Ht87RUUGii4ODU,606
53
+ boto3_assist-0.7.0.dist-info/licenses/LICENSE.txt,sha256=PXDhFWS5L5aOTkVhNvoitHKbAkgxqMI2uUPQyrnXGiI,1105
54
+ boto3_assist-0.7.0.dist-info/RECORD,,