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