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.
Files changed (240) hide show
  1. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/.gitignore +3 -1
  2. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/PKG-INFO +46 -15
  3. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/README.md +45 -14
  4. modmex_lambda-0.5.4/modmex_lambda/connectors/dynamodb.py +282 -0
  5. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/module.py +4 -1
  6. modmex_lambda-0.5.4/modmex_lambda/persistence/__init__.py +2 -0
  7. modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/__init__.py +39 -0
  8. modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/expressions.py +62 -0
  9. modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/keys.py +133 -0
  10. modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/materialized_views.py +80 -0
  11. modmex_lambda-0.5.4/modmex_lambda/persistence/dynamodb/stream_fields.py +121 -0
  12. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/dynamodb.py +5 -60
  13. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/pyproject.toml +1 -1
  14. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_dynamodb.py +171 -96
  15. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_lazy_clients.py +2 -3
  16. modmex_lambda-0.5.4/tests/persistence/__init__.py +1 -0
  17. modmex_lambda-0.5.4/tests/persistence/test_dynamodb_expressions.py +39 -0
  18. modmex_lambda-0.5.4/tests/persistence/test_dynamodb_keys.py +115 -0
  19. modmex_lambda-0.5.4/tests/persistence/test_dynamodb_materialized_views.py +115 -0
  20. modmex_lambda-0.5.4/tests/persistence/test_dynamodb_stream_fields.py +127 -0
  21. modmex_lambda-0.5.2/modmex_lambda/connectors/dynamodb.py +0 -123
  22. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/.github/workflows/ci.yml +0 -0
  23. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/.github/workflows/release.yml +0 -0
  24. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/LICENSE +0 -0
  25. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/__init__.py +0 -0
  26. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/__init__.py +0 -0
  27. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/cloudwatch.py +0 -0
  28. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/eventbridge.py +0 -0
  29. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/icloudwatch.py +0 -0
  30. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/idynamodb.py +0 -0
  31. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/ieventbridge.py +0 -0
  32. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/ilambda.py +0 -0
  33. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/is3.py +0 -0
  34. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/isns.py +0 -0
  35. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/isqs.py +0 -0
  36. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/lambda_.py +0 -0
  37. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/s3.py +0 -0
  38. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/sns.py +0 -0
  39. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/connectors/sqs.py +0 -0
  40. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/__init__.py +0 -0
  41. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_authorizer_event.py +0 -0
  42. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_proxy_event.py +0 -0
  43. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/api_gateway_websocket_event.py +0 -0
  44. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/cognito_user_pool_event.py +0 -0
  45. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/data_classes/common.py +0 -0
  46. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/dependencies.py +0 -0
  47. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/__init__.py +0 -0
  48. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/api_gateway.py +0 -0
  49. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/constants.py +0 -0
  50. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/content_types.py +0 -0
  51. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/cors.py +0 -0
  52. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/__init__.py +0 -0
  53. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/compat.py +0 -0
  54. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/dependant.py +0 -0
  55. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/dependency_middleware.py +0 -0
  56. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/depends.py +0 -0
  57. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/params.py +0 -0
  58. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/dependencies/types.py +0 -0
  59. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/exception_handler.py +0 -0
  60. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/exceptions.py +0 -0
  61. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/gateway_response.py +0 -0
  62. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/middlewares.py +0 -0
  63. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/params.py +0 -0
  64. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/request.py +0 -0
  65. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/response.py +0 -0
  66. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/routing.py +0 -0
  67. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/routing_fallbacks.py +0 -0
  68. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_handler/types.py +0 -0
  69. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/event_sources.py +0 -0
  70. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/exceptions.py +0 -0
  71. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/logging.py +0 -0
  72. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/params.py +0 -0
  73. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/parser.py +0 -0
  74. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/request.py +0 -0
  75. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/resolver.py +0 -0
  76. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/response.py +0 -0
  77. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/routing.py +0 -0
  78. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/__init__.py +0 -0
  79. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/cookies.py +0 -0
  80. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/headers_serializer.py +0 -0
  81. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/json_encoder.py +0 -0
  82. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/shared/types.py +0 -0
  83. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/__init__.py +0 -0
  84. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/__init__.py +0 -0
  85. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/dynamodb.py +0 -0
  86. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/kinesis.py +0 -0
  87. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/s3.py +0 -0
  88. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/sns.py +0 -0
  89. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/events/sqs.py +0 -0
  90. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/__init__.py +0 -0
  91. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/content.py +0 -0
  92. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/event_type.py +0 -0
  93. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/latch.py +0 -0
  94. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/filters/skip.py +0 -0
  95. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/__init__.py +0 -0
  96. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/base_flavor.py +0 -0
  97. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/cdc.py +0 -0
  98. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/collect.py +0 -0
  99. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/correlate.py +0 -0
  100. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/evaluate.py +0 -0
  101. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/expired.py +0 -0
  102. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/iflavor.py +0 -0
  103. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/job.py +0 -0
  104. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/materialize.py +0 -0
  105. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/s3.py +0 -0
  106. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/sns.py +0 -0
  107. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/task.py +0 -0
  108. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/flavors/update.py +0 -0
  109. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/irules_registry.py +0 -0
  110. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/__init__.py +0 -0
  111. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/cloudwatch.py +0 -0
  112. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/dynamodb.py +0 -0
  113. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/ioperator.py +0 -0
  114. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/lambda_.py +0 -0
  115. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/publisher.py +0 -0
  116. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/s3.py +0 -0
  117. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/sns.py +0 -0
  118. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/operators/sqs.py +0 -0
  119. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/rules_registry.py +0 -0
  120. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/runner.py +0 -0
  121. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/__init__.py +0 -0
  122. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/base.py +0 -0
  123. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/dynamodb.py +0 -0
  124. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/kinesis.py +0 -0
  125. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/s3.py +0 -0
  126. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/sns.py +0 -0
  127. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/sources/sqs.py +0 -0
  128. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/__init__.py +0 -0
  129. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/apigateway.py +0 -0
  130. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/aws.py +0 -0
  131. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/batch.py +0 -0
  132. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/cloudwatch.py +0 -0
  133. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/concurrency.py +0 -0
  134. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/contracts.py +0 -0
  135. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/data_classes/__init__.py +0 -0
  136. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/data_classes/dynamodb.py +0 -0
  137. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/decorators.py +0 -0
  138. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/eventbridge.py +0 -0
  139. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/faults.py +0 -0
  140. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/filters.py +0 -0
  141. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/json_encoder.py +0 -0
  142. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/lambda_.py +0 -0
  143. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/operators.py +0 -0
  144. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/pluralize.py +0 -0
  145. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/print.py +0 -0
  146. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/retry.py +0 -0
  147. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/s3.py +0 -0
  148. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/sns.py +0 -0
  149. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/split.py +0 -0
  150. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/sqs.py +0 -0
  151. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/tags.py +0 -0
  152. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/time.py +0 -0
  153. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/stream/utils/uow.py +0 -0
  154. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/tracing.py +0 -0
  155. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/modmex_lambda/validation.py +0 -0
  156. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/poetry.lock +0 -0
  157. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/__init__.py +0 -0
  158. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/conftest.py +0 -0
  159. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/__init__.py +0 -0
  160. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/conftest.py +0 -0
  161. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_cloudwatch.py +0 -0
  162. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_s3.py +0 -0
  163. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_simple_connectors.py +0 -0
  164. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_sns.py +0 -0
  165. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/connectors/test_sqs.py +0 -0
  166. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_api_gateway_proxy_event.py +0 -0
  167. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_cognito_user_pool_event.py +0 -0
  168. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/data_classes/test_common.py +0 -0
  169. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/__init__.py +0 -0
  170. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_api_gateway.py +0 -0
  171. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_cors.py +0 -0
  172. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_dependencies.py +0 -0
  173. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_exception_handler.py +0 -0
  174. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_gateway_response.py +0 -0
  175. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_request.py +0 -0
  176. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_response.py +0 -0
  177. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/event_handler/test_routing.py +0 -0
  178. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/shared/test_cookies_headers.py +0 -0
  179. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/shared/test_json_encoder.py +0 -0
  180. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/__init__.py +0 -0
  181. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/conftest.py +0 -0
  182. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_dynamodb.py +0 -0
  183. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_kinesis.py +0 -0
  184. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_s3.py +0 -0
  185. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_sns.py +0 -0
  186. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/events/test_sqs.py +0 -0
  187. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/__init__.py +0 -0
  188. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_content.py +0 -0
  189. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_event_type.py +0 -0
  190. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_latch.py +0 -0
  191. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/filters/test_skip.py +0 -0
  192. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/__init__.py +0 -0
  193. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/source_events.py +0 -0
  194. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_base_flavor.py +0 -0
  195. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_cdc.py +0 -0
  196. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_collect.py +0 -0
  197. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_correlate.py +0 -0
  198. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_evaluate.py +0 -0
  199. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_expired.py +0 -0
  200. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_job.py +0 -0
  201. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_materialize.py +0 -0
  202. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_s3.py +0 -0
  203. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_sns.py +0 -0
  204. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_task.py +0 -0
  205. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/flavors/test_update.py +0 -0
  206. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_dependency_resolver.py +0 -0
  207. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_rules_registry.py +0 -0
  208. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_runner.py +0 -0
  209. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_runner_pipeline.py +0 -0
  210. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/test_sources.py +0 -0
  211. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/__init__.py +0 -0
  212. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/faults.py +0 -0
  213. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_apigateway.py +0 -0
  214. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_aws.py +0 -0
  215. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_batch.py +0 -0
  216. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_cloudwatch.py +0 -0
  217. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_concurrency.py +0 -0
  218. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_decorators.py +0 -0
  219. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_dynamodb.py +0 -0
  220. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_eventbridge.py +0 -0
  221. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_filters.py +0 -0
  222. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_json_encoder.py +0 -0
  223. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_lambda.py +0 -0
  224. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_operators.py +0 -0
  225. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_pluralize.py +0 -0
  226. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_print.py +0 -0
  227. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_retry.py +0 -0
  228. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_s3.py +0 -0
  229. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_sns.py +0 -0
  230. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_split.py +0 -0
  231. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_sqs.py +0 -0
  232. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_tags.py +0 -0
  233. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_time.py +0 -0
  234. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/stream/utils/test_uow.py +0 -0
  235. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_lazy_imports.py +0 -0
  236. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_logging.py +0 -0
  237. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_parser_event_sources.py +0 -0
  238. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_reexports.py +0 -0
  239. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_tracing.py +0 -0
  240. {modmex_lambda-0.5.2 → modmex_lambda-0.5.4}/tests/test_validation.py +0 -0
@@ -218,4 +218,6 @@ __marimo__/
218
218
  .streamlit/secrets.toml
219
219
  .benchmark/
220
220
  .vscode/
221
- .doc/
221
+ .doc/
222
+
223
+ .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modmex-lambda
3
- Version: 0.5.2
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
- def to_thing_view_update(uow):
989
- thing = uow["event"]["thing"]
990
- return {
991
- "Key": {
992
- "pk": thing["id"],
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": to_thing_view_update,
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
- def to_thing_view_update(uow):
966
- thing = uow["event"]["thing"]
967
- return {
968
- "Key": {
969
- "pk": thing["id"],
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": to_thing_view_update,
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,2 @@
1
+ """Persistence helpers for common modmex-lambda application patterns."""
2
+
@@ -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
+ }