boto3-assist 0.5.1__tar.gz → 0.6.0__tar.gz
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-0.5.1 → boto3_assist-0.6.0}/PKG-INFO +1 -1
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/pyproject.toml +1 -1
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +19 -108
- boto3_assist-0.6.0/src/boto3_assist/models/serializable_model.py +9 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/serialization_utility.py +148 -6
- boto3_assist-0.6.0/src/boto3_assist/version.py +1 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/models/serializable_model_test.py +34 -9
- boto3_assist-0.5.1/src/boto3_assist/models/serializable_model.py +0 -30
- boto3_assist-0.5.1/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.env.docker +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.env.docker.001 +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.env.unittest +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.gitignore +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.vscode/settings.json +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/LICENSE.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/README.md +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/devops/build.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/devops/readme.md +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/module-headers.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/mypy.ini +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/requirements-dev.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/requirements.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/run-checks.sh +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/connection_tracker.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/s3/s3.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/s3/s3_connection.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/dictionaroy_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/string_utility.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/__top/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_model_serializtion_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_moto_sorting_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/cms/base.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/cms/content_block.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/cms/page.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/cms/template.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/simple_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/user_required_fields_model.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/examples_test/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/lambda/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/lambda/event_info_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/models/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/s3/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/s3/files/test.txt +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/s3/s3_file_upload_test.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/utilities/__init__.py +0 -0
- {boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/utilities/serialization_utility_test.py +0 -0
|
@@ -6,9 +6,10 @@ MIT License. See Project Root for the license information.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
import datetime as dt
|
|
9
|
-
|
|
10
|
-
import
|
|
11
|
-
import
|
|
9
|
+
|
|
10
|
+
# import decimal
|
|
11
|
+
# import inspect
|
|
12
|
+
# import uuid
|
|
12
13
|
from typing import TypeVar, List, Dict, Any
|
|
13
14
|
from boto3.dynamodb.types import TypeSerializer
|
|
14
15
|
from boto3_assist.utilities.serialization_utility import Serialization
|
|
@@ -19,6 +20,7 @@ from boto3_assist.dynamodb.dynamodb_index import (
|
|
|
19
20
|
)
|
|
20
21
|
from boto3_assist.dynamodb.dynamodb_reserved_words import DynamoDBReservedWords
|
|
21
22
|
from boto3_assist.utilities.datetime_utility import DatetimeUtility
|
|
23
|
+
from boto3_assist.models.serializable_model import SerializableModel
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
def exclude_from_serialization(method):
|
|
@@ -37,7 +39,7 @@ def exclude_indexes_from_serialization(method):
|
|
|
37
39
|
return method
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class DynamoDBModelBase:
|
|
42
|
+
class DynamoDBModelBase(SerializableModel):
|
|
41
43
|
"""DyanmoDb Model Base"""
|
|
42
44
|
|
|
43
45
|
T = TypeVar("T", bound="DynamoDBModelBase")
|
|
@@ -270,7 +272,9 @@ class DynamoDBSerializer:
|
|
|
270
272
|
return mapped
|
|
271
273
|
|
|
272
274
|
@staticmethod
|
|
273
|
-
def to_client_dictionary(
|
|
275
|
+
def to_client_dictionary(
|
|
276
|
+
instance: DynamoDBModelBase, include_indexes: bool = True
|
|
277
|
+
) -> Dict[str, Any]:
|
|
274
278
|
"""
|
|
275
279
|
Convert a Python class instance to a dictionary suitable for DynamoDB client.
|
|
276
280
|
|
|
@@ -281,16 +285,19 @@ class DynamoDBSerializer:
|
|
|
281
285
|
- dict: A dictionary representation of the class instance suitable for DynamoDB client.
|
|
282
286
|
"""
|
|
283
287
|
serializer = TypeSerializer()
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
288
|
+
d = Serialization.to_dict(instance, serializer.serialize)
|
|
289
|
+
|
|
290
|
+
if include_indexes:
|
|
291
|
+
d = DynamoDBSerializer._add_indexes(instance=instance, instance_dict=d)
|
|
292
|
+
|
|
293
|
+
return d
|
|
287
294
|
|
|
288
295
|
@staticmethod
|
|
289
296
|
def to_resource_dictionary(
|
|
290
297
|
instance: DynamoDBModelBase,
|
|
291
298
|
include_indexes: bool = True,
|
|
292
299
|
include_none: bool = False,
|
|
293
|
-
):
|
|
300
|
+
) -> Dict[str, Any]:
|
|
294
301
|
"""
|
|
295
302
|
Convert a Python class instance to a dictionary suitable for DynamoDB resource.
|
|
296
303
|
|
|
@@ -300,112 +307,16 @@ class DynamoDBSerializer:
|
|
|
300
307
|
Returns:
|
|
301
308
|
- dict: A dictionary representation of the class instance suitable for DynamoDB resource.
|
|
302
309
|
"""
|
|
303
|
-
|
|
310
|
+
d = Serialization.to_dict(
|
|
304
311
|
instance,
|
|
305
312
|
lambda x: x,
|
|
306
|
-
include_indexes=include_indexes,
|
|
307
313
|
include_none=include_none,
|
|
308
314
|
)
|
|
309
315
|
|
|
310
|
-
@staticmethod
|
|
311
|
-
def _serialize(
|
|
312
|
-
instance: DynamoDBModelBase,
|
|
313
|
-
serialize_fn,
|
|
314
|
-
include_indexes: bool = True,
|
|
315
|
-
include_none: bool = True,
|
|
316
|
-
):
|
|
317
|
-
def is_primitive(value):
|
|
318
|
-
"""Check if the value is a primitive data type."""
|
|
319
|
-
return isinstance(value, (str, int, bool, type(None)))
|
|
320
|
-
|
|
321
|
-
def serialize_value(value):
|
|
322
|
-
"""Serialize the value using the provided function."""
|
|
323
|
-
|
|
324
|
-
if isinstance(value, DynamoDBModelBase):
|
|
325
|
-
return serialize_fn(
|
|
326
|
-
value.to_resource_dictionary(
|
|
327
|
-
include_indexes=False, include_none=include_none
|
|
328
|
-
)
|
|
329
|
-
)
|
|
330
|
-
if isinstance(value, dt.datetime):
|
|
331
|
-
return serialize_fn(value.isoformat())
|
|
332
|
-
elif isinstance(value, float):
|
|
333
|
-
v = serialize_fn(decimal.Decimal(str(value)))
|
|
334
|
-
return v
|
|
335
|
-
elif isinstance(value, decimal.Decimal):
|
|
336
|
-
return serialize_fn(value)
|
|
337
|
-
elif isinstance(value, uuid.UUID):
|
|
338
|
-
return serialize_fn(str(value))
|
|
339
|
-
elif isinstance(value, (bytes, bytearray)):
|
|
340
|
-
return serialize_fn(value.hex())
|
|
341
|
-
elif is_primitive(value):
|
|
342
|
-
return serialize_fn(value)
|
|
343
|
-
elif isinstance(value, list):
|
|
344
|
-
return serialize_fn([serialize_value(v) for v in value])
|
|
345
|
-
elif isinstance(value, dict):
|
|
346
|
-
return serialize_fn({k: serialize_value(v) for k, v in value.items()})
|
|
347
|
-
else:
|
|
348
|
-
return serialize_fn(
|
|
349
|
-
DynamoDBSerializer._serialize(
|
|
350
|
-
value,
|
|
351
|
-
serialize_fn,
|
|
352
|
-
include_indexes=include_indexes,
|
|
353
|
-
include_none=include_none,
|
|
354
|
-
)
|
|
355
|
-
)
|
|
356
|
-
|
|
357
|
-
instance_dict = DynamoDBSerializer._add_properties(
|
|
358
|
-
instance, serialize_value, include_none=include_none
|
|
359
|
-
)
|
|
360
|
-
|
|
361
316
|
if include_indexes:
|
|
362
|
-
|
|
363
|
-
return instance_dict
|
|
317
|
+
d = DynamoDBSerializer._add_indexes(instance=instance, instance_dict=d)
|
|
364
318
|
|
|
365
|
-
|
|
366
|
-
def _add_properties(
|
|
367
|
-
instance: DynamoDBModelBase,
|
|
368
|
-
serialize_value,
|
|
369
|
-
include_none: bool = True,
|
|
370
|
-
) -> dict:
|
|
371
|
-
instance_dict = {}
|
|
372
|
-
|
|
373
|
-
# Add instance variables
|
|
374
|
-
for attr, value in instance.__dict__.items():
|
|
375
|
-
if str(attr) == "T":
|
|
376
|
-
continue
|
|
377
|
-
# don't get the private properties
|
|
378
|
-
if not str(attr).startswith("_"):
|
|
379
|
-
if value is not None or include_none:
|
|
380
|
-
instance_dict[attr] = serialize_value(value)
|
|
381
|
-
|
|
382
|
-
# Add properties
|
|
383
|
-
for name, _ in inspect.getmembers(
|
|
384
|
-
instance.__class__, predicate=inspect.isdatadescriptor
|
|
385
|
-
):
|
|
386
|
-
prop = None
|
|
387
|
-
try:
|
|
388
|
-
prop = getattr(instance.__class__, name)
|
|
389
|
-
except AttributeError:
|
|
390
|
-
continue
|
|
391
|
-
if isinstance(prop, property):
|
|
392
|
-
# Exclude properties marked with the exclude_from_serialization decorator
|
|
393
|
-
# Check if the property should be excluded
|
|
394
|
-
exclude = getattr(prop.fget, "exclude_from_serialization", False)
|
|
395
|
-
if exclude:
|
|
396
|
-
continue
|
|
397
|
-
|
|
398
|
-
# Skip TypeVar T or instances of DynamoDBModelBase
|
|
399
|
-
if str(name) == "T":
|
|
400
|
-
continue
|
|
401
|
-
|
|
402
|
-
# don't get the private properties
|
|
403
|
-
if not str(name).startswith("_"):
|
|
404
|
-
value = getattr(instance, name)
|
|
405
|
-
if value is not None or include_none:
|
|
406
|
-
instance_dict[name] = serialize_value(value)
|
|
407
|
-
|
|
408
|
-
return instance_dict
|
|
319
|
+
return d
|
|
409
320
|
|
|
410
321
|
@staticmethod
|
|
411
322
|
def _add_indexes(instance: DynamoDBModelBase, instance_dict: dict) -> dict:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from typing import TypeVar, Dict, Any
|
|
9
|
+
from boto3_assist.utilities.serialization_utility import SerializableModel
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/utilities/serialization_utility.py
RENAMED
|
@@ -2,17 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from decimal import Decimal
|
|
5
|
-
from typing import Dict, List, TypeVar
|
|
5
|
+
from typing import Dict, List, TypeVar, Any
|
|
6
6
|
import json
|
|
7
7
|
import jsons
|
|
8
|
+
import datetime as dt
|
|
9
|
+
import decimal
|
|
10
|
+
import inspect
|
|
11
|
+
import uuid
|
|
8
12
|
from aws_lambda_powertools import Logger
|
|
9
13
|
|
|
14
|
+
|
|
10
15
|
T = TypeVar("T")
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
logger = Logger()
|
|
14
19
|
|
|
15
20
|
|
|
21
|
+
class SerializableModel:
|
|
22
|
+
"""Library to Serialize object to a DynamoDB Format"""
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T", bound="SerializableModel")
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def map(
|
|
30
|
+
self: T,
|
|
31
|
+
source: Dict[str, Any] | "SerializableModel" | None,
|
|
32
|
+
coerce: bool = True,
|
|
33
|
+
) -> T:
|
|
34
|
+
"""
|
|
35
|
+
Map the source dictionary to the target object.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
- source: The dictionary to map from.
|
|
39
|
+
- target: The object to map to.
|
|
40
|
+
"""
|
|
41
|
+
mapped = Serialization.map(source=source, target=self, coerce=coerce)
|
|
42
|
+
if mapped is None:
|
|
43
|
+
raise ValueError("Unable to map source to target")
|
|
44
|
+
|
|
45
|
+
return mapped
|
|
46
|
+
|
|
47
|
+
def to_dictionary(self) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Convert the object to a dictionary.
|
|
50
|
+
"""
|
|
51
|
+
# return Serialization.convert_object_to_dict(self)
|
|
52
|
+
return Serialization.to_dict(
|
|
53
|
+
instance=self, serialize_fn=lambda x: x, include_none=True
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
16
57
|
class JsonEncoder(json.JSONEncoder):
|
|
17
58
|
"""
|
|
18
59
|
This class is used to serialize python generics which implement a __json_encode__ method
|
|
@@ -52,7 +93,7 @@ class JsonEncoder(json.JSONEncoder):
|
|
|
52
93
|
|
|
53
94
|
class Serialization:
|
|
54
95
|
"""
|
|
55
|
-
|
|
96
|
+
Serialization Class
|
|
56
97
|
"""
|
|
57
98
|
|
|
58
99
|
@staticmethod
|
|
@@ -60,11 +101,12 @@ class Serialization:
|
|
|
60
101
|
"""
|
|
61
102
|
Dumps an object to dictionary structure
|
|
62
103
|
"""
|
|
63
|
-
dump = jsons.dump(model, strip_privates=True)
|
|
64
|
-
if isinstance(dump, dict) or isinstance(dump, List):
|
|
65
|
-
return dump
|
|
66
104
|
|
|
67
|
-
|
|
105
|
+
dump = Serialization.to_dict(
|
|
106
|
+
instance=model, serialize_fn=lambda x: x, include_none=True
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return dump
|
|
68
110
|
|
|
69
111
|
@staticmethod
|
|
70
112
|
def map(source: object, target: T, coerce: bool = True) -> T | None:
|
|
@@ -185,3 +227,103 @@ class Serialization:
|
|
|
185
227
|
"This procedure will update the property from False to True while serializing, "
|
|
186
228
|
"then back to False once serialization is complete. "
|
|
187
229
|
) from e
|
|
230
|
+
|
|
231
|
+
@staticmethod
|
|
232
|
+
def to_dict(
|
|
233
|
+
instance: SerializableModel,
|
|
234
|
+
serialize_fn,
|
|
235
|
+
include_none: bool = True,
|
|
236
|
+
) -> Dict[str, Any]:
|
|
237
|
+
"""To Dict / Dictionary"""
|
|
238
|
+
|
|
239
|
+
def is_primitive(value):
|
|
240
|
+
"""Check if the value is a primitive data type."""
|
|
241
|
+
return isinstance(value, (str, int, bool, type(None)))
|
|
242
|
+
|
|
243
|
+
def serialize_value(value):
|
|
244
|
+
"""Serialize the value using the provided function."""
|
|
245
|
+
|
|
246
|
+
if isinstance(value, SerializableModel):
|
|
247
|
+
return serialize_fn(
|
|
248
|
+
Serialization.to_dict(
|
|
249
|
+
instance=value,
|
|
250
|
+
serialize_fn=lambda x: x,
|
|
251
|
+
include_none=include_none,
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
if isinstance(value, dt.datetime):
|
|
255
|
+
return serialize_fn(value.isoformat())
|
|
256
|
+
elif isinstance(value, float):
|
|
257
|
+
v = serialize_fn(decimal.Decimal(str(value)))
|
|
258
|
+
return v
|
|
259
|
+
elif isinstance(value, decimal.Decimal):
|
|
260
|
+
return serialize_fn(value)
|
|
261
|
+
elif isinstance(value, uuid.UUID):
|
|
262
|
+
return serialize_fn(str(value))
|
|
263
|
+
elif isinstance(value, (bytes, bytearray)):
|
|
264
|
+
return serialize_fn(value.hex())
|
|
265
|
+
elif is_primitive(value):
|
|
266
|
+
return serialize_fn(value)
|
|
267
|
+
elif isinstance(value, list):
|
|
268
|
+
return serialize_fn([serialize_value(v) for v in value])
|
|
269
|
+
elif isinstance(value, dict):
|
|
270
|
+
return serialize_fn({k: serialize_value(v) for k, v in value.items()})
|
|
271
|
+
else:
|
|
272
|
+
return serialize_fn(
|
|
273
|
+
Serialization.to_dict(
|
|
274
|
+
value,
|
|
275
|
+
serialize_fn,
|
|
276
|
+
include_none=include_none,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
instance_dict = Serialization._add_properties(
|
|
281
|
+
instance, serialize_value, include_none=include_none
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return instance_dict
|
|
285
|
+
|
|
286
|
+
@staticmethod
|
|
287
|
+
def _add_properties(
|
|
288
|
+
instance: SerializableModel,
|
|
289
|
+
serialize_value,
|
|
290
|
+
include_none: bool = True,
|
|
291
|
+
) -> dict:
|
|
292
|
+
instance_dict = {}
|
|
293
|
+
|
|
294
|
+
# Add instance variables
|
|
295
|
+
for attr, value in instance.__dict__.items():
|
|
296
|
+
if str(attr) == "T":
|
|
297
|
+
continue
|
|
298
|
+
# don't get the private properties
|
|
299
|
+
if not str(attr).startswith("_"):
|
|
300
|
+
if value is not None or include_none:
|
|
301
|
+
instance_dict[attr] = serialize_value(value)
|
|
302
|
+
|
|
303
|
+
# Add properties
|
|
304
|
+
for name, _ in inspect.getmembers(
|
|
305
|
+
instance.__class__, predicate=inspect.isdatadescriptor
|
|
306
|
+
):
|
|
307
|
+
prop = None
|
|
308
|
+
try:
|
|
309
|
+
prop = getattr(instance.__class__, name)
|
|
310
|
+
except AttributeError:
|
|
311
|
+
continue
|
|
312
|
+
if isinstance(prop, property):
|
|
313
|
+
# Exclude properties marked with the exclude_from_serialization decorator
|
|
314
|
+
# Check if the property should be excluded
|
|
315
|
+
exclude = getattr(prop.fget, "exclude_from_serialization", False)
|
|
316
|
+
if exclude:
|
|
317
|
+
continue
|
|
318
|
+
|
|
319
|
+
# Skip TypeVar T or instances of DynamoDBModelBase
|
|
320
|
+
if str(name) == "T":
|
|
321
|
+
continue
|
|
322
|
+
|
|
323
|
+
# don't get the private properties
|
|
324
|
+
if not str(name).startswith("_"):
|
|
325
|
+
value = getattr(instance, name)
|
|
326
|
+
if value is not None or include_none:
|
|
327
|
+
instance_dict[name] = serialize_value(value)
|
|
328
|
+
|
|
329
|
+
return instance_dict
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.6.0'
|
|
@@ -28,13 +28,11 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
28
28
|
"active": True,
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
self.target_object = ExampleModel()
|
|
32
|
-
|
|
33
31
|
def test_map_valid_data(self):
|
|
34
32
|
"""
|
|
35
33
|
Test mapping a valid dictionary to an object instance.
|
|
36
34
|
"""
|
|
37
|
-
result = ExampleModel.map(self.source_dict
|
|
35
|
+
result = ExampleModel().map(self.source_dict)
|
|
38
36
|
|
|
39
37
|
self.assertEqual(result.name, "John Doe")
|
|
40
38
|
self.assertEqual(result.age, 30)
|
|
@@ -45,7 +43,7 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
45
43
|
Test mapping a dictionary with partial data.
|
|
46
44
|
"""
|
|
47
45
|
partial_dict = {"name": "Jane Doe"}
|
|
48
|
-
result = ExampleModel.map(partial_dict
|
|
46
|
+
result = ExampleModel().map(partial_dict)
|
|
49
47
|
|
|
50
48
|
self.assertEqual(result.name, "Jane Doe")
|
|
51
49
|
self.assertEqual(result.age, 0) # Default value
|
|
@@ -56,7 +54,7 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
56
54
|
Test mapping an empty dictionary.
|
|
57
55
|
"""
|
|
58
56
|
empty_dict = {}
|
|
59
|
-
result = ExampleModel.map(empty_dict
|
|
57
|
+
result = ExampleModel().map(empty_dict)
|
|
60
58
|
|
|
61
59
|
self.assertEqual(result.name, "") # Default value
|
|
62
60
|
self.assertEqual(result.age, 0) # Default value
|
|
@@ -69,7 +67,7 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
69
67
|
invalid_dict = {"name": 123, "age": "thirty", "active": "yes"}
|
|
70
68
|
|
|
71
69
|
with self.assertRaises(ValueError):
|
|
72
|
-
ExampleModel.map(invalid_dict,
|
|
70
|
+
ExampleModel().map(invalid_dict, coerce=False)
|
|
73
71
|
|
|
74
72
|
def test_map_invalid_data_coerce_with_failure(self):
|
|
75
73
|
"""
|
|
@@ -77,7 +75,7 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
77
75
|
"""
|
|
78
76
|
invalid_dict = {"name": 123, "age": "thirty", "active": "yes"}
|
|
79
77
|
|
|
80
|
-
result = ExampleModel.map(invalid_dict
|
|
78
|
+
result = ExampleModel().map(invalid_dict)
|
|
81
79
|
|
|
82
80
|
self.assertEqual(result.name, "123")
|
|
83
81
|
# currently we're allowing this to happen
|
|
@@ -90,24 +88,51 @@ class TestSerializableModel(unittest.TestCase):
|
|
|
90
88
|
"""
|
|
91
89
|
invalid_dict = {"name": 123, "age": 30, "active": "yes"}
|
|
92
90
|
|
|
93
|
-
result = ExampleModel.map(invalid_dict
|
|
91
|
+
result = ExampleModel().map(invalid_dict)
|
|
94
92
|
|
|
95
93
|
self.assertEqual(result.name, "123")
|
|
96
94
|
self.assertEqual(result.age, 30)
|
|
97
95
|
self.assertEqual(result.active, True)
|
|
98
96
|
|
|
97
|
+
def test_map_mix_limited_fields(self):
|
|
98
|
+
"""
|
|
99
|
+
Test mapping a dictionary with invalid data types.
|
|
100
|
+
"""
|
|
101
|
+
model: ExampleModel = ExampleModel(name="Fred", age=25, active=True)
|
|
102
|
+
invalid_dict = {"age": 30}
|
|
103
|
+
|
|
104
|
+
result = model.map(invalid_dict)
|
|
105
|
+
|
|
106
|
+
self.assertEqual(result.name, "Fred")
|
|
107
|
+
self.assertEqual(result.age, 30)
|
|
108
|
+
self.assertEqual(result.active, True)
|
|
109
|
+
|
|
99
110
|
def test_map_non_dict_source(self):
|
|
100
111
|
"""
|
|
101
112
|
Test mapping from a non-dictionary source object.
|
|
102
113
|
"""
|
|
103
114
|
source_object = ExampleModel(name="Alice", age=25, active=True)
|
|
104
115
|
|
|
105
|
-
result = ExampleModel.map(source_object
|
|
116
|
+
result = ExampleModel().map(source_object)
|
|
106
117
|
|
|
107
118
|
self.assertEqual(result.name, "Alice")
|
|
108
119
|
self.assertEqual(result.age, 25)
|
|
109
120
|
self.assertEqual(result.active, True)
|
|
110
121
|
|
|
122
|
+
def test_to_dictionary(self):
|
|
123
|
+
"""
|
|
124
|
+
Test converting an object instance to a dictionary.
|
|
125
|
+
"""
|
|
126
|
+
model = ExampleModel(name="Bob", age=35, active=False)
|
|
127
|
+
|
|
128
|
+
result = model.to_dictionary()
|
|
129
|
+
|
|
130
|
+
self.assertIsInstance(result, dict)
|
|
131
|
+
self.assertEqual(len(result), 3)
|
|
132
|
+
self.assertEqual(result.get("name"), "Bob")
|
|
133
|
+
self.assertEqual(result.get("age"), 35)
|
|
134
|
+
self.assertEqual(result.get("active"), False)
|
|
135
|
+
|
|
111
136
|
|
|
112
137
|
if __name__ == "__main__":
|
|
113
138
|
unittest.main()
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Geek Cafe, LLC
|
|
3
|
-
Maintainers: Eric Wilson
|
|
4
|
-
MIT License. See Project Root for the license information.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
from typing import TypeVar
|
|
9
|
-
from boto3_assist.utilities.serialization_utility import Serialization
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SerializableModel:
|
|
13
|
-
"""Library to Serialize object to a DynamoDB Format"""
|
|
14
|
-
|
|
15
|
-
T = TypeVar("T", bound="SerializableModel")
|
|
16
|
-
|
|
17
|
-
@staticmethod
|
|
18
|
-
def map(source: dict | object, target: T, coerce: bool = True) -> T:
|
|
19
|
-
"""
|
|
20
|
-
Map the source dictionary to the target object.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
- source: The dictionary to map from.
|
|
24
|
-
- target: The object to map to.
|
|
25
|
-
"""
|
|
26
|
-
mapped = Serialization.map(source, target, coerce=coerce)
|
|
27
|
-
if mapped is None:
|
|
28
|
-
raise ValueError("Unable to map source to target")
|
|
29
|
-
|
|
30
|
-
return mapped
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.5.1'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/examples/dynamodb/services/user_service_client_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py
RENAMED
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/dynamodb_model_serializtion_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.5.1 → boto3_assist-0.6.0}/tests/dynamodb/models/user_required_fields_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|