velocity-python 0.1.74__tar.gz → 0.1.76__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 (215) hide show
  1. {velocity_python-0.1.74 → velocity_python-0.1.76}/PKG-INFO +1 -1
  2. {velocity_python-0.1.74 → velocity_python-0.1.76}/pyproject.toml +1 -1
  3. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/context.py +105 -6
  5. velocity_python-0.1.76/src/velocity/db/maintenance.py +319 -0
  6. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/PKG-INFO +1 -1
  7. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/SOURCES.txt +3 -0
  8. velocity_python-0.1.76/tests/test_db_maintenance.py +181 -0
  9. velocity_python-0.1.76/tests/test_enqueue_send_failures.py +146 -0
  10. {velocity_python-0.1.74 → velocity_python-0.1.76}/LICENSE +0 -0
  11. {velocity_python-0.1.74 → velocity_python-0.1.76}/README.md +0 -0
  12. {velocity_python-0.1.74 → velocity_python-0.1.76}/setup.cfg +0 -0
  13. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/__init__.py +0 -0
  14. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/amplify.py +0 -0
  15. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/amplify_build.py +0 -0
  16. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/__init__.py +0 -0
  17. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/backfill.py +0 -0
  18. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/indexing.py +0 -0
  19. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/references.py +0 -0
  20. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/service.py +0 -0
  21. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/assets/usage_index.py +0 -0
  22. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/dirty_pipeline.py +0 -0
  23. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/__init__.py +0 -0
  24. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/base_handler.py +0 -0
  25. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/context_factory.py +0 -0
  26. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/exceptions.py +0 -0
  27. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  28. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/masquerade.py +0 -0
  29. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  30. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  31. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  32. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/perf.py +0 -0
  33. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/response.py +0 -0
  34. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  35. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/s3.py +0 -0
  36. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/ssm_config.py +0 -0
  37. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/tests/__init__.py +0 -0
  38. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  39. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  40. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/aws/tests/test_response.py +0 -0
  41. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/__init__.py +0 -0
  42. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/__init__.py +0 -0
  43. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/async_support.py +0 -0
  44. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/column.py +0 -0
  45. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/database.py +0 -0
  46. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/decorators.py +0 -0
  47. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/engine.py +0 -0
  48. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/jsonproxy.py +0 -0
  49. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/result.py +0 -0
  50. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/row.py +0 -0
  51. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/sequence.py +0 -0
  52. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/table.py +0 -0
  53. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/transaction.py +0 -0
  54. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/core/view.py +0 -0
  55. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/exceptions.py +0 -0
  56. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/migrations.py +0 -0
  57. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/__init__.py +0 -0
  58. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/base/__init__.py +0 -0
  59. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/base/initializer.py +0 -0
  60. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/base/operators.py +0 -0
  61. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/base/sql.py +0 -0
  62. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/base/types.py +0 -0
  63. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/mysql/__init__.py +0 -0
  64. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/mysql/operators.py +0 -0
  65. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/mysql/reserved.py +0 -0
  66. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/mysql/sql.py +0 -0
  67. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/mysql/types.py +0 -0
  68. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/postgres/__init__.py +0 -0
  69. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/postgres/operators.py +0 -0
  70. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/postgres/reserved.py +0 -0
  71. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/postgres/sql.py +0 -0
  72. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/postgres/types.py +0 -0
  73. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  74. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlite/operators.py +0 -0
  75. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  76. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlite/sql.py +0 -0
  77. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlite/types.py +0 -0
  78. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  79. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  80. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  81. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  82. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/sqlserver/types.py +0 -0
  83. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/servers/tablehelper.py +0 -0
  84. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/__init__.py +0 -0
  85. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/common_db_test.py +0 -0
  86. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/__init__.py +0 -0
  87. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/common.py +0 -0
  88. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/conftest.py +0 -0
  89. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_column.py +0 -0
  90. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  91. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_database.py +0 -0
  92. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  93. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  94. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  95. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_result.py +0 -0
  96. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_row.py +0 -0
  97. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  98. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  99. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  100. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  101. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  102. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_table.py +0 -0
  103. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  104. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  105. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/sql/__init__.py +0 -0
  106. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/sql/common.py +0 -0
  107. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  108. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  109. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  110. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_db_utils.py +0 -0
  111. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_postgres.py +0 -0
  112. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  113. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  114. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_result_caching.py +0 -0
  115. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  116. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  117. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  118. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  119. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_sql_builder.py +0 -0
  120. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_tablehelper.py +0 -0
  121. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/tests/test_view_helper.py +0 -0
  122. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/db/utils.py +0 -0
  123. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/logging.py +0 -0
  124. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/__init__.py +0 -0
  125. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/conv/__init__.py +0 -0
  126. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/conv/iconv.py +0 -0
  127. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/conv/oconv.py +0 -0
  128. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/db.py +0 -0
  129. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/export.py +0 -0
  130. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/format.py +0 -0
  131. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/mail.py +0 -0
  132. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/merge.py +0 -0
  133. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/pdf.py +0 -0
  134. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/__init__.py +0 -0
  135. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_db.py +0 -0
  136. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_fix.py +0 -0
  137. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_format.py +0 -0
  138. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_iconv.py +0 -0
  139. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_merge.py +0 -0
  140. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_oconv.py +0 -0
  141. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_original_error.py +0 -0
  142. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tests/test_timer.py +0 -0
  143. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/timer.py +0 -0
  144. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/misc/tools.py +0 -0
  145. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/__init__.py +0 -0
  146. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/authorizenet_adapter.py +0 -0
  147. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/authorizenet_mirror.py +0 -0
  148. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/base_adapter.py +0 -0
  149. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/braintree_adapter.py +0 -0
  150. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/braintree_mirror.py +0 -0
  151. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/charge_rules.py +0 -0
  152. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/stripe_adapter.py +0 -0
  153. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity/payment/stripe_mirror.py +0 -0
  154. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  155. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/entry_points.txt +0 -0
  156. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/requires.txt +0 -0
  157. {velocity_python-0.1.74 → velocity_python-0.1.76}/src/velocity_python.egg-info/top_level.txt +0 -0
  158. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_amplify_build.py +0 -0
  159. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_asset_indexing.py +0 -0
  160. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_asset_references.py +0 -0
  161. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_assets_service.py +0 -0
  162. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_async_support.py +0 -0
  163. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_batch_operations.py +0 -0
  164. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_column_tx_arg.py +0 -0
  165. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_concurrency_safety.py +0 -0
  166. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_connection_pool.py +0 -0
  167. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_connection_resilience.py +0 -0
  168. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_context_job_descriptions.py +0 -0
  169. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_db_credentials_ssm_cascade.py +0 -0
  170. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_decorators.py +0 -0
  171. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_dirty_pipeline_fast_path.py +0 -0
  172. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_email_processing.py +0 -0
  173. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_get_cognito_user_provider.py +0 -0
  174. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_http_handler_rollback.py +0 -0
  175. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_iconv_money_to_cents.py +0 -0
  176. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_identifier_injection_guard.py +0 -0
  177. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_json_columns.py +0 -0
  178. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_jsonb_dict_adapter.py +0 -0
  179. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_lambda_handler.py +0 -0
  180. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_lambda_handler_auth.py +0 -0
  181. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_lambda_handler_masquerade.py +0 -0
  182. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_masquerade_grant.py +0 -0
  183. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_mixins_import.py +0 -0
  184. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_n_plus_one.py +0 -0
  185. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_observability.py +0 -0
  186. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_payment_authorizenet_adapter.py +0 -0
  187. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_payment_braintree_adapter.py +0 -0
  188. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_payment_braintree_mirror.py +0 -0
  189. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_payment_profile_sorting.py +0 -0
  190. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_payment_stripe_adapter.py +0 -0
  191. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_pdf.py +0 -0
  192. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_prepared_statements.py +0 -0
  193. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_psycopg3_upgrade.py +0 -0
  194. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_query_cache.py +0 -0
  195. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_restricted_direct_tables.py +0 -0
  196. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_retry_side_effect_guard.py +0 -0
  197. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_return_default_safety.py +0 -0
  198. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_row_batch_update.py +0 -0
  199. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_row_cache_staleness.py +0 -0
  200. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_schema_migrations.py +0 -0
  201. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_security_hardening.py +0 -0
  202. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_server_cursor.py +0 -0
  203. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_single_autocommit_safety.py +0 -0
  204. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_spreadsheet_functions.py +0 -0
  205. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_sqlite_backend.py +0 -0
  206. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_sqs_per_record_transactions.py +0 -0
  207. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_ssm_config.py +0 -0
  208. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_store_user_data.py +0 -0
  209. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  210. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_table_alter.py +0 -0
  211. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_transaction_class_wrapping.py +0 -0
  212. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_transaction_commit_and_ownership.py +0 -0
  213. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_transaction_edge_cases.py +0 -0
  214. {velocity_python-0.1.74 → velocity_python-0.1.76}/tests/test_where_clause_validation.py +0 -0
  215. {velocity_python-0.1.74 → velocity_python-0.1.76}/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.76
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.76"
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.76"
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
 
@@ -0,0 +1,319 @@
1
+ """PostgreSQL database maintenance toolkit.
2
+
3
+ Safe, efficient database-admin operations that velocity's normal transactional
4
+ model can't express. ``CREATE``/``DROP``/``RENAME DATABASE`` and template clones
5
+ must run in **autocommit** and must **not** run while connected to the source or
6
+ destination database. ``DatabaseMaintenance`` connects to a neutral *maintenance*
7
+ database (default ``postgres``) so the source/dest are never the active session,
8
+ runs DDL in autocommit, and terminates other sessions as needed.
9
+
10
+ from velocity.db import maintenance
11
+
12
+ m = maintenance.DatabaseMaintenance() # env (DBHost/DBUser/...), maint db 'postgres'
13
+
14
+ # Server-side template clone -- instant, no dump/restore round-trip:
15
+ m.refresh("caringcent-production", "caringcent-develop") # snapshot dest, then clone source -> dest
16
+ m.snapshot("caringcent-develop") # -> caringcent-develop-snapshot-<ts>
17
+ m.prune_snapshots("caringcent-develop", keep=3) # drop all but the 3 newest snapshots
18
+
19
+ Every database name is identifier-quoted; string-literal comparisons are escaped.
20
+ A configurable ``protected`` set blocks destructive ops (drop / rename / overwrite)
21
+ on critical databases (default: ``caringcent-production``).
22
+
23
+ Snapshot databases are named ``<db>-snapshot-<YYYYMMDD-HHMMSS>`` -- a **sortable**
24
+ timestamp, so listing/pruning by name is chronological.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import datetime
30
+ import os
31
+ from typing import Iterable, List, Optional
32
+
33
+ from velocity.db.servers import postgres
34
+
35
+ SNAPSHOT_INFIX = "-snapshot-"
36
+ TS_FORMAT = "%Y%m%d-%H%M%S" # sortable: lexical order == chronological order
37
+ DEFAULT_PROTECTED = frozenset({"caringcent-production"})
38
+
39
+ _BACKUP_FORMAT_FLAGS = {"custom": "-Fc", "plain": "-Fp", "directory": "-Fd", "tar": "-Ft"}
40
+
41
+
42
+ def quote_identifier(name: str) -> str:
43
+ """Quote a database identifier (doubles embedded double-quotes)."""
44
+ return '"' + str(name).replace('"', '""') + '"'
45
+
46
+
47
+ def quote_literal(value: str) -> str:
48
+ """Quote a string literal (doubles embedded single-quotes)."""
49
+ return "'" + str(value).replace("'", "''") + "'"
50
+
51
+
52
+ def timestamp() -> str:
53
+ return datetime.datetime.now().strftime(TS_FORMAT)
54
+
55
+
56
+ class DatabaseMaintenance:
57
+ """Connection-pooled, autocommit-aware PostgreSQL admin operations."""
58
+
59
+ def __init__(
60
+ self,
61
+ *,
62
+ maintenance_db: str = "postgres",
63
+ config: Optional[dict] = None,
64
+ protected: Iterable[str] = DEFAULT_PROTECTED,
65
+ engine=None,
66
+ ):
67
+ self.maintenance_db = maintenance_db
68
+ self.protected = frozenset(protected or ())
69
+ self._config = dict(config or {})
70
+ # Lazily initialized so construction never opens a connection (keeps the
71
+ # class unit-testable without a live database).
72
+ self._engine = engine
73
+
74
+ # ------------------------------------------------------------------ engine
75
+ def _get_engine(self):
76
+ if self._engine is None:
77
+ cfg = {"dbname": self.maintenance_db}
78
+ cfg.update(self._config)
79
+ self._engine = postgres.initialize(config=cfg)
80
+ return self._engine
81
+
82
+ def _exec(self, *statements: str) -> None:
83
+ """Run DDL/admin statements in autocommit (required for CREATE/DROP/RENAME DATABASE)."""
84
+ engine = self._get_engine()
85
+
86
+ @engine.transaction
87
+ def _run(tx):
88
+ tx.rollback() # discard the implicit transaction opened on checkout
89
+ tx.connection.autocommit = True
90
+ for sql in statements:
91
+ if sql:
92
+ tx.execute(sql)
93
+
94
+ _run()
95
+
96
+ def _query(self, sql: str) -> List[dict]:
97
+ engine = self._get_engine()
98
+
99
+ @engine.transaction
100
+ def _run(tx):
101
+ return list(tx.execute(sql).as_dict())
102
+
103
+ return _run()
104
+
105
+ def _conn_params(self) -> dict:
106
+ return {
107
+ "host": self._config.get("host") or os.environ.get("DBHost"),
108
+ "port": self._config.get("port") or os.environ.get("DBPort"),
109
+ "user": self._config.get("user") or os.environ.get("DBUser"),
110
+ "password": self._config.get("password") or os.environ.get("DBPassword"),
111
+ }
112
+
113
+ # ------------------------------------------------------------------ guards
114
+ def _guard(self, db: str, action: str) -> None:
115
+ if db in self.protected:
116
+ raise PermissionError(
117
+ f"Refusing to {action} protected database {db!r}. "
118
+ "Construct with protected=... to override."
119
+ )
120
+
121
+ # ------------------------------------------------------------------ SQL helpers
122
+ @staticmethod
123
+ def _terminate_sql(db: str) -> str:
124
+ return (
125
+ "SELECT pg_terminate_backend(pid) FROM pg_stat_activity "
126
+ f"WHERE datname = {quote_literal(db)} AND pid <> pg_backend_pid()"
127
+ )
128
+
129
+ # ------------------------------------------------------------------ queries
130
+ def exists(self, db: str) -> bool:
131
+ return bool(
132
+ self._query(f"SELECT 1 AS x FROM pg_database WHERE datname = {quote_literal(db)}")
133
+ )
134
+
135
+ def list_databases(self, *, pattern: Optional[str] = None) -> List[str]:
136
+ where = "WHERE datistemplate = false"
137
+ if pattern:
138
+ where += f" AND datname LIKE {quote_literal(pattern)}"
139
+ rows = self._query(f"SELECT datname FROM pg_database {where} ORDER BY datname")
140
+ return [r["datname"] for r in rows]
141
+
142
+ def list_snapshots(self, db: str) -> List[str]:
143
+ """Snapshot databases for ``db``, oldest first (sortable timestamp suffix)."""
144
+ return sorted(self.list_databases(pattern=f"{db}{SNAPSHOT_INFIX}%"))
145
+
146
+ # ------------------------------------------------------------------ connection control
147
+ def terminate_connections(self, db: str) -> None:
148
+ self._exec(self._terminate_sql(db))
149
+
150
+ # ------------------------------------------------------------------ core ops
151
+ def clone(self, source: str, dest: str, *, drop_existing: bool = False) -> str:
152
+ """``CREATE DATABASE dest WITH TEMPLATE source`` (server-side, near-instant)."""
153
+ self._guard(dest, "overwrite")
154
+ if self.exists(dest):
155
+ if not drop_existing:
156
+ raise ValueError(
157
+ f"Destination {dest!r} already exists. Pass drop_existing=True to replace it."
158
+ )
159
+ self.drop(dest)
160
+ # A template clone requires no other sessions on the source.
161
+ self._exec(
162
+ self._terminate_sql(source),
163
+ f"CREATE DATABASE {quote_identifier(dest)} WITH TEMPLATE {quote_identifier(source)}",
164
+ )
165
+ return dest
166
+
167
+ def snapshot(self, db: str, *, label: Optional[str] = None) -> str:
168
+ """Clone ``db`` to ``<db>-snapshot-<ts>`` (or a custom ``label``)."""
169
+ dest = f"{db}{SNAPSHOT_INFIX}{label or timestamp()}"
170
+ return self.clone(db, dest)
171
+
172
+ def rename(self, old: str, new: str) -> str:
173
+ self._guard(old, "rename")
174
+ self._exec(
175
+ self._terminate_sql(old),
176
+ f"ALTER DATABASE {quote_identifier(old)} RENAME TO {quote_identifier(new)}",
177
+ )
178
+ return new
179
+
180
+ def drop(self, db: str, *, if_exists: bool = True) -> None:
181
+ self._guard(db, "drop")
182
+ clause = "DROP DATABASE IF EXISTS" if if_exists else "DROP DATABASE"
183
+ self._exec(self._terminate_sql(db), f"{clause} {quote_identifier(db)}")
184
+
185
+ def refresh(self, source: str, dest: str, *, keep_snapshot: bool = True) -> str:
186
+ """Replace ``dest`` with a fresh template-clone of ``source``.
187
+
188
+ Safe sequence: terminate ``dest`` sessions -> rename ``dest`` to
189
+ ``dest-snapshot-<ts>`` (or drop it when ``keep_snapshot=False``) -> clone
190
+ ``source`` -> ``dest``. Keeping the snapshot makes the refresh reversible.
191
+ """
192
+ self._guard(dest, "overwrite")
193
+ if self.exists(dest):
194
+ if keep_snapshot:
195
+ self.rename(dest, f"{dest}{SNAPSHOT_INFIX}{timestamp()}")
196
+ else:
197
+ self.drop(dest)
198
+ return self.clone(source, dest)
199
+
200
+ def prune_snapshots(
201
+ self,
202
+ db: str,
203
+ *,
204
+ keep: int = 3,
205
+ older_than_days: Optional[int] = None,
206
+ dry_run: bool = False,
207
+ ) -> List[str]:
208
+ """Drop old snapshot databases for ``db``.
209
+
210
+ With ``older_than_days`` set, drops snapshots whose timestamp is older than
211
+ the cutoff. Otherwise keeps the ``keep`` newest and drops the rest. Returns
212
+ the names dropped (or that would be dropped, when ``dry_run=True``).
213
+ """
214
+ snaps = self.list_snapshots(db) # ascending (oldest first)
215
+ to_drop: List[str] = []
216
+ if older_than_days is not None:
217
+ cutoff = datetime.datetime.now() - datetime.timedelta(days=older_than_days)
218
+ prefix = f"{db}{SNAPSHOT_INFIX}"
219
+ for name in snaps:
220
+ suffix = name[len(prefix):]
221
+ try:
222
+ when = datetime.datetime.strptime(suffix, TS_FORMAT)
223
+ except ValueError:
224
+ continue # unrecognized suffix -> leave it alone
225
+ if when < cutoff:
226
+ to_drop.append(name)
227
+ elif keep >= 0 and len(snaps) > keep:
228
+ to_drop = snaps[: len(snaps) - keep]
229
+
230
+ if not dry_run:
231
+ for name in to_drop:
232
+ self.drop(name)
233
+ return to_drop
234
+
235
+ # ------------------------------------------------------------------ portable backup/restore
236
+ def backup_to_file(
237
+ self, db: str, path: str, *, fmt: str = "custom", extra_args: Iterable[str] = ()
238
+ ) -> str:
239
+ """Portable off-instance backup via ``pg_dump`` (use for cross-server moves;
240
+ prefer ``snapshot``/``clone`` for same-server copies). ``extra_args`` are
241
+ passed through to ``pg_dump`` (e.g. ``("-O",)`` for no-owner)."""
242
+ if fmt not in _BACKUP_FORMAT_FLAGS:
243
+ raise ValueError(f"Unknown fmt {fmt!r}; choose one of {sorted(_BACKUP_FORMAT_FLAGS)}.")
244
+ p = self._conn_params()
245
+ self._run_tool(
246
+ ["pg_dump", "-h", str(p["host"]), "-p", str(p["port"]), "-U", str(p["user"]),
247
+ _BACKUP_FORMAT_FLAGS[fmt], *extra_args, "-f", path, db]
248
+ )
249
+ return path
250
+
251
+ def restore_from_file(
252
+ self,
253
+ path: str,
254
+ dest: str,
255
+ *,
256
+ fmt: str = "custom",
257
+ drop_existing: bool = False,
258
+ extra_args: Iterable[str] = (),
259
+ ) -> str:
260
+ """Restore a ``pg_dump`` file into a freshly created ``dest`` database.
261
+ ``extra_args`` are passed to ``pg_restore``/``psql``."""
262
+ self._guard(dest, "overwrite")
263
+ if self.exists(dest):
264
+ if not drop_existing:
265
+ raise ValueError(
266
+ f"Destination {dest!r} already exists. Pass drop_existing=True to replace it."
267
+ )
268
+ self.drop(dest)
269
+ self._exec(f"CREATE DATABASE {quote_identifier(dest)}")
270
+ p = self._conn_params()
271
+ base = ["-h", str(p["host"]), "-p", str(p["port"]), "-U", str(p["user"])]
272
+ if fmt == "plain":
273
+ self._run_tool(["psql", *base, *extra_args, "-d", dest, "-f", path])
274
+ else:
275
+ self._run_tool(["pg_restore", *base, *extra_args, "-d", dest, path])
276
+ return dest
277
+
278
+ def _run_tool(self, cmd: List[str]) -> None:
279
+ import subprocess
280
+
281
+ env = dict(os.environ)
282
+ password = self._conn_params().get("password")
283
+ if password:
284
+ env["PGPASSWORD"] = str(password)
285
+ subprocess.run(cmd, check=True, env=env, capture_output=True, text=True)
286
+
287
+ # ------------------------------------------------------------------ maintenance
288
+ def vacuum(self, db: str, *, analyze: bool = True, full: bool = False) -> None:
289
+ """``VACUUM`` the target database (autocommit; cannot run in a transaction block)."""
290
+ opts = []
291
+ if full:
292
+ opts.append("FULL")
293
+ if analyze:
294
+ opts.append("ANALYZE")
295
+ stmt = "VACUUM" + (f" ({', '.join(opts)})" if opts else "")
296
+ self._exec_in(db, stmt)
297
+
298
+ def analyze(self, db: str) -> None:
299
+ self._exec_in(db, "ANALYZE")
300
+
301
+ def reindex(self, db: str) -> None:
302
+ """``REINDEX DATABASE`` the target database."""
303
+ self._exec_in(db, f"REINDEX DATABASE {quote_identifier(db)}")
304
+
305
+ def _exec_in(self, db: str, *statements: str) -> None:
306
+ """Run autocommit statements against ``db`` itself (not the maintenance db)."""
307
+ cfg = dict(self._config)
308
+ cfg["dbname"] = db
309
+ engine = postgres.initialize(config=cfg)
310
+
311
+ @engine.transaction
312
+ def _run(tx):
313
+ tx.rollback()
314
+ tx.connection.autocommit = True
315
+ for sql in statements:
316
+ if sql:
317
+ tx.execute(sql)
318
+
319
+ _run()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.74
3
+ Version: 0.1.76
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
@@ -34,6 +34,7 @@ src/velocity/aws/tests/test_lambda_handler_json_serialization.py
34
34
  src/velocity/aws/tests/test_response.py
35
35
  src/velocity/db/__init__.py
36
36
  src/velocity/db/exceptions.py
37
+ src/velocity/db/maintenance.py
37
38
  src/velocity/db/migrations.py
38
39
  src/velocity/db/utils.py
39
40
  src/velocity/db/core/__init__.py
@@ -162,9 +163,11 @@ tests/test_connection_pool.py
162
163
  tests/test_connection_resilience.py
163
164
  tests/test_context_job_descriptions.py
164
165
  tests/test_db_credentials_ssm_cascade.py
166
+ tests/test_db_maintenance.py
165
167
  tests/test_decorators.py
166
168
  tests/test_dirty_pipeline_fast_path.py
167
169
  tests/test_email_processing.py
170
+ tests/test_enqueue_send_failures.py
168
171
  tests/test_get_cognito_user_provider.py
169
172
  tests/test_http_handler_rollback.py
170
173
  tests/test_iconv_money_to_cents.py