boto3-assist 0.32.0__tar.gz → 0.34.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.
Files changed (193) hide show
  1. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/PKG-INFO +1 -1
  2. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/pyproject.toml +1 -1
  3. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/connection.py +4 -0
  4. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +220 -1
  5. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/s3/s3_connection.py +10 -0
  6. boto3_assist-0.34.0/src/boto3_assist/version.py +1 -0
  7. boto3_assist-0.34.0/tests/unit/dynamodb_tests/dynamodb_model_merge_test.py +427 -0
  8. boto3_assist-0.32.0/src/boto3_assist/version.py +0 -1
  9. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.env.docker +0 -0
  10. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.env.docker.001 +0 -0
  11. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.env.docker.nosql.workbench +0 -0
  12. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.env.unittest +0 -0
  13. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.gitignore +0 -0
  14. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.vscode/launch.json +0 -0
  15. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.vscode/settings.json +0 -0
  16. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.vscode/tasks.json +0 -0
  17. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/.windsurf/rules/cascade.yaml +0 -0
  18. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/LICENSE-EXPLAINED.txt +0 -0
  19. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/LICENSE.txt +0 -0
  20. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/README.md +0 -0
  21. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/aws_regions_with_status.csv +0 -0
  22. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/aws_regions_with_status.json +0 -0
  23. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/devops/build.py +0 -0
  24. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/devops/readme.md +0 -0
  25. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/design-patterns.md +0 -0
  26. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/001-guide-single-table-design.md +0 -0
  27. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/002-guide-defining-models.md +0 -0
  28. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/003-guide-service-layers.md +0 -0
  29. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/004-guide-testing-with-moto.md +0 -0
  30. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/005-guide-projections-and-reserved-keywords.md +0 -0
  31. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/006-guide-how-dynamodb-stores-data.md +0 -0
  32. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/007-guide-batch-operations.md +0 -0
  33. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/008-guide-transactions.md +0 -0
  34. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/009-guide-conditional-writes.md +0 -0
  35. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/help/dynamodb/010-guide-update-expressions.md +0 -0
  36. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/issues/BOTO3_ASSIST_BEFORE_AFTER.md +0 -0
  37. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/issues/BOTO3_ASSIST_DECIMAL_PATTERN.md +0 -0
  38. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/issues/BOTO3_ASSIST_IMPLEMENTATION_CHECKLIST.md +0 -0
  39. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/overview.md +0 -0
  40. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/roadmap.md +0 -0
  41. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/tech-debt.md +0 -0
  42. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/docs/unit-test-patterns.md +0 -0
  43. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/__init__.py +0 -0
  44. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/cloudwatch/log_report.py +0 -0
  45. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/QUICK_REFERENCE_KEY_DEBUGGING.md +0 -0
  46. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/RUNTIME_KEY_DEBUGGING_SUMMARY.md +0 -0
  47. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/batch_operations_example.py +0 -0
  48. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/conditional_writes_example.py +0 -0
  49. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/debug_keys_example.py +0 -0
  50. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/decimal_conversion_example.py +0 -0
  51. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/models/order_item_model.py +0 -0
  52. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/models/order_model.py +0 -0
  53. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/models/product_model.py +0 -0
  54. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/models/user_model.py +0 -0
  55. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/models/user_post_model.py +0 -0
  56. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/order_example/main.py +0 -0
  57. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/order_example/products.json +0 -0
  58. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/runtime_key_debugging_example.py +0 -0
  59. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/order_item_service.py +0 -0
  60. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/order_service.py +0 -0
  61. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/product_service.py +0 -0
  62. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/table_service.py +0 -0
  63. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/user_post_service.py +0 -0
  64. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/user_service.py +0 -0
  65. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
  66. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
  67. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/transactions_example.py +0 -0
  68. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/update_expressions_example.py +0 -0
  69. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/dynamodb/user_post_example/main.py +0 -0
  70. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/examples/ec2/regions_report.py +0 -0
  71. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/module-headers.txt +0 -0
  72. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/mypy.ini +0 -0
  73. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/publish_to_pypi.py +0 -0
  74. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/publish_to_pypi.sh +0 -0
  75. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/pysetup.py +0 -0
  76. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/pysetup.sh +0 -0
  77. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/requirements.dev.txt +0 -0
  78. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/requirements.txt +0 -0
  79. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/run-checks.sh +0 -0
  80. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/run-unit-tests.sh +0 -0
  81. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/__init__.py +0 -0
  82. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/aws_config.py +0 -0
  83. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
  84. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/aws_lambda/mock_context.py +0 -0
  85. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/boto3session.py +0 -0
  86. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
  87. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
  88. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
  89. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
  90. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
  91. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
  92. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
  93. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
  94. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
  95. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/cognito/user.py +0 -0
  96. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/connection_tracker.py +0 -0
  97. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
  98. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
  99. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
  100. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
  101. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
  102. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
  103. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
  104. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
  105. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_re_indexer.py +0 -0
  106. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
  107. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
  108. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
  109. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/readme.md +0 -0
  110. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
  111. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
  112. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/environment_services/__init__.py +0 -0
  113. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
  114. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
  115. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/erc/__init__.py +0 -0
  116. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/erc/ecr_connection.py +0 -0
  117. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
  118. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/http_status_codes.py +0 -0
  119. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/models/serializable_model.py +0 -0
  120. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/role_assumption_mixin.py +0 -0
  121. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/s3/s3.py +0 -0
  122. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/s3/s3_bucket.py +0 -0
  123. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/s3/s3_event_data.py +0 -0
  124. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/s3/s3_object.py +0 -0
  125. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/securityhub/securityhub.py +0 -0
  126. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/securityhub/securityhub_connection.py +0 -0
  127. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/session_setup_mixin.py +0 -0
  128. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/ssm/connection.py +0 -0
  129. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py +0 -0
  130. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
  131. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/decimal_conversion_utility.py +0 -0
  132. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/dictionary_utility.py +0 -0
  133. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/file_operations.py +0 -0
  134. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/http_utility.py +0 -0
  135. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
  136. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
  137. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/serialization_utility.py +0 -0
  138. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/src/boto3_assist/utilities/string_utility.py +0 -0
  139. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/__init__.py +0 -0
  140. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/integration/cross_account_connection_test.py +0 -0
  141. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/integration/tenant.py +0 -0
  142. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/integration/tenant_services.py +0 -0
  143. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/aws_config_test.py +0 -0
  144. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/common/db_test_helpers.py +0 -0
  145. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb/decimal_backward_compatibility_test.py +0 -0
  146. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb/decimal_conversion_integration_test.py +0 -0
  147. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb/test_dynamodb_key_to_dict.py +0 -0
  148. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/__init__.py +0 -0
  149. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/cms/base.py +0 -0
  150. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/cms/content_block.py +0 -0
  151. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/cms/page.py +0 -0
  152. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/cms/template.py +0 -0
  153. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/simple_model.py +0 -0
  154. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/task.py +0 -0
  155. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/user_model.py +0 -0
  156. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/db_models/user_required_fields_model.py +0 -0
  157. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_batch_operations_test.py +0 -0
  158. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_conditional_test.py +0 -0
  159. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_fail_if_exists_test.py +0 -0
  160. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_model_base_test.py +0 -0
  161. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_model_projections_test.py +0 -0
  162. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_model_serializtion_test.py +0 -0
  163. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_moto_sorting_test.py +0 -0
  164. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_get_test.py +0 -0
  165. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_sort_test.py +0 -0
  166. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_query_test.py +0 -0
  167. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_reindex_test.py +0 -0
  168. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_transactions_test.py +0 -0
  169. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/dynamodb_tests/dynamodb_update_expressions_test.py +0 -0
  170. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/examples_test/README.md +0 -0
  171. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/examples_test/__init__.py +0 -0
  172. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/examples_test/order_service_test.py +0 -0
  173. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/examples_test/user_service_test.py +0 -0
  174. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/lambda_tests/__init__.py +0 -0
  175. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/lambda_tests/event_info_test.py +0 -0
  176. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/__init__.py +0 -0
  177. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/models/person.py +0 -0
  178. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/models/user.py +0 -0
  179. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/serializable_model_person_test.py +0 -0
  180. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/serializable_model_user_test.py +0 -0
  181. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/models_tests/serializable_model_wide_test.py +0 -0
  182. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/parameter_store/__init__.py +0 -0
  183. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/parameter_store/parameter_store_test.py +0 -0
  184. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/s3/__init__.py +0 -0
  185. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/s3/files/test.txt +0 -0
  186. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/s3/s3_event_data_test.py +0 -0
  187. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/s3/s3_file_delete_test.py +0 -0
  188. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/s3/s3_file_upload_test.py +0 -0
  189. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/session_tests/test_boto3_session_manager.py +0 -0
  190. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/utilities/__init__.py +0 -0
  191. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/utilities/decimal_conversion_utility_test.py +0 -0
  192. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/utilities/serialization_utility_test.py +0 -0
  193. {boto3_assist-0.32.0 → boto3_assist-0.34.0}/tests/unit/utilities/string_utility_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boto3_assist
3
- Version: 0.32.0
3
+ Version: 0.34.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
@@ -15,7 +15,7 @@ addopts = "-m 'not integration'"
15
15
 
16
16
  [project]
17
17
  name = "boto3_assist"
18
- version = "0.32.0"
18
+ version = "0.34.0"
19
19
 
20
20
  authors = [
21
21
  { name="Eric Wilson", email="boto3-assist@geekcafe.com" }
@@ -7,6 +7,7 @@ MIT License. See Project Root for the license information.
7
7
  from typing import Optional, List
8
8
 
9
9
  from aws_lambda_powertools import Logger
10
+ from botocore.config import Config
10
11
  from boto3_assist.boto3session import Boto3SessionManager
11
12
  from boto3_assist.environment_services.environment_variables import (
12
13
  EnvironmentVariables,
@@ -33,6 +34,7 @@ class Connection:
33
34
  assume_role_arn: Optional[str] = None,
34
35
  assume_role_chain: Optional[List[str]] = None,
35
36
  assume_role_duration_seconds: Optional[int] = 3600,
37
+ config: Optional[Config] = None,
36
38
  ) -> None:
37
39
  self.__aws_profile = aws_profile
38
40
  self.__aws_region = aws_region
@@ -44,6 +46,7 @@ class Connection:
44
46
  self.__service_name: str | None = service_name
45
47
  self.__assume_role_chain = assume_role_chain
46
48
  self.__assume_role_duration_seconds = assume_role_duration_seconds
49
+ self.__config = config
47
50
  if self.__service_name is None:
48
51
  raise RuntimeError(
49
52
  "Service Name is not available. The service name is required."
@@ -80,6 +83,7 @@ class Connection:
80
83
  assume_role_arn=self.__assume_role_arn,
81
84
  assume_role_chain=self.__assume_role_chain,
82
85
  assume_role_duration_seconds=self.__assume_role_duration_seconds,
86
+ config=self.__config,
83
87
  )
84
88
 
85
89
  tracker.add(service_name=self.service_name)
@@ -6,11 +6,12 @@ MIT License. See Project Root for the license information.
6
6
 
7
7
  from __future__ import annotations
8
8
  import datetime as dt
9
+ from enum import Enum
9
10
 
10
11
  # import decimal
11
12
  # import inspect
12
13
  # import uuid
13
- from typing import TypeVar, List, Dict, Any
14
+ from typing import TypeVar, List, Dict, Any, Set
14
15
  from boto3.dynamodb.types import TypeSerializer, TypeDeserializer
15
16
  from boto3_assist.utilities.serialization_utility import Serialization
16
17
  from boto3_assist.utilities.decimal_conversion_utility import DecimalConversionUtility
@@ -25,6 +26,37 @@ from boto3_assist.models.serializable_model import SerializableModel
25
26
  from boto3_assist.utilities.string_utility import StringUtility
26
27
 
27
28
 
29
+ class MergeStrategy(Enum):
30
+ """Strategy for merging updates into an existing model."""
31
+
32
+ NON_NULL_WINS = "non_null_wins"
33
+ """Only overwrite if the update value is not None (default, most common)."""
34
+
35
+ UPDATES_WIN = "updates_win"
36
+ """Update values always win, even if None."""
37
+
38
+ EXISTING_WINS = "existing_wins"
39
+ """Only fill in fields that are currently None in the existing model."""
40
+
41
+
42
+ class _ClearFieldSentinel:
43
+ """Sentinel class to explicitly mark a field for clearing to None."""
44
+
45
+ _instance = None
46
+
47
+ def __new__(cls):
48
+ if cls._instance is None:
49
+ cls._instance = super().__new__(cls)
50
+ return cls._instance
51
+
52
+ def __repr__(self) -> str:
53
+ return "CLEAR_FIELD"
54
+
55
+
56
+ # Singleton sentinel value - use this to explicitly clear a field to None
57
+ CLEAR_FIELD = _ClearFieldSentinel()
58
+
59
+
28
60
  def exclude_from_serialization(method):
29
61
  """
30
62
  Decorator to mark methods or properties to be excluded from serialization.
@@ -179,6 +211,80 @@ class DynamoDBModelBase(SerializableModel):
179
211
  # attempt to map it
180
212
  return DynamoDBSerializer.map(source=item, target=self)
181
213
 
214
+ def merge(
215
+ self: T,
216
+ updates: Dict[str, Any] | DynamoDBModelBase | None,
217
+ strategy: MergeStrategy = MergeStrategy.NON_NULL_WINS,
218
+ include_fields: Set[str] | List[str] | None = None,
219
+ exclude_fields: Set[str] | List[str] | None = None,
220
+ ) -> T:
221
+ """
222
+ Merge updates into this instance based on the specified strategy.
223
+
224
+ Unlike map() which overwrites all fields, merge() selectively updates
225
+ fields based on the strategy and handles the common case where you want
226
+ to apply partial updates from an API request.
227
+
228
+ Args:
229
+ updates: The source of updates - can be a dict or another model instance.
230
+ strategy: How to handle the merge:
231
+ - NON_NULL_WINS (default): Only overwrite if update value is not None.
232
+ Use CLEAR_FIELD sentinel to explicitly set a field to None.
233
+ - UPDATES_WIN: Update values always win, even if None.
234
+ - EXISTING_WINS: Only fill in fields that are currently None.
235
+ include_fields: If provided, only these fields will be considered for merge.
236
+ exclude_fields: Fields to exclude from the merge (e.g., 'id', 'created_at').
237
+
238
+ Returns:
239
+ Self with merged updates applied.
240
+
241
+ Example:
242
+ # Load existing from DB
243
+ existing = Product().map(db_response)
244
+
245
+ # Merge partial updates (only non-null fields applied)
246
+ existing.merge({"name": "New Name", "price": None}) # price unchanged
247
+
248
+ # Explicitly clear a field
249
+ from boto3_assist.dynamodb import CLEAR_FIELD
250
+ existing.merge({"description": CLEAR_FIELD}) # description set to None
251
+
252
+ # Fill gaps only (useful for defaults)
253
+ existing.merge(defaults, strategy=MergeStrategy.EXISTING_WINS)
254
+ """
255
+ if updates is None:
256
+ return self
257
+
258
+ # Convert to dict if needed
259
+ updates_dict: Dict[str, Any]
260
+ if isinstance(updates, DynamoDBModelBase):
261
+ updates_dict = updates.to_resource_dictionary(include_indexes=False)
262
+ elif isinstance(updates, dict):
263
+ updates_dict = updates.copy()
264
+ else:
265
+ raise ValueError("Updates must be a dictionary or DynamoDBModelBase")
266
+
267
+ # Convert decimals if present
268
+ updates_dict = DecimalConversionUtility.convert_decimals_to_native_types(
269
+ updates_dict
270
+ )
271
+
272
+ # Apply field filters
273
+ if include_fields is not None:
274
+ include_set = set(include_fields)
275
+ updates_dict = {k: v for k, v in updates_dict.items() if k in include_set}
276
+
277
+ if exclude_fields is not None:
278
+ exclude_set = set(exclude_fields)
279
+ updates_dict = {
280
+ k: v for k, v in updates_dict.items() if k not in exclude_set
281
+ }
282
+
283
+ # Apply merge based on strategy
284
+ return DynamoDBSerializer.merge(
285
+ updates=updates_dict, target=self, strategy=strategy
286
+ )
287
+
182
288
  def to_client_dictionary(self, include_indexes: bool = True):
183
289
  """
184
290
  Convert the instance to a dictionary suitable for DynamoDB client.
@@ -380,3 +486,116 @@ class DynamoDBSerializer:
380
486
  instance_dict[key.sort_key.attribute_name] = key.sort_key.value
381
487
 
382
488
  return instance_dict
489
+
490
+ @staticmethod
491
+ def merge(updates: Dict[str, Any], target: T, strategy: MergeStrategy) -> T:
492
+ """
493
+ Merge updates into the target object based on the specified strategy.
494
+
495
+ Args:
496
+ updates: Dictionary of field updates to apply.
497
+ target: The target object to merge into.
498
+ strategy: The merge strategy to use.
499
+
500
+ Returns:
501
+ The target object with updates merged.
502
+ """
503
+ for key, update_value in updates.items():
504
+ if not Serialization.has_attribute(target, key):
505
+ continue
506
+
507
+ current_value = getattr(target, key, None)
508
+
509
+ # Handle CLEAR_FIELD sentinel - always clears to None
510
+ if isinstance(update_value, _ClearFieldSentinel):
511
+ try:
512
+ setattr(target, key, None)
513
+ except (AttributeError, TypeError):
514
+ pass # Property without setter or type issue
515
+ continue
516
+
517
+ # Apply strategy
518
+ should_update = False
519
+
520
+ if strategy == MergeStrategy.UPDATES_WIN:
521
+ # Updates always win
522
+ should_update = True
523
+
524
+ elif strategy == MergeStrategy.NON_NULL_WINS:
525
+ # Only update if the new value is not None
526
+ should_update = update_value is not None
527
+
528
+ elif strategy == MergeStrategy.EXISTING_WINS:
529
+ # Only update if current value is None
530
+ should_update = current_value is None
531
+
532
+ if should_update:
533
+ try:
534
+ # Handle nested objects/dicts
535
+ if (
536
+ isinstance(current_value, dict)
537
+ and isinstance(update_value, dict)
538
+ and strategy != MergeStrategy.UPDATES_WIN
539
+ ):
540
+ # Recursively merge dicts
541
+ DynamoDBSerializer._merge_dict(
542
+ current_value, update_value, strategy
543
+ )
544
+ elif hasattr(current_value, "__dict__") and isinstance(
545
+ update_value, dict
546
+ ):
547
+ # Nested object - recursively merge
548
+ DynamoDBSerializer.merge(
549
+ updates=update_value,
550
+ target=current_value,
551
+ strategy=strategy,
552
+ )
553
+ else:
554
+ setattr(target, key, update_value)
555
+ except (AttributeError, TypeError):
556
+ pass # Property without setter or type issue
557
+
558
+ return target
559
+
560
+ @staticmethod
561
+ def _merge_dict(
562
+ target_dict: Dict[str, Any],
563
+ updates_dict: Dict[str, Any],
564
+ strategy: MergeStrategy,
565
+ ) -> None:
566
+ """
567
+ Merge updates into a target dictionary based on strategy.
568
+
569
+ Args:
570
+ target_dict: The dictionary to merge into (modified in place).
571
+ updates_dict: The dictionary of updates.
572
+ strategy: The merge strategy to use.
573
+ """
574
+ for key, update_value in updates_dict.items():
575
+ current_value = target_dict.get(key)
576
+
577
+ # Handle CLEAR_FIELD sentinel
578
+ if isinstance(update_value, _ClearFieldSentinel):
579
+ target_dict[key] = None
580
+ continue
581
+
582
+ should_update = False
583
+
584
+ if strategy == MergeStrategy.UPDATES_WIN:
585
+ should_update = True
586
+ elif strategy == MergeStrategy.NON_NULL_WINS:
587
+ should_update = update_value is not None
588
+ elif strategy == MergeStrategy.EXISTING_WINS:
589
+ should_update = current_value is None
590
+
591
+ if should_update:
592
+ if (
593
+ isinstance(current_value, dict)
594
+ and isinstance(update_value, dict)
595
+ and strategy != MergeStrategy.UPDATES_WIN
596
+ ):
597
+ DynamoDBSerializer._merge_dict(
598
+ current_value, update_value, strategy
599
+ )
600
+ else:
601
+ target_dict[key] = update_value
@@ -4,10 +4,12 @@ Maintainers: Eric Wilson
4
4
  MIT License. See Project Root for the license information.
5
5
  """
6
6
 
7
+ import os
7
8
  from typing import Optional
8
9
  from typing import TYPE_CHECKING
9
10
 
10
11
  from aws_lambda_powertools import Logger
12
+ from botocore.config import Config
11
13
 
12
14
  from boto3_assist.connection import Connection
13
15
 
@@ -32,7 +34,14 @@ class S3Connection(Connection):
32
34
  aws_end_point_url: Optional[str] = None,
33
35
  aws_access_key_id: Optional[str] = None,
34
36
  aws_secret_access_key: Optional[str] = None,
37
+ signature_version: Optional[str] = None,
35
38
  ) -> None:
39
+ # Build S3-specific config if signature_version is specified
40
+ config: Optional[Config] = None
41
+ signature_version = signature_version or os.getenv("AWS_S3_SIGNATURE_VERSION")
42
+ if signature_version:
43
+ config = Config(signature_version=signature_version)
44
+
36
45
  super().__init__(
37
46
  service_name="s3",
38
47
  aws_profile=aws_profile,
@@ -40,6 +49,7 @@ class S3Connection(Connection):
40
49
  aws_access_key_id=aws_access_key_id,
41
50
  aws_secret_access_key=aws_secret_access_key,
42
51
  aws_end_point_url=aws_end_point_url,
52
+ config=config,
43
53
  )
44
54
 
45
55
  self.__client: S3Client | None = None
@@ -0,0 +1 @@
1
+ __version__ = "0.34.0"