boto3-assist 0.30.0__tar.gz → 0.31.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 (199) hide show
  1. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/PKG-INFO +1 -1
  2. boto3_assist-0.31.0/docs/help/dynamodb/001-guide-single-table-design.md +460 -0
  3. boto3_assist-0.31.0/docs/help/dynamodb/002-guide-defining-models.md +627 -0
  4. boto3_assist-0.31.0/docs/help/dynamodb/003-guide-service-layers.md +887 -0
  5. boto3_assist-0.31.0/docs/help/dynamodb/004-guide-testing-with-moto.md +853 -0
  6. boto3_assist-0.31.0/docs/help/dynamodb/005-guide-projections-and-reserved-keywords.md +731 -0
  7. boto3_assist-0.31.0/docs/help/dynamodb/006-guide-how-dynamodb-stores-data.md +751 -0
  8. boto3_assist-0.31.0/docs/help/dynamodb/007-guide-batch-operations.md +582 -0
  9. boto3_assist-0.31.0/docs/help/dynamodb/008-guide-transactions.md +576 -0
  10. boto3_assist-0.31.0/docs/help/dynamodb/009-guide-conditional-writes.md +553 -0
  11. boto3_assist-0.31.0/docs/help/dynamodb/010-guide-update-expressions.md +634 -0
  12. boto3_assist-0.31.0/docs/overview.md +482 -0
  13. boto3_assist-0.31.0/docs/roadmap.md +517 -0
  14. boto3_assist-0.31.0/docs/tech-debt.md +783 -0
  15. boto3_assist-0.31.0/examples/dynamodb/QUICK_REFERENCE_KEY_DEBUGGING.md +197 -0
  16. boto3_assist-0.31.0/examples/dynamodb/RUNTIME_KEY_DEBUGGING_SUMMARY.md +138 -0
  17. boto3_assist-0.31.0/examples/dynamodb/batch_operations_example.py +272 -0
  18. boto3_assist-0.31.0/examples/dynamodb/conditional_writes_example.py +379 -0
  19. boto3_assist-0.31.0/examples/dynamodb/debug_keys_example.py +344 -0
  20. boto3_assist-0.31.0/examples/dynamodb/runtime_key_debugging_example.py +317 -0
  21. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/order_service.py +2 -2
  22. boto3_assist-0.31.0/examples/dynamodb/transactions_example.py +426 -0
  23. boto3_assist-0.31.0/examples/dynamodb/update_expressions_example.py +392 -0
  24. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pyproject.toml +1 -1
  25. boto3_assist-0.31.0/requirements.dev.txt +34 -0
  26. boto3_assist-0.31.0/src/boto3_assist/dynamodb/dynamodb.py +1206 -0
  27. boto3_assist-0.31.0/src/boto3_assist/dynamodb/dynamodb_index.py +507 -0
  28. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_key.py +19 -0
  29. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +10 -3
  30. boto3_assist-0.31.0/src/boto3_assist/version.py +1 -0
  31. boto3_assist-0.31.0/tests/unit/dynamodb/test_dynamodb_key_to_dict.py +327 -0
  32. boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_batch_operations_test.py +337 -0
  33. boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_conditional_test.py +395 -0
  34. boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_transactions_test.py +484 -0
  35. boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_update_expressions_test.py +508 -0
  36. boto3_assist-0.31.0/tests/unit/examples_test/README.md +114 -0
  37. boto3_assist-0.31.0/tests/unit/examples_test/order_service_test.py +303 -0
  38. boto3_assist-0.30.0/docs/defining-models.md +0 -237
  39. boto3_assist-0.30.0/docs/defining-services.md +0 -150
  40. boto3_assist-0.30.0/requirements-dev.txt +0 -26
  41. boto3_assist-0.30.0/requirements.dev.txt +0 -10
  42. boto3_assist-0.30.0/setup.sh +0 -55
  43. boto3_assist-0.30.0/src/boto3_assist/dynamodb/dynamodb.py +0 -548
  44. boto3_assist-0.30.0/src/boto3_assist/dynamodb/dynamodb_index.py +0 -249
  45. boto3_assist-0.30.0/src/boto3_assist/version.py +0 -1
  46. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker +0 -0
  47. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker.001 +0 -0
  48. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker.nosql.workbench +0 -0
  49. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.unittest +0 -0
  50. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.gitignore +0 -0
  51. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/launch.json +0 -0
  52. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/settings.json +0 -0
  53. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/tasks.json +0 -0
  54. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.windsurf/rules/cascade.yaml +0 -0
  55. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/LICENSE-EXPLAINED.txt +0 -0
  56. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/LICENSE.txt +0 -0
  57. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/README.md +0 -0
  58. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/aws_regions_with_status.csv +0 -0
  59. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/aws_regions_with_status.json +0 -0
  60. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/devops/build.py +0 -0
  61. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/devops/readme.md +0 -0
  62. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/design-patterns.md +0 -0
  63. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_BEFORE_AFTER.md +0 -0
  64. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_DECIMAL_PATTERN.md +0 -0
  65. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_IMPLEMENTATION_CHECKLIST.md +0 -0
  66. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/unit-test-patterns.md +0 -0
  67. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/__init__.py +0 -0
  68. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/cloudwatch/log_report.py +0 -0
  69. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/decimal_conversion_example.py +0 -0
  70. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/order_item_model.py +0 -0
  71. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/order_model.py +0 -0
  72. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/product_model.py +0 -0
  73. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/user_model.py +0 -0
  74. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/user_post_model.py +0 -0
  75. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/order_example/main.py +0 -0
  76. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/order_example/products.json +0 -0
  77. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/order_item_service.py +0 -0
  78. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/product_service.py +0 -0
  79. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/table_service.py +0 -0
  80. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_post_service.py +0 -0
  81. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service.py +0 -0
  82. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
  83. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
  84. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/user_post_example/main.py +0 -0
  85. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/ec2/regions_report.py +0 -0
  86. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/module-headers.txt +0 -0
  87. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/mypy.ini +0 -0
  88. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/publish_to_pypi.py +0 -0
  89. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/publish_to_pypi.sh +0 -0
  90. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pysetup.py +0 -0
  91. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pysetup.sh +0 -0
  92. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/requirements.txt +0 -0
  93. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/run-checks.sh +0 -0
  94. /boto3_assist-0.30.0/run_unit_tests.sh → /boto3_assist-0.31.0/run-unit-tests.sh +0 -0
  95. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/__init__.py +0 -0
  96. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_config.py +0 -0
  97. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
  98. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_lambda/mock_context.py +0 -0
  99. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/boto3session.py +0 -0
  100. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
  101. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
  102. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
  103. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
  104. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
  105. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
  106. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
  107. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
  108. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
  109. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/user.py +0 -0
  110. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/connection.py +0 -0
  111. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/connection_tracker.py +0 -0
  112. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
  113. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
  114. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
  115. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
  116. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
  117. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_re_indexer.py +0 -0
  118. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
  119. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
  120. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
  121. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/readme.md +0 -0
  122. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
  123. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
  124. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/__init__.py +0 -0
  125. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
  126. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
  127. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/erc/__init__.py +0 -0
  128. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/erc/ecr_connection.py +0 -0
  129. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
  130. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/http_status_codes.py +0 -0
  131. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/models/serializable_model.py +0 -0
  132. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/role_assumption_mixin.py +0 -0
  133. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3.py +0 -0
  134. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_bucket.py +0 -0
  135. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_connection.py +0 -0
  136. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_event_data.py +0 -0
  137. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_object.py +0 -0
  138. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/securityhub/securityhub.py +0 -0
  139. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/securityhub/securityhub_connection.py +0 -0
  140. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/session_setup_mixin.py +0 -0
  141. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ssm/connection.py +0 -0
  142. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py +0 -0
  143. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
  144. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/decimal_conversion_utility.py +0 -0
  145. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/dictionary_utility.py +0 -0
  146. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/file_operations.py +0 -0
  147. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/http_utility.py +0 -0
  148. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
  149. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
  150. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/serialization_utility.py +0 -0
  151. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/string_utility.py +0 -0
  152. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/__init__.py +0 -0
  153. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/cross_account_connection_test.py +0 -0
  154. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/tenant.py +0 -0
  155. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/tenant_services.py +0 -0
  156. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/aws_config_test.py +0 -0
  157. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/common/db_test_helpers.py +0 -0
  158. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb/decimal_backward_compatibility_test.py +0 -0
  159. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb/decimal_conversion_integration_test.py +0 -0
  160. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/__init__.py +0 -0
  161. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/base.py +0 -0
  162. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/content_block.py +0 -0
  163. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/page.py +0 -0
  164. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/template.py +0 -0
  165. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/simple_model.py +0 -0
  166. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/task.py +0 -0
  167. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/user_model.py +0 -0
  168. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/user_required_fields_model.py +0 -0
  169. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_fail_if_exists_test.py +0 -0
  170. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_base_test.py +0 -0
  171. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_projections_test.py +0 -0
  172. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_serializtion_test.py +0 -0
  173. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_moto_sorting_test.py +0 -0
  174. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_get_test.py +0 -0
  175. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_sort_test.py +0 -0
  176. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_query_test.py +0 -0
  177. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_reindex_test.py +0 -0
  178. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/examples_test/__init__.py +0 -0
  179. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/examples_test/user_service_test.py +0 -0
  180. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/lambda_tests/__init__.py +0 -0
  181. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/lambda_tests/event_info_test.py +0 -0
  182. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/__init__.py +0 -0
  183. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/models/person.py +0 -0
  184. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/models/user.py +0 -0
  185. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_person_test.py +0 -0
  186. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_user_test.py +0 -0
  187. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_wide_test.py +0 -0
  188. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/parameter_store/__init__.py +0 -0
  189. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/parameter_store/parameter_store_test.py +0 -0
  190. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/__init__.py +0 -0
  191. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/files/test.txt +0 -0
  192. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_event_data_test.py +0 -0
  193. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_file_delete_test.py +0 -0
  194. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_file_upload_test.py +0 -0
  195. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/session_tests/test_boto3_session_manager.py +0 -0
  196. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/__init__.py +0 -0
  197. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/decimal_conversion_utility_test.py +0 -0
  198. {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/serialization_utility_test.py +0 -0
  199. {boto3_assist-0.30.0 → boto3_assist-0.31.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.30.0
3
+ Version: 0.31.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
@@ -0,0 +1,460 @@
1
+ # Understanding Single Table Design with boto3-assist
2
+
3
+ ## Introduction
4
+
5
+ Single Table Design is a DynamoDB best practice where you store all your application's data in a single table rather than creating separate tables for each entity type. While this may seem counterintuitive to those familiar with relational databases, it's the recommended approach for DynamoDB and offers significant benefits in terms of performance, cost, and scalability.
6
+
7
+ This guide will help you understand the fundamentals of single table design and how boto3-assist makes it easy to implement this pattern in your applications.
8
+
9
+ ## Why Single Table Design?
10
+
11
+ ### Benefits
12
+
13
+ - **Performance**: All related data can be retrieved in a single query, reducing the number of round trips to the database
14
+ - **Cost**: Fewer tables mean fewer provisioned resources and lower costs
15
+ - **Simplicity**: One table to manage instead of many
16
+ - **Atomic Transactions**: You can perform transactions across multiple entity types within the same table
17
+ - **Better Data Modeling**: Forces you to think about access patterns upfront
18
+
19
+ ### Traditional vs. Single Table
20
+
21
+ **Traditional Multi-Table Approach:**
22
+ ```
23
+ Users Table: user_id, name, email
24
+ Orders Table: order_id, user_id, total
25
+ Products Table: product_id, name, price
26
+ ```
27
+
28
+ **Single Table Design:**
29
+ ```
30
+ AppTable: pk, sk, ... (all entity attributes)
31
+ ```
32
+
33
+ All your users, orders, and products live in one table, differentiated by their partition and sort keys.
34
+
35
+ ## The Foundation: Partition Keys (pk) and Sort Keys (sk)
36
+
37
+ In single table design, the `pk` (partition key) and `sk` (sort key) are the secret sauce. They determine:
38
+ - **Where** your data is stored (partition key)
39
+ - **How** your data is organized within that partition (sort key)
40
+ - **What** query patterns you can support
41
+
42
+ ### Anatomy of a Key
43
+
44
+ Keys in boto3-assist follow a structured pattern:
45
+
46
+ ```
47
+ entityType#entityId
48
+ ```
49
+
50
+ For example:
51
+ - `user#123` - Represents user with ID 123
52
+ - `product#abc-456` - Represents product with ID abc-456
53
+ - `order#xyz-789` - Represents order with ID xyz-789
54
+
55
+ The `#` delimiter separates the entity type from the identifier, making keys readable and queryable.
56
+
57
+ ## Simple Entity Storage
58
+
59
+ Let's start with the simplest case: storing a single entity type.
60
+
61
+ ### Example: Product Entity
62
+
63
+ ```python
64
+ from boto3_assist.dynamodb.dynamodb_model_base import DynamoDBModelBase
65
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
66
+
67
+ class Product(DynamoDBModelBase):
68
+ def __init__(self, id=None, name=None, price=0.0):
69
+ super().__init__()
70
+ self.id = id
71
+ self.name = name
72
+ self.price = price
73
+ self._setup_indexes()
74
+
75
+ def _setup_indexes(self):
76
+ primary = DynamoDBIndex()
77
+ primary.partition_key.attribute_name = "pk"
78
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
79
+ primary.sort_key.attribute_name = "sk"
80
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
81
+ self.indexes.add_primary(primary)
82
+ ```
83
+
84
+ **What gets stored:**
85
+ ```json
86
+ {
87
+ "pk": "product#abc-123",
88
+ "sk": "product#abc-123",
89
+ "id": "abc-123",
90
+ "name": "Widget",
91
+ "price": 29.99
92
+ }
93
+ ```
94
+
95
+ **Access pattern:** Get product by ID
96
+ - Query: `pk = "product#abc-123" AND sk = "product#abc-123"`
97
+ - Returns: The specific product
98
+
99
+ ## One-to-Many Relationships: The Power of pk/sk
100
+
101
+ This is where single table design really shines. By cleverly using partition and sort keys, you can model one-to-many relationships without joins.
102
+
103
+ ### The Pattern
104
+
105
+ **For a one-to-many relationship:**
106
+ - **Parent entity**: `pk = parent_type#parent_id`, `sk = parent_type#parent_id`
107
+ - **Child entities**: `pk = parent_type#parent_id`, `sk = child_type#child_id`
108
+
109
+ Notice that child items share the **same partition key** as their parent but have **different sort keys**.
110
+
111
+ ### Example: Order → Order Items (1:Many)
112
+
113
+ An order can have many order items. Let's see how this works:
114
+
115
+ ```python
116
+ class Order(DynamoDBModelBase):
117
+ def __init__(self, id=None):
118
+ super().__init__()
119
+ self.id = id
120
+ self.user_id = None
121
+ self.total = 0.0
122
+ self._setup_indexes()
123
+
124
+ def _setup_indexes(self):
125
+ primary = DynamoDBIndex()
126
+ primary.partition_key.attribute_name = "pk"
127
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
128
+ primary.sort_key.attribute_name = "sk"
129
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("order", self.id))
130
+ self.indexes.add_primary(primary)
131
+
132
+ class OrderItem(DynamoDBModelBase):
133
+ def __init__(self):
134
+ super().__init__()
135
+ self.id = None
136
+ self.order_id = None # Parent order's ID
137
+ self.product_id = None
138
+ self.quantity = 0
139
+ self._setup_indexes()
140
+
141
+ def _setup_indexes(self):
142
+ primary = DynamoDBIndex()
143
+ # Same partition key as the parent order
144
+ primary.partition_key.attribute_name = "pk"
145
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("order", self.order_id))
146
+ # Different sort key for this item
147
+ primary.sort_key.attribute_name = "sk"
148
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("item", self.id))
149
+ self.indexes.add_primary(primary)
150
+ ```
151
+
152
+ **What gets stored:**
153
+
154
+ ```json
155
+ // Order (parent)
156
+ {
157
+ "pk": "order#xyz-789",
158
+ "sk": "order#xyz-789",
159
+ "id": "xyz-789",
160
+ "user_id": "user-123",
161
+ "total": 99.99
162
+ }
163
+
164
+ // Order Item 1 (child)
165
+ {
166
+ "pk": "order#xyz-789",
167
+ "sk": "item#item-001",
168
+ "id": "item-001",
169
+ "order_id": "xyz-789",
170
+ "product_id": "prod-456",
171
+ "quantity": 2
172
+ }
173
+
174
+ // Order Item 2 (child)
175
+ {
176
+ "pk": "order#xyz-789",
177
+ "sk": "item#item-002",
178
+ "id": "item-002",
179
+ "order_id": "xyz-789",
180
+ "product_id": "prod-789",
181
+ "quantity": 1
182
+ }
183
+ ```
184
+
185
+ **Access patterns enabled:**
186
+
187
+ 1. **Get order only:**
188
+ - Query: `pk = "order#xyz-789" AND sk = "order#xyz-789"`
189
+ - Returns: Just the order
190
+
191
+ 2. **Get order with all items:**
192
+ - Query: `pk = "order#xyz-789"` (partition key only, no sort key filter)
193
+ - Returns: Order + all order items in a single query!
194
+
195
+ 3. **Get items only:**
196
+ - Query: `pk = "order#xyz-789" AND sk begins_with "item#"`
197
+ - Returns: Only the order items (no order)
198
+
199
+ This is the magic of single table design. Because all related items share the same partition key, DynamoDB returns them together efficiently.
200
+
201
+ ### Why This Works
202
+
203
+ 1. **Same Partition**: The order and its items are stored in the same partition (`pk = "order#xyz-789"`)
204
+ 2. **Sort Key Differentiation**: Each item type has a unique sort key pattern
205
+ - Order: `sk = "order#xyz-789"`
206
+ - Items: `sk = "item#item-001"`, `sk = "item#item-002"`, etc.
207
+ 3. **Flexible Querying**: The sort key is **optional** in queries
208
+ - Omit it → get everything with that partition key
209
+ - Specify it exactly → get one specific item
210
+ - Use `begins_with` → get items matching a pattern
211
+
212
+ ### Important: Sort Key is Optional in Queries
213
+
214
+ **This is a critical concept many developers miss:**
215
+
216
+ In DynamoDB:
217
+ - The **partition key is REQUIRED** for all queries
218
+ - The **sort key is OPTIONAL** for queries (but required when defining the table)
219
+
220
+ ```python
221
+ # Pattern 1: Get with both pk and sk (using db.get())
222
+ model = Order(id="xyz-789")
223
+ response = db.get(model=model, table_name=table_name)
224
+ # Uses: pk="order#xyz-789" AND sk="order#xyz-789"
225
+ # Returns: Just the order
226
+
227
+ # Pattern 2: Query with pk only (using db.query())
228
+ model = Order(id="xyz-789")
229
+ key = model.indexes.primary.key(include_sort_key=False)
230
+ response = db.query(key=key, table_name=table_name)
231
+ # Uses: pk="order#xyz-789" (no sk filter)
232
+ # Returns: Order + all items (everything with that pk)
233
+
234
+ # Pattern 3: Query with pk + sk condition (using db.query())
235
+ from boto3.dynamodb.conditions import Key
236
+ key = Key("pk").eq("order#xyz-789") & Key("sk").begins_with("item#")
237
+ response = db.query(key=key, table_name=table_name)
238
+ # Uses: pk="order#xyz-789" AND sk starts with "item#"
239
+ # Returns: Only the items
240
+ ```
241
+
242
+ **Real-World Service Code:**
243
+
244
+ ```python
245
+ class OrderService:
246
+ def get_order(self, order_id: str, include_items: bool = False):
247
+ model = Order(id=order_id)
248
+
249
+ if include_items:
250
+ # Query with pk only - gets order + items
251
+ key = model.indexes.primary.key(include_sort_key=False)
252
+ response = self.db.query(key=key, table_name=self.table_name)
253
+ # Returns: {'Items': [order, item1, item2, ...]}
254
+ else:
255
+ # Get with pk + sk - gets just the order
256
+ response = self.db.get(model=model, table_name=self.table_name)
257
+ # Returns: {'Item': order}
258
+
259
+ return response
260
+ ```
261
+
262
+ See the complete working example in [tests/unit/examples_test/order_service_test.py](../tests/unit/examples_test/order_service_test.py) which demonstrates all three patterns.
263
+
264
+ ## Another Example: User → Posts (1:Many)
265
+
266
+ Let's look at another common pattern:
267
+
268
+ ```python
269
+ class User(DynamoDBModelBase):
270
+ def __init__(self, id=None):
271
+ super().__init__()
272
+ self.id = id
273
+ self.name = None
274
+ self._setup_indexes()
275
+
276
+ def _setup_indexes(self):
277
+ primary = DynamoDBIndex()
278
+ primary.partition_key.attribute_name = "pk"
279
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("user", self.id))
280
+ primary.sort_key.attribute_name = "sk"
281
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("user", self.id))
282
+ self.indexes.add_primary(primary)
283
+
284
+ class Post(DynamoDBModelBase):
285
+ def __init__(self):
286
+ super().__init__()
287
+ self.id = None
288
+ self.user_id = None # Parent user's ID
289
+ self.title = None
290
+ self.content = None
291
+ self.created_at = None
292
+ self._setup_indexes()
293
+
294
+ def _setup_indexes(self):
295
+ primary = DynamoDBIndex()
296
+ # Share partition key with user
297
+ primary.partition_key.attribute_name = "pk"
298
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("user", self.user_id))
299
+ # Unique sort key for posts
300
+ primary.sort_key.attribute_name = "sk"
301
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("post", self.id))
302
+ self.indexes.add_primary(primary)
303
+ ```
304
+
305
+ **Storage:**
306
+
307
+ ```json
308
+ // User
309
+ {
310
+ "pk": "user#alice",
311
+ "sk": "user#alice",
312
+ "id": "alice",
313
+ "name": "Alice Johnson"
314
+ }
315
+
316
+ // Post 1
317
+ {
318
+ "pk": "user#alice",
319
+ "sk": "post#post-001",
320
+ "id": "post-001",
321
+ "user_id": "alice",
322
+ "title": "My First Post"
323
+ }
324
+
325
+ // Post 2
326
+ {
327
+ "pk": "user#alice",
328
+ "sk": "post#post-002",
329
+ "id": "post-002",
330
+ "user_id": "alice",
331
+ "title": "Another Great Post"
332
+ }
333
+ ```
334
+
335
+ **Query all of Alice's posts:**
336
+ ```python
337
+ # Query with partition key only
338
+ key = Key('pk').eq('user#alice')
339
+ # Returns: User record + all posts
340
+ ```
341
+
342
+ ## Advanced: Hierarchical Relationships
343
+
344
+ You can even model deeper hierarchies using sort key patterns.
345
+
346
+ ### Example: Category → Product → Reviews
347
+
348
+ ```python
349
+ # Category
350
+ pk = "category#electronics"
351
+ sk = "category#electronics"
352
+
353
+ # Product in category
354
+ pk = "category#electronics"
355
+ sk = "product#laptop-001"
356
+
357
+ # Review for product
358
+ pk = "category#electronics"
359
+ sk = "product#laptop-001#review#review-001"
360
+ ```
361
+
362
+ This allows queries like:
363
+ - All items in a category: `pk = "category#electronics"`
364
+ - All products: `pk = "category#electronics" AND sk begins_with "product#"`
365
+ - All reviews for a product: `pk = "category#electronics" AND sk begins_with "product#laptop-001#review#"`
366
+
367
+ ## Global Secondary Indexes (GSIs): Alternative Access Patterns
368
+
369
+ While pk/sk handle one-to-many relationships, GSIs let you query data in different ways.
370
+
371
+ ### Example: Query All Products by Name
372
+
373
+ ```python
374
+ class Product(DynamoDBModelBase):
375
+ def _setup_indexes(self):
376
+ # Primary index (same as before)
377
+ primary = DynamoDBIndex()
378
+ primary.partition_key.attribute_name = "pk"
379
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
380
+ primary.sort_key.attribute_name = "sk"
381
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("product", self.id))
382
+ self.indexes.add_primary(primary)
383
+
384
+ # GSI to query all products
385
+ self.indexes.add_secondary(
386
+ DynamoDBIndex(
387
+ index_name="gsi0",
388
+ partition_key=DynamoDBKey(
389
+ attribute_name="gsi0_pk",
390
+ value=lambda: "products" # Static value
391
+ ),
392
+ sort_key=DynamoDBKey(
393
+ attribute_name="gsi0_sk",
394
+ value=lambda: DynamoDBKey.build_key(("name", self.name))
395
+ )
396
+ )
397
+ )
398
+ ```
399
+
400
+ **Access pattern:**
401
+ - Query all products sorted by name: `gsi0_pk = "products"` on GSI0
402
+
403
+ ## Key Principles of Single Table Design
404
+
405
+ 1. **Know Your Access Patterns First**: Design your keys based on how you'll query the data
406
+ 2. **Partition Key for Item Collection**: Items you want to retrieve together share a partition key
407
+ 3. **Sort Key for Differentiation**: Sort key differentiates items within a partition
408
+ 4. **One-to-Many via pk/sk**: Parent and children share `pk`, differ in `sk`
409
+ 5. **GSIs for Alternative Queries**: Add GSIs for query patterns that don't fit your primary key structure
410
+
411
+ ## Common Patterns Summary
412
+
413
+ | Pattern | Partition Key | Sort Key | Use Case |
414
+ |---------|--------------|----------|----------|
415
+ | **Single Item** | `entity#id` | `entity#id` | Get specific entity |
416
+ | **One-to-Many** | `parent#id` | `child#id` | Get parent with children |
417
+ | **Hierarchical** | `root#id` | `level1#id#level2#id` | Multi-level relationships |
418
+ | **All Items** | Static value (via GSI) | `entity#attribute` | List all of entity type |
419
+
420
+ ## Best Practices
421
+
422
+ 1. **Use Lambda Functions**: Always use `lambda` for key values so they're evaluated at runtime
423
+ 2. **Consistent Delimiters**: Stick with `#` as your delimiter
424
+ 3. **Descriptive Prefixes**: Use entity type names (`user#`, `order#`) for clarity
425
+ 4. **GSIs for Flexibility**: Add GSIs for access patterns that don't fit your primary keys
426
+ 5. **Think in Collections**: Design partition keys around how you want to group and retrieve data
427
+
428
+ ## Next Steps
429
+
430
+ Now that you understand single table design, you're ready to:
431
+ - [Define Models](2-guide-defining-models.md) - Learn how to create model classes
432
+ - [Create Service Layers](3-guide-service-layers.md) - Build services to interact with your models
433
+ - [Test with Moto](4-guide-testing-with-moto.md) - Set up local testing
434
+
435
+ ## Real-World Example
436
+
437
+ Let's put it all together with an e-commerce system:
438
+
439
+ ```
440
+ Primary Table Access Patterns:
441
+ 1. pk="product#123", sk="product#123" → Get product
442
+ 2. pk="order#456", sk="order#456" → Get order header
443
+ 3. pk="order#456", sk begins_with "item#" → Get all order items
444
+ 4. pk="order#456" → Get order + items (single query!)
445
+ 5. pk="user#alice", sk="user#alice" → Get user
446
+ 6. pk="user#alice", sk begins_with "order#" → Get user's orders
447
+
448
+ GSI Access Patterns:
449
+ 1. gsi0: All products sorted by name
450
+ 2. gsi1: All orders for a user sorted by date
451
+ 3. gsi2: All orders on a specific date
452
+ ```
453
+
454
+ All of this in **one table**!
455
+
456
+ ## Conclusion
457
+
458
+ Single table design is a paradigm shift from relational databases, but it's optimized for DynamoDB's strengths. By cleverly using partition and sort keys, especially for one-to-many relationships, you can build highly performant applications that retrieve all related data in a single query.
459
+
460
+ The key insight: **Items that are queried together should be stored together**, and boto3-assist makes implementing this pattern straightforward and maintainable.