velocity-python 0.1.99__tar.gz → 0.1.101__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 (267) hide show
  1. {velocity_python-0.1.99 → velocity_python-0.1.101}/PKG-INFO +1 -1
  2. {velocity_python-0.1.99 → velocity_python-0.1.101}/pyproject.toml +1 -1
  3. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.1.101/src/velocity/aws/blobs.py +91 -0
  5. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/mixins/data_service.py +14 -0
  6. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/stripe_adapter.py +149 -0
  7. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/PKG-INFO +1 -1
  8. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/SOURCES.txt +3 -0
  9. velocity_python-0.1.101/tests/test_blob_store.py +64 -0
  10. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_stripe_adapter.py +139 -0
  11. velocity_python-0.1.101/tests/test_query_missing_table_unlocked.py +98 -0
  12. {velocity_python-0.1.99 → velocity_python-0.1.101}/LICENSE +0 -0
  13. {velocity_python-0.1.99 → velocity_python-0.1.101}/README.md +0 -0
  14. {velocity_python-0.1.99 → velocity_python-0.1.101}/setup.cfg +0 -0
  15. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/__init__.py +0 -0
  16. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/amplify.py +0 -0
  17. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/amplify_build.py +0 -0
  18. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/__init__.py +0 -0
  19. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/backfill.py +0 -0
  20. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/indexing.py +0 -0
  21. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/references.py +0 -0
  22. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/service.py +0 -0
  23. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/assets/usage_index.py +0 -0
  24. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/__init__.py +0 -0
  25. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/config.py +0 -0
  26. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/operations.py +0 -0
  27. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/policies.py +0 -0
  28. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/reconcile.py +0 -0
  29. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/deploy/targets.py +0 -0
  30. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/dirty_pipeline.py +0 -0
  31. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/email.py +0 -0
  32. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/__init__.py +0 -0
  33. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/base_handler.py +0 -0
  34. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/context.py +0 -0
  35. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/context_factory.py +0 -0
  36. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/exceptions.py +0 -0
  37. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  38. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/masquerade.py +0 -0
  39. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  40. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  41. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  42. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/perf.py +0 -0
  43. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/response.py +0 -0
  44. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  45. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/job_results.py +0 -0
  46. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/s3.py +0 -0
  47. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/sms.py +0 -0
  48. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/ssm_config.py +0 -0
  49. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/__init__.py +0 -0
  50. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  51. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  52. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  53. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  54. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  55. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_email.py +0 -0
  56. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  57. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_response.py +0 -0
  58. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/aws/tests/test_sms.py +0 -0
  59. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/__init__.py +0 -0
  60. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/__init__.py +0 -0
  61. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/async_support.py +0 -0
  62. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/column.py +0 -0
  63. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/database.py +0 -0
  64. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/decorators.py +0 -0
  65. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/engine.py +0 -0
  66. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/jsonproxy.py +0 -0
  67. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/result.py +0 -0
  68. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/row.py +0 -0
  69. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/sequence.py +0 -0
  70. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/table.py +0 -0
  71. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/transaction.py +0 -0
  72. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/core/view.py +0 -0
  73. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/exceptions.py +0 -0
  74. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/maintenance.py +0 -0
  75. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/migrations.py +0 -0
  76. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/__init__.py +0 -0
  77. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/base/__init__.py +0 -0
  78. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/base/initializer.py +0 -0
  79. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/base/operators.py +0 -0
  80. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/base/sql.py +0 -0
  81. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/base/types.py +0 -0
  82. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/mysql/__init__.py +0 -0
  83. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/mysql/operators.py +0 -0
  84. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/mysql/reserved.py +0 -0
  85. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/mysql/sql.py +0 -0
  86. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/mysql/types.py +0 -0
  87. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/postgres/__init__.py +0 -0
  88. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/postgres/operators.py +0 -0
  89. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/postgres/reserved.py +0 -0
  90. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/postgres/sql.py +0 -0
  91. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/postgres/types.py +0 -0
  92. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  93. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlite/operators.py +0 -0
  94. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  95. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlite/sql.py +0 -0
  96. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlite/types.py +0 -0
  97. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  98. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  99. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  100. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  101. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/sqlserver/types.py +0 -0
  102. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/servers/tablehelper.py +0 -0
  103. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/__init__.py +0 -0
  104. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/common_db_test.py +0 -0
  105. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/__init__.py +0 -0
  106. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/common.py +0 -0
  107. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/conftest.py +0 -0
  108. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_column.py +0 -0
  109. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  110. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_database.py +0 -0
  111. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  112. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  113. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  114. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_result.py +0 -0
  115. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_row.py +0 -0
  116. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  117. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  118. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  119. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  120. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  121. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_table.py +0 -0
  122. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  123. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  124. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/sql/__init__.py +0 -0
  125. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/sql/common.py +0 -0
  126. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  127. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  128. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  129. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_db_utils.py +0 -0
  130. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_postgres.py +0 -0
  131. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  132. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  133. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_result_caching.py +0 -0
  134. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  135. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  136. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  137. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  138. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_sql_builder.py +0 -0
  139. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_tablehelper.py +0 -0
  140. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/tests/test_view_helper.py +0 -0
  141. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/db/utils.py +0 -0
  142. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/logging.py +0 -0
  143. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/__init__.py +0 -0
  144. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/conv/__init__.py +0 -0
  145. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/conv/iconv.py +0 -0
  146. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/conv/oconv.py +0 -0
  147. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/db.py +0 -0
  148. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/export.py +0 -0
  149. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/format.py +0 -0
  150. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/mail.py +0 -0
  151. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/merge.py +0 -0
  152. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/pdf.py +0 -0
  153. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/__init__.py +0 -0
  154. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_db.py +0 -0
  155. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_fix.py +0 -0
  156. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_format.py +0 -0
  157. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_iconv.py +0 -0
  158. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_mail_send.py +0 -0
  159. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_merge.py +0 -0
  160. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_oconv.py +0 -0
  161. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_original_error.py +0 -0
  162. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tests/test_timer.py +0 -0
  163. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/timer.py +0 -0
  164. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/misc/tools.py +0 -0
  165. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/__init__.py +0 -0
  166. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/core/__init__.py +0 -0
  167. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/core/capabilities.py +0 -0
  168. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/core/store.py +0 -0
  169. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/core/where.py +0 -0
  170. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/exceptions.py +0 -0
  171. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/__init__.py +0 -0
  172. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/base/__init__.py +0 -0
  173. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  174. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  175. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  176. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  177. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  178. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  179. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  180. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  181. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  182. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  183. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  184. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  185. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/__init__.py +0 -0
  186. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/authorizenet_adapter.py +0 -0
  187. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/authorizenet_mirror.py +0 -0
  188. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/base_adapter.py +0 -0
  189. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/braintree_adapter.py +0 -0
  190. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/braintree_mirror.py +0 -0
  191. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/charge_rules.py +0 -0
  192. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity/payment/stripe_mirror.py +0 -0
  193. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  194. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/entry_points.txt +0 -0
  195. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/requires.txt +0 -0
  196. {velocity_python-0.1.99 → velocity_python-0.1.101}/src/velocity_python.egg-info/top_level.txt +0 -0
  197. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_activity_tracking_aborted_tx.py +0 -0
  198. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_amplify_build.py +0 -0
  199. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_asset_indexing.py +0 -0
  200. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_asset_references.py +0 -0
  201. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_assets_service.py +0 -0
  202. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_async_support.py +0 -0
  203. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_batch_operations.py +0 -0
  204. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_column_tx_arg.py +0 -0
  205. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_concurrency_safety.py +0 -0
  206. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_connection_pool.py +0 -0
  207. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_connection_resilience.py +0 -0
  208. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_context_job_descriptions.py +0 -0
  209. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_db_credentials_ssm_cascade.py +0 -0
  210. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_db_maintenance.py +0 -0
  211. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_decorators.py +0 -0
  212. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_delete_hook_fk_blocked.py +0 -0
  213. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_dirty_pipeline_fast_path.py +0 -0
  214. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_email_processing.py +0 -0
  215. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_enqueue_send_failures.py +0 -0
  216. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_get_cognito_user_provider.py +0 -0
  217. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_http_handler_rollback.py +0 -0
  218. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_iconv_money_to_cents.py +0 -0
  219. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_identifier_injection_guard.py +0 -0
  220. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_job_handler.py +0 -0
  221. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_job_results.py +0 -0
  222. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_json_columns.py +0 -0
  223. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_jsonb_dict_adapter.py +0 -0
  224. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_lambda_handler.py +0 -0
  225. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_lambda_handler_auth.py +0 -0
  226. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_lambda_handler_masquerade.py +0 -0
  227. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_masquerade_grant.py +0 -0
  228. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_mixins_import.py +0 -0
  229. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_n_plus_one.py +0 -0
  230. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_dynamodb.py +0 -0
  231. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_memory.py +0 -0
  232. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_mongodb.py +0 -0
  233. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_redis.py +0 -0
  234. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_translation.py +0 -0
  235. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_nosql_where.py +0 -0
  236. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_observability.py +0 -0
  237. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_on_commit.py +0 -0
  238. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_authorizenet_adapter.py +0 -0
  239. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_braintree_adapter.py +0 -0
  240. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_braintree_mirror.py +0 -0
  241. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_mirror_empty_table.py +0 -0
  242. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_payment_profile_sorting.py +0 -0
  243. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_pdf.py +0 -0
  244. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_prepared_statements.py +0 -0
  245. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_psycopg3_upgrade.py +0 -0
  246. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_query_cache.py +0 -0
  247. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_restricted_direct_tables.py +0 -0
  248. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_retry_side_effect_guard.py +0 -0
  249. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_return_default_safety.py +0 -0
  250. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_row_batch_update.py +0 -0
  251. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_row_cache_staleness.py +0 -0
  252. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_schema_migrations.py +0 -0
  253. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_security_hardening.py +0 -0
  254. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_server_cursor.py +0 -0
  255. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_single_autocommit_safety.py +0 -0
  256. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_spreadsheet_functions.py +0 -0
  257. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_sqlite_backend.py +0 -0
  258. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_sqs_per_record_transactions.py +0 -0
  259. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_ssm_config.py +0 -0
  260. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_store_user_data.py +0 -0
  261. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  262. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_table_alter.py +0 -0
  263. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_transaction_class_wrapping.py +0 -0
  264. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_transaction_commit_and_ownership.py +0 -0
  265. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_transaction_edge_cases.py +0 -0
  266. {velocity_python-0.1.99 → velocity_python-0.1.101}/tests/test_where_clause_validation.py +0 -0
  267. {velocity_python-0.1.99 → velocity_python-0.1.101}/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.99
3
+ Version: 0.1.101
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.99"
7
+ version = "0.1.101"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.99"
1
+ __version__ = version = "0.1.101"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,91 @@
1
+ """Content-addressed S3 blob store.
2
+
3
+ For offloading large row payloads (raw emails, documents, exports) out of
4
+ Postgres while the row keeps only the content hash. Keys are derived from
5
+ the sha256 of the content, so storage is idempotent and deduplicating by
6
+ construction: the same bytes always land on the same key, and a re-upload
7
+ of an existing object is skipped.
8
+
9
+ store = BlobStore("my-bucket", prefix="demo/gmail-raw", suffix=".eml")
10
+ sha, key = store.put_bytes(raw) # no-op if already stored
11
+ raw = store.get_bytes(sha) # or get_bytes(key)
12
+ url = store.presigned_get(sha) # hand-off to a browser
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import hashlib
18
+ from typing import Optional, Tuple
19
+
20
+
21
+ class BlobStore:
22
+ def __init__(
23
+ self,
24
+ bucket: str,
25
+ prefix: str = "",
26
+ suffix: str = "",
27
+ region: Optional[str] = None,
28
+ client=None,
29
+ ):
30
+ if not bucket:
31
+ raise ValueError("BlobStore requires a bucket name")
32
+ self.bucket = bucket
33
+ self.prefix = prefix.strip("/")
34
+ self.suffix = suffix
35
+ self.region = region
36
+ self._client = client
37
+
38
+ @property
39
+ def client(self):
40
+ if self._client is None:
41
+ import boto3
42
+
43
+ kwargs = {"region_name": self.region} if self.region else {}
44
+ self._client = boto3.client("s3", **kwargs)
45
+ return self._client
46
+
47
+ def key_for(self, sha256_hex: str) -> str:
48
+ name = f"{sha256_hex}{self.suffix}"
49
+ return f"{self.prefix}/{name}" if self.prefix else name
50
+
51
+ def _resolve_key(self, sha_or_key: str) -> str:
52
+ # A bare 64-hex-char sha resolves through key_for; anything else is
53
+ # treated as a full key (lets callers store either form).
54
+ value = str(sha_or_key)
55
+ if len(value) == 64 and all(c in "0123456789abcdef" for c in value.lower()):
56
+ return self.key_for(value.lower())
57
+ return value
58
+
59
+ def exists(self, sha_or_key: str) -> bool:
60
+ try:
61
+ self.client.head_object(
62
+ Bucket=self.bucket, Key=self._resolve_key(sha_or_key)
63
+ )
64
+ return True
65
+ except Exception:
66
+ return False
67
+
68
+ def put_bytes(self, data: bytes, content_type: Optional[str] = None) -> Tuple[str, str]:
69
+ """Store ``data`` (idempotent); returns (sha256_hex, key)."""
70
+ payload = bytes(data or b"")
71
+ sha = hashlib.sha256(payload).hexdigest()
72
+ key = self.key_for(sha)
73
+ if not self.exists(key):
74
+ params = {"Bucket": self.bucket, "Key": key, "Body": payload}
75
+ if content_type:
76
+ params["ContentType"] = content_type
77
+ self.client.put_object(**params)
78
+ return sha, key
79
+
80
+ def get_bytes(self, sha_or_key: str) -> bytes:
81
+ response = self.client.get_object(
82
+ Bucket=self.bucket, Key=self._resolve_key(sha_or_key)
83
+ )
84
+ return response["Body"].read()
85
+
86
+ def presigned_get(self, sha_or_key: str, expires_seconds: int = 900) -> str:
87
+ return self.client.generate_presigned_url(
88
+ "get_object",
89
+ Params={"Bucket": self.bucket, "Key": self._resolve_key(sha_or_key)},
90
+ ExpiresIn=expires_seconds,
91
+ )
@@ -442,6 +442,20 @@ class DataServiceMixin:
442
442
  if swallowed_error:
443
443
  tx._last_return_default_error = None
444
444
 
445
+ # A missing table under UNLOCKED auto-schema is a legitimate empty
446
+ # state, not an error: velocity creates tables on first write, so a
447
+ # table nothing has written to yet (user_data on a fresh site / for a
448
+ # new user) is simply empty — answer quietly instead of toasting a
449
+ # scary "relation does not exist" at the user. Under a LOCKED schema
450
+ # (production) tables exist only via ensure_schema, so a missing one
451
+ # is a real deploy bug and stays loud.
452
+ if (
453
+ swallowed_error
454
+ and swallowed_error.get("type") == "DbTableMissingError"
455
+ and not tx.engine.schema_locked
456
+ ):
457
+ swallowed_error = None
458
+
445
459
  _file_export_formats = {
446
460
  "excel",
447
461
  "csv",
@@ -853,6 +853,155 @@ class StripeAdapter(PaymentProcessorAdapter):
853
853
  metadata={"charge_id": processor_charge_id},
854
854
  )
855
855
 
856
+ # ========================================================================
857
+ # CHECKOUT SESSIONS (hosted payment page)
858
+ # ========================================================================
859
+
860
+ def create_checkout_session(
861
+ self, tx, session_data: Dict[str, Any]
862
+ ) -> Dict[str, Any]:
863
+ """
864
+ Create a hosted Stripe Checkout Session for a one-off payment.
865
+
866
+ The buyer is redirected to Stripe's hosted page, so the application
867
+ never touches card data (minimal PCI scope). Works on a direct
868
+ account by default; pass ``processor_account_id`` (and optionally
869
+ ``application_fee_amount``) for a Connect destination charge.
870
+
871
+ Args:
872
+ tx: Database transaction (reserved for mirror hooks; may be None)
873
+ session_data: Dictionary containing:
874
+ - amount: Amount in cents (required)
875
+ - name: Line-item name shown on the Stripe page (required)
876
+ - success_url: Redirect after payment (required; include
877
+ ``{CHECKOUT_SESSION_ID}`` to receive the session id back)
878
+ - cancel_url: Redirect when the buyer backs out (required)
879
+ - currency: default 'usd'
880
+ - customer_email: prefills the Stripe page (optional)
881
+ - metadata: dict stamped on session AND payment intent
882
+ - processor_account_id / application_fee_amount: Connect only
883
+ - idempotency_key: collapses double-submits (optional)
884
+
885
+ Returns:
886
+ {"success": True, "session_id", "url", "status", "payment_status"}
887
+ """
888
+ del tx # no mirror writes at create time; nothing has settled yet
889
+ try:
890
+ metadata = dict(session_data.get("metadata") or {})
891
+ metadata.setdefault("environment", self.environment)
892
+ if self.platform:
893
+ metadata.setdefault("platform", str(self.platform))
894
+
895
+ params: Dict[str, Any] = {
896
+ "mode": "payment",
897
+ "line_items": [
898
+ {
899
+ "quantity": 1,
900
+ "price_data": {
901
+ "currency": session_data.get("currency", "usd"),
902
+ "unit_amount": int(session_data["amount"]),
903
+ "product_data": {"name": session_data["name"]},
904
+ },
905
+ }
906
+ ],
907
+ "success_url": session_data["success_url"],
908
+ "cancel_url": session_data["cancel_url"],
909
+ "metadata": metadata,
910
+ "payment_intent_data": {"metadata": metadata},
911
+ "api_key": self._api_key,
912
+ }
913
+ if session_data.get("customer_email"):
914
+ params["customer_email"] = session_data["customer_email"]
915
+ processor_account_id = session_data.get("processor_account_id")
916
+ if processor_account_id:
917
+ transfer: Dict[str, Any] = {
918
+ "transfer_data": {"destination": processor_account_id}
919
+ }
920
+ fee = session_data.get("application_fee_amount")
921
+ if fee is not None:
922
+ transfer["application_fee_amount"] = int(fee)
923
+ params["payment_intent_data"] = {
924
+ **params["payment_intent_data"],
925
+ **transfer,
926
+ }
927
+ idempotency_key = session_data.get("idempotency_key")
928
+ if idempotency_key:
929
+ params["idempotency_key"] = str(idempotency_key)
930
+
931
+ session = stripe.checkout.Session.create(**params)
932
+ return {
933
+ "success": True,
934
+ "session_id": session.id,
935
+ "url": session.url,
936
+ "status": session.status,
937
+ "payment_status": session.payment_status,
938
+ }
939
+ except KeyError as e:
940
+ raise ProcessorError(
941
+ processor="stripe",
942
+ error_code="missing_field",
943
+ error_message=f"checkout session requires {e}",
944
+ metadata={},
945
+ )
946
+ except stripe.error.StripeError as e:
947
+ raise ProcessorError(
948
+ processor="stripe",
949
+ error_code=getattr(e, "code", None) or "unknown",
950
+ error_message=str(e),
951
+ metadata={},
952
+ )
953
+
954
+ def retrieve_checkout_session(self, tx, session_id: str) -> Dict[str, Any]:
955
+ """
956
+ Retrieve a Checkout Session and report whether it is paid.
957
+
958
+ Server-side verification for the success redirect: the client hands
959
+ back only the session id; the truth (``payment_status``) comes from
960
+ Stripe. A settled session's PaymentIntent artifacts are mirrored
961
+ when a transaction is supplied.
962
+
963
+ Returns:
964
+ {"success": True, "session_id", "status", "payment_status",
965
+ "paid": bool, "amount_total", "currency",
966
+ "payment_intent_id", "customer_email", "metadata"}
967
+ """
968
+ try:
969
+ # latest_charge stays an ID on purpose: the mirror helper
970
+ # re-retrieves it with balance_transaction expanded (real fees).
971
+ session = stripe.checkout.Session.retrieve(
972
+ session_id,
973
+ expand=["payment_intent"],
974
+ api_key=self._api_key,
975
+ )
976
+ payment_intent = session.payment_intent
977
+ paid = session.payment_status == "paid"
978
+ if paid and tx is not None and payment_intent is not None:
979
+ self._mirror_payment_intent_artifacts(
980
+ tx,
981
+ payment_intent,
982
+ source="api:stripe_adapter.retrieve_checkout_session",
983
+ )
984
+ details = session.get("customer_details") or {}
985
+ return {
986
+ "success": True,
987
+ "session_id": session.id,
988
+ "status": session.status,
989
+ "payment_status": session.payment_status,
990
+ "paid": paid,
991
+ "amount_total": session.amount_total,
992
+ "currency": session.currency,
993
+ "payment_intent_id": getattr(payment_intent, "id", payment_intent),
994
+ "customer_email": session.get("customer_email") or details.get("email"),
995
+ "metadata": dict(session.metadata or {}),
996
+ }
997
+ except stripe.error.StripeError as e:
998
+ raise ProcessorError(
999
+ processor="stripe",
1000
+ error_code=getattr(e, "code", None) or "unknown",
1001
+ error_message=str(e),
1002
+ metadata={"session_id": session_id},
1003
+ )
1004
+
856
1005
  # ========================================================================
857
1006
  # UTILITY METHODS
858
1007
  # ========================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.99
3
+ Version: 0.1.101
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
@@ -6,6 +6,7 @@ src/velocity/logging.py
6
6
  src/velocity/aws/__init__.py
7
7
  src/velocity/aws/amplify.py
8
8
  src/velocity/aws/amplify_build.py
9
+ src/velocity/aws/blobs.py
9
10
  src/velocity/aws/dirty_pipeline.py
10
11
  src/velocity/aws/email.py
11
12
  src/velocity/aws/job_results.py
@@ -195,6 +196,7 @@ tests/test_asset_references.py
195
196
  tests/test_assets_service.py
196
197
  tests/test_async_support.py
197
198
  tests/test_batch_operations.py
199
+ tests/test_blob_store.py
198
200
  tests/test_column_tx_arg.py
199
201
  tests/test_concurrency_safety.py
200
202
  tests/test_connection_pool.py
@@ -239,6 +241,7 @@ tests/test_pdf.py
239
241
  tests/test_prepared_statements.py
240
242
  tests/test_psycopg3_upgrade.py
241
243
  tests/test_query_cache.py
244
+ tests/test_query_missing_table_unlocked.py
242
245
  tests/test_restricted_direct_tables.py
243
246
  tests/test_retry_side_effect_guard.py
244
247
  tests/test_return_default_safety.py
@@ -0,0 +1,64 @@
1
+ """Content-addressed BlobStore: idempotent puts, sha/key duality."""
2
+
3
+ import hashlib
4
+ import unittest
5
+ from unittest.mock import MagicMock
6
+
7
+ from velocity.aws.blobs import BlobStore
8
+
9
+
10
+ def make_client(existing_keys=()):
11
+ client = MagicMock()
12
+ existing = set(existing_keys)
13
+
14
+ def head_object(Bucket, Key):
15
+ if Key in existing:
16
+ return {}
17
+ raise Exception("404")
18
+
19
+ client.head_object.side_effect = head_object
20
+ return client
21
+
22
+
23
+ PAYLOAD = b"raw email bytes"
24
+ SHA = hashlib.sha256(PAYLOAD).hexdigest()
25
+
26
+
27
+ class TestBlobStore(unittest.TestCase):
28
+ def test_put_is_content_addressed_and_uploads_once(self):
29
+ client = make_client()
30
+ store = BlobStore("bkt", prefix="demo/gmail-raw", suffix=".eml", client=client)
31
+ sha, key = store.put_bytes(PAYLOAD)
32
+ self.assertEqual(sha, SHA)
33
+ self.assertEqual(key, f"demo/gmail-raw/{SHA}.eml")
34
+ client.put_object.assert_called_once()
35
+ self.assertEqual(client.put_object.call_args.kwargs["Key"], key)
36
+
37
+ def test_put_skips_upload_when_object_exists(self):
38
+ key = f"demo/gmail-raw/{SHA}.eml"
39
+ client = make_client(existing_keys=[key])
40
+ store = BlobStore("bkt", prefix="demo/gmail-raw", suffix=".eml", client=client)
41
+ sha, got_key = store.put_bytes(PAYLOAD)
42
+ self.assertEqual((sha, got_key), (SHA, key))
43
+ client.put_object.assert_not_called()
44
+
45
+ def test_get_accepts_bare_sha_or_full_key(self):
46
+ client = make_client()
47
+ client.get_object.return_value = {"Body": MagicMock(read=lambda: PAYLOAD)}
48
+ store = BlobStore("bkt", prefix="p", suffix=".eml", client=client)
49
+ self.assertEqual(store.get_bytes(SHA), PAYLOAD)
50
+ self.assertEqual(
51
+ client.get_object.call_args.kwargs["Key"], f"p/{SHA}.eml"
52
+ )
53
+ store.get_bytes("explicit/key.eml")
54
+ self.assertEqual(
55
+ client.get_object.call_args.kwargs["Key"], "explicit/key.eml"
56
+ )
57
+
58
+ def test_requires_bucket(self):
59
+ with self.assertRaises(ValueError):
60
+ BlobStore("")
61
+
62
+
63
+ if __name__ == "__main__":
64
+ unittest.main()
@@ -597,3 +597,142 @@ def test_create_account_upserts_account_mirror_before_business_sync(
597
597
  },
598
598
  )
599
599
  ]
600
+
601
+
602
+ class FakeCheckoutSession(dict):
603
+ """Attribute+item access like a Stripe object."""
604
+
605
+ def __init__(self, **kwargs):
606
+ super().__init__(**kwargs)
607
+ self.__dict__.update(kwargs)
608
+
609
+
610
+ def test_create_checkout_session_builds_hosted_payment(adapter, monkeypatch):
611
+ captured = {}
612
+
613
+ def fake_create(**params):
614
+ captured.update(params)
615
+ return FakeCheckoutSession(
616
+ id="cs_test_1",
617
+ url="https://checkout.stripe.com/pay/cs_test_1",
618
+ status="open",
619
+ payment_status="unpaid",
620
+ )
621
+
622
+ monkeypatch.setattr(
623
+ "velocity.payment.stripe_adapter.stripe.checkout.Session.create", fake_create
624
+ )
625
+ result = adapter.create_checkout_session(
626
+ None,
627
+ {
628
+ "amount": 12500,
629
+ "name": "2026/2027 School Year Registration",
630
+ "success_url": "https://x/register?session_id={CHECKOUT_SESSION_ID}",
631
+ "cancel_url": "https://x/register",
632
+ "customer_email": "ada@example.com",
633
+ "metadata": {"registration_sys_id": "55"},
634
+ "idempotency_key": "reg-55",
635
+ },
636
+ )
637
+ assert result["success"] is True
638
+ assert result["url"].startswith("https://checkout.stripe.com/")
639
+ assert captured["mode"] == "payment"
640
+ item = captured["line_items"][0]["price_data"]
641
+ assert item["unit_amount"] == 12500
642
+ assert item["product_data"]["name"] == "2026/2027 School Year Registration"
643
+ assert captured["customer_email"] == "ada@example.com"
644
+ # metadata rides on the session AND the payment intent, env stamped
645
+ assert captured["metadata"]["registration_sys_id"] == "55"
646
+ assert captured["metadata"]["environment"] == "test"
647
+ assert captured["payment_intent_data"]["metadata"]["registration_sys_id"] == "55"
648
+ assert captured["idempotency_key"] == "reg-55"
649
+ assert "transfer_data" not in captured["payment_intent_data"] # direct account
650
+
651
+
652
+ def test_create_checkout_session_connect_destination(adapter, monkeypatch):
653
+ captured = {}
654
+ monkeypatch.setattr(
655
+ "velocity.payment.stripe_adapter.stripe.checkout.Session.create",
656
+ lambda **p: captured.update(p)
657
+ or FakeCheckoutSession(
658
+ id="cs", url="u", status="open", payment_status="unpaid"
659
+ ),
660
+ )
661
+ adapter.create_checkout_session(
662
+ None,
663
+ {
664
+ "amount": 1000,
665
+ "name": "x",
666
+ "success_url": "https://x/s",
667
+ "cancel_url": "https://x/c",
668
+ "processor_account_id": "acct_123",
669
+ "application_fee_amount": 250,
670
+ },
671
+ )
672
+ intent_data = captured["payment_intent_data"]
673
+ assert intent_data["transfer_data"] == {"destination": "acct_123"}
674
+ assert intent_data["application_fee_amount"] == 250
675
+
676
+
677
+ def test_create_checkout_session_missing_field_is_processor_error(adapter):
678
+ with pytest.raises(ProcessorError) as err:
679
+ adapter.create_checkout_session(None, {"amount": 100, "name": "x"})
680
+ assert err.value.error_code == "missing_field"
681
+
682
+
683
+ def test_retrieve_checkout_session_verifies_paid_and_mirrors(adapter, monkeypatch):
684
+ intent = SimpleNamespace(id="pi_9", charges=None, latest_charge=None)
685
+ session = FakeCheckoutSession(
686
+ id="cs_test_1",
687
+ status="complete",
688
+ payment_status="paid",
689
+ amount_total=12500,
690
+ currency="usd",
691
+ payment_intent=intent,
692
+ metadata={"registration_sys_id": "55"},
693
+ customer_email=None,
694
+ customer_details={"email": "ada@example.com"},
695
+ )
696
+ monkeypatch.setattr(
697
+ "velocity.payment.stripe_adapter.stripe.checkout.Session.retrieve",
698
+ lambda sid, **kw: session,
699
+ )
700
+ mirrored = {}
701
+ monkeypatch.setattr(
702
+ adapter,
703
+ "_mirror_payment_intent_artifacts",
704
+ lambda tx, pi, source: mirrored.update({"pi": pi.id, "source": source}),
705
+ )
706
+ result = adapter.retrieve_checkout_session(object(), "cs_test_1")
707
+ assert result["paid"] is True
708
+ assert result["amount_total"] == 12500
709
+ assert result["payment_intent_id"] == "pi_9"
710
+ assert result["customer_email"] == "ada@example.com"
711
+ assert result["metadata"] == {"registration_sys_id": "55"}
712
+ assert mirrored["pi"] == "pi_9"
713
+
714
+
715
+ def test_retrieve_checkout_session_unpaid_never_mirrors(adapter, monkeypatch):
716
+ session = FakeCheckoutSession(
717
+ id="cs_test_1",
718
+ status="open",
719
+ payment_status="unpaid",
720
+ amount_total=12500,
721
+ currency="usd",
722
+ payment_intent=None,
723
+ metadata={},
724
+ customer_email=None,
725
+ customer_details=None,
726
+ )
727
+ monkeypatch.setattr(
728
+ "velocity.payment.stripe_adapter.stripe.checkout.Session.retrieve",
729
+ lambda sid, **kw: session,
730
+ )
731
+ monkeypatch.setattr(
732
+ adapter,
733
+ "_mirror_payment_intent_artifacts",
734
+ lambda *a, **k: (_ for _ in ()).throw(AssertionError("must not mirror")),
735
+ )
736
+ result = adapter.retrieve_checkout_session(object(), "cs_test_1")
737
+ assert result["paid"] is False
738
+ assert result["payment_intent_id"] is None
@@ -0,0 +1,98 @@
1
+ """A missing table under UNLOCKED auto-schema is empty, not an error.
2
+
3
+ velocity creates tables on first write, so a table nothing has written to
4
+ yet (``user_data`` on a fresh factory site / for a brand-new user) is
5
+ semantically an empty table. The query path must answer with empty rows and
6
+ NO error toast in that mode — the 2026-09-01 dance.10sp.us first sign-in
7
+ toasted 'relation "user_data" does not exist' at the operator. Under a
8
+ LOCKED schema (production), tables exist only via ensure_schema, so a
9
+ missing one is a real deploy bug and must stay loud.
10
+ """
11
+
12
+ import unittest
13
+ from unittest.mock import MagicMock
14
+
15
+ from velocity.aws.handlers.mixins.data_service import DataServiceMixin
16
+
17
+
18
+ class _Service(DataServiceMixin):
19
+ pass
20
+
21
+
22
+ def _harness(schema_locked):
23
+ service = _Service()
24
+ service._call_rwx_hook = MagicMock()
25
+
26
+ result = MagicMock()
27
+ result.all.return_value = []
28
+ result.sql = None
29
+ result.headers = []
30
+
31
+ table = MagicMock()
32
+ table.select.return_value = result
33
+
34
+ tx = MagicMock()
35
+ tx.table.return_value = table
36
+ tx.engine.schema_locked = schema_locked
37
+ # what @return_default records when the select swallowed 42P01
38
+ tx._last_return_default_error = None
39
+
40
+ def select_with_swallow(**params):
41
+ tx._last_return_default_error = {
42
+ "type": "DbTableMissingError",
43
+ "message": 'relation "user_data" does not exist',
44
+ "function": "velocity.db.core.table.Table.select",
45
+ }
46
+ return result
47
+
48
+ table.select.side_effect = select_with_swallow
49
+
50
+ context = MagicMock()
51
+ return service, tx, context
52
+
53
+
54
+ class MissingTableUnlockedTest(unittest.TestCase):
55
+ def test_unlocked_missing_table_is_quiet_empty(self):
56
+ service, tx, context = _harness(schema_locked=False)
57
+ data = service.query_hook(
58
+ tx, "user_data", {"obj": "user_data", "params": {}}, context
59
+ )
60
+ self.assertEqual(data["rows"], [])
61
+ self.assertNotIn("error", data["config"])
62
+ context.response().toast.assert_not_called()
63
+
64
+ def test_locked_missing_table_stays_loud(self):
65
+ service, tx, context = _harness(schema_locked=True)
66
+ data = service.query_hook(
67
+ tx, "user_data", {"obj": "user_data", "params": {}}, context
68
+ )
69
+ self.assertEqual(data["rows"], [])
70
+ self.assertEqual(data["config"]["error"]["type"], "DbTableMissingError")
71
+ context.response().toast.assert_called()
72
+
73
+ def test_other_swallowed_errors_still_surface_when_unlocked(self):
74
+ service, tx, context = _harness(schema_locked=False)
75
+ tx.table.return_value.select.side_effect = None
76
+
77
+ def swallow_column_error(**params):
78
+ tx._last_return_default_error = {
79
+ "type": "DbColumnMissingError",
80
+ "message": 'column "bogus" does not exist',
81
+ "function": "velocity.db.core.table.Table.select",
82
+ }
83
+ result = MagicMock()
84
+ result.all.return_value = []
85
+ result.sql = None
86
+ result.headers = []
87
+ return result
88
+
89
+ tx.table.return_value.select.side_effect = swallow_column_error
90
+ data = service.query_hook(
91
+ tx, "user_data", {"obj": "user_data", "params": {}}, context
92
+ )
93
+ self.assertEqual(data["config"]["error"]["type"], "DbColumnMissingError")
94
+ context.response().toast.assert_called()
95
+
96
+
97
+ if __name__ == "__main__":
98
+ unittest.main()