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.
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/PKG-INFO +1 -1
- boto3_assist-0.31.0/docs/help/dynamodb/001-guide-single-table-design.md +460 -0
- boto3_assist-0.31.0/docs/help/dynamodb/002-guide-defining-models.md +627 -0
- boto3_assist-0.31.0/docs/help/dynamodb/003-guide-service-layers.md +887 -0
- boto3_assist-0.31.0/docs/help/dynamodb/004-guide-testing-with-moto.md +853 -0
- boto3_assist-0.31.0/docs/help/dynamodb/005-guide-projections-and-reserved-keywords.md +731 -0
- boto3_assist-0.31.0/docs/help/dynamodb/006-guide-how-dynamodb-stores-data.md +751 -0
- boto3_assist-0.31.0/docs/help/dynamodb/007-guide-batch-operations.md +582 -0
- boto3_assist-0.31.0/docs/help/dynamodb/008-guide-transactions.md +576 -0
- boto3_assist-0.31.0/docs/help/dynamodb/009-guide-conditional-writes.md +553 -0
- boto3_assist-0.31.0/docs/help/dynamodb/010-guide-update-expressions.md +634 -0
- boto3_assist-0.31.0/docs/overview.md +482 -0
- boto3_assist-0.31.0/docs/roadmap.md +517 -0
- boto3_assist-0.31.0/docs/tech-debt.md +783 -0
- boto3_assist-0.31.0/examples/dynamodb/QUICK_REFERENCE_KEY_DEBUGGING.md +197 -0
- boto3_assist-0.31.0/examples/dynamodb/RUNTIME_KEY_DEBUGGING_SUMMARY.md +138 -0
- boto3_assist-0.31.0/examples/dynamodb/batch_operations_example.py +272 -0
- boto3_assist-0.31.0/examples/dynamodb/conditional_writes_example.py +379 -0
- boto3_assist-0.31.0/examples/dynamodb/debug_keys_example.py +344 -0
- boto3_assist-0.31.0/examples/dynamodb/runtime_key_debugging_example.py +317 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/order_service.py +2 -2
- boto3_assist-0.31.0/examples/dynamodb/transactions_example.py +426 -0
- boto3_assist-0.31.0/examples/dynamodb/update_expressions_example.py +392 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pyproject.toml +1 -1
- boto3_assist-0.31.0/requirements.dev.txt +34 -0
- boto3_assist-0.31.0/src/boto3_assist/dynamodb/dynamodb.py +1206 -0
- boto3_assist-0.31.0/src/boto3_assist/dynamodb/dynamodb_index.py +507 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_key.py +19 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +10 -3
- boto3_assist-0.31.0/src/boto3_assist/version.py +1 -0
- boto3_assist-0.31.0/tests/unit/dynamodb/test_dynamodb_key_to_dict.py +327 -0
- boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_batch_operations_test.py +337 -0
- boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_conditional_test.py +395 -0
- boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_transactions_test.py +484 -0
- boto3_assist-0.31.0/tests/unit/dynamodb_tests/dynamodb_update_expressions_test.py +508 -0
- boto3_assist-0.31.0/tests/unit/examples_test/README.md +114 -0
- boto3_assist-0.31.0/tests/unit/examples_test/order_service_test.py +303 -0
- boto3_assist-0.30.0/docs/defining-models.md +0 -237
- boto3_assist-0.30.0/docs/defining-services.md +0 -150
- boto3_assist-0.30.0/requirements-dev.txt +0 -26
- boto3_assist-0.30.0/requirements.dev.txt +0 -10
- boto3_assist-0.30.0/setup.sh +0 -55
- boto3_assist-0.30.0/src/boto3_assist/dynamodb/dynamodb.py +0 -548
- boto3_assist-0.30.0/src/boto3_assist/dynamodb/dynamodb_index.py +0 -249
- boto3_assist-0.30.0/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker.001 +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.env.unittest +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.gitignore +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/settings.json +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/.windsurf/rules/cascade.yaml +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/LICENSE.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/README.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/devops/build.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/devops/readme.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/design-patterns.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_BEFORE_AFTER.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_DECIMAL_PATTERN.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/issues/BOTO3_ASSIST_IMPLEMENTATION_CHECKLIST.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/docs/unit-test-patterns.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/decimal_conversion_example.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/module-headers.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/mypy.ini +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/publish_to_pypi.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/publish_to_pypi.sh +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pysetup.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/pysetup.sh +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/requirements.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/run-checks.sh +0 -0
- /boto3_assist-0.30.0/run_unit_tests.sh → /boto3_assist-0.31.0/run-unit-tests.sh +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_config.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/aws_lambda/mock_context.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/connection_tracker.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_re_indexer.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/environment_loader.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/erc/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/erc/ecr_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/models/serializable_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/role_assumption_mixin.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_bucket.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_event_data.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/s3/s3_object.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/securityhub/securityhub.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/securityhub/securityhub_connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/session_setup_mixin.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ssm/connection.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/ssm/parameter_store/parameter_store.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/decimal_conversion_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/dictionary_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/numbers_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/serialization_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/src/boto3_assist/utilities/string_utility.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/cross_account_connection_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/tenant.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/integration/tenant_services.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/aws_config_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/common/db_test_helpers.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb/decimal_backward_compatibility_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb/decimal_conversion_integration_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/base.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/content_block.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/page.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/cms/template.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/simple_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/task.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/user_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/db_models/user_required_fields_model.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_fail_if_exists_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_model_serializtion_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_moto_sorting_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_get_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_primary_key_sort_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_query_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/dynamodb_tests/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/examples_test/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/lambda_tests/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/lambda_tests/event_info_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/models/person.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/models/user.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_person_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_user_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/models_tests/serializable_model_wide_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/parameter_store/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/parameter_store/parameter_store_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/files/test.txt +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_event_data_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_file_delete_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/s3/s3_file_upload_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/session_tests/test_boto3_session_manager.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/__init__.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/decimal_conversion_utility_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/serialization_utility_test.py +0 -0
- {boto3_assist-0.30.0 → boto3_assist-0.31.0}/tests/unit/utilities/string_utility_test.py +0 -0
|
@@ -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.
|