velocity-python 0.1.74__tar.gz → 0.1.75__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 (213) hide show
  1. {velocity_python-0.1.74 → velocity_python-0.1.75}/PKG-INFO +1 -1
  2. {velocity_python-0.1.74 → velocity_python-0.1.75}/pyproject.toml +1 -1
  3. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/context.py +105 -6
  5. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/PKG-INFO +1 -1
  6. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  7. velocity_python-0.1.75/tests/test_enqueue_send_failures.py +146 -0
  8. {velocity_python-0.1.74 → velocity_python-0.1.75}/LICENSE +0 -0
  9. {velocity_python-0.1.74 → velocity_python-0.1.75}/README.md +0 -0
  10. {velocity_python-0.1.74 → velocity_python-0.1.75}/setup.cfg +0 -0
  11. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/__init__.py +0 -0
  12. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/amplify.py +0 -0
  13. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/amplify_build.py +0 -0
  14. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/__init__.py +0 -0
  15. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/backfill.py +0 -0
  16. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/indexing.py +0 -0
  17. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/references.py +0 -0
  18. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/service.py +0 -0
  19. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/assets/usage_index.py +0 -0
  20. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/dirty_pipeline.py +0 -0
  21. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/__init__.py +0 -0
  22. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/base_handler.py +0 -0
  23. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/context_factory.py +0 -0
  24. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/exceptions.py +0 -0
  25. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  26. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/masquerade.py +0 -0
  27. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  28. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  29. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  30. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/perf.py +0 -0
  31. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/response.py +0 -0
  32. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  33. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/s3.py +0 -0
  34. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/ssm_config.py +0 -0
  35. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/tests/__init__.py +0 -0
  36. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  37. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  38. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/aws/tests/test_response.py +0 -0
  39. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/__init__.py +0 -0
  40. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/__init__.py +0 -0
  41. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/async_support.py +0 -0
  42. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/column.py +0 -0
  43. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/database.py +0 -0
  44. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/decorators.py +0 -0
  45. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/engine.py +0 -0
  46. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/jsonproxy.py +0 -0
  47. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/result.py +0 -0
  48. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/row.py +0 -0
  49. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/sequence.py +0 -0
  50. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/table.py +0 -0
  51. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/transaction.py +0 -0
  52. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/core/view.py +0 -0
  53. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/exceptions.py +0 -0
  54. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/migrations.py +0 -0
  55. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/__init__.py +0 -0
  56. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/base/__init__.py +0 -0
  57. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/base/initializer.py +0 -0
  58. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/base/operators.py +0 -0
  59. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/base/sql.py +0 -0
  60. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/base/types.py +0 -0
  61. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/mysql/__init__.py +0 -0
  62. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/mysql/operators.py +0 -0
  63. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/mysql/reserved.py +0 -0
  64. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/mysql/sql.py +0 -0
  65. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/mysql/types.py +0 -0
  66. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/postgres/__init__.py +0 -0
  67. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/postgres/operators.py +0 -0
  68. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/postgres/reserved.py +0 -0
  69. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/postgres/sql.py +0 -0
  70. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/postgres/types.py +0 -0
  71. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  72. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlite/operators.py +0 -0
  73. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  74. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlite/sql.py +0 -0
  75. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlite/types.py +0 -0
  76. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  77. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  78. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  79. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  80. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/sqlserver/types.py +0 -0
  81. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/servers/tablehelper.py +0 -0
  82. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/__init__.py +0 -0
  83. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/common_db_test.py +0 -0
  84. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/__init__.py +0 -0
  85. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/common.py +0 -0
  86. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/conftest.py +0 -0
  87. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_column.py +0 -0
  88. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  89. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_database.py +0 -0
  90. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  91. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  92. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  93. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_result.py +0 -0
  94. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_row.py +0 -0
  95. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  96. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  97. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  98. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  99. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  100. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_table.py +0 -0
  101. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  102. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  103. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/sql/__init__.py +0 -0
  104. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/sql/common.py +0 -0
  105. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  106. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  107. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  108. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_db_utils.py +0 -0
  109. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_postgres.py +0 -0
  110. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  111. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  112. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_result_caching.py +0 -0
  113. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  114. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  115. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  116. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  117. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_sql_builder.py +0 -0
  118. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_tablehelper.py +0 -0
  119. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/tests/test_view_helper.py +0 -0
  120. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/db/utils.py +0 -0
  121. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/logging.py +0 -0
  122. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/__init__.py +0 -0
  123. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/conv/__init__.py +0 -0
  124. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/conv/iconv.py +0 -0
  125. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/conv/oconv.py +0 -0
  126. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/db.py +0 -0
  127. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/export.py +0 -0
  128. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/format.py +0 -0
  129. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/mail.py +0 -0
  130. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/merge.py +0 -0
  131. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/pdf.py +0 -0
  132. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/__init__.py +0 -0
  133. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_db.py +0 -0
  134. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_fix.py +0 -0
  135. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_format.py +0 -0
  136. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_iconv.py +0 -0
  137. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_merge.py +0 -0
  138. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_oconv.py +0 -0
  139. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_original_error.py +0 -0
  140. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tests/test_timer.py +0 -0
  141. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/timer.py +0 -0
  142. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/misc/tools.py +0 -0
  143. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/__init__.py +0 -0
  144. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/authorizenet_adapter.py +0 -0
  145. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/authorizenet_mirror.py +0 -0
  146. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/base_adapter.py +0 -0
  147. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/braintree_adapter.py +0 -0
  148. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/braintree_mirror.py +0 -0
  149. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/charge_rules.py +0 -0
  150. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/stripe_adapter.py +0 -0
  151. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity/payment/stripe_mirror.py +0 -0
  152. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  153. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/entry_points.txt +0 -0
  154. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/requires.txt +0 -0
  155. {velocity_python-0.1.74 → velocity_python-0.1.75}/src/velocity_python.egg-info/top_level.txt +0 -0
  156. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_amplify_build.py +0 -0
  157. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_asset_indexing.py +0 -0
  158. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_asset_references.py +0 -0
  159. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_assets_service.py +0 -0
  160. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_async_support.py +0 -0
  161. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_batch_operations.py +0 -0
  162. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_column_tx_arg.py +0 -0
  163. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_concurrency_safety.py +0 -0
  164. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_connection_pool.py +0 -0
  165. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_connection_resilience.py +0 -0
  166. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_context_job_descriptions.py +0 -0
  167. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_db_credentials_ssm_cascade.py +0 -0
  168. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_decorators.py +0 -0
  169. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_dirty_pipeline_fast_path.py +0 -0
  170. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_email_processing.py +0 -0
  171. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_get_cognito_user_provider.py +0 -0
  172. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_http_handler_rollback.py +0 -0
  173. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_iconv_money_to_cents.py +0 -0
  174. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_identifier_injection_guard.py +0 -0
  175. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_json_columns.py +0 -0
  176. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_jsonb_dict_adapter.py +0 -0
  177. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_lambda_handler.py +0 -0
  178. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_lambda_handler_auth.py +0 -0
  179. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_lambda_handler_masquerade.py +0 -0
  180. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_masquerade_grant.py +0 -0
  181. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_mixins_import.py +0 -0
  182. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_n_plus_one.py +0 -0
  183. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_observability.py +0 -0
  184. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_payment_authorizenet_adapter.py +0 -0
  185. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_payment_braintree_adapter.py +0 -0
  186. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_payment_braintree_mirror.py +0 -0
  187. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_payment_profile_sorting.py +0 -0
  188. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_payment_stripe_adapter.py +0 -0
  189. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_pdf.py +0 -0
  190. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_prepared_statements.py +0 -0
  191. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_psycopg3_upgrade.py +0 -0
  192. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_query_cache.py +0 -0
  193. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_restricted_direct_tables.py +0 -0
  194. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_retry_side_effect_guard.py +0 -0
  195. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_return_default_safety.py +0 -0
  196. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_row_batch_update.py +0 -0
  197. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_row_cache_staleness.py +0 -0
  198. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_schema_migrations.py +0 -0
  199. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_security_hardening.py +0 -0
  200. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_server_cursor.py +0 -0
  201. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_single_autocommit_safety.py +0 -0
  202. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_spreadsheet_functions.py +0 -0
  203. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_sqlite_backend.py +0 -0
  204. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_sqs_per_record_transactions.py +0 -0
  205. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_ssm_config.py +0 -0
  206. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_store_user_data.py +0 -0
  207. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  208. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_table_alter.py +0 -0
  209. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_transaction_class_wrapping.py +0 -0
  210. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_transaction_commit_and_ownership.py +0 -0
  211. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_transaction_edge_cases.py +0 -0
  212. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_where_clause_validation.py +0 -0
  213. {velocity_python-0.1.74 → velocity_python-0.1.75}/tests/test_write_hook_create_flow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.74
3
+ Version: 0.1.75
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.74"
7
+ version = "0.1.75"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.74"
1
+ __version__ = version = "0.1.75"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -36,6 +36,38 @@ def _get_work_queue_name() -> str:
36
36
  return queue_name
37
37
 
38
38
 
39
+ def _emit_enqueue_failure_metric(action, count):
40
+ """Emit a CloudWatch ``CaringCent/Enqueue SendFailures`` metric.
41
+
42
+ SQS send failures are a pre-queue loss the dead-letter queue can never see
43
+ (a message that was never sent never reaches the DLQ), so this metric makes
44
+ send-side losses observable/alarmable the same way DLQ arrivals are.
45
+ Best-effort: never raises, so a metrics outage cannot break enqueueing.
46
+ """
47
+ if not count:
48
+ return
49
+ try:
50
+ stage = config_get_stage("") or "unknown"
51
+ boto3.client("cloudwatch").put_metric_data(
52
+ Namespace="CaringCent/Enqueue",
53
+ MetricData=[
54
+ {
55
+ "MetricName": "SendFailures",
56
+ "Value": float(count),
57
+ "Unit": "Count",
58
+ "Dimensions": [{"Name": "Stage", "Value": str(stage)}],
59
+ }
60
+ ],
61
+ )
62
+ except Exception:
63
+ logger.warning(
64
+ "Failed to emit enqueue SendFailures metric (action=%s, count=%s)",
65
+ action,
66
+ count,
67
+ exc_info=True,
68
+ )
69
+
70
+
39
71
  def _get_controls_vars() -> dict:
40
72
  app_stage = config_get_stage()
41
73
  return {
@@ -266,6 +298,32 @@ class Context:
266
298
  self._job_record_cache.pop(job_id, None)
267
299
  tx.commit()
268
300
 
301
+ def fail_enqueue(self, tx, job_id=None, reason=None):
302
+ """Mark a job-activity row Failed when its SQS message could not be sent.
303
+
304
+ ``enqueue`` writes the ``aws_job_activity`` row (status ``Initialized``)
305
+ *before* sending to SQS. If the send fails and is never reconciled, the
306
+ row is stuck in ``Initialized`` forever — never consumed, and never
307
+ counted as a failure (the consumer's ``onError`` is the only thing that
308
+ writes ``Failed``). This flips such an orphaned row to ``Failed`` so it
309
+ surfaces on the operational dashboard instead of inflating the stuck
310
+ count. Runs in its own transaction like ``create_job``/``update_job``.
311
+ """
312
+ if not job_id:
313
+ return
314
+ tx.table(self.JOB_ACTIVITY_TABLE).update(
315
+ self._sanitize_job_data(
316
+ {
317
+ "status": "Failed",
318
+ "message": "Job enqueue failed (SQS send failure)",
319
+ "error": reason or "SQS send failed",
320
+ }
321
+ ),
322
+ {"job_id": job_id},
323
+ )
324
+ self._job_record_cache.pop(job_id, None)
325
+ tx.commit()
326
+
269
327
  def _sanitize_job_data(self, data):
270
328
  """Sanitize sensitive data before storing in aws_job_activity table."""
271
329
  if not isinstance(data, dict):
@@ -444,6 +502,50 @@ class Context:
444
502
  messages = []
445
503
  if user is None:
446
504
  user = self.session().get("email_address") or "EnqueueTasks"
505
+
506
+ def _flush(batch):
507
+ """Send one batch and reconcile SQS send failures.
508
+
509
+ A failed send would otherwise leave the job-activity row stuck in
510
+ ``Initialized`` forever, so transient (non-``SenderFault``) failures
511
+ are retried once and any message that still never reaches the queue
512
+ has its job row marked ``Failed`` via ``fail_enqueue``.
513
+ """
514
+ nonlocal results
515
+ if not batch:
516
+ return
517
+ result = queue.send_messages(Entries=batch) or {}
518
+ results = deep_merge(results, result)
519
+ failed = result.get("Failed") or []
520
+ if not failed:
521
+ return
522
+ by_id = {entry["Id"]: entry for entry in batch}
523
+ still_failed = {f["Id"]: f for f in failed if f.get("SenderFault")}
524
+ transient = [
525
+ by_id[f["Id"]]
526
+ for f in failed
527
+ if not f.get("SenderFault") and f.get("Id") in by_id
528
+ ]
529
+ if transient:
530
+ # The retry result is consulted only for entries that still
531
+ # failed; it is not merged into ``results`` because ``deep_merge``
532
+ # cannot dedup SQS's list-of-dict ``Successful``/``Failed`` payloads.
533
+ retry = queue.send_messages(Entries=transient) or {}
534
+ for f in retry.get("Failed") or []:
535
+ still_failed[f["Id"]] = f
536
+ if not still_failed:
537
+ return
538
+ if not suppress_job_activity:
539
+ for job_id, f in still_failed.items():
540
+ self.fail_enqueue(
541
+ job_id=job_id,
542
+ reason="SQS send failed: {} {}".format(
543
+ f.get("Code") or "", f.get("Message") or ""
544
+ ).strip(),
545
+ )
546
+ _emit_enqueue_failure_metric(action, len(still_failed))
547
+ results.setdefault("FailedToEnqueue", []).extend(still_failed.keys())
548
+
447
549
  for item in payload:
448
550
  message = {"action": action, "payload": item}
449
551
  id = str(uuid.uuid4()).split("-")[0]
@@ -474,13 +576,10 @@ class Context:
474
576
  messages.append({"Id": id, "MessageBody": to_json(message)})
475
577
 
476
578
  if len(messages) == 10:
477
- result = queue.send_messages(Entries=messages)
478
- results = deep_merge(results, result)
479
- messages.clear()
579
+ _flush(messages)
580
+ messages = []
480
581
 
481
- if messages:
482
- result = queue.send_messages(Entries=messages)
483
- results = deep_merge(results, result)
582
+ _flush(messages)
484
583
 
485
584
  return results
486
585
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.74
3
+ Version: 0.1.75
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -165,6 +165,7 @@ tests/test_db_credentials_ssm_cascade.py
165
165
  tests/test_decorators.py
166
166
  tests/test_dirty_pipeline_fast_path.py
167
167
  tests/test_email_processing.py
168
+ tests/test_enqueue_send_failures.py
168
169
  tests/test_get_cognito_user_provider.py
169
170
  tests/test_http_handler_rollback.py
170
171
  tests/test_iconv_money_to_cents.py
@@ -0,0 +1,146 @@
1
+ import unittest
2
+ from unittest.mock import patch
3
+
4
+ from velocity.aws.handlers import context as handler_context
5
+
6
+
7
+ class _FakeQueue:
8
+ """Fake SQS queue whose send_messages fails for chosen entry Ids.
9
+
10
+ ``fail_mode`` controls the Failed payload:
11
+ - "transient": fail with SenderFault=False (retryable)
12
+ - "permanent": fail with SenderFault=True (no retry)
13
+ ``fail_calls`` lets a transient failure succeed on retry (e.g. [True, False]).
14
+ """
15
+
16
+ def __init__(self, fail_mode=None, fail_calls=None):
17
+ self.fail_mode = fail_mode
18
+ self.calls = []
19
+ self._fail_calls = list(fail_calls) if fail_calls is not None else None
20
+
21
+ def send_messages(self, Entries):
22
+ self.calls.append([e["Id"] for e in Entries])
23
+ fail_this_call = True
24
+ if self._fail_calls is not None:
25
+ fail_this_call = self._fail_calls.pop(0) if self._fail_calls else False
26
+ if not self.fail_mode or not fail_this_call:
27
+ return {"Successful": [{"Id": e["Id"]} for e in Entries], "Failed": []}
28
+ sender_fault = self.fail_mode == "permanent"
29
+ return {
30
+ "Successful": [],
31
+ "Failed": [
32
+ {
33
+ "Id": e["Id"],
34
+ "Code": "ServiceUnavailable",
35
+ "Message": "throttled",
36
+ "SenderFault": sender_fault,
37
+ }
38
+ for e in Entries
39
+ ],
40
+ }
41
+
42
+
43
+ class _FakeSqsResource:
44
+ def __init__(self, queue):
45
+ self.queue = queue
46
+
47
+ def get_queue_by_name(self, QueueName):
48
+ return self.queue
49
+
50
+
51
+ class _FakeContext:
52
+ """Captures create_job/fail_enqueue so we can assert reconciliation."""
53
+
54
+ def __init__(self):
55
+ self.jobs = []
56
+ self.failed = []
57
+
58
+ def session(self):
59
+ return {"email_address": "owner@example.com"}
60
+
61
+ def create_job(self, job_data=None):
62
+ self.jobs.append(job_data)
63
+
64
+ def fail_enqueue(self, job_id=None, reason=None):
65
+ self.failed.append({"job_id": job_id, "reason": reason})
66
+
67
+
68
+ def _run_enqueue(fake_context, fake_queue, payload, suppress=False):
69
+ fake_context.metric_calls = []
70
+
71
+ def _record(action, count):
72
+ fake_context.metric_calls.append((action, count))
73
+
74
+ with patch.dict(
75
+ handler_context.os.environ, {"SqsWorkQueue": "example-queue"}, clear=False
76
+ ), patch.object(
77
+ handler_context.boto3, "resource", return_value=_FakeSqsResource(fake_queue)
78
+ ), patch.object(
79
+ handler_context, "_emit_enqueue_failure_metric", side_effect=_record
80
+ ):
81
+ return handler_context.Context.enqueue(
82
+ fake_context,
83
+ "resolve-donor-billing-states",
84
+ payload=payload,
85
+ suppress_job_activity=suppress,
86
+ )
87
+
88
+
89
+ class TestEnqueueSendFailures(unittest.TestCase):
90
+ def test_transient_send_failure_is_retried_then_marked_failed(self):
91
+ ctx = _FakeContext()
92
+ queue = _FakeQueue(fail_mode="transient") # fails every time
93
+ result = _run_enqueue(ctx, queue, [{"id": 1}, {"id": 2}])
94
+
95
+ # Each job row was created (Initialized) then reconciled to Failed.
96
+ self.assertEqual(2, len(ctx.jobs))
97
+ failed_ids = {f["job_id"] for f in ctx.failed}
98
+ created_ids = {j["job_id"] for j in ctx.jobs}
99
+ self.assertEqual(created_ids, failed_ids)
100
+ self.assertEqual(sorted(failed_ids), sorted(result["FailedToEnqueue"]))
101
+ # Transient failure means the batch was retried once (2 send calls).
102
+ self.assertEqual(2, len(queue.calls))
103
+ # A SendFailures metric is emitted with the action and failed count.
104
+ self.assertEqual([("resolve-donor-billing-states", 2)], ctx.metric_calls)
105
+
106
+ def test_transient_failure_that_succeeds_on_retry_is_not_marked(self):
107
+ ctx = _FakeContext()
108
+ queue = _FakeQueue(fail_mode="transient", fail_calls=[True, False])
109
+ result = _run_enqueue(ctx, queue, [{"id": 1}])
110
+
111
+ self.assertEqual(1, len(ctx.jobs))
112
+ self.assertEqual([], ctx.failed)
113
+ self.assertNotIn("FailedToEnqueue", result)
114
+ self.assertEqual(2, len(queue.calls)) # original + successful retry
115
+
116
+ def test_permanent_send_failure_is_not_retried(self):
117
+ ctx = _FakeContext()
118
+ queue = _FakeQueue(fail_mode="permanent")
119
+ _run_enqueue(ctx, queue, [{"id": 1}])
120
+
121
+ self.assertEqual(1, len(ctx.failed))
122
+ self.assertEqual(1, len(queue.calls)) # no retry on SenderFault
123
+
124
+ def test_suppressed_jobs_do_not_create_or_fail_rows(self):
125
+ ctx = _FakeContext()
126
+ queue = _FakeQueue(fail_mode="transient")
127
+ _run_enqueue(ctx, queue, [{"id": 1}], suppress=True)
128
+
129
+ self.assertEqual([], ctx.jobs)
130
+ self.assertEqual([], ctx.failed)
131
+ # Send loss is still metered even when job-activity rows are suppressed.
132
+ self.assertEqual([("resolve-donor-billing-states", 1)], ctx.metric_calls)
133
+
134
+ def test_successful_send_marks_nothing(self):
135
+ ctx = _FakeContext()
136
+ queue = _FakeQueue() # always succeeds
137
+ result = _run_enqueue(ctx, queue, [{"id": 1}, {"id": 2}])
138
+
139
+ self.assertEqual(2, len(ctx.jobs))
140
+ self.assertEqual([], ctx.failed)
141
+ self.assertNotIn("FailedToEnqueue", result)
142
+ self.assertEqual([], ctx.metric_calls) # no failures -> no metric
143
+
144
+
145
+ if __name__ == "__main__":
146
+ unittest.main()