modmex-lambda 0.5.6__tar.gz → 0.5.7__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.
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/PKG-INFO +1 -1
- modmex_lambda-0.5.7/modmex_lambda/persistence/dynamodb/keys.py +212 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/pyproject.toml +1 -1
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/persistence/test_dynamodb_keys.py +92 -14
- modmex_lambda-0.5.6/modmex_lambda/persistence/dynamodb/keys.py +0 -131
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/.github/workflows/ci.yml +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/.github/workflows/release.yml +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/.gitignore +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/LICENSE +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/README.md +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/cloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/eventbridge.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/icloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/idynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/ieventbridge.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/ilambda.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/is3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/isns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/isqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/lambda_.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/module.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/connectors/sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/api_gateway_authorizer_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/api_gateway_websocket_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/common.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/dependencies.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/api_gateway.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/constants.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/content_types.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/cors.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/compat.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/dependant.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/dependency_middleware.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/depends.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/params.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/types.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/exception_handler.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/exceptions.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/gateway_response.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/middlewares.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/params.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/request.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/response.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/routing.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/routing_fallbacks.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/types.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_sources.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/exceptions.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/logging.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/params.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/parser.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/persistence/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/persistence/dynamodb/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/persistence/dynamodb/expressions.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/persistence/dynamodb/materialized_views.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/persistence/dynamodb/stream_fields.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/request.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/resolver.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/response.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/routing.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/shared/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/shared/cookies.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/shared/headers_serializer.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/shared/json_encoder.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/shared/types.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/kinesis.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/events/sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/filters/content.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/filters/event_type.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/filters/latch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/filters/skip.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/base_flavor.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/cdc.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/collect.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/correlate.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/evaluate.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/expired.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/iflavor.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/job.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/materialize.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/task.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/flavors/update.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/irules_registry.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/cloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/ioperator.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/lambda_.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/publisher.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/operators/sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/rules_registry.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/runner.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/base.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/kinesis.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/sources/sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/apigateway.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/aws.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/batch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/cloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/concurrency.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/contracts.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/data_classes/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/data_classes/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/decorators.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/eventbridge.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/faults.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/filters.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/json_encoder.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/lambda_.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/operators.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/pluralize.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/print.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/retry.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/split.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/tags.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/time.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/stream/utils/uow.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/tracing.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/validation.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/poetry.lock +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/conftest.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/conftest.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_cloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_lazy_clients.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_simple_connectors.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/connectors/test_sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/data_classes/test_api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/data_classes/test_cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/data_classes/test_common.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_api_gateway.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_cors.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_dependencies.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_exception_handler.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_gateway_response.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_request.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_response.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/event_handler/test_routing.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/persistence/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/persistence/test_dynamodb_expressions.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/persistence/test_dynamodb_materialized_views.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/persistence/test_dynamodb_stream_fields.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/shared/test_cookies_headers.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/shared/test_json_encoder.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/conftest.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/events/test_dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/events/test_kinesis.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/events/test_s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/events/test_sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/events/test_sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/filters/test_content.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/filters/test_event_type.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/filters/test_latch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/filters/test_skip.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/source_events.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_base_flavor.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_cdc.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_collect.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_correlate.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_evaluate.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_expired.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_job.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_materialize.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_task.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/flavors/test_update.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/test_dependency_resolver.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/test_rules_registry.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/test_runner.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/test_runner_pipeline.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/test_sources.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/faults.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_apigateway.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_aws.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_batch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_cloudwatch.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_concurrency.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_decorators.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_dynamodb.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_eventbridge.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_filters.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_json_encoder.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_lambda.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_operators.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_pluralize.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_print.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_retry.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_s3.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_sns.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_split.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_sqs.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_tags.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_time.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/stream/utils/test_uow.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_lazy_imports.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_logging.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_parser_event_sources.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_reexports.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_tracing.py +0 -0
- {modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/tests/test_validation.py +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""Reusable DynamoDB key strategies.
|
|
2
|
+
|
|
3
|
+
These helpers keep key-shaping decisions explicit while letting repositories
|
|
4
|
+
stay focused on persistence behavior.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Callable, Mapping
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
AggregateNameResolver = Callable[..., str]
|
|
16
|
+
AggregateIdResolver = Callable[..., str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class KeyStrategy(ABC):
|
|
20
|
+
"""Build DynamoDB primary keys for ids and entities."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def key_for_id(self, record_id: Any, **context: Any) -> dict[str, str]:
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def key_for_record(self, record: Any, **context: Any) -> dict[str, str]:
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class SingleEntityKeyStrategy(KeyStrategy):
|
|
33
|
+
"""Use the record id as pk and a fixed discriminator as sk."""
|
|
34
|
+
|
|
35
|
+
discriminator: str
|
|
36
|
+
separator: str = "#"
|
|
37
|
+
|
|
38
|
+
def key_for_id(self, record_id: Any, **context: Any) -> dict[str, str]:
|
|
39
|
+
return {
|
|
40
|
+
"pk": f"{self.discriminator}{self.separator}{record_id}",
|
|
41
|
+
"sk": self.discriminator,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def key_for_record(self, record: Any, **context: Any) -> dict[str, str]:
|
|
45
|
+
return self.key_for_id(_record_attr(record, "id"), **context)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class TenantScopedSortKeyStrategy(KeyStrategy):
|
|
50
|
+
"""Use discriminator + record id as pk and tenant name + tenant id as sk."""
|
|
51
|
+
|
|
52
|
+
discriminator: str
|
|
53
|
+
tenant_name: str = "tenant"
|
|
54
|
+
tenant_field: str = "tenant_id"
|
|
55
|
+
separator: str = "#"
|
|
56
|
+
|
|
57
|
+
def key_for_id(self, record_id: Any, **context: Any) -> dict[str, str]:
|
|
58
|
+
tenant_id = _context_value(context, self.tenant_field)
|
|
59
|
+
return {
|
|
60
|
+
"pk": f"{self.discriminator}{self.separator}{record_id}",
|
|
61
|
+
"sk": f"{self.tenant_name}{self.separator}{tenant_id}",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def key_for_record(self, record: Any, **context: Any) -> dict[str, str]:
|
|
65
|
+
tenant_id = _context_or_record_value(context, record, self.tenant_field)
|
|
66
|
+
return self.key_for_id(_record_attr(record, "id"), **{self.tenant_field: tenant_id})
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class TenantPartitionKeyStrategy(KeyStrategy):
|
|
72
|
+
"""Use tenant name + tenant id as pk and discriminator + record id as sk."""
|
|
73
|
+
|
|
74
|
+
discriminator: str
|
|
75
|
+
tenant_name: str = "tenant"
|
|
76
|
+
tenant_field: str = "tenant_id"
|
|
77
|
+
separator: str = "#"
|
|
78
|
+
|
|
79
|
+
def key_for_id(self, record_id: Any, **context: Any) -> dict[str, str]:
|
|
80
|
+
tenant_id = _context_value(context, self.tenant_field)
|
|
81
|
+
return {
|
|
82
|
+
"pk": f"{self.tenant_name}{self.separator}{tenant_id}",
|
|
83
|
+
"sk": f"{self.discriminator}{self.separator}{record_id}",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def key_for_record(self, record: Any, **context: Any) -> dict[str, str]:
|
|
87
|
+
tenant_id = _context_or_record_value(context, record, self.tenant_field)
|
|
88
|
+
return self.key_for_id(_record_attr(record, "id"), **{self.tenant_field: tenant_id})
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True)
|
|
91
|
+
class AggregateKeyStrategy(KeyStrategy):
|
|
92
|
+
"""Use aggregate name + aggregate id as pk and discriminator + record id as sk."""
|
|
93
|
+
|
|
94
|
+
discriminator: str
|
|
95
|
+
aggregate_name: str | AggregateNameResolver
|
|
96
|
+
aggregate_field: str | AggregateIdResolver
|
|
97
|
+
separator: str = "#"
|
|
98
|
+
|
|
99
|
+
def key_for_id(self, record_id: Any, **context: Any) -> dict[str, str]:
|
|
100
|
+
aggregate_name = self._resolve_aggregate_name(
|
|
101
|
+
record=None,
|
|
102
|
+
record_id=record_id,
|
|
103
|
+
context=context,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
aggregate_id = self._resolve_aggregate_id(
|
|
107
|
+
record=None,
|
|
108
|
+
record_id=record_id,
|
|
109
|
+
context=context,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return self._build_key(
|
|
113
|
+
aggregate_name=aggregate_name,
|
|
114
|
+
aggregate_id=aggregate_id,
|
|
115
|
+
record_id=record_id,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def key_for_record(self, record: Any, **context: Any) -> dict[str, str]:
|
|
119
|
+
record_id = _record_attr(record, "id")
|
|
120
|
+
|
|
121
|
+
aggregate_name = self._resolve_aggregate_name(
|
|
122
|
+
record=record,
|
|
123
|
+
record_id=record_id,
|
|
124
|
+
context=context,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
aggregate_id = self._resolve_aggregate_id(
|
|
128
|
+
record=record,
|
|
129
|
+
record_id=record_id,
|
|
130
|
+
context=context,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return self._build_key(
|
|
134
|
+
aggregate_name=aggregate_name,
|
|
135
|
+
aggregate_id=aggregate_id,
|
|
136
|
+
record_id=record_id,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _build_key(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
aggregate_name: str,
|
|
143
|
+
aggregate_id: Any,
|
|
144
|
+
record_id: Any,
|
|
145
|
+
) -> dict[str, str]:
|
|
146
|
+
return {
|
|
147
|
+
"pk": f"{aggregate_name}{self.separator}{aggregate_id}",
|
|
148
|
+
"sk": f"{self.discriminator}{self.separator}{record_id}",
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def _resolve_aggregate_name(
|
|
152
|
+
self,
|
|
153
|
+
*,
|
|
154
|
+
record: Any | None,
|
|
155
|
+
record_id: Any,
|
|
156
|
+
context: Mapping[str, Any],
|
|
157
|
+
) -> str:
|
|
158
|
+
if callable(self.aggregate_name):
|
|
159
|
+
return self.aggregate_name(
|
|
160
|
+
record=record,
|
|
161
|
+
record_id=record_id,
|
|
162
|
+
context=context,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return self.aggregate_name
|
|
166
|
+
|
|
167
|
+
def _resolve_aggregate_id(
|
|
168
|
+
self,
|
|
169
|
+
*,
|
|
170
|
+
record: Any | None,
|
|
171
|
+
record_id: Any,
|
|
172
|
+
context: Mapping[str, Any],
|
|
173
|
+
) -> Any:
|
|
174
|
+
if callable(self.aggregate_field):
|
|
175
|
+
return self.aggregate_field(
|
|
176
|
+
record=record,
|
|
177
|
+
record_id=record_id,
|
|
178
|
+
context=context,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if record is None:
|
|
182
|
+
return _context_value(context, self.aggregate_field)
|
|
183
|
+
|
|
184
|
+
return _context_or_record_value(context, record, self.aggregate_field)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _context_value(context: dict[str, Any], field_name: str) -> Any:
|
|
189
|
+
if field_name in context and context[field_name] is not None:
|
|
190
|
+
return context[field_name]
|
|
191
|
+
raise KeyError(f"Missing required key context: {field_name}")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _context_or_record_value(context: dict[str, Any], record: Any, field_name: str) -> Any:
|
|
195
|
+
if field_name in context and context[field_name] is not None:
|
|
196
|
+
return context[field_name]
|
|
197
|
+
value = _record_attr(record, field_name)
|
|
198
|
+
if value is not None:
|
|
199
|
+
return value
|
|
200
|
+
raise AttributeError(f"record is missing required field: {field_name}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _record_attr(record: Any, field_name: str) -> Any:
|
|
204
|
+
if isinstance(record, dict):
|
|
205
|
+
try:
|
|
206
|
+
return record[field_name]
|
|
207
|
+
except KeyError as exc:
|
|
208
|
+
raise AttributeError(f"record is missing required field: {field_name}") from exc
|
|
209
|
+
try:
|
|
210
|
+
return getattr(record, field_name)
|
|
211
|
+
except AttributeError as exc:
|
|
212
|
+
raise AttributeError(f"record is missing required field: {field_name}") from exc
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modmex-lambda"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.7"
|
|
8
8
|
description = "Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<4.0"
|
|
@@ -27,26 +27,26 @@ def test_key_strategy_is_abstract() -> None:
|
|
|
27
27
|
KeyStrategy()
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def
|
|
30
|
+
def test_single_record_key_strategy_uses_id_as_pk_and_discriminator_as_sk() -> None:
|
|
31
31
|
strategy = SingleEntityKeyStrategy("thing")
|
|
32
32
|
|
|
33
33
|
assert strategy.key_for_id("thing-1") == {"pk": "thing#thing-1", "sk": "thing"}
|
|
34
|
-
assert strategy.
|
|
35
|
-
assert strategy.
|
|
34
|
+
assert strategy.key_for_record(Thing(id="thing-2")) == {"pk": "thing#thing-2", "sk": "thing"}
|
|
35
|
+
assert strategy.key_for_record({"id": "thing-3"}) == {"pk": "thing#thing-3", "sk": "thing"}
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def test_tenant_scoped_sort_key_strategy_uses_record_id_and_tenant_scoped_sk() -> None:
|
|
39
39
|
strategy = TenantScopedSortKeyStrategy("thing")
|
|
40
40
|
|
|
41
41
|
assert strategy.key_for_id("thing-1", tenant_id="acme") == {
|
|
42
42
|
"pk": "thing#thing-1",
|
|
43
43
|
"sk": "tenant#acme",
|
|
44
44
|
}
|
|
45
|
-
assert strategy.
|
|
45
|
+
assert strategy.key_for_record(Thing(id="thing-2", tenant_id="modmex")) == {
|
|
46
46
|
"pk": "thing#thing-2",
|
|
47
47
|
"sk": "tenant#modmex",
|
|
48
48
|
}
|
|
49
|
-
assert strategy.
|
|
49
|
+
assert strategy.key_for_record(Thing(id="thing-3", tenant_id="ignored"), tenant_id="acme") == {
|
|
50
50
|
"pk": "thing#thing-3",
|
|
51
51
|
"sk": "tenant#acme",
|
|
52
52
|
}
|
|
@@ -60,20 +60,20 @@ def test_tenant_scoped_sort_key_strategy_supports_custom_field_and_separator() -
|
|
|
60
60
|
tenant_field="account_id",
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
-
assert strategy.
|
|
63
|
+
assert strategy.key_for_record(Thing(id="thing-1", account_id="acct-1")) == {
|
|
64
64
|
"pk": "thing:thing-1",
|
|
65
65
|
"sk": "account:acct-1",
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def
|
|
69
|
+
def test_tenant_partition_key_strategy_uses_tenant_as_pk_and_record_scoped_sk() -> None:
|
|
70
70
|
strategy = TenantPartitionKeyStrategy("thing")
|
|
71
71
|
|
|
72
72
|
assert strategy.key_for_id("thing-1", tenant_id="acme") == {
|
|
73
73
|
"pk": "tenant#acme",
|
|
74
74
|
"sk": "thing#thing-1",
|
|
75
75
|
}
|
|
76
|
-
assert strategy.
|
|
76
|
+
assert strategy.key_for_record(Thing(id="thing-2", tenant_id="modmex")) == {
|
|
77
77
|
"pk": "tenant#modmex",
|
|
78
78
|
"sk": "thing#thing-2",
|
|
79
79
|
}
|
|
@@ -87,20 +87,20 @@ def test_tenant_partition_key_strategy_supports_custom_field_and_separator() ->
|
|
|
87
87
|
tenant_field="account_id",
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
assert strategy.
|
|
90
|
+
assert strategy.key_for_record(Thing(id="thing-1", account_id="acct-1")) == {
|
|
91
91
|
"pk": "account:acct-1",
|
|
92
92
|
"sk": "thing:thing-1",
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def
|
|
96
|
+
def test_aggregate_key_strategy_uses_aggregate_pk_and_record_sk() -> None:
|
|
97
97
|
strategy = AggregateKeyStrategy("item", "order", "aggregate_id")
|
|
98
98
|
|
|
99
99
|
assert strategy.key_for_id("item-1", aggregate_id="order-1") == {
|
|
100
100
|
"pk": "order#order-1",
|
|
101
101
|
"sk": "item#item-1",
|
|
102
102
|
}
|
|
103
|
-
assert strategy.
|
|
103
|
+
assert strategy.key_for_record(Thing(id="item-2", aggregate_id="order-2")) == {
|
|
104
104
|
"pk": "order#order-2",
|
|
105
105
|
"sk": "item#item-2",
|
|
106
106
|
}
|
|
@@ -109,12 +109,90 @@ def test_aggregate_key_strategy_uses_aggregate_pk_and_entity_sk() -> None:
|
|
|
109
109
|
def test_aggregate_key_strategy_supports_custom_field_and_separator() -> None:
|
|
110
110
|
strategy = AggregateKeyStrategy("item", "order", separator=":", aggregate_field="order_id")
|
|
111
111
|
|
|
112
|
-
assert strategy.
|
|
112
|
+
assert strategy.key_for_record(Thing(id="item-1", order_id="order-1")) == {
|
|
113
113
|
"pk": "order:order-1",
|
|
114
114
|
"sk": "item:item-1",
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def test_aggregate_key_strategy_for_entity_with_callable() -> None:
|
|
119
|
+
strategy = AggregateKeyStrategy(
|
|
120
|
+
discriminator="audit",
|
|
121
|
+
aggregate_name=lambda record, **_: record.entity_type,
|
|
122
|
+
aggregate_field=lambda record, **_: record.entity_id,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class Audit:
|
|
127
|
+
id: str
|
|
128
|
+
entity_type: str
|
|
129
|
+
entity_id: str
|
|
130
|
+
|
|
131
|
+
entity = Audit(id="audit-1", entity_type="order", entity_id="order-1")
|
|
132
|
+
|
|
133
|
+
assert strategy.key_for_record(entity) == {
|
|
134
|
+
"pk": "order#order-1",
|
|
135
|
+
"sk": "audit#audit-1",
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_aggregate_key_strategy_for_id_with_callable() -> None:
|
|
140
|
+
strategy = AggregateKeyStrategy(
|
|
141
|
+
discriminator="audit",
|
|
142
|
+
aggregate_name=lambda context, **_: context["entity_type"],
|
|
143
|
+
aggregate_field=lambda context, **_: context["entity_id"],
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert strategy.key_for_id(
|
|
147
|
+
"audit-1",
|
|
148
|
+
entity_type="order",
|
|
149
|
+
entity_id="order-1",
|
|
150
|
+
) == {
|
|
151
|
+
"pk": "order#order-1",
|
|
152
|
+
"sk": "audit#audit-1",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_aggregate_key_strategy_callable_aggregate_name_supports_record_and_id() -> None:
|
|
157
|
+
@dataclass
|
|
158
|
+
class Audit:
|
|
159
|
+
id: str
|
|
160
|
+
entity_type: str
|
|
161
|
+
entity_id: str
|
|
162
|
+
|
|
163
|
+
strategy = AggregateKeyStrategy(
|
|
164
|
+
discriminator="audit",
|
|
165
|
+
aggregate_name=lambda record=None, context=None, **_: (
|
|
166
|
+
record.entity_type if record is not None else context["entity_type"]
|
|
167
|
+
),
|
|
168
|
+
aggregate_field=lambda record=None, context=None, **_: (
|
|
169
|
+
record.entity_id if record is not None else context["entity_id"]
|
|
170
|
+
),
|
|
171
|
+
separator="#",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
entity = Audit(
|
|
175
|
+
id="audit-1",
|
|
176
|
+
entity_type="order",
|
|
177
|
+
entity_id="order-1",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
assert strategy.key_for_record(entity) == {
|
|
181
|
+
"pk": "order#order-1",
|
|
182
|
+
"sk": "audit#audit-1",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
assert strategy.key_for_id(
|
|
186
|
+
"audit-1",
|
|
187
|
+
entity_type="order",
|
|
188
|
+
entity_id="order-1",
|
|
189
|
+
) == {
|
|
190
|
+
"pk": "order#order-1",
|
|
191
|
+
"sk": "audit#audit-1",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
118
196
|
def test_required_context_errors_are_explicit() -> None:
|
|
119
197
|
strategy = TenantPartitionKeyStrategy("thing")
|
|
120
198
|
|
|
@@ -122,4 +200,4 @@ def test_required_context_errors_are_explicit() -> None:
|
|
|
122
200
|
strategy.key_for_id("thing-1")
|
|
123
201
|
|
|
124
202
|
with pytest.raises(AttributeError, match="tenant_id"):
|
|
125
|
-
strategy.
|
|
203
|
+
strategy.key_for_record(Thing(id="thing-1"))
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"""Reusable DynamoDB key strategies.
|
|
2
|
-
|
|
3
|
-
These helpers keep key-shaping decisions explicit while letting repositories
|
|
4
|
-
stay focused on persistence behavior.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from abc import ABC, abstractmethod
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class KeyStrategy(ABC):
|
|
15
|
-
"""Build DynamoDB primary keys for ids and entities."""
|
|
16
|
-
|
|
17
|
-
@abstractmethod
|
|
18
|
-
def key_for_id(self, entity_id: Any, **context: Any) -> dict[str, str]:
|
|
19
|
-
raise NotImplementedError
|
|
20
|
-
|
|
21
|
-
@abstractmethod
|
|
22
|
-
def key_for_entity(self, entity: Any, **context: Any) -> dict[str, str]:
|
|
23
|
-
raise NotImplementedError
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@dataclass(frozen=True)
|
|
27
|
-
class SingleEntityKeyStrategy(KeyStrategy):
|
|
28
|
-
"""Use the entity id as pk and a fixed discriminator as sk."""
|
|
29
|
-
|
|
30
|
-
discriminator: str
|
|
31
|
-
separator: str = "#"
|
|
32
|
-
|
|
33
|
-
def key_for_id(self, entity_id: Any, **context: Any) -> dict[str, str]:
|
|
34
|
-
return {
|
|
35
|
-
"pk": f"{self.discriminator}{self.separator}{entity_id}",
|
|
36
|
-
"sk": self.discriminator,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
def key_for_entity(self, entity: Any, **context: Any) -> dict[str, str]:
|
|
40
|
-
return self.key_for_id(_entity_attr(entity, "id"), **context)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass(frozen=True)
|
|
44
|
-
class TenantScopedSortKeyStrategy(KeyStrategy):
|
|
45
|
-
"""Use discriminator + entity id as pk and tenant name + tenant id as sk."""
|
|
46
|
-
|
|
47
|
-
discriminator: str
|
|
48
|
-
tenant_name: str = "tenant"
|
|
49
|
-
tenant_field: str = "tenant_id"
|
|
50
|
-
separator: str = "#"
|
|
51
|
-
|
|
52
|
-
def key_for_id(self, entity_id: Any, **context: Any) -> dict[str, str]:
|
|
53
|
-
tenant_id = _context_value(context, self.tenant_field)
|
|
54
|
-
return {
|
|
55
|
-
"pk": f"{self.discriminator}{self.separator}{entity_id}",
|
|
56
|
-
"sk": f"{self.tenant_name}{self.separator}{tenant_id}",
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
def key_for_entity(self, entity: Any, **context: Any) -> dict[str, str]:
|
|
60
|
-
tenant_id = _context_or_entity_value(context, entity, self.tenant_field)
|
|
61
|
-
return self.key_for_id(_entity_attr(entity, "id"), **{self.tenant_field: tenant_id})
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclass(frozen=True)
|
|
66
|
-
class TenantPartitionKeyStrategy(KeyStrategy):
|
|
67
|
-
"""Use tenant name + tenant id as pk and discriminator + entity id as sk."""
|
|
68
|
-
|
|
69
|
-
discriminator: str
|
|
70
|
-
tenant_name: str = "tenant"
|
|
71
|
-
tenant_field: str = "tenant_id"
|
|
72
|
-
separator: str = "#"
|
|
73
|
-
|
|
74
|
-
def key_for_id(self, entity_id: Any, **context: Any) -> dict[str, str]:
|
|
75
|
-
tenant_id = _context_value(context, self.tenant_field)
|
|
76
|
-
return {
|
|
77
|
-
"pk": f"{self.tenant_name}{self.separator}{tenant_id}",
|
|
78
|
-
"sk": f"{self.discriminator}{self.separator}{entity_id}",
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
def key_for_entity(self, entity: Any, **context: Any) -> dict[str, str]:
|
|
82
|
-
tenant_id = _context_or_entity_value(context, entity, self.tenant_field)
|
|
83
|
-
return self.key_for_id(_entity_attr(entity, "id"), **{self.tenant_field: tenant_id})
|
|
84
|
-
|
|
85
|
-
@dataclass(frozen=True)
|
|
86
|
-
class AggregateKeyStrategy(KeyStrategy):
|
|
87
|
-
"""Use aggregate name + aggregate id as pk and discriminator + entity id as sk."""
|
|
88
|
-
|
|
89
|
-
discriminator: str
|
|
90
|
-
aggregate_name: str
|
|
91
|
-
aggregate_field: str
|
|
92
|
-
separator: str = "#"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def key_for_id(self, entity_id: Any, **context: Any) -> dict[str, str]:
|
|
96
|
-
aggregate_id = _context_value(context, self.aggregate_field)
|
|
97
|
-
return {
|
|
98
|
-
"pk": f"{self.aggregate_name}{self.separator}{aggregate_id}",
|
|
99
|
-
"sk": f"{self.discriminator}{self.separator}{entity_id}",
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
def key_for_entity(self, entity: Any, **context: Any) -> dict[str, str]:
|
|
103
|
-
aggregate_id = _context_or_entity_value(context, entity, self.aggregate_field)
|
|
104
|
-
return self.key_for_id(_entity_attr(entity, "id"), **{self.aggregate_field: aggregate_id})
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _context_value(context: dict[str, Any], field_name: str) -> Any:
|
|
108
|
-
if field_name in context and context[field_name] is not None:
|
|
109
|
-
return context[field_name]
|
|
110
|
-
raise KeyError(f"Missing required key context: {field_name}")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _context_or_entity_value(context: dict[str, Any], entity: Any, field_name: str) -> Any:
|
|
114
|
-
if field_name in context and context[field_name] is not None:
|
|
115
|
-
return context[field_name]
|
|
116
|
-
value = _entity_attr(entity, field_name)
|
|
117
|
-
if value is not None:
|
|
118
|
-
return value
|
|
119
|
-
raise AttributeError(f"Entity is missing required field: {field_name}")
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _entity_attr(entity: Any, field_name: str) -> Any:
|
|
123
|
-
if isinstance(entity, dict):
|
|
124
|
-
try:
|
|
125
|
-
return entity[field_name]
|
|
126
|
-
except KeyError as exc:
|
|
127
|
-
raise AttributeError(f"Entity is missing required field: {field_name}") from exc
|
|
128
|
-
try:
|
|
129
|
-
return getattr(entity, field_name)
|
|
130
|
-
except AttributeError as exc:
|
|
131
|
-
raise AttributeError(f"Entity is missing required field: {field_name}") from exc
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/api_gateway_proxy_event.py
RENAMED
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/data_classes/cognito_user_pool_event.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/__init__.py
RENAMED
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/compat.py
RENAMED
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/dependant.py
RENAMED
|
File without changes
|
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/depends.py
RENAMED
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/params.py
RENAMED
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/dependencies/types.py
RENAMED
|
File without changes
|
{modmex_lambda-0.5.6 → modmex_lambda-0.5.7}/modmex_lambda/event_handler/exception_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|