modmex-lambda 0.4.1__tar.gz → 0.5.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.
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/PKG-INFO +70 -1
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/README.md +69 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/__init__.py +2 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/logging.py +55 -26
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/base_flavor.py +3 -3
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/publisher.py +1 -1
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/runner.py +20 -4
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/eventbridge.py +6 -7
- modmex_lambda-0.5.0/modmex_lambda/tracing.py +189 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/pyproject.toml +1 -1
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_dependencies.py +113 -1
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/filters/test_event_type.py +9 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_base_flavor.py +7 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_collect.py +38 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/test_runner.py +34 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/test_runner_pipeline.py +1 -11
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/faults.py +9 -1
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_uow.py +20 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/test_lazy_imports.py +25 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/test_logging.py +98 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/test_parser_event_sources.py +26 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/test_reexports.py +2 -0
- modmex_lambda-0.5.0/tests/test_tracing.py +167 -0
- modmex_lambda-0.4.1/modmex_lambda/stream/utils/opt.py +0 -15
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/.github/workflows/ci.yml +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/.github/workflows/release.yml +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/.gitignore +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/LICENSE +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/cloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/eventbridge.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/icloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/idynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/ieventbridge.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/ilambda.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/is3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/isns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/isqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/lambda_.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/module.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/connectors/sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/api_gateway_authorizer_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/api_gateway_websocket_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/data_classes/common.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/dependencies.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/api_gateway.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/constants.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/content_types.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/cors.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/compat.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/dependant.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/dependency_middleware.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/depends.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/params.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/dependencies/types.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/exception_handler.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/exceptions.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/gateway_response.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/middlewares.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/params.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/request.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/response.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/routing.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/routing_fallbacks.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_handler/types.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/event_sources.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/exceptions.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/params.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/parser.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/request.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/resolver.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/response.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/routing.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/shared/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/shared/cookies.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/shared/headers_serializer.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/shared/json_encoder.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/shared/types.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/kinesis.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/events/sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/filters/content.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/filters/event_type.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/filters/latch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/filters/skip.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/cdc.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/collect.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/correlate.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/evaluate.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/expired.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/iflavor.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/job.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/materialize.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/task.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/flavors/update.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/irules_registry.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/cloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/ioperator.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/lambda_.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/operators/sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/rules_registry.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/base.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/kinesis.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/sources/sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/apigateway.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/aws.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/batch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/cloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/concurrency.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/contracts.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/data_classes/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/data_classes/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/decorators.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/faults.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/filters.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/json_encoder.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/lambda_.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/operators.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/pluralize.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/print.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/retry.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/split.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/tags.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/time.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/stream/utils/uow.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/modmex_lambda/validation.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/poetry.lock +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/conftest.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/conftest.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_cloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_lazy_clients.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_simple_connectors.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/connectors/test_sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/data_classes/test_api_gateway_proxy_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/data_classes/test_cognito_user_pool_event.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/data_classes/test_common.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_api_gateway.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_cors.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_exception_handler.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_gateway_response.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_request.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_response.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/event_handler/test_routing.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/shared/test_cookies_headers.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/shared/test_json_encoder.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/conftest.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/events/test_dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/events/test_kinesis.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/events/test_s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/events/test_sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/events/test_sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/filters/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/filters/test_content.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/filters/test_latch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/filters/test_skip.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/source_events.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_cdc.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_correlate.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_evaluate.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_expired.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_job.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_materialize.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_task.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/flavors/test_update.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/test_dependency_resolver.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/test_rules_registry.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/test_sources.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/__init__.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_apigateway.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_aws.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_batch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_cloudwatch.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_concurrency.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_decorators.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_dynamodb.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_eventbridge.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_filters.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_json_encoder.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_lambda.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_operators.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_pluralize.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_print.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_retry.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_s3.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_sns.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_split.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_sqs.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_tags.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/stream/utils/test_time.py +0 -0
- {modmex_lambda-0.4.1 → modmex_lambda-0.5.0}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modmex-lambda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling.
|
|
5
5
|
Author: Modmex
|
|
6
6
|
License: MIT
|
|
@@ -607,6 +607,75 @@ The logger emits structured JSON, reads `LOG_LEVEL`, uses `SERVICE_NAME` or
|
|
|
607
607
|
`AWS_LAMBDA_FUNCTION_NAME` when no service is passed, and can extract Lambda
|
|
608
608
|
request IDs and API Gateway correlation IDs.
|
|
609
609
|
|
|
610
|
+
## Tracing
|
|
611
|
+
|
|
612
|
+
Tracing is optional and lazy. The core package does not require
|
|
613
|
+
`opentelemetry-api`, does not initialize an SDK/exporter, and does not import
|
|
614
|
+
OpenTelemetry during `import modmex_lambda`.
|
|
615
|
+
|
|
616
|
+
`Tracer` uses OpenTelemetry when an OpenTelemetry runtime is available. For
|
|
617
|
+
Lambda, the recommended runtime is the AWS Distro for OpenTelemetry (ADOT)
|
|
618
|
+
Lambda layer.
|
|
619
|
+
|
|
620
|
+
The example below uses Serverless Framework. If you use another IaC tool, the
|
|
621
|
+
same pieces are required: enable Lambda tracing, attach the ADOT Python layer
|
|
622
|
+
for the AWS region where the function runs, and configure the OpenTelemetry
|
|
623
|
+
environment variables.
|
|
624
|
+
|
|
625
|
+
```yaml
|
|
626
|
+
provider:
|
|
627
|
+
name: aws
|
|
628
|
+
runtime: python3.12
|
|
629
|
+
region: mx-central-1
|
|
630
|
+
tracing:
|
|
631
|
+
lambda: true
|
|
632
|
+
|
|
633
|
+
functions:
|
|
634
|
+
api:
|
|
635
|
+
handler: app.lambda_handler
|
|
636
|
+
layers:
|
|
637
|
+
- arn:aws:lambda:mx-central-1:610118373846:layer:AWSOpenTelemetryDistroPython:13
|
|
638
|
+
environment:
|
|
639
|
+
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
|
|
640
|
+
OTEL_SERVICE_NAME: ${self:service}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Do not bundle `opentelemetry-*` packages in your function when using the ADOT
|
|
644
|
+
layer. The layer provides the OpenTelemetry runtime; bundling a different
|
|
645
|
+
version can conflict with the layer. Public ADOT layer ARNs are regional, so
|
|
646
|
+
use the ARN that matches your configured AWS region.
|
|
647
|
+
|
|
648
|
+
```python
|
|
649
|
+
from modmex_lambda import Tracer
|
|
650
|
+
|
|
651
|
+
tracer = Tracer(service="orders")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@tracer.capture_lambda_handler
|
|
655
|
+
def lambda_handler(event, context):
|
|
656
|
+
return handle(event)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
@tracer.capture_method(name="handle_order")
|
|
660
|
+
def handle(event):
|
|
661
|
+
tracer.set_attribute("tenant_id", event.get("tenant_id"))
|
|
662
|
+
return {"ok": True}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
If OpenTelemetry is not installed or configured, the tracer is a no-op. Use an
|
|
666
|
+
external OpenTelemetry setup, such as an ADOT Lambda layer or your own SDK
|
|
667
|
+
configuration, when spans should be exported.
|
|
668
|
+
|
|
669
|
+
When decorating route handlers, put the route decorator above
|
|
670
|
+
`capture_method`, so the router registers the traced function:
|
|
671
|
+
|
|
672
|
+
```python
|
|
673
|
+
@app.post("/todos")
|
|
674
|
+
@tracer.capture_method(name="create_todo")
|
|
675
|
+
def create_todo(todo: Annotated[Todo, Body()]) -> dict:
|
|
676
|
+
return todo.model_dump()
|
|
677
|
+
```
|
|
678
|
+
|
|
610
679
|
|
|
611
680
|
## Stream Handlers
|
|
612
681
|
|
|
@@ -584,6 +584,75 @@ The logger emits structured JSON, reads `LOG_LEVEL`, uses `SERVICE_NAME` or
|
|
|
584
584
|
`AWS_LAMBDA_FUNCTION_NAME` when no service is passed, and can extract Lambda
|
|
585
585
|
request IDs and API Gateway correlation IDs.
|
|
586
586
|
|
|
587
|
+
## Tracing
|
|
588
|
+
|
|
589
|
+
Tracing is optional and lazy. The core package does not require
|
|
590
|
+
`opentelemetry-api`, does not initialize an SDK/exporter, and does not import
|
|
591
|
+
OpenTelemetry during `import modmex_lambda`.
|
|
592
|
+
|
|
593
|
+
`Tracer` uses OpenTelemetry when an OpenTelemetry runtime is available. For
|
|
594
|
+
Lambda, the recommended runtime is the AWS Distro for OpenTelemetry (ADOT)
|
|
595
|
+
Lambda layer.
|
|
596
|
+
|
|
597
|
+
The example below uses Serverless Framework. If you use another IaC tool, the
|
|
598
|
+
same pieces are required: enable Lambda tracing, attach the ADOT Python layer
|
|
599
|
+
for the AWS region where the function runs, and configure the OpenTelemetry
|
|
600
|
+
environment variables.
|
|
601
|
+
|
|
602
|
+
```yaml
|
|
603
|
+
provider:
|
|
604
|
+
name: aws
|
|
605
|
+
runtime: python3.12
|
|
606
|
+
region: mx-central-1
|
|
607
|
+
tracing:
|
|
608
|
+
lambda: true
|
|
609
|
+
|
|
610
|
+
functions:
|
|
611
|
+
api:
|
|
612
|
+
handler: app.lambda_handler
|
|
613
|
+
layers:
|
|
614
|
+
- arn:aws:lambda:mx-central-1:610118373846:layer:AWSOpenTelemetryDistroPython:13
|
|
615
|
+
environment:
|
|
616
|
+
AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument
|
|
617
|
+
OTEL_SERVICE_NAME: ${self:service}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
Do not bundle `opentelemetry-*` packages in your function when using the ADOT
|
|
621
|
+
layer. The layer provides the OpenTelemetry runtime; bundling a different
|
|
622
|
+
version can conflict with the layer. Public ADOT layer ARNs are regional, so
|
|
623
|
+
use the ARN that matches your configured AWS region.
|
|
624
|
+
|
|
625
|
+
```python
|
|
626
|
+
from modmex_lambda import Tracer
|
|
627
|
+
|
|
628
|
+
tracer = Tracer(service="orders")
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
@tracer.capture_lambda_handler
|
|
632
|
+
def lambda_handler(event, context):
|
|
633
|
+
return handle(event)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
@tracer.capture_method(name="handle_order")
|
|
637
|
+
def handle(event):
|
|
638
|
+
tracer.set_attribute("tenant_id", event.get("tenant_id"))
|
|
639
|
+
return {"ok": True}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
If OpenTelemetry is not installed or configured, the tracer is a no-op. Use an
|
|
643
|
+
external OpenTelemetry setup, such as an ADOT Lambda layer or your own SDK
|
|
644
|
+
configuration, when spans should be exported.
|
|
645
|
+
|
|
646
|
+
When decorating route handlers, put the route decorator above
|
|
647
|
+
`capture_method`, so the router registers the traced function:
|
|
648
|
+
|
|
649
|
+
```python
|
|
650
|
+
@app.post("/todos")
|
|
651
|
+
@tracer.capture_method(name="create_todo")
|
|
652
|
+
def create_todo(todo: Annotated[Todo, Body()]) -> dict:
|
|
653
|
+
return todo.model_dump()
|
|
654
|
+
```
|
|
655
|
+
|
|
587
656
|
|
|
588
657
|
## Stream Handlers
|
|
589
658
|
|
|
@@ -16,6 +16,7 @@ __all__ = [
|
|
|
16
16
|
"create_dependency_resolver",
|
|
17
17
|
"default_dependency_resolver",
|
|
18
18
|
"Logger",
|
|
19
|
+
"Tracer",
|
|
19
20
|
"ModmexValidator",
|
|
20
21
|
"ValidationError",
|
|
21
22
|
]
|
|
@@ -38,6 +39,7 @@ def __getattr__(name):
|
|
|
38
39
|
"create_dependency_resolver": ("modmex_lambda.dependencies", "create_dependency_resolver"),
|
|
39
40
|
"default_dependency_resolver": ("modmex_lambda.dependencies", "default_dependency_resolver"),
|
|
40
41
|
"Logger": ("modmex_lambda.logging", "Logger"),
|
|
42
|
+
"Tracer": ("modmex_lambda.tracing", "Tracer"),
|
|
41
43
|
"ModmexValidator": ("modmex_lambda.validation", "ModmexValidator"),
|
|
42
44
|
"ValidationError": ("modmex_lambda.validation", "ValidationError"),
|
|
43
45
|
}.get(name)
|
|
@@ -7,8 +7,11 @@ import os
|
|
|
7
7
|
import sys
|
|
8
8
|
import traceback
|
|
9
9
|
from datetime import datetime, timezone
|
|
10
|
+
from threading import RLock
|
|
10
11
|
from typing import Any, Callable, TextIO
|
|
11
12
|
|
|
13
|
+
from modmex_lambda.shared.json_encoder import JSONEncoder
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
_LEVELS = {
|
|
14
17
|
"DEBUG": 10,
|
|
@@ -19,6 +22,17 @@ _LEVELS = {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
|
|
25
|
+
class _LoggerJSONEncoder(JSONEncoder):
|
|
26
|
+
def default(self, obj: object) -> object:
|
|
27
|
+
if hasattr(obj, "model_dump"):
|
|
28
|
+
value = obj.model_dump()
|
|
29
|
+
try:
|
|
30
|
+
return json.loads(value)
|
|
31
|
+
except (TypeError, ValueError):
|
|
32
|
+
return value
|
|
33
|
+
return super().default(obj)
|
|
34
|
+
|
|
35
|
+
|
|
22
36
|
class Logger:
|
|
23
37
|
def __init__(
|
|
24
38
|
self,
|
|
@@ -31,28 +45,36 @@ class Logger:
|
|
|
31
45
|
) -> None:
|
|
32
46
|
self._service = service or os.getenv("SERVICE_NAME") or os.getenv("AWS_LAMBDA_FUNCTION_NAME") or "service"
|
|
33
47
|
self._stream = stream or sys.stdout
|
|
34
|
-
self._serialize = json_serializer or
|
|
48
|
+
self._serialize = json_serializer or self._serialize_payload
|
|
35
49
|
self._correlation_id_header = correlation_id_header.lower()
|
|
36
50
|
self._level = self._resolve_level(level)
|
|
37
51
|
self._persistent_keys: dict[str, Any] = {}
|
|
38
52
|
self._context: object | None = None
|
|
39
53
|
self._event: dict[str, Any] | None = None
|
|
54
|
+
self._lock = RLock()
|
|
40
55
|
|
|
41
56
|
def set_context(self, *, context: object | None = None, event: dict[str, Any] | None = None) -> None:
|
|
42
|
-
self.
|
|
43
|
-
|
|
57
|
+
with self._lock:
|
|
58
|
+
self._context = context
|
|
59
|
+
self._event = event
|
|
44
60
|
|
|
45
61
|
def append_keys(self, **kwargs: Any) -> None:
|
|
46
|
-
self.
|
|
62
|
+
with self._lock:
|
|
63
|
+
self._persistent_keys.update(kwargs)
|
|
47
64
|
|
|
48
65
|
def clear_state(self) -> None:
|
|
49
|
-
self.
|
|
66
|
+
with self._lock:
|
|
67
|
+
self._persistent_keys.clear()
|
|
68
|
+
self._context = None
|
|
69
|
+
self._event = None
|
|
50
70
|
|
|
51
71
|
def set_level(self, level: str | int) -> None:
|
|
52
|
-
|
|
72
|
+
with self._lock:
|
|
73
|
+
self._level = self._resolve_level(level)
|
|
53
74
|
|
|
54
75
|
def is_enabled_for(self, level: str | int) -> bool:
|
|
55
|
-
|
|
76
|
+
with self._lock:
|
|
77
|
+
return self._resolve_level(level) >= self._level
|
|
56
78
|
|
|
57
79
|
def debug(self, message: Any, *args: Any, **kwargs: Any) -> None:
|
|
58
80
|
self._log("DEBUG", message, *args, **kwargs)
|
|
@@ -70,31 +92,32 @@ class Logger:
|
|
|
70
92
|
self._log("CRITICAL", message, *args, exc_info=exc_info, **kwargs)
|
|
71
93
|
|
|
72
94
|
def _log(self, level: str, message: Any, *args: Any, exc_info: bool = False, **kwargs: Any) -> None:
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
with self._lock:
|
|
96
|
+
if not self.is_enabled_for(level):
|
|
97
|
+
return
|
|
75
98
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
record: dict[str, Any] = {
|
|
100
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
101
|
+
"level": level,
|
|
102
|
+
"service": self._service,
|
|
103
|
+
"message": self._format_message(message, args),
|
|
104
|
+
}
|
|
105
|
+
record.update(self._persistent_keys)
|
|
83
106
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
request_id = self._extract_request_id()
|
|
108
|
+
if request_id:
|
|
109
|
+
record["request_id"] = request_id
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
correlation_id = self._extract_correlation_id()
|
|
112
|
+
if correlation_id and "correlation_id" not in record:
|
|
113
|
+
record["correlation_id"] = correlation_id
|
|
91
114
|
|
|
92
|
-
|
|
115
|
+
record.update(kwargs)
|
|
93
116
|
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
if exc_info:
|
|
118
|
+
record["exception"] = traceback.format_exc()
|
|
96
119
|
|
|
97
|
-
|
|
120
|
+
self._stream.write(self._serialize(record) + "\n")
|
|
98
121
|
|
|
99
122
|
def _resolve_level(self, level: str | int | None) -> int:
|
|
100
123
|
if isinstance(level, int):
|
|
@@ -102,6 +125,12 @@ class Logger:
|
|
|
102
125
|
level_name = (level or os.getenv("LOG_LEVEL") or "INFO").upper()
|
|
103
126
|
return _LEVELS.get(level_name, _LEVELS["INFO"])
|
|
104
127
|
|
|
128
|
+
def _serialize_payload(self, payload: dict[str, Any]) -> str:
|
|
129
|
+
try:
|
|
130
|
+
return json.dumps(payload, separators=(",", ":"), cls=_LoggerJSONEncoder)
|
|
131
|
+
except TypeError:
|
|
132
|
+
return json.dumps(payload, separators=(",", ":"), default=str)
|
|
133
|
+
|
|
105
134
|
def _format_message(self, message: Any, args: tuple[Any, ...]) -> Any:
|
|
106
135
|
if not args:
|
|
107
136
|
return message
|
|
@@ -11,10 +11,10 @@ from modmex_lambda.dependencies import (
|
|
|
11
11
|
DependencyResolver,
|
|
12
12
|
default_dependency_resolver,
|
|
13
13
|
)
|
|
14
|
+
from modmex_lambda.logging import Logger
|
|
14
15
|
from modmex_lambda.stream.flavors.iflavor import IFlavor
|
|
15
16
|
from modmex_lambda.stream.operators.publisher import Publisher, PublisherOptions
|
|
16
17
|
from modmex_lambda.stream.utils.contracts import Event
|
|
17
|
-
from modmex_lambda.stream.utils.opt import DEFAULT_OPTIONS
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
@@ -39,7 +39,7 @@ class BaseFlavor(Generic[TEvent], IFlavor):
|
|
|
39
39
|
dependency_resolver: Optional[DependencyResolver] = None,
|
|
40
40
|
publisher_options: Optional[PublisherOptions] = None,
|
|
41
41
|
) -> None:
|
|
42
|
-
self.logger = logger or
|
|
42
|
+
self.logger = logger or Logger()
|
|
43
43
|
self.connector = connector
|
|
44
44
|
self.dependency_resolver = dependency_resolver
|
|
45
45
|
self.publisher_options = publisher_options or {}
|
|
@@ -127,7 +127,7 @@ class BaseFlavor(Generic[TEvent], IFlavor):
|
|
|
127
127
|
self._publisher = Publisher[TEvent](
|
|
128
128
|
connector=self.connector or self.resolve(IEventBridgeConnector),
|
|
129
129
|
logger=self.logger,
|
|
130
|
-
bus_name=options.get("bus_name")
|
|
130
|
+
bus_name=options.get("bus_name"),
|
|
131
131
|
source=options.get("source"),
|
|
132
132
|
event_field=options.get("event_field"),
|
|
133
133
|
publish_request_entry_field=options.get("publish_request_entry_field"),
|
|
@@ -42,7 +42,7 @@ class Publisher(Generic[TEvent], IOperator[Uow[TEvent]]):
|
|
|
42
42
|
logger: Optional[object] = None,
|
|
43
43
|
):
|
|
44
44
|
self.connector = connector or Connector()
|
|
45
|
-
self.bus_name = bus_name or os.getenv('BUS_NAME')
|
|
45
|
+
self.bus_name = bus_name or os.getenv('BUS_NAME') or 'undefined'
|
|
46
46
|
self.source = source or os.getenv('BUS_SRC') or 'custom'
|
|
47
47
|
self.event_field = event_field or 'event'
|
|
48
48
|
self.publish_request_entry_field = publish_request_entry_field or 'publish_request_entry'
|
|
@@ -1,29 +1,45 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import multiprocessing
|
|
3
|
+
import os
|
|
3
4
|
import threading
|
|
4
|
-
from typing import Iterable,
|
|
5
|
+
from typing import Any, Callable, Iterable, TypedDict
|
|
5
6
|
|
|
6
7
|
from reactivex import Observable, from_list, operators as ops
|
|
7
8
|
from reactivex.scheduler import ThreadPoolScheduler
|
|
8
9
|
|
|
9
10
|
from modmex_lambda.stream.irules_registry import IRulesRegistry
|
|
11
|
+
from modmex_lambda.stream.utils.eventbridge import publish_to_event_bridge
|
|
10
12
|
from modmex_lambda.stream.utils.faults import flush_faults
|
|
11
|
-
from modmex_lambda.stream.utils.opt import DEFAULT_OPTIONS
|
|
12
13
|
from modmex_lambda.logging import Logger
|
|
13
14
|
from modmex_lambda.stream.utils.operators import tap
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
PublishFactory = Callable[[dict[str, Any]], Any]
|
|
18
|
+
BusName = str | Callable[[dict[str, Any]], str] | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RunOptions(TypedDict, total=False):
|
|
22
|
+
bus_name: BusName
|
|
23
|
+
publish: PublishFactory
|
|
24
|
+
logger: Any
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _publish_to_event_bridge(params: dict[str, Any]) -> Any:
|
|
28
|
+
return publish_to_event_bridge(**params)
|
|
29
|
+
|
|
30
|
+
|
|
16
31
|
def run(
|
|
17
32
|
events: Iterable,
|
|
18
33
|
registry: IRulesRegistry,
|
|
19
|
-
opt:
|
|
34
|
+
opt: RunOptions | None = None,
|
|
20
35
|
on_next=None,
|
|
21
36
|
on_error=None,
|
|
22
37
|
on_completed=None,
|
|
23
38
|
concurrency=True,
|
|
24
39
|
):
|
|
25
40
|
opt = {
|
|
26
|
-
|
|
41
|
+
'bus_name': os.getenv('BUS_NAME'),
|
|
42
|
+
'publish': _publish_to_event_bridge,
|
|
27
43
|
**(opt or {}),
|
|
28
44
|
}
|
|
29
45
|
optimal_thread_count = multiprocessing.cpu_count()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from modmex_lambda.logging import Logger
|
|
3
2
|
from modmex_lambda.connectors.eventbridge import Connector
|
|
4
3
|
from modmex_lambda.stream.operators.publisher import Publisher
|
|
@@ -6,12 +5,12 @@ from modmex_lambda.stream.operators.publisher import Publisher
|
|
|
6
5
|
# pylint: disable=unused-argument,too-many-arguments
|
|
7
6
|
def publish_to_event_bridge(
|
|
8
7
|
logger=None,
|
|
9
|
-
bus_name=
|
|
10
|
-
source=
|
|
11
|
-
event_field=
|
|
12
|
-
publish_request_entry_field=
|
|
13
|
-
publish_request_field=
|
|
14
|
-
batch_size=
|
|
8
|
+
bus_name=None,
|
|
9
|
+
source=None,
|
|
10
|
+
event_field=None,
|
|
11
|
+
publish_request_entry_field=None,
|
|
12
|
+
publish_request_field=None,
|
|
13
|
+
batch_size=None,
|
|
15
14
|
):
|
|
16
15
|
return Publisher(
|
|
17
16
|
connector=Connector(),
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Lazy OpenTelemetry-compatible tracing helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from functools import wraps
|
|
9
|
+
from importlib import import_module
|
|
10
|
+
from typing import Any, Callable, Iterator, Mapping, TypeVar
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _NoopSpan:
|
|
17
|
+
def set_attribute(self, _key: str, _value: Any) -> None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def add_event(self, _name: str, attributes: Mapping[str, Any] | None = None) -> None:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def record_exception(self, _exception: BaseException) -> None:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Tracer:
|
|
28
|
+
"""Tiny lazy tracer facade.
|
|
29
|
+
|
|
30
|
+
The core package does not depend on OpenTelemetry. If ``opentelemetry-api``
|
|
31
|
+
is unavailable, all operations are no-ops.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
service: str | None = None,
|
|
38
|
+
enabled: bool = True,
|
|
39
|
+
tracer_provider: Any | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
self.service = service or os.getenv("SERVICE_NAME") or os.getenv("AWS_LAMBDA_FUNCTION_NAME") or "service"
|
|
42
|
+
self.enabled = enabled
|
|
43
|
+
self.tracer_provider = tracer_provider
|
|
44
|
+
self._tracer: Any | None = None
|
|
45
|
+
self._cold_start = True
|
|
46
|
+
|
|
47
|
+
def capture_method(
|
|
48
|
+
self,
|
|
49
|
+
func: F | None = None,
|
|
50
|
+
*,
|
|
51
|
+
name: str | None = None,
|
|
52
|
+
attributes: Mapping[str, Any] | None = None,
|
|
53
|
+
) -> F | Callable[[F], F]:
|
|
54
|
+
def decorator(method: F) -> F:
|
|
55
|
+
span_name = name or method.__qualname__
|
|
56
|
+
|
|
57
|
+
if inspect.iscoroutinefunction(method):
|
|
58
|
+
|
|
59
|
+
@wraps(method)
|
|
60
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
61
|
+
with self._start_span(span_name, attributes=attributes):
|
|
62
|
+
return await method(*args, **kwargs)
|
|
63
|
+
|
|
64
|
+
return async_wrapper # type: ignore[return-value]
|
|
65
|
+
|
|
66
|
+
@wraps(method)
|
|
67
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
68
|
+
with self._start_span(span_name, attributes=attributes):
|
|
69
|
+
return method(*args, **kwargs)
|
|
70
|
+
|
|
71
|
+
return wrapper # type: ignore[return-value]
|
|
72
|
+
|
|
73
|
+
if func is None:
|
|
74
|
+
return decorator
|
|
75
|
+
return decorator(func)
|
|
76
|
+
|
|
77
|
+
def capture_lambda_handler(
|
|
78
|
+
self,
|
|
79
|
+
func: F | None = None,
|
|
80
|
+
*,
|
|
81
|
+
name: str | None = None,
|
|
82
|
+
attributes: Mapping[str, Any] | None = None,
|
|
83
|
+
) -> F | Callable[[F], F]:
|
|
84
|
+
def decorator(handler: F) -> F:
|
|
85
|
+
span_name = name or handler.__name__
|
|
86
|
+
|
|
87
|
+
@wraps(handler)
|
|
88
|
+
def wrapper(event: Any, context: Any, *args: Any, **kwargs: Any) -> Any:
|
|
89
|
+
with self._start_span(span_name, attributes=attributes) as span:
|
|
90
|
+
self._set_lambda_context_attributes(span, context)
|
|
91
|
+
return handler(event, context, *args, **kwargs)
|
|
92
|
+
|
|
93
|
+
return wrapper # type: ignore[return-value]
|
|
94
|
+
|
|
95
|
+
if func is None:
|
|
96
|
+
return decorator
|
|
97
|
+
return decorator(func)
|
|
98
|
+
|
|
99
|
+
def set_attribute(self, key: str, value: Any) -> None:
|
|
100
|
+
span = self._current_span()
|
|
101
|
+
span.set_attribute(key, value)
|
|
102
|
+
|
|
103
|
+
def add_event(self, name: str, attributes: Mapping[str, Any] | None = None) -> None:
|
|
104
|
+
span = self._current_span()
|
|
105
|
+
span.add_event(name, attributes=attributes)
|
|
106
|
+
|
|
107
|
+
@contextmanager
|
|
108
|
+
def _start_span(
|
|
109
|
+
self,
|
|
110
|
+
name: str,
|
|
111
|
+
*,
|
|
112
|
+
attributes: Mapping[str, Any] | None = None,
|
|
113
|
+
) -> Iterator[Any]:
|
|
114
|
+
tracer = self._get_tracer()
|
|
115
|
+
if tracer is None:
|
|
116
|
+
yield _NoopSpan()
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
span_context = tracer.start_as_current_span(name)
|
|
121
|
+
except AttributeError:
|
|
122
|
+
yield _NoopSpan()
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
with span_context as span:
|
|
126
|
+
for key, value in (attributes or {}).items():
|
|
127
|
+
span.set_attribute(key, value)
|
|
128
|
+
try:
|
|
129
|
+
yield span
|
|
130
|
+
except Exception as exc:
|
|
131
|
+
span.record_exception(exc)
|
|
132
|
+
self._set_error_status(span)
|
|
133
|
+
raise
|
|
134
|
+
|
|
135
|
+
def _get_tracer(self) -> Any | None:
|
|
136
|
+
if not self.enabled:
|
|
137
|
+
return None
|
|
138
|
+
if self._tracer is not None:
|
|
139
|
+
return self._tracer
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
trace = import_module("opentelemetry.trace")
|
|
143
|
+
except ImportError:
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
self._tracer = trace.get_tracer(
|
|
147
|
+
__name__,
|
|
148
|
+
tracer_provider=self.tracer_provider,
|
|
149
|
+
)
|
|
150
|
+
return self._tracer
|
|
151
|
+
|
|
152
|
+
def _current_span(self) -> Any:
|
|
153
|
+
if not self.enabled:
|
|
154
|
+
return _NoopSpan()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
trace = import_module("opentelemetry.trace")
|
|
158
|
+
except ImportError:
|
|
159
|
+
return _NoopSpan()
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
return trace.get_current_span()
|
|
163
|
+
except AttributeError:
|
|
164
|
+
return _NoopSpan()
|
|
165
|
+
|
|
166
|
+
def _set_lambda_context_attributes(self, span: Any, context: Any) -> None:
|
|
167
|
+
if self._cold_start:
|
|
168
|
+
span.set_attribute("faas.coldstart", True)
|
|
169
|
+
self._cold_start = False
|
|
170
|
+
else:
|
|
171
|
+
span.set_attribute("faas.coldstart", False)
|
|
172
|
+
|
|
173
|
+
request_id = getattr(context, "aws_request_id", None)
|
|
174
|
+
if request_id:
|
|
175
|
+
span.set_attribute("faas.execution", str(request_id))
|
|
176
|
+
|
|
177
|
+
function_name = getattr(context, "function_name", None)
|
|
178
|
+
if function_name:
|
|
179
|
+
span.set_attribute("faas.name", str(function_name))
|
|
180
|
+
|
|
181
|
+
def _set_error_status(self, span: Any) -> None:
|
|
182
|
+
try:
|
|
183
|
+
trace = import_module("opentelemetry.trace")
|
|
184
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
185
|
+
except (ImportError, AttributeError):
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
__all__ = ["Tracer"]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modmex-lambda"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.5.0"
|
|
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"
|