velocity-python 0.1.78__tar.gz → 0.1.80__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 (217) hide show
  1. {velocity_python-0.1.78 → velocity_python-0.1.80}/PKG-INFO +1 -1
  2. {velocity_python-0.1.78 → velocity_python-0.1.80}/pyproject.toml +1 -1
  3. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/mixins/web_handler.py +34 -2
  5. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/transaction.py +34 -0
  6. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/braintree_mirror.py +4 -1
  7. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/stripe_mirror.py +8 -2
  8. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/PKG-INFO +1 -1
  9. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  10. velocity_python-0.1.80/tests/test_activity_tracking_aborted_tx.py +116 -0
  11. velocity_python-0.1.80/tests/test_payment_mirror_empty_table.py +58 -0
  12. {velocity_python-0.1.78 → velocity_python-0.1.80}/LICENSE +0 -0
  13. {velocity_python-0.1.78 → velocity_python-0.1.80}/README.md +0 -0
  14. {velocity_python-0.1.78 → velocity_python-0.1.80}/setup.cfg +0 -0
  15. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/__init__.py +0 -0
  16. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/amplify.py +0 -0
  17. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/amplify_build.py +0 -0
  18. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/__init__.py +0 -0
  19. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/backfill.py +0 -0
  20. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/indexing.py +0 -0
  21. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/references.py +0 -0
  22. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/service.py +0 -0
  23. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/assets/usage_index.py +0 -0
  24. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/dirty_pipeline.py +0 -0
  25. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/__init__.py +0 -0
  26. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/base_handler.py +0 -0
  27. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/context.py +0 -0
  28. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/context_factory.py +0 -0
  29. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/exceptions.py +0 -0
  30. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  31. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/masquerade.py +0 -0
  32. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  33. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  34. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/perf.py +0 -0
  35. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/response.py +0 -0
  36. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  37. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/s3.py +0 -0
  38. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/ssm_config.py +0 -0
  39. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/tests/__init__.py +0 -0
  40. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  41. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  42. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/aws/tests/test_response.py +0 -0
  43. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/__init__.py +0 -0
  44. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/__init__.py +0 -0
  45. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/async_support.py +0 -0
  46. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/column.py +0 -0
  47. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/database.py +0 -0
  48. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/decorators.py +0 -0
  49. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/engine.py +0 -0
  50. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/jsonproxy.py +0 -0
  51. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/result.py +0 -0
  52. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/row.py +0 -0
  53. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/sequence.py +0 -0
  54. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/table.py +0 -0
  55. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/core/view.py +0 -0
  56. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/exceptions.py +0 -0
  57. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/maintenance.py +0 -0
  58. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/migrations.py +0 -0
  59. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/__init__.py +0 -0
  60. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/base/__init__.py +0 -0
  61. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/base/initializer.py +0 -0
  62. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/base/operators.py +0 -0
  63. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/base/sql.py +0 -0
  64. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/base/types.py +0 -0
  65. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/mysql/__init__.py +0 -0
  66. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/mysql/operators.py +0 -0
  67. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/mysql/reserved.py +0 -0
  68. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/mysql/sql.py +0 -0
  69. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/mysql/types.py +0 -0
  70. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/postgres/__init__.py +0 -0
  71. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/postgres/operators.py +0 -0
  72. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/postgres/reserved.py +0 -0
  73. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/postgres/sql.py +0 -0
  74. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/postgres/types.py +0 -0
  75. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  76. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlite/operators.py +0 -0
  77. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  78. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlite/sql.py +0 -0
  79. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlite/types.py +0 -0
  80. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  81. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  82. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  83. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  84. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/sqlserver/types.py +0 -0
  85. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/servers/tablehelper.py +0 -0
  86. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/__init__.py +0 -0
  87. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/common_db_test.py +0 -0
  88. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/__init__.py +0 -0
  89. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/common.py +0 -0
  90. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/conftest.py +0 -0
  91. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_column.py +0 -0
  92. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  93. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_database.py +0 -0
  94. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  95. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  96. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  97. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_result.py +0 -0
  98. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_row.py +0 -0
  99. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  100. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  101. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  102. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  103. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  104. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_table.py +0 -0
  105. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  106. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  107. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/sql/__init__.py +0 -0
  108. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/sql/common.py +0 -0
  109. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  110. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  111. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  112. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_db_utils.py +0 -0
  113. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_postgres.py +0 -0
  114. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  115. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  116. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_result_caching.py +0 -0
  117. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  118. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  119. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  120. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  121. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_sql_builder.py +0 -0
  122. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_tablehelper.py +0 -0
  123. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/tests/test_view_helper.py +0 -0
  124. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/db/utils.py +0 -0
  125. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/logging.py +0 -0
  126. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/__init__.py +0 -0
  127. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/conv/__init__.py +0 -0
  128. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/conv/iconv.py +0 -0
  129. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/conv/oconv.py +0 -0
  130. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/db.py +0 -0
  131. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/export.py +0 -0
  132. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/format.py +0 -0
  133. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/mail.py +0 -0
  134. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/merge.py +0 -0
  135. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/pdf.py +0 -0
  136. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/__init__.py +0 -0
  137. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_db.py +0 -0
  138. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_fix.py +0 -0
  139. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_format.py +0 -0
  140. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_iconv.py +0 -0
  141. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_merge.py +0 -0
  142. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_oconv.py +0 -0
  143. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_original_error.py +0 -0
  144. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tests/test_timer.py +0 -0
  145. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/timer.py +0 -0
  146. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/misc/tools.py +0 -0
  147. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/__init__.py +0 -0
  148. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/authorizenet_adapter.py +0 -0
  149. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/authorizenet_mirror.py +0 -0
  150. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/base_adapter.py +0 -0
  151. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/braintree_adapter.py +0 -0
  152. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/charge_rules.py +0 -0
  153. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity/payment/stripe_adapter.py +0 -0
  154. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  155. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/entry_points.txt +0 -0
  156. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/requires.txt +0 -0
  157. {velocity_python-0.1.78 → velocity_python-0.1.80}/src/velocity_python.egg-info/top_level.txt +0 -0
  158. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_amplify_build.py +0 -0
  159. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_asset_indexing.py +0 -0
  160. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_asset_references.py +0 -0
  161. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_assets_service.py +0 -0
  162. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_async_support.py +0 -0
  163. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_batch_operations.py +0 -0
  164. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_column_tx_arg.py +0 -0
  165. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_concurrency_safety.py +0 -0
  166. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_connection_pool.py +0 -0
  167. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_connection_resilience.py +0 -0
  168. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_context_job_descriptions.py +0 -0
  169. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_db_credentials_ssm_cascade.py +0 -0
  170. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_db_maintenance.py +0 -0
  171. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_decorators.py +0 -0
  172. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_dirty_pipeline_fast_path.py +0 -0
  173. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_email_processing.py +0 -0
  174. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_enqueue_send_failures.py +0 -0
  175. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_get_cognito_user_provider.py +0 -0
  176. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_http_handler_rollback.py +0 -0
  177. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_iconv_money_to_cents.py +0 -0
  178. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_identifier_injection_guard.py +0 -0
  179. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_json_columns.py +0 -0
  180. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_jsonb_dict_adapter.py +0 -0
  181. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_lambda_handler.py +0 -0
  182. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_lambda_handler_auth.py +0 -0
  183. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_lambda_handler_masquerade.py +0 -0
  184. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_masquerade_grant.py +0 -0
  185. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_mixins_import.py +0 -0
  186. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_n_plus_one.py +0 -0
  187. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_observability.py +0 -0
  188. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_payment_authorizenet_adapter.py +0 -0
  189. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_payment_braintree_adapter.py +0 -0
  190. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_payment_braintree_mirror.py +0 -0
  191. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_payment_profile_sorting.py +0 -0
  192. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_payment_stripe_adapter.py +0 -0
  193. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_pdf.py +0 -0
  194. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_prepared_statements.py +0 -0
  195. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_psycopg3_upgrade.py +0 -0
  196. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_query_cache.py +0 -0
  197. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_restricted_direct_tables.py +0 -0
  198. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_retry_side_effect_guard.py +0 -0
  199. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_return_default_safety.py +0 -0
  200. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_row_batch_update.py +0 -0
  201. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_row_cache_staleness.py +0 -0
  202. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_schema_migrations.py +0 -0
  203. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_security_hardening.py +0 -0
  204. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_server_cursor.py +0 -0
  205. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_single_autocommit_safety.py +0 -0
  206. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_spreadsheet_functions.py +0 -0
  207. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_sqlite_backend.py +0 -0
  208. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_sqs_per_record_transactions.py +0 -0
  209. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_ssm_config.py +0 -0
  210. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_store_user_data.py +0 -0
  211. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  212. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_table_alter.py +0 -0
  213. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_transaction_class_wrapping.py +0 -0
  214. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_transaction_commit_and_ownership.py +0 -0
  215. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_transaction_edge_cases.py +0 -0
  216. {velocity_python-0.1.78 → velocity_python-0.1.80}/tests/test_where_clause_validation.py +0 -0
  217. {velocity_python-0.1.78 → velocity_python-0.1.80}/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.78
3
+ Version: 0.1.80
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.78"
7
+ version = "0.1.80"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.78"
1
+ __version__ = version = "0.1.80"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -522,8 +522,40 @@ Request Details:
522
522
  self.track_activity_start(tx, context)
523
523
 
524
524
  def _enhanced_after_action(self, tx, context):
525
- """Enhanced afterAction that adds activity tracking"""
526
- self.track_activity_success(tx, context)
525
+ """Enhanced afterAction that adds activity tracking.
526
+
527
+ Activity tracking is bookkeeping — it must never convert a completed
528
+ action into a 500. Two guards:
529
+
530
+ * If the transaction is already aborted, the action caught a DB error and
531
+ returned a "success" response WITHOUT rolling back to a savepoint. The
532
+ tracking UPDATE would then fail at ``SAVEPOINT`` with "current
533
+ transaction is aborted", masking the real error (prod incident
534
+ 2026-07-01). Skip the write and log loudly — the swallow-without-
535
+ savepoint is the actual bug; wrap the fallible statement in
536
+ ``with tx.savepoint():``.
537
+ * Any other failure of the tracking write is caught and logged rather than
538
+ propagated, so bookkeeping can't fail an otherwise-successful request.
539
+ """
540
+ try:
541
+ if getattr(tx, "is_aborted", None) and tx.is_aborted():
542
+ logger.error(
543
+ "Skipping success activity tracking: transaction is aborted "
544
+ "after a 'successful' action (action=%s handler=%s). An earlier "
545
+ "DB error was caught without rolling back to a savepoint — wrap "
546
+ "the fallible statement in `with tx.savepoint():`.",
547
+ context.action(),
548
+ self.__class__.__name__,
549
+ )
550
+ else:
551
+ self.track_activity_success(tx, context)
552
+ except Exception:
553
+ logger.exception(
554
+ "Success activity tracking failed (action=%s handler=%s); "
555
+ "continuing without it.",
556
+ context.action(),
557
+ self.__class__.__name__,
558
+ )
527
559
  if self._perf_timing_enabled:
528
560
  elapsed_ms = context.perf.log("web_handler action total")
529
561
  if elapsed_ms is not None:
@@ -577,6 +577,40 @@ class Transaction:
577
577
  # Transaction is clean again — no uncommitted work pending.
578
578
  self._in_progress = False
579
579
 
580
+ def is_aborted(self):
581
+ """Return True if the connection is in a failed/aborted transaction.
582
+
583
+ After a statement errors on PostgreSQL, the whole transaction is poisoned:
584
+ every subsequent statement — *including* ``SAVEPOINT`` — fails with
585
+ "current transaction is aborted, commands ignored until end of transaction
586
+ block" until a ``ROLLBACK`` (or rollback to a savepoint taken *before* the
587
+ error). Because even ``SAVEPOINT`` fails in this state, a ``tx.savepoint()``
588
+ wrapper cannot rescue an already-poisoned transaction — callers that want to
589
+ run bookkeeping after a fallible step should check this first and skip (and
590
+ fix the swallow-without-savepoint upstream).
591
+
592
+ Best-effort and driver-specific: returns False when it cannot be determined
593
+ (no open connection, or a driver without a transaction-status concept). For
594
+ psycopg (v2/v3) this maps to libpq ``PQTRANS_INERROR`` (numeric 3).
595
+ """
596
+ conn = self.connection
597
+ if conn is None:
598
+ return False
599
+ try:
600
+ status = conn.info.transaction_status
601
+ except Exception:
602
+ return False
603
+ if status is None:
604
+ return False
605
+ # psycopg3: pq.TransactionStatus.INERROR (IntEnum, name 'INERROR', value 3).
606
+ # Compare by name and by the stable libpq numeric to stay driver-robust.
607
+ try:
608
+ if getattr(status, "name", None) == "INERROR":
609
+ return True
610
+ return int(status) == 3
611
+ except Exception:
612
+ return False
613
+
580
614
  def create_savepoint(self, sp=None, cursor=None):
581
615
  """
582
616
  Creates a savepoint named `sp`. If none given, uses a random name.
@@ -114,7 +114,10 @@ def _filtered_payload_for_table(table, payload: Dict[str, Any]) -> Dict[str, Any
114
114
 
115
115
  def _upsert(tx, table_name: str, payload: Dict[str, Any], key: Dict[str, Any]):
116
116
  table = _table(tx, table_name)
117
- if not table:
117
+ # `is None`, NOT falsy: a velocity Table's truthiness is its row count
118
+ # (__len__), so `if not table` is True for an EMPTY table -- which would
119
+ # silently skip the first write to an empty mirror table (chicken-and-egg).
120
+ if table is None:
118
121
  return None
119
122
  payload = _filtered_payload_for_table(table, payload)
120
123
  table.upsert(payload, key)
@@ -50,7 +50,10 @@ def _table(tx, table_name: str):
50
50
 
51
51
  def _upsert(tx, table_name: str, payload: Dict[str, Any], key: Dict[str, Any]):
52
52
  table = _table(tx, table_name)
53
- if not table:
53
+ # `is None`, NOT falsy: a velocity Table's truthiness is its row count
54
+ # (__len__), so `if not table` is True for an EMPTY table -- which would
55
+ # silently skip the first write to any empty mirror table (chicken-and-egg).
56
+ if table is None:
54
57
  return None
55
58
  table.upsert(payload, key)
56
59
  return payload
@@ -99,7 +102,10 @@ def sync_customer_from_stripe_account(tx, account: Any, client_id: Any):
99
102
  if not client_id:
100
103
  return None
101
104
  table = _table(tx, "customers")
102
- if not table:
105
+ # `is None`, NOT falsy: a velocity Table's truthiness is its row count
106
+ # (__len__), so `if not table` is True for an EMPTY table -- which would
107
+ # silently skip the first write to any empty mirror table (chicken-and-egg).
108
+ if table is None:
103
109
  return None
104
110
  row = table.find({"client_id": client_id})
105
111
  if not row:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.78
3
+ Version: 0.1.80
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
@@ -151,6 +151,7 @@ src/velocity_python.egg-info/dependency_links.txt
151
151
  src/velocity_python.egg-info/entry_points.txt
152
152
  src/velocity_python.egg-info/requires.txt
153
153
  src/velocity_python.egg-info/top_level.txt
154
+ tests/test_activity_tracking_aborted_tx.py
154
155
  tests/test_amplify_build.py
155
156
  tests/test_asset_indexing.py
156
157
  tests/test_asset_references.py
@@ -184,6 +185,7 @@ tests/test_observability.py
184
185
  tests/test_payment_authorizenet_adapter.py
185
186
  tests/test_payment_braintree_adapter.py
186
187
  tests/test_payment_braintree_mirror.py
188
+ tests/test_payment_mirror_empty_table.py
187
189
  tests/test_payment_profile_sorting.py
188
190
  tests/test_payment_stripe_adapter.py
189
191
  tests/test_pdf.py
@@ -0,0 +1,116 @@
1
+ """Bookkeeping must never turn a completed action into a 500.
2
+
3
+ If an action catches a DB error and returns a "success" response WITHOUT rolling
4
+ back to a savepoint, the transaction stays aborted. velocity's afterAction
5
+ activity-tracking UPDATE would then fail at SAVEPOINT with "current transaction is
6
+ aborted", masking the real error and producing an unhandled 500 (prod incident
7
+ 2026-07-01, BackOfficeData BillingDashboard). These tests pin:
8
+
9
+ * Transaction.is_aborted() detects the poisoned state.
10
+ * _enhanced_after_action skips the tracking write when aborted (and never
11
+ raises), and still tracks on a healthy transaction.
12
+ """
13
+
14
+ import unittest
15
+ from unittest.mock import MagicMock
16
+
17
+ from velocity.db.core.transaction import Transaction
18
+ from velocity.aws.handlers.mixins.web_handler import WebHandler
19
+
20
+
21
+ class IsAbortedTest(unittest.TestCase):
22
+ def _tx_with_status(self, status):
23
+ tx = Transaction.__new__(Transaction)
24
+ conn = MagicMock()
25
+ conn.info.transaction_status = status
26
+ tx.connection = conn
27
+ return tx
28
+
29
+ def test_no_connection_is_not_aborted(self):
30
+ tx = Transaction.__new__(Transaction)
31
+ tx.connection = None
32
+ self.assertFalse(tx.is_aborted())
33
+
34
+ def test_inerror_by_name_is_aborted(self):
35
+ status = MagicMock()
36
+ status.name = "INERROR"
37
+ self.assertTrue(self._tx_with_status(status).is_aborted())
38
+
39
+ def test_inerror_by_numeric_is_aborted(self):
40
+ # A driver enum member whose .name isn't 'INERROR' but int() == 3.
41
+ class _Status:
42
+ name = "PQTRANS_INERROR"
43
+
44
+ def __int__(self):
45
+ return 3
46
+
47
+ self.assertTrue(self._tx_with_status(_Status()).is_aborted())
48
+
49
+ def test_intrans_is_not_aborted(self):
50
+ status = MagicMock()
51
+ status.name = "INTRANS"
52
+ status.__int__ = lambda self: 2
53
+ self.assertFalse(self._tx_with_status(status).is_aborted())
54
+
55
+ def test_status_probe_failure_is_not_aborted(self):
56
+ tx = Transaction.__new__(Transaction)
57
+ conn = MagicMock()
58
+ type(conn).info = property(lambda self: (_ for _ in ()).throw(RuntimeError()))
59
+ tx.connection = conn
60
+ self.assertFalse(tx.is_aborted())
61
+
62
+
63
+ class _Handler(WebHandler):
64
+ """Minimal harness exercising the real _enhanced_after_action."""
65
+
66
+ def __init__(self):
67
+ self._perf_timing_enabled = False
68
+ self.track_activity_success = MagicMock()
69
+
70
+
71
+ def _context():
72
+ ctx = MagicMock()
73
+ ctx.action.return_value = "query"
74
+ return ctx
75
+
76
+
77
+ class EnhancedAfterActionGuardTest(unittest.TestCase):
78
+ def test_skips_tracking_when_tx_aborted(self):
79
+ handler = _Handler()
80
+ tx = MagicMock()
81
+ tx.is_aborted.return_value = True
82
+
83
+ handler._enhanced_after_action(tx, _context()) # must not raise
84
+
85
+ handler.track_activity_success.assert_not_called()
86
+
87
+ def test_tracks_when_tx_healthy(self):
88
+ handler = _Handler()
89
+ tx = MagicMock()
90
+ tx.is_aborted.return_value = False
91
+
92
+ handler._enhanced_after_action(tx, _context())
93
+
94
+ handler.track_activity_success.assert_called_once()
95
+
96
+ def test_tracking_failure_is_swallowed(self):
97
+ handler = _Handler()
98
+ handler.track_activity_success.side_effect = RuntimeError("boom")
99
+ tx = MagicMock()
100
+ tx.is_aborted.return_value = False
101
+
102
+ # A failing bookkeeping write must not propagate out of afterAction.
103
+ handler._enhanced_after_action(tx, _context())
104
+
105
+ def test_missing_is_aborted_falls_back_to_tracking(self):
106
+ # Older Transaction without is_aborted() (getattr guard) still tracks.
107
+ handler = _Handler()
108
+ tx = MagicMock(spec=[]) # no is_aborted attribute
109
+
110
+ handler._enhanced_after_action(tx, _context())
111
+
112
+ handler.track_activity_success.assert_called_once()
113
+
114
+
115
+ if __name__ == "__main__":
116
+ unittest.main()
@@ -0,0 +1,58 @@
1
+ """Regression: mirror `_upsert` must write to an EMPTY table.
2
+
3
+ A velocity ``Table``'s truthiness is its row count (``__len__``), so an empty
4
+ table is falsy. The mirror guards must therefore test ``table is None``, not
5
+ ``if not table`` -- otherwise the FIRST write to any empty mirror table silently
6
+ no-ops (chicken-and-egg: the table stays empty forever, so it never becomes
7
+ truthy and never gets written). This bit the Stripe client-account sync: an
8
+ empty ``stripe_accounts`` / ``client_payment_accounts`` could never be seeded.
9
+
10
+ The prior FakeTable in the mirror tests did not implement ``__len__``, so it was
11
+ always truthy and never exercised this path.
12
+ """
13
+ from velocity.payment import braintree_mirror, stripe_mirror
14
+
15
+
16
+ class EmptyTable:
17
+ """Models a real velocity Table whose truthiness is its row count: 0 -> falsy."""
18
+
19
+ def __init__(self):
20
+ self.upserts = []
21
+
22
+ def __len__(self):
23
+ return 0 # empty table -> bool(table) is False
24
+
25
+ def exists(self):
26
+ return True
27
+
28
+ def sys_columns(self):
29
+ return ["stripe_id", "client_id", "id", "amount", "status"]
30
+
31
+ def upsert(self, payload, key):
32
+ self.upserts.append((payload, key))
33
+
34
+
35
+ class FakeTx:
36
+ def __init__(self, table):
37
+ self._table = table
38
+
39
+ def table(self, _name):
40
+ return self._table
41
+
42
+
43
+ def test_stripe_mirror_upsert_writes_to_empty_table():
44
+ t = EmptyTable()
45
+ result = stripe_mirror._upsert(
46
+ FakeTx(t), "stripe_accounts", {"stripe_id": "acct_x"}, {"stripe_id": "acct_x"}
47
+ )
48
+ assert result is not None, "write to an empty table silently no-op'd"
49
+ assert len(t.upserts) == 1
50
+
51
+
52
+ def test_braintree_mirror_upsert_writes_to_empty_table():
53
+ t = EmptyTable()
54
+ result = braintree_mirror._upsert(
55
+ FakeTx(t), "braintree_transaction", {"id": "txn_x", "amount": "1.00"}, {"id": "txn_x"}
56
+ )
57
+ assert result is not None, "write to an empty table silently no-op'd"
58
+ assert len(t.upserts) == 1