modmex-lambda 0.5.2__tar.gz → 0.5.4__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.2 → modmex_lambda-0.5.4}/.gitignore +3 -1
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/PKG-INFO +46 -15
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/README.md +45 -14
- modmex_lambda-0.5.4/modmex_lambda/connectors/dynamodb.py +282 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/module.py +4 -1
- modmex_lambda-0.5.4/modmex_lambda/persistence/__init__.py +2 -0
- modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/__init__.py +39 -0
- modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/expressions.py +62 -0
- modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/keys.py +133 -0
- modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/materialized_views.py +80 -0
- modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/stream_fields.py +121 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/dynamodb.py +5 -60
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/pyproject.toml +1 -1
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_dynamodb.py +171 -96
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_lazy_clients.py +2 -3
- modmex_lambda-0.5.4/tests/persistence/__init__.py +1 -0
- modmex_lambda-0.5.4/tests/persistence/test_dynamodb_expressions.py +39 -0
- modmex_lambda-0.5.4/tests/persistence/test_dynamodb_keys.py +115 -0
- modmex_lambda-0.5.4/tests/persistence/test_dynamodb_materialized_views.py +115 -0
- modmex_lambda-0.5.4/tests/persistence/test_dynamodb_stream_fields.py +127 -0
- modmex_lambda-0.5.2/modmex_lambda/connectors/dynamodb.py +0 -123
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/.github/workflows/ci.yml +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/.github/workflows/release.yml +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/LICENSE +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/cloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/eventbridge.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/icloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/idynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/ieventbridge.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/ilambda.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/is3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/isns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/isqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/lambda_.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_authorizer_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_websocket_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/common.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/dependencies.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/api_gateway.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/constants.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/content_types.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/cors.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/compat.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/dependant.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/dependency_middleware.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/depends.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/params.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/types.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/exception_handler.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/exceptions.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/gateway_response.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/middlewares.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/params.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/request.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/response.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/routing.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/routing_fallbacks.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/types.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_sources.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/exceptions.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/logging.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/params.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/parser.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/request.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/resolver.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/response.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/routing.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/cookies.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/headers_serializer.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/json_encoder.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/types.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/kinesis.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/content.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/event_type.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/latch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/skip.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/base_flavor.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/cdc.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/collect.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/correlate.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/evaluate.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/expired.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/iflavor.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/job.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/materialize.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/task.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/update.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/irules_registry.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/cloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/ioperator.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/lambda_.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/publisher.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/rules_registry.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/runner.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/base.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/kinesis.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/apigateway.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/aws.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/batch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/cloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/concurrency.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/contracts.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/data_classes/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/data_classes/dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/decorators.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/eventbridge.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/faults.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/filters.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/json_encoder.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/lambda_.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/operators.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/pluralize.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/print.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/retry.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/split.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/tags.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/time.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/uow.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/tracing.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/validation.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/poetry.lock +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/conftest.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/conftest.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_cloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_simple_connectors.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_common.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_api_gateway.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_cors.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_dependencies.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_exception_handler.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_gateway_response.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_request.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_response.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_routing.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/shared/test_cookies_headers.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/shared/test_json_encoder.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/conftest.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_kinesis.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_content.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_event_type.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_latch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_skip.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/source_events.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_base_flavor.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_cdc.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_collect.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_correlate.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_evaluate.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_expired.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_job.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_materialize.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_task.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_update.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_dependency_resolver.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_rules_registry.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_runner.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_runner_pipeline.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_sources.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/faults.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_apigateway.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_aws.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_batch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_cloudwatch.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_concurrency.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_decorators.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_dynamodb.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_eventbridge.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_filters.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_json_encoder.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_lambda.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_operators.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_pluralize.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_print.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_retry.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_s3.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_sns.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_split.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_sqs.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_tags.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_time.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_uow.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_lazy_imports.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_logging.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_parser_event_sources.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_reexports.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_tracing.py +0 -0
- {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modmex-lambda
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling.
|
|
5
5
|
Author: Modmex
|
|
6
6
|
License: MIT
|
|
@@ -36,6 +36,8 @@ API Gateway proxy events, fast routing, request binding, response serialization,
|
|
|
36
36
|
middleware, dependency injection, stream sources, parsing, and structured
|
|
37
37
|
logging.
|
|
38
38
|
|
|
39
|
+
The fuller documentation site lives in [`docs/`](docs/).
|
|
40
|
+
|
|
39
41
|
## Install
|
|
40
42
|
|
|
41
43
|
```bash
|
|
@@ -979,37 +981,66 @@ materialize so queries stay local and fast, and each service owns the model it
|
|
|
979
981
|
needs instead of querying another service synchronously.
|
|
980
982
|
|
|
981
983
|
```python
|
|
984
|
+
from modmex_lambda.persistence.dynamodb import MaterializedViewMixin
|
|
982
985
|
from modmex_lambda.stream.flavors.materialize import Materialize
|
|
983
986
|
from modmex_lambda.stream.rules_registry import RulesRegistry
|
|
984
987
|
from modmex_lambda.stream.sources import KinesisSource
|
|
985
|
-
from modmex_lambda.stream.utils.dynamodb import update_expression
|
|
986
988
|
|
|
987
989
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
"sk": "THING",
|
|
994
|
-
},
|
|
995
|
-
**update_expression({
|
|
996
|
-
"name": thing["name"],
|
|
997
|
-
"timestamp": uow["event"]["timestamp"],
|
|
998
|
-
}),
|
|
999
|
-
}
|
|
990
|
+
class ThingViewRepository(MaterializedViewMixin):
|
|
991
|
+
discriminator = "thing"
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
to_update_request = ThingViewRepository.materialized_update_request_mapper()
|
|
1000
995
|
|
|
1001
996
|
|
|
1002
997
|
registry = RulesRegistry().registry(
|
|
1003
998
|
Materialize({
|
|
1004
999
|
"id": "materialize-thing",
|
|
1005
1000
|
"event_type": "thing-created",
|
|
1006
|
-
"to_update_request":
|
|
1001
|
+
"to_update_request": to_update_request,
|
|
1007
1002
|
})
|
|
1008
1003
|
)
|
|
1009
1004
|
|
|
1010
1005
|
handler = KinesisSource(registry, concurrency=False).handle
|
|
1011
1006
|
```
|
|
1012
1007
|
|
|
1008
|
+
`MaterializedViewMixin` is a persistence helper for the common DynamoDB case:
|
|
1009
|
+
copy the entity from `uow["event"][discriminator]`, build a `Key` with
|
|
1010
|
+
`pk=entity["id"]` and `sk=discriminator`, add stream-compatible fields, and
|
|
1011
|
+
protect the write with `timestamp_condition()` so older events do not overwrite
|
|
1012
|
+
newer records. It adds stream-compatible fields such as `discriminator`,
|
|
1013
|
+
`timestamp`, `deleted`, `latched`, `ttl`, and `awsregion` to the update
|
|
1014
|
+
expression.
|
|
1015
|
+
|
|
1016
|
+
Override the hooks when the event source, key, projection, or enrichment is
|
|
1017
|
+
different from the default:
|
|
1018
|
+
|
|
1019
|
+
```python
|
|
1020
|
+
from modmex_lambda.persistence.dynamodb import MaterializedViewMixin
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
class ThingSearchViewRepository(MaterializedViewMixin):
|
|
1024
|
+
discriminator = "thing-search"
|
|
1025
|
+
materialized_source_name = "thing"
|
|
1026
|
+
|
|
1027
|
+
def materialized_key(self, uow, thing):
|
|
1028
|
+
return {
|
|
1029
|
+
"pk": thing["tenant_id"],
|
|
1030
|
+
"sk": f"thing-search#{thing['id']}",
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
def materialized_fields(self, uow, thing):
|
|
1034
|
+
return {
|
|
1035
|
+
**super().materialized_fields(uow, thing),
|
|
1036
|
+
"search_text": f"{thing['name']} {thing['tenant_id']}",
|
|
1037
|
+
}
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
Use `DynamoDBUpdateRequestMixin.build_update_request()` directly when you already
|
|
1041
|
+
have the key and fields but still want the standard `UpdateItem` expression and
|
|
1042
|
+
timestamp guard.
|
|
1043
|
+
|
|
1013
1044
|
Use `split_on` and `split_target_field` when one event updates several records,
|
|
1014
1045
|
for example one `order-created` event materializing each order item.
|
|
1015
1046
|
|
|
@@ -13,6 +13,8 @@ API Gateway proxy events, fast routing, request binding, response serialization,
|
|
|
13
13
|
middleware, dependency injection, stream sources, parsing, and structured
|
|
14
14
|
logging.
|
|
15
15
|
|
|
16
|
+
The fuller documentation site lives in [`docs/`](docs/).
|
|
17
|
+
|
|
16
18
|
## Install
|
|
17
19
|
|
|
18
20
|
```bash
|
|
@@ -956,37 +958,66 @@ materialize so queries stay local and fast, and each service owns the model it
|
|
|
956
958
|
needs instead of querying another service synchronously.
|
|
957
959
|
|
|
958
960
|
```python
|
|
961
|
+
from modmex_lambda.persistence.dynamodb import MaterializedViewMixin
|
|
959
962
|
from modmex_lambda.stream.flavors.materialize import Materialize
|
|
960
963
|
from modmex_lambda.stream.rules_registry import RulesRegistry
|
|
961
964
|
from modmex_lambda.stream.sources import KinesisSource
|
|
962
|
-
from modmex_lambda.stream.utils.dynamodb import update_expression
|
|
963
965
|
|
|
964
966
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
"sk": "THING",
|
|
971
|
-
},
|
|
972
|
-
**update_expression({
|
|
973
|
-
"name": thing["name"],
|
|
974
|
-
"timestamp": uow["event"]["timestamp"],
|
|
975
|
-
}),
|
|
976
|
-
}
|
|
967
|
+
class ThingViewRepository(MaterializedViewMixin):
|
|
968
|
+
discriminator = "thing"
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
to_update_request = ThingViewRepository.materialized_update_request_mapper()
|
|
977
972
|
|
|
978
973
|
|
|
979
974
|
registry = RulesRegistry().registry(
|
|
980
975
|
Materialize({
|
|
981
976
|
"id": "materialize-thing",
|
|
982
977
|
"event_type": "thing-created",
|
|
983
|
-
"to_update_request":
|
|
978
|
+
"to_update_request": to_update_request,
|
|
984
979
|
})
|
|
985
980
|
)
|
|
986
981
|
|
|
987
982
|
handler = KinesisSource(registry, concurrency=False).handle
|
|
988
983
|
```
|
|
989
984
|
|
|
985
|
+
`MaterializedViewMixin` is a persistence helper for the common DynamoDB case:
|
|
986
|
+
copy the entity from `uow["event"][discriminator]`, build a `Key` with
|
|
987
|
+
`pk=entity["id"]` and `sk=discriminator`, add stream-compatible fields, and
|
|
988
|
+
protect the write with `timestamp_condition()` so older events do not overwrite
|
|
989
|
+
newer records. It adds stream-compatible fields such as `discriminator`,
|
|
990
|
+
`timestamp`, `deleted`, `latched`, `ttl`, and `awsregion` to the update
|
|
991
|
+
expression.
|
|
992
|
+
|
|
993
|
+
Override the hooks when the event source, key, projection, or enrichment is
|
|
994
|
+
different from the default:
|
|
995
|
+
|
|
996
|
+
```python
|
|
997
|
+
from modmex_lambda.persistence.dynamodb import MaterializedViewMixin
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
class ThingSearchViewRepository(MaterializedViewMixin):
|
|
1001
|
+
discriminator = "thing-search"
|
|
1002
|
+
materialized_source_name = "thing"
|
|
1003
|
+
|
|
1004
|
+
def materialized_key(self, uow, thing):
|
|
1005
|
+
return {
|
|
1006
|
+
"pk": thing["tenant_id"],
|
|
1007
|
+
"sk": f"thing-search#{thing['id']}",
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
def materialized_fields(self, uow, thing):
|
|
1011
|
+
return {
|
|
1012
|
+
**super().materialized_fields(uow, thing),
|
|
1013
|
+
"search_text": f"{thing['name']} {thing['tenant_id']}",
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
Use `DynamoDBUpdateRequestMixin.build_update_request()` directly when you already
|
|
1018
|
+
have the key and fields but still want the standard `UpdateItem` expression and
|
|
1019
|
+
timestamp guard.
|
|
1020
|
+
|
|
990
1021
|
Use `split_on` and `split_target_field` when one event updates several records,
|
|
991
1022
|
for example one `order-created` event materializing each order item.
|
|
992
1023
|
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
from modmex_lambda.connectors.idynamodb import IDynamodbConnector
|
|
5
|
+
from modmex_lambda.stream.utils.retry import (
|
|
6
|
+
assert_max_retries,
|
|
7
|
+
DEFAULT_RETRY_CONFIG,
|
|
8
|
+
wait,
|
|
9
|
+
get_delay
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Connector(IDynamodbConnector):
|
|
14
|
+
|
|
15
|
+
def __init__(self,# pylint: disable=W0102
|
|
16
|
+
table_name = 'undefined',
|
|
17
|
+
retry_config = DEFAULT_RETRY_CONFIG,
|
|
18
|
+
client = None) -> None:
|
|
19
|
+
self.table_name = table_name
|
|
20
|
+
self._client = client
|
|
21
|
+
self.retry_config = retry_config
|
|
22
|
+
self._serializer = None
|
|
23
|
+
self._deserializer = None
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def client(self):
|
|
27
|
+
if not self._client:
|
|
28
|
+
import boto3
|
|
29
|
+
self._client = boto3.client('dynamodb')
|
|
30
|
+
return self._client
|
|
31
|
+
|
|
32
|
+
def get(self, input_params):
|
|
33
|
+
response = self.client.get_item(
|
|
34
|
+
TableName=self.table_name,
|
|
35
|
+
**self._marshall_request(input_params),
|
|
36
|
+
)
|
|
37
|
+
return self._unmarshall_response(response)
|
|
38
|
+
|
|
39
|
+
def update(self, input_params):
|
|
40
|
+
response = self.client.update_item(
|
|
41
|
+
TableName=self.table_name,
|
|
42
|
+
**self._marshall_request(input_params),
|
|
43
|
+
)
|
|
44
|
+
return self._unmarshall_response(response)
|
|
45
|
+
|
|
46
|
+
def put(self, input_params):
|
|
47
|
+
response = self.client.put_item(
|
|
48
|
+
TableName=self.table_name,
|
|
49
|
+
**self._marshall_request(input_params),
|
|
50
|
+
)
|
|
51
|
+
return self._unmarshall_response(response)
|
|
52
|
+
|
|
53
|
+
def query(self, input_params):
|
|
54
|
+
response = self.client.query(
|
|
55
|
+
TableName=self.table_name,
|
|
56
|
+
**self._marshall_request(input_params),
|
|
57
|
+
)
|
|
58
|
+
return self._unmarshall_response(response)
|
|
59
|
+
|
|
60
|
+
def query_page(self, input_params):
|
|
61
|
+
return self.query(input_params)
|
|
62
|
+
|
|
63
|
+
def scan(self, input_params):
|
|
64
|
+
response = self.client.scan(
|
|
65
|
+
TableName=self.table_name,
|
|
66
|
+
**self._marshall_request(input_params),
|
|
67
|
+
)
|
|
68
|
+
return self._unmarshall_response(response)
|
|
69
|
+
|
|
70
|
+
def query_all(self, input_params):
|
|
71
|
+
items = []
|
|
72
|
+
while True:
|
|
73
|
+
result = self.query(input_params)
|
|
74
|
+
items.extend(result.get('Items', []))
|
|
75
|
+
if result.get('LastEvaluatedKey'):
|
|
76
|
+
input_params['ExclusiveStartKey'] = result['LastEvaluatedKey']
|
|
77
|
+
else:
|
|
78
|
+
break
|
|
79
|
+
return items
|
|
80
|
+
|
|
81
|
+
def batch_get(self, input_params):
|
|
82
|
+
return self._batch_get(input_params, [])
|
|
83
|
+
|
|
84
|
+
def _batch_get(self, params, attempts):
|
|
85
|
+
assert_max_retries(attempts, self.retry_config['max_retries'])
|
|
86
|
+
wait(get_delay(self.retry_config['retry_wait'], len(attempts)))
|
|
87
|
+
response = self.client.batch_get_item(**self._marshall_batch_get_request(params))
|
|
88
|
+
response = self._unmarshall_response(response)
|
|
89
|
+
if response.get('UnprocessedKeys'):
|
|
90
|
+
return self._batch_get(
|
|
91
|
+
unprocessed(params, response),
|
|
92
|
+
[*attempts, response]
|
|
93
|
+
)
|
|
94
|
+
return accumulate(attempts, response)
|
|
95
|
+
|
|
96
|
+
def bulk_insert(self, items: Iterable):
|
|
97
|
+
self._batch_write([
|
|
98
|
+
{
|
|
99
|
+
'PutRequest': {
|
|
100
|
+
'Item': item,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
for item in items
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
def bulk_delete(self, items: Iterable):
|
|
107
|
+
self._batch_write([
|
|
108
|
+
{
|
|
109
|
+
'DeleteRequest': {
|
|
110
|
+
'Key': key,
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
for key in items
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
def _marshall_request(self, params):
|
|
117
|
+
return {
|
|
118
|
+
key: self._marshall_value(key, value)
|
|
119
|
+
for key, value in params.items()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def _marshall_batch_get_request(self, params):
|
|
123
|
+
return {
|
|
124
|
+
**params,
|
|
125
|
+
'RequestItems': {
|
|
126
|
+
table_name: {
|
|
127
|
+
**request,
|
|
128
|
+
'Keys': [
|
|
129
|
+
self._marshall_item(key)
|
|
130
|
+
for key in request.get('Keys', [])
|
|
131
|
+
],
|
|
132
|
+
}
|
|
133
|
+
for table_name, request in params.get('RequestItems', {}).items()
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
def _marshall_value(self, key, value):
|
|
138
|
+
if key in {'Key', 'Item', 'LastEvaluatedKey', 'ExclusiveStartKey'}:
|
|
139
|
+
return self._marshall_item(value)
|
|
140
|
+
if key == 'ExpressionAttributeValues':
|
|
141
|
+
return {
|
|
142
|
+
attr: self.serializer.serialize(attr_value)
|
|
143
|
+
for attr, attr_value in value.items()
|
|
144
|
+
}
|
|
145
|
+
return value
|
|
146
|
+
|
|
147
|
+
def _marshall_item(self, item):
|
|
148
|
+
return {
|
|
149
|
+
key: self.serializer.serialize(value)
|
|
150
|
+
for key, value in item.items()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
def _unmarshall_response(self, response):
|
|
154
|
+
return {
|
|
155
|
+
key: self._unmarshall_value(key, value)
|
|
156
|
+
for key, value in response.items()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
def _unmarshall_value(self, key, value):
|
|
160
|
+
if key in {'Item', 'LastEvaluatedKey'}:
|
|
161
|
+
return self._unmarshall_item(value)
|
|
162
|
+
if key == 'Items':
|
|
163
|
+
return [self._unmarshall_item(item) for item in value]
|
|
164
|
+
if key == 'Attributes':
|
|
165
|
+
return self._unmarshall_item(value)
|
|
166
|
+
if key == 'Responses':
|
|
167
|
+
return {
|
|
168
|
+
table_name: [self._unmarshall_item(item) for item in items]
|
|
169
|
+
for table_name, items in value.items()
|
|
170
|
+
}
|
|
171
|
+
if key == 'UnprocessedKeys':
|
|
172
|
+
return {
|
|
173
|
+
table_name: {
|
|
174
|
+
**request,
|
|
175
|
+
'Keys': [self._unmarshall_item(item) for item in request.get('Keys', [])],
|
|
176
|
+
}
|
|
177
|
+
for table_name, request in value.items()
|
|
178
|
+
}
|
|
179
|
+
return value
|
|
180
|
+
|
|
181
|
+
def _unmarshall_item(self, item):
|
|
182
|
+
return {
|
|
183
|
+
key: self.deserializer.deserialize(value)
|
|
184
|
+
for key, value in item.items()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def serializer(self):
|
|
189
|
+
if not self._serializer:
|
|
190
|
+
self._load_dynamodb_types()
|
|
191
|
+
return self._serializer
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def deserializer(self):
|
|
195
|
+
if not self._deserializer:
|
|
196
|
+
self._load_dynamodb_types()
|
|
197
|
+
return self._deserializer
|
|
198
|
+
|
|
199
|
+
def _load_dynamodb_types(self):
|
|
200
|
+
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
|
|
201
|
+
self._serializer = TypeSerializer()
|
|
202
|
+
self._deserializer = TypeDeserializer()
|
|
203
|
+
|
|
204
|
+
def _batch_write(self, requests):
|
|
205
|
+
for index in range(0, len(requests), 25):
|
|
206
|
+
self._batch_write_chunk(requests[index:index + 25], [])
|
|
207
|
+
|
|
208
|
+
def _batch_write_chunk(self, requests, attempts):
|
|
209
|
+
if not requests:
|
|
210
|
+
return
|
|
211
|
+
assert_max_retries(attempts, self.retry_config['max_retries'])
|
|
212
|
+
wait(get_delay(self.retry_config['retry_wait'], len(attempts)))
|
|
213
|
+
response = self.client.batch_write_item(
|
|
214
|
+
RequestItems={
|
|
215
|
+
self.table_name: [
|
|
216
|
+
self._marshall_write_request(request)
|
|
217
|
+
for request in requests
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
unprocessed_items = response.get('UnprocessedItems', {}).get(self.table_name, [])
|
|
222
|
+
if unprocessed_items:
|
|
223
|
+
self._batch_write_chunk(
|
|
224
|
+
[
|
|
225
|
+
self._unmarshall_write_request(request)
|
|
226
|
+
for request in unprocessed_items
|
|
227
|
+
],
|
|
228
|
+
[*attempts, response],
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _marshall_write_request(self, request):
|
|
232
|
+
if 'PutRequest' in request:
|
|
233
|
+
return {
|
|
234
|
+
'PutRequest': {
|
|
235
|
+
'Item': self._marshall_item(request['PutRequest']['Item']),
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
'DeleteRequest': {
|
|
240
|
+
'Key': self._marshall_item(request['DeleteRequest']['Key']),
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
def _unmarshall_write_request(self, request):
|
|
245
|
+
if 'PutRequest' in request:
|
|
246
|
+
return {
|
|
247
|
+
'PutRequest': {
|
|
248
|
+
'Item': self._unmarshall_item(request['PutRequest']['Item']),
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
'DeleteRequest': {
|
|
253
|
+
'Key': self._unmarshall_item(request['DeleteRequest']['Key']),
|
|
254
|
+
},
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def unprocessed(params, resp):
|
|
259
|
+
return {
|
|
260
|
+
**params,
|
|
261
|
+
'RequestItems': resp['UnprocessedKeys']
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
def accumulate(attempts, resp):
|
|
265
|
+
def reducer(a, c):
|
|
266
|
+
return {
|
|
267
|
+
**a,
|
|
268
|
+
'Responses': reduce(
|
|
269
|
+
lambda a2, c2: {
|
|
270
|
+
**a2,
|
|
271
|
+
c2: [
|
|
272
|
+
*a2.get(c2, []),
|
|
273
|
+
*a['Responses'].get(c2, [])
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
list(a['Responses']),
|
|
277
|
+
{**c['Responses']}
|
|
278
|
+
),
|
|
279
|
+
'attempts': [*attempts, resp]
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return reduce(reducer, reversed(attempts), resp)
|
|
@@ -17,9 +17,12 @@ class AwsConnectorsModule(Module):
|
|
|
17
17
|
@singleton
|
|
18
18
|
@provider
|
|
19
19
|
def provide_dynamodb(self) -> IDynamodbConnector:
|
|
20
|
+
import os
|
|
20
21
|
from modmex_lambda.connectors.dynamodb import Connector
|
|
21
22
|
|
|
22
|
-
return Connector(
|
|
23
|
+
return Connector(
|
|
24
|
+
table_name=os.getenv('ENTITY_TABLE_NAME') or os.getenv('EVENT_TABLE_NAME'),
|
|
25
|
+
)
|
|
23
26
|
|
|
24
27
|
@singleton
|
|
25
28
|
@provider
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""DynamoDB persistence helpers."""
|
|
2
|
+
|
|
3
|
+
from modmex_lambda.persistence.dynamodb.expressions import (
|
|
4
|
+
pk_condition,
|
|
5
|
+
timestamp_condition,
|
|
6
|
+
update_expression,
|
|
7
|
+
)
|
|
8
|
+
from modmex_lambda.persistence.dynamodb.keys import (
|
|
9
|
+
AggregateKeyStrategy,
|
|
10
|
+
KeyStrategy,
|
|
11
|
+
SingleEntityKeyStrategy,
|
|
12
|
+
TenantPartitionKeyStrategy,
|
|
13
|
+
TenantScopedSortKeyStrategy,
|
|
14
|
+
)
|
|
15
|
+
from modmex_lambda.persistence.dynamodb.materialized_views import (
|
|
16
|
+
DynamoDBUpdateRequestMixin,
|
|
17
|
+
MaterializedViewMixin,
|
|
18
|
+
)
|
|
19
|
+
from modmex_lambda.persistence.dynamodb.stream_fields import (
|
|
20
|
+
DefaultStreamFieldsStrategy,
|
|
21
|
+
StreamFieldsStrategy,
|
|
22
|
+
stream_entity_fields,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AggregateKeyStrategy",
|
|
27
|
+
"DynamoDBUpdateRequestMixin",
|
|
28
|
+
"KeyStrategy",
|
|
29
|
+
"MaterializedViewMixin",
|
|
30
|
+
"SingleEntityKeyStrategy",
|
|
31
|
+
"DefaultStreamFieldsStrategy",
|
|
32
|
+
"StreamFieldsStrategy",
|
|
33
|
+
"TenantPartitionKeyStrategy",
|
|
34
|
+
"TenantScopedSortKeyStrategy",
|
|
35
|
+
"pk_condition",
|
|
36
|
+
"stream_entity_fields",
|
|
37
|
+
"timestamp_condition",
|
|
38
|
+
"update_expression",
|
|
39
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from functools import reduce
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def update_expression(item: dict):
|
|
5
|
+
keys = item.keys()
|
|
6
|
+
result = {}
|
|
7
|
+
result['ExpressionAttributeNames'] = reduce(
|
|
8
|
+
lambda accumulator, el: {**accumulator, **el},
|
|
9
|
+
map(
|
|
10
|
+
lambda attrName: {f"#{attrName}": attrName },
|
|
11
|
+
keys
|
|
12
|
+
),
|
|
13
|
+
{}
|
|
14
|
+
)
|
|
15
|
+
result['ExpressionAttributeValues'] = reduce(
|
|
16
|
+
lambda accumulator, el: {**accumulator, **el},
|
|
17
|
+
map(
|
|
18
|
+
lambda attrName: {f":{attrName}": item[attrName] },
|
|
19
|
+
filter(
|
|
20
|
+
lambda attrName: item[attrName] is not None,
|
|
21
|
+
keys
|
|
22
|
+
)
|
|
23
|
+
),
|
|
24
|
+
{}
|
|
25
|
+
)
|
|
26
|
+
result['UpdateExpression'] = "SET "+", ".join(map(
|
|
27
|
+
lambda attrName: f"#{attrName} = :{attrName}",
|
|
28
|
+
filter(
|
|
29
|
+
lambda attrName: item[attrName] is not None,
|
|
30
|
+
keys
|
|
31
|
+
)
|
|
32
|
+
))
|
|
33
|
+
update_expression_remove = ", ".join(map(
|
|
34
|
+
lambda attrName: f"#{attrName}",
|
|
35
|
+
filter(
|
|
36
|
+
lambda attrName: item[attrName] is None,
|
|
37
|
+
keys
|
|
38
|
+
)
|
|
39
|
+
))
|
|
40
|
+
if update_expression_remove:
|
|
41
|
+
result['UpdateExpression'] = "{} REMOVE {}".format(
|
|
42
|
+
result['UpdateExpression'],
|
|
43
|
+
update_expression_remove
|
|
44
|
+
)
|
|
45
|
+
result['ReturnValues'] = 'ALL_NEW'
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def timestamp_condition(field_name = 'timestamp'):
|
|
50
|
+
return {
|
|
51
|
+
'ConditionExpression': "attribute_not_exists(#{fn}) OR #{fn} < :{fn}".format(
|
|
52
|
+
fn=field_name
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pk_condition(field_name = 'pk'):
|
|
58
|
+
return {
|
|
59
|
+
'ConditionExpression': "attribute_not_exists({})".format(
|
|
60
|
+
field_name
|
|
61
|
+
)
|
|
62
|
+
}
|