boto3-assist 0.27.0__tar.gz → 0.28.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.27.0 → boto3_assist-0.28.0}/.gitignore +2 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/PKG-INFO +1 -1
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/pyproject.toml +1 -1
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/serialization_utility.py +81 -0
- boto3_assist-0.28.0/src/boto3_assist/version.py +1 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/utilities/serialization_utility_test.py +146 -1
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/utilities/string_utility_test.py +1 -1
- boto3_assist-0.27.0/.pypirc +0 -13
- boto3_assist-0.27.0/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.env.docker +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.env.docker.001 +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.env.unittest +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.vscode/settings.json +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/LICENSE.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/README.md +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/devops/build.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/devops/readme.md +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/module-headers.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/mypy.ini +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/publish_to_pypi.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/publish_to_pypi.sh +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/pysetup.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/pysetup.sh +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/requirements-dev.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/requirements.dev.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/requirements.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/run-checks.sh +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/run_unit_tests.sh +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/setup.sh +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/aws_config.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/aws_lambda/mock_context.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/connection_tracker.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_re_indexer.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/erc/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/erc/ecr_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/models/serializable_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/role_assumption_mixin.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/s3/s3.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/s3/s3_bucket.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/s3/s3_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/s3/s3_event_data.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/s3/s3_object.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/securityhub/securityhub.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/securityhub/securityhub_connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/session_setup_mixin.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/ssm/connection.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/dictionary_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/string_utility.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/integration/cross_account_connection_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/integration/tenant.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/integration/tenant_services.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/aws_config_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/common/db_test_helpers.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/base.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/content_block.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/page.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/template.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/simple_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/task.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/user_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/user_required_fields_model.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_fail_if_exists_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_model_serializtion_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_moto_sorting_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_get_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_sort_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_query_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/examples_test/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/lambda_tests/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/lambda_tests/event_info_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/models/person.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/models/user.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/serializable_model_person_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/serializable_model_user_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/serializable_model_wide_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/parameter_store/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/parameter_store/parameter_store_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/s3/__init__.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/s3/files/test.txt +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/s3/s3_event_data_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/s3/s3_file_delete_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/s3/s3_file_upload_test.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/session_tests/test_boto3_session_manager.py +0 -0
- {boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/utilities/__init__.py +0 -0
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/serialization_utility.py
RENAMED
|
@@ -8,6 +8,7 @@ import datetime as dt
|
|
|
8
8
|
import decimal
|
|
9
9
|
import inspect
|
|
10
10
|
import json
|
|
11
|
+
import typing
|
|
11
12
|
import uuid
|
|
12
13
|
from datetime import datetime
|
|
13
14
|
from decimal import Decimal
|
|
@@ -116,6 +117,86 @@ class JsonConversions:
|
|
|
116
117
|
Used for snake_case to camelCase and vice versa
|
|
117
118
|
"""
|
|
118
119
|
|
|
120
|
+
@staticmethod
|
|
121
|
+
def string_to_json_obj(
|
|
122
|
+
value: str | list | dict, raise_on_error: bool = True, retry: int = 0
|
|
123
|
+
) -> typing.Union[dict, typing.Any, None]:
|
|
124
|
+
"""
|
|
125
|
+
Converts a string to a JSON object.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
value: The value to convert (string, list, or dict).
|
|
129
|
+
raise_on_error: Whether to raise an exception on error.
|
|
130
|
+
retry: The number of retry attempts made.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
The converted JSON object, or the original value if conversion fails.
|
|
134
|
+
"""
|
|
135
|
+
# Handle empty/None values
|
|
136
|
+
if not value:
|
|
137
|
+
return {}
|
|
138
|
+
|
|
139
|
+
# Return dicts unchanged
|
|
140
|
+
if isinstance(value, dict):
|
|
141
|
+
return value
|
|
142
|
+
|
|
143
|
+
# Check retry limit early
|
|
144
|
+
if retry > 5:
|
|
145
|
+
raise RuntimeError("Too many attempts to convert string to JSON")
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
# Convert to string if needed
|
|
149
|
+
if not isinstance(value, str):
|
|
150
|
+
value = str(value)
|
|
151
|
+
|
|
152
|
+
# Clean up the string
|
|
153
|
+
value = value.replace("\n", "").strip()
|
|
154
|
+
if value.startswith("'") and value.endswith("'"):
|
|
155
|
+
value = value.strip("'").strip()
|
|
156
|
+
|
|
157
|
+
# Parse JSON
|
|
158
|
+
parsed_value = json.loads(value)
|
|
159
|
+
|
|
160
|
+
# Handle nested string JSON (recursive case)
|
|
161
|
+
if isinstance(parsed_value, str):
|
|
162
|
+
return JsonConversions.string_to_json_obj(parsed_value, raise_on_error, retry + 1)
|
|
163
|
+
|
|
164
|
+
return parsed_value
|
|
165
|
+
|
|
166
|
+
except json.JSONDecodeError as e:
|
|
167
|
+
# Try to fix malformed JSON with single quotes
|
|
168
|
+
if "Expecting property name enclosed in double quotes" in str(e) and retry < 5:
|
|
169
|
+
if isinstance(value, str):
|
|
170
|
+
fixed_json = JsonConversions.convert_bad_json_string(value)
|
|
171
|
+
return JsonConversions.string_to_json_obj(fixed_json, raise_on_error, retry + 1)
|
|
172
|
+
|
|
173
|
+
if raise_on_error:
|
|
174
|
+
raise e
|
|
175
|
+
return {}
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
if raise_on_error:
|
|
179
|
+
logger.exception({"source": "string_to_json_obj", "error": str(e), "value": value})
|
|
180
|
+
raise e
|
|
181
|
+
|
|
182
|
+
logger.warning({"source": "string_to_json_obj", "returning_original": True, "value": value})
|
|
183
|
+
return value
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def convert_bad_json_string(bad_json: str) -> str:
|
|
188
|
+
"""
|
|
189
|
+
Fixes malformed JSON by converting single quotes to double quotes.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
bad_json: Malformed JSON string with single quotes.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Fixed JSON string with proper double quotes.
|
|
196
|
+
"""
|
|
197
|
+
# Use a placeholder to safely swap quotes
|
|
198
|
+
return bad_json.replace("'", "§§§").replace('"', "'").replace("§§§", '"')
|
|
199
|
+
|
|
119
200
|
@staticmethod
|
|
120
201
|
def _camel_to_snake(value: str) -> str:
|
|
121
202
|
"""Converts a camelCase to a snake_case"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.28.0"
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/utilities/serialization_utility_test.py
RENAMED
|
@@ -5,11 +5,12 @@ MIT License. See Project Root for the license information.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import unittest
|
|
8
|
+
import json
|
|
8
9
|
from datetime import datetime, UTC
|
|
9
10
|
from datetime import timedelta
|
|
10
11
|
from typing import cast
|
|
11
12
|
from typing import Optional, List
|
|
12
|
-
from boto3_assist.utilities.serialization_utility import Serialization
|
|
13
|
+
from boto3_assist.utilities.serialization_utility import Serialization, JsonConversions
|
|
13
14
|
from boto3_assist.dynamodb.dynamodb_model_base import (
|
|
14
15
|
DynamoDBModelBase,
|
|
15
16
|
exclude_indexes_from_serialization,
|
|
@@ -264,3 +265,147 @@ class SerializationUnitTest(unittest.TestCase):
|
|
|
264
265
|
self.assertIsNone(active_subscription.get("sk"))
|
|
265
266
|
self.assertIsNone(active_subscription.get("gsi0_pk"))
|
|
266
267
|
self.assertIsNone(active_subscription.get("gsi0_sk"))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class JsonConversionsUnitTest(unittest.TestCase):
|
|
271
|
+
"""Unit tests for JsonConversions.string_to_json_obj function"""
|
|
272
|
+
|
|
273
|
+
def setUp(self):
|
|
274
|
+
"""Set up common test data"""
|
|
275
|
+
self.sample_dict = {"name": "John", "age": 30}
|
|
276
|
+
self.sample_json = '{"name": "John", "age": 30}'
|
|
277
|
+
|
|
278
|
+
def test_valid_json_conversions(self):
|
|
279
|
+
"""Test converting valid JSON strings and data types"""
|
|
280
|
+
test_cases = [
|
|
281
|
+
(
|
|
282
|
+
'{"name": "John", "age": 30, "active": true}',
|
|
283
|
+
{"name": "John", "age": 30, "active": True},
|
|
284
|
+
),
|
|
285
|
+
(
|
|
286
|
+
'[{"name": "John"}, {"name": "Jane"}]',
|
|
287
|
+
[{"name": "John"}, {"name": "Jane"}],
|
|
288
|
+
),
|
|
289
|
+
(
|
|
290
|
+
'{"message": "Hello 🌍", "symbol": "©"}',
|
|
291
|
+
{"message": "Hello 🌍", "symbol": "©"},
|
|
292
|
+
),
|
|
293
|
+
(
|
|
294
|
+
'{"name": "John", "middle_name": null}',
|
|
295
|
+
{"name": "John", "middle_name": None},
|
|
296
|
+
),
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
for json_input, expected in test_cases:
|
|
300
|
+
with self.subTest(json_input=json_input):
|
|
301
|
+
result = JsonConversions.string_to_json_obj(json_input)
|
|
302
|
+
self.assertEqual(result, expected)
|
|
303
|
+
|
|
304
|
+
def test_empty_and_none_inputs(self):
|
|
305
|
+
"""Test edge cases with empty/None inputs"""
|
|
306
|
+
test_cases = [
|
|
307
|
+
("", {}),
|
|
308
|
+
(None, {}),
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
for input_val, expected in test_cases:
|
|
312
|
+
with self.subTest(input_val=input_val):
|
|
313
|
+
result = JsonConversions.string_to_json_obj(input_val)
|
|
314
|
+
self.assertEqual(result, expected)
|
|
315
|
+
|
|
316
|
+
def test_passthrough_types(self):
|
|
317
|
+
"""Test that certain types pass through unchanged"""
|
|
318
|
+
input_dict = {"name": "John", "age": 30}
|
|
319
|
+
result = JsonConversions.string_to_json_obj(input_dict)
|
|
320
|
+
self.assertEqual(result, input_dict)
|
|
321
|
+
self.assertIs(result, input_dict)
|
|
322
|
+
|
|
323
|
+
def test_string_preprocessing(self):
|
|
324
|
+
"""Test string preprocessing (whitespace, quotes)"""
|
|
325
|
+
test_cases = [
|
|
326
|
+
('\n {"name": "John", "age": 30} \n', self.sample_dict),
|
|
327
|
+
('\'{"name": "John", "age": 30}\'', self.sample_dict),
|
|
328
|
+
('"{\\"name\\": \\"John\\", \\"age\\": 30}"', self.sample_dict),
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
for json_input, expected in test_cases:
|
|
332
|
+
with self.subTest(json_input=json_input[:20] + "..."):
|
|
333
|
+
result = JsonConversions.string_to_json_obj(json_input)
|
|
334
|
+
self.assertEqual(result, expected)
|
|
335
|
+
|
|
336
|
+
def test_malformed_json_auto_fix(self):
|
|
337
|
+
"""Test auto-fixing of malformed JSON"""
|
|
338
|
+
bad_json = "{'name': 'John', 'age': 30}"
|
|
339
|
+
result = JsonConversions.string_to_json_obj(bad_json)
|
|
340
|
+
self.assertEqual(result, self.sample_dict)
|
|
341
|
+
|
|
342
|
+
def test_error_handling_with_raise_on_error_true(self):
|
|
343
|
+
"""Test error handling when raise_on_error=True"""
|
|
344
|
+
test_cases = [
|
|
345
|
+
("{'name': 'John', 'age': 30, 'invalid'}", json.JSONDecodeError),
|
|
346
|
+
('{"valid": "json"}', RuntimeError), # retry=10 triggers RuntimeError
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
for invalid_input, expected_exception in test_cases:
|
|
350
|
+
with self.subTest(invalid_input=invalid_input):
|
|
351
|
+
with self.assertRaises(expected_exception):
|
|
352
|
+
if expected_exception == RuntimeError:
|
|
353
|
+
JsonConversions.string_to_json_obj(invalid_input, retry=10)
|
|
354
|
+
else:
|
|
355
|
+
JsonConversions.string_to_json_obj(
|
|
356
|
+
invalid_input, raise_on_error=True
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def test_error_handling_with_raise_on_error_false(self):
|
|
360
|
+
"""Test graceful error handling when raise_on_error=False"""
|
|
361
|
+
test_cases = [
|
|
362
|
+
("{'name': 'John', 'age': 30, 'invalid'}", {}),
|
|
363
|
+
(12345, 12345), # Non-string input
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
for invalid_input, expected in test_cases:
|
|
367
|
+
with self.subTest(invalid_input=invalid_input):
|
|
368
|
+
result = JsonConversions.string_to_json_obj(
|
|
369
|
+
invalid_input, raise_on_error=False
|
|
370
|
+
)
|
|
371
|
+
self.assertEqual(result, expected)
|
|
372
|
+
|
|
373
|
+
def test_retry_limit(self):
|
|
374
|
+
"""Test retry limit enforcement"""
|
|
375
|
+
with self.assertRaises(RuntimeError) as context:
|
|
376
|
+
JsonConversions.string_to_json_obj(self.sample_json, retry=6)
|
|
377
|
+
self.assertIn("Too many attempts", str(context.exception))
|
|
378
|
+
|
|
379
|
+
def test_complex_nested_structure(self):
|
|
380
|
+
"""Test complex nested JSON structure"""
|
|
381
|
+
complex_json = """{
|
|
382
|
+
"user": {"name": "John Doe", "details": {"age": 30, "preferences": ["reading", "coding"]}},
|
|
383
|
+
"metadata": {"version": 1.2}
|
|
384
|
+
}"""
|
|
385
|
+
|
|
386
|
+
result = JsonConversions.string_to_json_obj(complex_json)
|
|
387
|
+
|
|
388
|
+
# Verify key nested values
|
|
389
|
+
self.assertEqual(result["user"]["name"], "John Doe")
|
|
390
|
+
self.assertEqual(result["user"]["details"]["age"], 30)
|
|
391
|
+
self.assertEqual(
|
|
392
|
+
result["user"]["details"]["preferences"], ["reading", "coding"]
|
|
393
|
+
)
|
|
394
|
+
self.assertEqual(result["metadata"]["version"], 1.2)
|
|
395
|
+
|
|
396
|
+
def test_data_type_preservation(self):
|
|
397
|
+
"""Test that JSON data types are properly preserved"""
|
|
398
|
+
json_with_types = """{
|
|
399
|
+
"string_val": "text", "int_val": 42, "float_val": 3.14,
|
|
400
|
+
"bool_true": true, "bool_false": false, "null_val": null
|
|
401
|
+
}"""
|
|
402
|
+
|
|
403
|
+
result = JsonConversions.string_to_json_obj(json_with_types)
|
|
404
|
+
|
|
405
|
+
# Verify all data types
|
|
406
|
+
self.assertEqual(result["string_val"], "text")
|
|
407
|
+
self.assertEqual(result["int_val"], 42)
|
|
408
|
+
self.assertEqual(result["float_val"], 3.14)
|
|
409
|
+
self.assertTrue(result["bool_true"])
|
|
410
|
+
self.assertFalse(result["bool_false"])
|
|
411
|
+
self.assertIsNone(result["null_val"])
|
|
@@ -17,7 +17,7 @@ class StringUtilityUnitTest(unittest.TestCase):
|
|
|
17
17
|
"String Utility Tests"
|
|
18
18
|
|
|
19
19
|
def test_uuid_idempotency(self):
|
|
20
|
-
"""Testing
|
|
20
|
+
"""Testing Idempotent UUID generation."""
|
|
21
21
|
# must be consistent
|
|
22
22
|
namespace: uuid.UUID = uuid.UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
23
23
|
|
boto3_assist-0.27.0/.pypirc
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
[distutils]
|
|
2
|
-
index-servers =
|
|
3
|
-
pypi
|
|
4
|
-
testpypi
|
|
5
|
-
|
|
6
|
-
[pypi]
|
|
7
|
-
username = __token__
|
|
8
|
-
password = pypi-AgEIcHlwaS5vcmcCJGE0NDBkMmNlLWIzMWUtNDRjZC1iOGQxLTdiYmIzYmZkNWE0NAACKlszLCI3YzZmZTIzYS04MDVkLTQ0YjYtODlkZS04YTJiODJiNjBmMDQiXQAABiA_5UuF03-RSqW69zwSeC9I5rR8EIR4uZ8Zcj9p73XCXA
|
|
9
|
-
|
|
10
|
-
[testpypi]
|
|
11
|
-
repository = https://test.pypi.org/legacy/
|
|
12
|
-
username = __token__
|
|
13
|
-
password = pypi-AgENdGVzdC5weXBpLm9yZwIkZmEwOTVlMTAtMzE5ZC00MjU5LTk2ZjUtNjkzODg4MWIxMjE4AAIqWzMsImE3YzE2NzI0LTZjYmQtNDQzZi1iYzYyLTNhM2I3NWI4NDg5OSJdAAAGIGQlPqWLM1ziye_G9C1kLOG47bBEAAxTQmnnpaimREd1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.27.0"
|
|
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.27.0 → boto3_assist-0.28.0}/examples/dynamodb/services/order_item_service.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
|
|
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.27.0 → boto3_assist-0.28.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.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
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_re_indexer.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py
RENAMED
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/environment_services/__init__.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
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/securityhub/securityhub_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/src/boto3_assist/utilities/dictionary_utility.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/integration/cross_account_connection_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/content_block.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/cms/template.py
RENAMED
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/simple_model.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/db_models/user_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_model_base_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_moto_sorting_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_query_test.py
RENAMED
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/dynamodb_tests/dynamodb_reindex_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.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/serializable_model_user_test.py
RENAMED
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/models_tests/serializable_model_wide_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/parameter_store/parameter_store_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.27.0 → boto3_assist-0.28.0}/tests/unit/session_tests/test_boto3_session_manager.py
RENAMED
|
File without changes
|
|
File without changes
|