velocity-python 0.1.27__tar.gz → 0.1.29__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 (188) hide show
  1. {velocity_python-0.1.27/src/velocity_python.egg-info → velocity_python-0.1.29}/PKG-INFO +5 -1
  2. {velocity_python-0.1.27 → velocity_python-0.1.29}/pyproject.toml +7 -1
  3. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/mixins/data_service.py +165 -6
  5. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/authorizenet_adapter.py +185 -14
  6. velocity_python-0.1.29/src/velocity/payment/authorizenet_mirror.py +211 -0
  7. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/braintree_adapter.py +74 -28
  8. velocity_python-0.1.29/src/velocity/payment/braintree_mirror.py +210 -0
  9. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/stripe_adapter.py +129 -40
  10. velocity_python-0.1.29/src/velocity/payment/stripe_mirror.py +310 -0
  11. {velocity_python-0.1.27 → velocity_python-0.1.29/src/velocity_python.egg-info}/PKG-INFO +5 -1
  12. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity_python.egg-info/SOURCES.txt +4 -0
  13. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity_python.egg-info/requires.txt +6 -0
  14. velocity_python-0.1.29/tests/test_payment_authorizenet_adapter.py +173 -0
  15. velocity_python-0.1.29/tests/test_payment_braintree_adapter.py +182 -0
  16. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_payment_stripe_adapter.py +175 -1
  17. velocity_python-0.1.27/tests/test_payment_braintree_adapter.py +0 -77
  18. {velocity_python-0.1.27 → velocity_python-0.1.29}/LICENSE +0 -0
  19. {velocity_python-0.1.27 → velocity_python-0.1.29}/README.md +0 -0
  20. {velocity_python-0.1.27 → velocity_python-0.1.29}/setup.cfg +0 -0
  21. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/__init__.py +0 -0
  22. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/amplify.py +0 -0
  23. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/amplify_build.py +0 -0
  24. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/__init__.py +0 -0
  25. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/backfill.py +0 -0
  26. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/indexing.py +0 -0
  27. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/references.py +0 -0
  28. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/service.py +0 -0
  29. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/assets/usage_index.py +0 -0
  30. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/dirty_pipeline.py +0 -0
  31. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/__init__.py +0 -0
  32. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/base_handler.py +0 -0
  33. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/context.py +0 -0
  34. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/context_factory.py +0 -0
  35. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/exceptions.py +0 -0
  36. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  37. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  38. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  39. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/perf.py +0 -0
  40. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/response.py +0 -0
  41. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  42. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/s3.py +0 -0
  43. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/ssm_config.py +0 -0
  44. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/tests/__init__.py +0 -0
  45. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  46. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  47. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/aws/tests/test_response.py +0 -0
  48. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/__init__.py +0 -0
  49. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/__init__.py +0 -0
  50. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/async_support.py +0 -0
  51. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/column.py +0 -0
  52. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/database.py +0 -0
  53. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/decorators.py +0 -0
  54. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/engine.py +0 -0
  55. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/result.py +0 -0
  56. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/row.py +0 -0
  57. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/sequence.py +0 -0
  58. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/table.py +0 -0
  59. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/transaction.py +0 -0
  60. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/core/view.py +0 -0
  61. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/exceptions.py +0 -0
  62. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/migrations.py +0 -0
  63. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/__init__.py +0 -0
  64. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/base/__init__.py +0 -0
  65. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/base/initializer.py +0 -0
  66. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/base/operators.py +0 -0
  67. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/base/sql.py +0 -0
  68. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/base/types.py +0 -0
  69. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/mysql/__init__.py +0 -0
  70. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/mysql/operators.py +0 -0
  71. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/mysql/reserved.py +0 -0
  72. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/mysql/sql.py +0 -0
  73. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/mysql/types.py +0 -0
  74. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/postgres/__init__.py +0 -0
  75. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/postgres/operators.py +0 -0
  76. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/postgres/reserved.py +0 -0
  77. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/postgres/sql.py +0 -0
  78. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/postgres/types.py +0 -0
  79. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  80. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlite/operators.py +0 -0
  81. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  82. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlite/sql.py +0 -0
  83. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlite/types.py +0 -0
  84. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  85. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  86. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  87. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  88. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/sqlserver/types.py +0 -0
  89. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/servers/tablehelper.py +0 -0
  90. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/__init__.py +0 -0
  91. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/common_db_test.py +0 -0
  92. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/__init__.py +0 -0
  93. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/common.py +0 -0
  94. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_column.py +0 -0
  95. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  96. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_database.py +0 -0
  97. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  98. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  99. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  100. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_result.py +0 -0
  101. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_row.py +0 -0
  102. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  103. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  104. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  105. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  106. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  107. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_table.py +0 -0
  108. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  109. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  110. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/sql/__init__.py +0 -0
  111. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/sql/common.py +0 -0
  112. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  113. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  114. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  115. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_db_utils.py +0 -0
  116. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_postgres.py +0 -0
  117. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  118. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  119. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_result_caching.py +0 -0
  120. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  121. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  122. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  123. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  124. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_sql_builder.py +0 -0
  125. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_tablehelper.py +0 -0
  126. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/tests/test_view_helper.py +0 -0
  127. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/db/utils.py +0 -0
  128. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/logging.py +0 -0
  129. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/__init__.py +0 -0
  130. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/conv/__init__.py +0 -0
  131. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/conv/iconv.py +0 -0
  132. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/conv/oconv.py +0 -0
  133. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/db.py +0 -0
  134. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/export.py +0 -0
  135. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/format.py +0 -0
  136. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/mail.py +0 -0
  137. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/merge.py +0 -0
  138. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/pdf.py +0 -0
  139. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/__init__.py +0 -0
  140. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_db.py +0 -0
  141. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_fix.py +0 -0
  142. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_format.py +0 -0
  143. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_iconv.py +0 -0
  144. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_merge.py +0 -0
  145. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_oconv.py +0 -0
  146. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_original_error.py +0 -0
  147. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tests/test_timer.py +0 -0
  148. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/timer.py +0 -0
  149. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/misc/tools.py +0 -0
  150. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/__init__.py +0 -0
  151. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/base_adapter.py +0 -0
  152. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity/payment/charge_rules.py +0 -0
  153. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  154. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity_python.egg-info/entry_points.txt +0 -0
  155. {velocity_python-0.1.27 → velocity_python-0.1.29}/src/velocity_python.egg-info/top_level.txt +0 -0
  156. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_amplify_build.py +0 -0
  157. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_asset_indexing.py +0 -0
  158. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_asset_references.py +0 -0
  159. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_assets_service.py +0 -0
  160. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_async_support.py +0 -0
  161. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_batch_operations.py +0 -0
  162. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_concurrency_safety.py +0 -0
  163. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_connection_pool.py +0 -0
  164. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_connection_resilience.py +0 -0
  165. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_decorators.py +0 -0
  166. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_dirty_pipeline_fast_path.py +0 -0
  167. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_email_processing.py +0 -0
  168. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_iconv_money_to_cents.py +0 -0
  169. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_lambda_handler.py +0 -0
  170. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_lambda_handler_auth.py +0 -0
  171. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_mixins_import.py +0 -0
  172. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_n_plus_one.py +0 -0
  173. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_observability.py +0 -0
  174. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_payment_profile_sorting.py +0 -0
  175. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_pdf.py +0 -0
  176. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_prepared_statements.py +0 -0
  177. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_psycopg3_upgrade.py +0 -0
  178. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_query_cache.py +0 -0
  179. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_row_batch_update.py +0 -0
  180. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_row_cache_staleness.py +0 -0
  181. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_row_dirty_tracking.py +0 -0
  182. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_schema_migrations.py +0 -0
  183. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_security_hardening.py +0 -0
  184. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_spreadsheet_functions.py +0 -0
  185. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_sqs_per_record_transactions.py +0 -0
  186. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  187. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_table_alter.py +0 -0
  188. {velocity_python-0.1.27 → velocity_python-0.1.29}/tests/test_where_clause_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.27
3
+ Version: 0.1.29
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
@@ -27,6 +27,10 @@ Requires-Dist: boto3>=1.35.0; extra == "aws"
27
27
  Requires-Dist: requests>=2.32.0; extra == "aws"
28
28
  Provides-Extra: excel
29
29
  Requires-Dist: openpyxl>=3.1.0; extra == "excel"
30
+ Provides-Extra: parquet
31
+ Requires-Dist: pyarrow>=15.0.0; extra == "parquet"
32
+ Provides-Extra: ods
33
+ Requires-Dist: odfpy>=1.4.0; extra == "ods"
30
34
  Provides-Extra: templates
31
35
  Requires-Dist: jinja2>=3.1.0; extra == "templates"
32
36
  Provides-Extra: pdf
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.27"
7
+ version = "0.1.29"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -45,6 +45,12 @@ aws = [
45
45
  excel = [
46
46
  "openpyxl>=3.1.0",
47
47
  ]
48
+ parquet = [
49
+ "pyarrow>=15.0.0",
50
+ ]
51
+ ods = [
52
+ "odfpy>=1.4.0",
53
+ ]
48
54
  templates = [
49
55
  "jinja2>=3.1.0",
50
56
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.27"
1
+ __version__ = version = "0.1.29"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -6,11 +6,14 @@ that uses velocity.db for database access.
6
6
  """
7
7
 
8
8
  import base64
9
+ import csv
9
10
  import datetime
10
11
  import importlib
12
+ import json
11
13
  import logging
12
14
  import re
13
- from io import BytesIO
15
+ import xml.etree.ElementTree as ET
16
+ from io import BytesIO, StringIO
14
17
 
15
18
  from velocity.aws import dirty_pipeline
16
19
  from velocity.misc import export
@@ -333,7 +336,8 @@ class DataServiceMixin:
333
336
  "message": message,
334
337
  "missing": missing,
335
338
  }
336
- if result_format == "excel":
339
+ _file_export_formats_early = {"excel", "csv", "tsv", "json", "jsonl", "xml", "html", "markdown", "xlsx", "parquet", "ods"}
340
+ if result_format in _file_export_formats_early:
337
341
  return {
338
342
  "headers": payload.get("headers", []),
339
343
  "rows": [],
@@ -354,7 +358,10 @@ class DataServiceMixin:
354
358
  swallowed_error = getattr(tx, "_last_return_default_error", None)
355
359
  if swallowed_error:
356
360
  tx._last_return_default_error = None
357
- if result_format == "excel":
361
+
362
+ _file_export_formats = {"excel", "csv", "tsv", "json", "jsonl", "xml", "html", "markdown", "xlsx", "parquet", "ods"}
363
+
364
+ if result_format in _file_export_formats:
358
365
  data = {
359
366
  "headers": payload.get(
360
367
  "headers", [x.replace("_", " ").title() for x in result.headers]
@@ -373,7 +380,7 @@ class DataServiceMixin:
373
380
 
374
381
  # If the DB call failed but was swallowed (return_default), surface a reason.
375
382
  # Common symptoms are empty rows + null SQL.
376
- if swallowed_error and result_format == "excel":
383
+ if swallowed_error and result_format in _file_export_formats:
377
384
  error_message = swallowed_error.get("message") or "Unknown database error"
378
385
  context.response().toast(
379
386
  f"Query failed for '{obj}': {error_message.splitlines()[0]}",
@@ -575,6 +582,143 @@ class DataServiceMixin:
575
582
  #
576
583
  # Payload parameters
577
584
 
585
+ # ---------------------------------------------------------------------------
586
+ # Export format helpers
587
+ # ---------------------------------------------------------------------------
588
+
589
+ @staticmethod
590
+ def _safe_xml_tag(name):
591
+ """Sanitise a column name so it is a valid XML element name."""
592
+ tag = re.sub(r"[^a-zA-Z0-9_.-]", "_", str(name))
593
+ if tag and tag[0].isdigit():
594
+ tag = "_" + tag
595
+ return tag or "_col"
596
+
597
+ @classmethod
598
+ def _build_export_buffer(cls, result_format, headers, rows, filename_base):
599
+ """
600
+ Build a (bytes_buffer, filename, mime_type) tuple for the given
601
+ result_format. Returns None if the format is not handled here.
602
+ """
603
+ if result_format == "csv":
604
+ buf = StringIO()
605
+ writer = csv.writer(buf)
606
+ writer.writerow(headers)
607
+ writer.writerows(rows)
608
+ return buf.getvalue().encode("utf-8"), f"{filename_base}.csv", "text/csv"
609
+
610
+ if result_format == "tsv":
611
+ buf = StringIO()
612
+ writer = csv.writer(buf, delimiter="\t")
613
+ writer.writerow(headers)
614
+ writer.writerows(rows)
615
+ return buf.getvalue().encode("utf-8"), f"{filename_base}.tsv", "text/tab-separated-values"
616
+
617
+ if result_format == "json":
618
+ data = [dict(zip(headers, row)) for row in rows]
619
+ return json.dumps(data, default=str).encode("utf-8"), f"{filename_base}.json", "application/json"
620
+
621
+ if result_format == "jsonl":
622
+ lines = [json.dumps(dict(zip(headers, row)), default=str) for row in rows]
623
+ return "\n".join(lines).encode("utf-8"), f"{filename_base}.jsonl", "application/x-ndjson"
624
+
625
+ if result_format == "xml":
626
+ root = ET.Element("rows")
627
+ for row in rows:
628
+ row_el = ET.SubElement(root, "row")
629
+ for header, value in zip(headers, row):
630
+ col_el = ET.SubElement(row_el, cls._safe_xml_tag(header))
631
+ col_el.text = "" if value is None else str(value)
632
+ xml_bytes = ET.tostring(root, encoding="unicode").encode("utf-8")
633
+ return xml_bytes, f"{filename_base}.xml", "application/xml"
634
+
635
+ if result_format == "html":
636
+ th_cells = "".join(f"<th>{h}</th>" for h in headers)
637
+ tr_rows = "".join(
638
+ "<tr>" + "".join(f"<td>{'' if v is None else str(v)}</td>" for v in row) + "</tr>"
639
+ for row in rows
640
+ )
641
+ html = (
642
+ f"<!DOCTYPE html><html><head><meta charset='utf-8'></head>"
643
+ f"<body><table border='1'><thead><tr>{th_cells}</tr></thead>"
644
+ f"<tbody>{tr_rows}</tbody></table></body></html>"
645
+ )
646
+ return html.encode("utf-8"), f"{filename_base}.html", "text/html"
647
+
648
+ if result_format == "markdown":
649
+ header_row = "| " + " | ".join(str(h) for h in headers) + " |"
650
+ separator = "| " + " | ".join("---" for _ in headers) + " |"
651
+ data_rows = [
652
+ "| " + " | ".join("" if v is None else str(v) for v in row) + " |"
653
+ for row in rows
654
+ ]
655
+ md = "\n".join([header_row, separator] + data_rows)
656
+ return md.encode("utf-8"), f"{filename_base}.md", "text/markdown"
657
+
658
+ if result_format == "xlsx":
659
+ try:
660
+ from openpyxl import Workbook
661
+ except ImportError:
662
+ raise ImportError("openpyxl is required for xlsx export (install velocity-python[excel])")
663
+ wb = Workbook()
664
+ ws = wb.active
665
+ ws.append(list(headers))
666
+ for row in rows:
667
+ ws.append(list(row))
668
+ buf = BytesIO()
669
+ wb.save(buf)
670
+ return (
671
+ buf.getvalue(),
672
+ f"{filename_base}.xlsx",
673
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
674
+ )
675
+
676
+ if result_format == "parquet":
677
+ try:
678
+ import pyarrow as pa
679
+ import pyarrow.parquet as pq
680
+ except ImportError:
681
+ raise ImportError("pyarrow is required for parquet export (install velocity-python[parquet])")
682
+ table = pa.table({h: [row[i] for row in rows] for i, h in enumerate(headers)})
683
+ buf = BytesIO()
684
+ pq.write_table(table, buf)
685
+ return buf.getvalue(), f"{filename_base}.parquet", "application/octet-stream"
686
+
687
+ if result_format == "ods":
688
+ try:
689
+ from odf.opendocument import OpenDocumentSpreadsheet
690
+ from odf.table import Table, TableRow, TableCell
691
+ from odf.text import P
692
+ except ImportError:
693
+ raise ImportError("odfpy is required for ods export (install velocity-python[ods])")
694
+ doc = OpenDocumentSpreadsheet()
695
+ sheet = Table(name="Sheet1")
696
+ doc.spreadsheet.addElement(sheet)
697
+ header_row_el = TableRow()
698
+ for h in headers:
699
+ cell = TableCell()
700
+ cell.addElement(P(text=str(h)))
701
+ header_row_el.addElement(cell)
702
+ sheet.addElement(header_row_el)
703
+ for row in rows:
704
+ tr = TableRow()
705
+ for v in row:
706
+ cell = TableCell()
707
+ cell.addElement(P(text="" if v is None else str(v)))
708
+ tr.addElement(cell)
709
+ sheet.addElement(tr)
710
+ buf = BytesIO()
711
+ doc.save(buf)
712
+ return (
713
+ buf.getvalue(),
714
+ f"{filename_base}.ods",
715
+ "application/vnd.oasis.opendocument.spreadsheet",
716
+ )
717
+
718
+ return None
719
+
720
+ # ---------------------------------------------------------------------------
721
+
578
722
  def OnActionQuery(self, tx, context):
579
723
  payload = context.payload()
580
724
 
@@ -586,8 +730,10 @@ class DataServiceMixin:
586
730
  if not table:
587
731
  raise ValueError("Parameter 'obj' cannot be empty")
588
732
 
733
+ result_format = payload.get("result_format")
589
734
  data = self.query_hook(tx, table, payload, context)
590
- if payload.get("result_format") == "excel":
735
+
736
+ if result_format == "excel":
591
737
  filebuffer = BytesIO()
592
738
  export.create_spreadsheet(data["headers"], data["rows"], filebuffer)
593
739
  context.response().file_download(
@@ -598,7 +744,20 @@ class DataServiceMixin:
598
744
  )
599
745
  return
600
746
 
601
- if payload.get("result_format") == "raw":
747
+ if result_format in ("csv", "tsv", "json", "jsonl", "xml", "html", "markdown", "xlsx", "parquet", "ods"):
748
+ filename_base = payload.get("filename_base") or table
749
+ built = self._build_export_buffer(result_format, data.get("headers", []), data.get("rows", []), filename_base)
750
+ if built is not None:
751
+ file_bytes, filename, _mime = built
752
+ context.response().file_download(
753
+ {
754
+ "filename": filename,
755
+ "data": base64.b64encode(file_bytes).decode(),
756
+ }
757
+ )
758
+ return
759
+
760
+ if result_format == "raw":
602
761
  context.response().set_body(data)
603
762
  else:
604
763
  context.response().set_table(
@@ -29,6 +29,13 @@ from typing import Any, Dict, List, Optional
29
29
  from authorizenet import apicontractsv1, apicontrollers
30
30
 
31
31
  from .base_adapter import PaymentProcessorAdapter, ProcessorError
32
+ from .authorizenet_mirror import (
33
+ sync_client_payment_account_from_authorize_account,
34
+ upsert_authorize_account_record,
35
+ upsert_authorize_customer_profile_record,
36
+ upsert_authorize_payment_profile_record,
37
+ upsert_authorize_transaction_record,
38
+ )
32
39
 
33
40
  logger = logging.getLogger(__name__)
34
41
 
@@ -132,20 +139,25 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
132
139
  placeholder record so the routing layer can treat it uniformly.
133
140
  """
134
141
  client_id = client_data["client_id"]
135
-
136
- payment_account = tx.table("client_payment_accounts").new()
137
- payment_account["client_id"] = client_id
138
- payment_account["processor_type"] = "authorize_net"
139
- payment_account["processor_account_id"] = f"an_{client_id}"
140
- payment_account["account_status"] = "active"
141
- payment_account["charges_enabled"] = True
142
- payment_account["payouts_enabled"] = False
143
- payment_account["details_submitted"] = True
144
- payment_account["requirements_data"] = {}
145
- payment_account["capabilities"] = {
146
- "card_payments": True,
147
- "manual_settlement": True,
142
+ account = {
143
+ "client_id": client_id,
144
+ "processor_account_id": f"an_{client_id}",
145
+ "status": "active",
146
+ "charges_enabled": True,
147
+ "payouts_enabled": False,
148
+ "verification_status": "verified",
149
+ "metadata": {"account_type": "shared_merchant"},
148
150
  }
151
+ upsert_authorize_account_record(
152
+ tx,
153
+ account,
154
+ source="api:authorizenet_adapter.create_account",
155
+ )
156
+ sync_client_payment_account_from_authorize_account(
157
+ tx,
158
+ account,
159
+ client_id=client_id,
160
+ )
149
161
 
150
162
  return {
151
163
  "processor_account_id": f"an_{client_id}",
@@ -161,7 +173,7 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
161
173
  """
162
174
  Authorize.Net shared merchant is always active if credentials are valid.
163
175
  """
164
- return {
176
+ account = {
165
177
  "account_id": processor_account_id,
166
178
  "status": "active",
167
179
  "charges_enabled": True,
@@ -172,6 +184,26 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
172
184
  "verification_status": "verified",
173
185
  "metadata": {"account_type": "shared_merchant"},
174
186
  }
187
+ payment_account = tx.table("client_payment_accounts").find(
188
+ {
189
+ "processor_account_id": processor_account_id,
190
+ "processor_type": "authorize_net",
191
+ }
192
+ )
193
+ client_id = payment_account.get("client_id") if payment_account else None
194
+ if client_id:
195
+ account["client_id"] = client_id
196
+ upsert_authorize_account_record(
197
+ tx,
198
+ account,
199
+ source="api:authorizenet_adapter.get_account_status",
200
+ )
201
+ sync_client_payment_account_from_authorize_account(
202
+ tx,
203
+ account,
204
+ client_id=client_id,
205
+ )
206
+ return account
175
207
 
176
208
  def create_onboarding_link(
177
209
  self, tx, processor_account_id: str, return_url: str, refresh_url: str
@@ -225,12 +257,33 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
225
257
  parts = e.error_message.split()
226
258
  for part in parts:
227
259
  if part.strip().isdigit():
260
+ upsert_authorize_customer_profile_record(
261
+ tx,
262
+ {
263
+ "customer_profile_id": part.strip(),
264
+ "email_address": email_address,
265
+ "description": customer_data.get(
266
+ "name", email_address
267
+ ),
268
+ },
269
+ source="api:authorizenet_adapter.get_or_create_customer_profile",
270
+ )
228
271
  return {
229
272
  "customer_profile_id": part.strip(),
230
273
  "created": False,
231
274
  }
232
275
  raise
233
276
 
277
+ upsert_authorize_customer_profile_record(
278
+ tx,
279
+ {
280
+ "customer_profile_id": response["customerProfileId"],
281
+ "email_address": email_address,
282
+ "description": customer_data.get("name", email_address),
283
+ },
284
+ source="api:authorizenet_adapter.get_or_create_customer_profile",
285
+ )
286
+
234
287
  return {
235
288
  "customer_profile_id": response["customerProfileId"],
236
289
  "created": True,
@@ -287,6 +340,24 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
287
340
 
288
341
  controller = apicontrollers.createCustomerPaymentProfileController(request)
289
342
  response = self._execute(controller)
343
+ upsert_authorize_payment_profile_record(
344
+ tx,
345
+ {
346
+ "customer_profile_id": customer_profile_id,
347
+ "payment_profile_id": response["customerPaymentProfileId"],
348
+ "last4": str(payment_data.get("card_number") or "")[-4:] or None,
349
+ "expiration_date": payment_data.get("expiration_date"),
350
+ "billing_first_name": payment_data.get("first_name"),
351
+ "billing_last_name": payment_data.get("last_name"),
352
+ "billing_address": payment_data.get("address"),
353
+ "billing_city": payment_data.get("city"),
354
+ "billing_state": payment_data.get("state"),
355
+ "billing_zip": payment_data.get("zip"),
356
+ "is_default": payment_data.get("set_default", True),
357
+ "metadata": payment_data.get("metadata") or {},
358
+ },
359
+ source="api:authorizenet_adapter.attach_payment_method",
360
+ )
290
361
 
291
362
  return {
292
363
  "customer_profile_id": customer_profile_id,
@@ -326,6 +397,16 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
326
397
  else:
327
398
  raise
328
399
 
400
+ upsert_authorize_payment_profile_record(
401
+ tx,
402
+ {
403
+ "customer_profile_id": customer_profile_id,
404
+ "payment_profile_id": payment_method_id,
405
+ "detached": True,
406
+ },
407
+ source="api:authorizenet_adapter.detach_payment_method",
408
+ )
409
+
329
410
  return {"payment_profile_id": payment_method_id, "detached": True}
330
411
 
331
412
  def delete_customer_profile(self, tx, customer_profile_id: str) -> Dict[str, Any]:
@@ -343,6 +424,12 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
343
424
  else:
344
425
  raise
345
426
 
427
+ upsert_authorize_customer_profile_record(
428
+ tx,
429
+ {"customer_profile_id": customer_profile_id, "deleted": True},
430
+ source="api:authorizenet_adapter.delete_customer_profile",
431
+ )
432
+
346
433
  return {"customer_profile_id": customer_profile_id, "deleted": True}
347
434
 
348
435
  # ========================================================================
@@ -385,6 +472,24 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
385
472
  response = self._execute(controller)
386
473
  trans = response.get("transactionResponse", {})
387
474
  code = int(trans.get("responseCode", 3))
475
+ status = "authorized" if RESPONSE_CODES.get(code) == "Approved" else "failed"
476
+ upsert_authorize_transaction_record(
477
+ tx,
478
+ {
479
+ "processor_transaction_id": trans.get("transId"),
480
+ "amount": float(amount_dollars),
481
+ "status": status,
482
+ "reason_for_transaction": payment_data.get("description"),
483
+ "remote_authcode": trans.get("authCode"),
484
+ "remote_responsecode": code,
485
+ "supported_id": payment_data.get("client_id"),
486
+ "supported_name": payment_data.get("description"),
487
+ "email_address": payment_data.get("customer_email")
488
+ or payment_data.get("donor_email"),
489
+ "raw_payload": response,
490
+ },
491
+ source="api:authorizenet_adapter.authorize_payment",
492
+ )
388
493
 
389
494
  if RESPONSE_CODES.get(code) == "Approved":
390
495
  return {
@@ -492,6 +597,24 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
492
597
  response = self._execute(controller)
493
598
  trans = response.get("transactionResponse", {})
494
599
  code = int(trans.get("responseCode", 3))
600
+ status = RESPONSE_CODES.get(code, "Error")
601
+ upsert_authorize_transaction_record(
602
+ tx,
603
+ {
604
+ "processor_transaction_id": trans.get("transId"),
605
+ "amount": float(amount_dollars),
606
+ "status": status,
607
+ "reason_for_transaction": order.description if client_id or campaign else None,
608
+ "remote_authcode": trans.get("authCode"),
609
+ "remote_responsecode": code,
610
+ "supported_id": client_id or None,
611
+ "supported_name": campaign or None,
612
+ "email_address": payment_data.get("customer_email")
613
+ or payment_data.get("donor_email"),
614
+ "raw_payload": response,
615
+ },
616
+ source="api:authorizenet_adapter.charge_stored_payment_method",
617
+ )
495
618
 
496
619
  if RESPONSE_CODES.get(code) == "Approved":
497
620
  return {
@@ -581,6 +704,23 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
581
704
  response = self._execute(controller)
582
705
  trans = response.get("transactionResponse", {})
583
706
  code = int(trans.get("responseCode", 3))
707
+ upsert_authorize_transaction_record(
708
+ tx,
709
+ {
710
+ "processor_transaction_id": trans.get("transId")
711
+ or processor_transaction_id,
712
+ "amount": float(decimal.Decimal(amount) / 100)
713
+ if amount is not None
714
+ else None,
715
+ "status": "captured"
716
+ if RESPONSE_CODES.get(code) == "Approved"
717
+ else "failed",
718
+ "remote_authcode": trans.get("authCode"),
719
+ "remote_responsecode": code,
720
+ "raw_payload": response,
721
+ },
722
+ source="api:authorizenet_adapter.capture_payment",
723
+ )
584
724
 
585
725
  if RESPONSE_CODES.get(code) == "Approved":
586
726
  captured_cents = amount if amount is not None else 0
@@ -635,6 +775,20 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
635
775
  response = self._execute(controller)
636
776
  trans = response.get("transactionResponse", {})
637
777
  code = int(trans.get("responseCode", 3))
778
+ upsert_authorize_transaction_record(
779
+ tx,
780
+ {
781
+ "processor_transaction_id": trans.get("transId")
782
+ or processor_transaction_id,
783
+ "status": "cancelled"
784
+ if RESPONSE_CODES.get(code) == "Approved"
785
+ else "failed",
786
+ "remote_authcode": trans.get("authCode"),
787
+ "remote_responsecode": code,
788
+ "raw_payload": response,
789
+ },
790
+ source="api:authorizenet_adapter.cancel_payment",
791
+ )
638
792
 
639
793
  if RESPONSE_CODES.get(code) == "Approved":
640
794
  return {
@@ -695,6 +849,23 @@ class AuthorizeNetAdapter(PaymentProcessorAdapter):
695
849
  response = self._execute(controller)
696
850
  trans = response.get("transactionResponse", {})
697
851
  code = int(trans.get("responseCode", 3))
852
+ upsert_authorize_transaction_record(
853
+ tx,
854
+ {
855
+ "processor_transaction_id": trans.get("transId")
856
+ or processor_charge_id,
857
+ "amount": float(decimal.Decimal(amount) / 100)
858
+ if amount is not None
859
+ else None,
860
+ "status": "Refunded"
861
+ if RESPONSE_CODES.get(code) == "Approved"
862
+ else "Error",
863
+ "remote_authcode": trans.get("authCode"),
864
+ "remote_responsecode": code,
865
+ "raw_payload": response,
866
+ },
867
+ source="api:authorizenet_adapter.refund_payment",
868
+ )
698
869
 
699
870
  if RESPONSE_CODES.get(code) == "Approved":
700
871
  return {