velocity-python 0.1.29__tar.gz → 0.1.31__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.29/src/velocity_python.egg-info → velocity_python-0.1.31}/PKG-INFO +1 -1
  2. {velocity_python-0.1.29 → velocity_python-0.1.31}/pyproject.toml +1 -1
  3. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/mixins/data_service.py +2 -3
  5. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/table.py +46 -0
  6. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/transaction.py +85 -0
  7. {velocity_python-0.1.29 → velocity_python-0.1.31/src/velocity_python.egg-info}/PKG-INFO +1 -1
  8. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  9. velocity_python-0.1.31/tests/test_server_cursor.py +168 -0
  10. {velocity_python-0.1.29 → velocity_python-0.1.31}/LICENSE +0 -0
  11. {velocity_python-0.1.29 → velocity_python-0.1.31}/README.md +0 -0
  12. {velocity_python-0.1.29 → velocity_python-0.1.31}/setup.cfg +0 -0
  13. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/__init__.py +0 -0
  14. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/amplify.py +0 -0
  15. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/amplify_build.py +0 -0
  16. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/__init__.py +0 -0
  17. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/backfill.py +0 -0
  18. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/indexing.py +0 -0
  19. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/references.py +0 -0
  20. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/service.py +0 -0
  21. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/assets/usage_index.py +0 -0
  22. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/dirty_pipeline.py +0 -0
  23. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/__init__.py +0 -0
  24. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/base_handler.py +0 -0
  25. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/context.py +0 -0
  26. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/context_factory.py +0 -0
  27. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/exceptions.py +0 -0
  28. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  29. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  30. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  31. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/perf.py +0 -0
  32. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/response.py +0 -0
  33. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  34. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/s3.py +0 -0
  35. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/ssm_config.py +0 -0
  36. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/tests/__init__.py +0 -0
  37. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  38. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  39. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/aws/tests/test_response.py +0 -0
  40. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/__init__.py +0 -0
  41. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/__init__.py +0 -0
  42. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/async_support.py +0 -0
  43. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/column.py +0 -0
  44. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/database.py +0 -0
  45. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/decorators.py +0 -0
  46. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/engine.py +0 -0
  47. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/result.py +0 -0
  48. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/row.py +0 -0
  49. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/sequence.py +0 -0
  50. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/core/view.py +0 -0
  51. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/exceptions.py +0 -0
  52. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/migrations.py +0 -0
  53. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/__init__.py +0 -0
  54. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/base/__init__.py +0 -0
  55. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/base/initializer.py +0 -0
  56. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/base/operators.py +0 -0
  57. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/base/sql.py +0 -0
  58. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/base/types.py +0 -0
  59. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/mysql/__init__.py +0 -0
  60. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/mysql/operators.py +0 -0
  61. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/mysql/reserved.py +0 -0
  62. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/mysql/sql.py +0 -0
  63. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/mysql/types.py +0 -0
  64. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/postgres/__init__.py +0 -0
  65. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/postgres/operators.py +0 -0
  66. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/postgres/reserved.py +0 -0
  67. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/postgres/sql.py +0 -0
  68. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/postgres/types.py +0 -0
  69. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  70. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlite/operators.py +0 -0
  71. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  72. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlite/sql.py +0 -0
  73. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlite/types.py +0 -0
  74. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  75. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  76. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  77. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  78. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/sqlserver/types.py +0 -0
  79. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/servers/tablehelper.py +0 -0
  80. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/__init__.py +0 -0
  81. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/common_db_test.py +0 -0
  82. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/__init__.py +0 -0
  83. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/common.py +0 -0
  84. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_column.py +0 -0
  85. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  86. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_database.py +0 -0
  87. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  88. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  89. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  90. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_result.py +0 -0
  91. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_row.py +0 -0
  92. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  93. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  94. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  95. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  96. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  97. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_table.py +0 -0
  98. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  99. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  100. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/sql/__init__.py +0 -0
  101. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/sql/common.py +0 -0
  102. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  103. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  104. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  105. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_db_utils.py +0 -0
  106. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_postgres.py +0 -0
  107. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  108. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  109. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_result_caching.py +0 -0
  110. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  111. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  112. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  113. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  114. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_sql_builder.py +0 -0
  115. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_tablehelper.py +0 -0
  116. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/tests/test_view_helper.py +0 -0
  117. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/db/utils.py +0 -0
  118. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/logging.py +0 -0
  119. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/__init__.py +0 -0
  120. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/conv/__init__.py +0 -0
  121. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/conv/iconv.py +0 -0
  122. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/conv/oconv.py +0 -0
  123. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/db.py +0 -0
  124. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/export.py +0 -0
  125. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/format.py +0 -0
  126. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/mail.py +0 -0
  127. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/merge.py +0 -0
  128. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/pdf.py +0 -0
  129. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/__init__.py +0 -0
  130. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_db.py +0 -0
  131. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_fix.py +0 -0
  132. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_format.py +0 -0
  133. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_iconv.py +0 -0
  134. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_merge.py +0 -0
  135. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_oconv.py +0 -0
  136. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_original_error.py +0 -0
  137. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tests/test_timer.py +0 -0
  138. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/timer.py +0 -0
  139. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/misc/tools.py +0 -0
  140. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/__init__.py +0 -0
  141. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/authorizenet_adapter.py +0 -0
  142. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/authorizenet_mirror.py +0 -0
  143. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/base_adapter.py +0 -0
  144. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/braintree_adapter.py +0 -0
  145. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/braintree_mirror.py +0 -0
  146. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/charge_rules.py +0 -0
  147. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/stripe_adapter.py +0 -0
  148. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity/payment/stripe_mirror.py +0 -0
  149. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  150. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity_python.egg-info/entry_points.txt +0 -0
  151. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity_python.egg-info/requires.txt +0 -0
  152. {velocity_python-0.1.29 → velocity_python-0.1.31}/src/velocity_python.egg-info/top_level.txt +0 -0
  153. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_amplify_build.py +0 -0
  154. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_asset_indexing.py +0 -0
  155. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_asset_references.py +0 -0
  156. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_assets_service.py +0 -0
  157. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_async_support.py +0 -0
  158. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_batch_operations.py +0 -0
  159. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_concurrency_safety.py +0 -0
  160. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_connection_pool.py +0 -0
  161. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_connection_resilience.py +0 -0
  162. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_decorators.py +0 -0
  163. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_dirty_pipeline_fast_path.py +0 -0
  164. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_email_processing.py +0 -0
  165. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_iconv_money_to_cents.py +0 -0
  166. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_lambda_handler.py +0 -0
  167. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_lambda_handler_auth.py +0 -0
  168. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_mixins_import.py +0 -0
  169. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_n_plus_one.py +0 -0
  170. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_observability.py +0 -0
  171. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_payment_authorizenet_adapter.py +0 -0
  172. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_payment_braintree_adapter.py +0 -0
  173. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_payment_profile_sorting.py +0 -0
  174. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_payment_stripe_adapter.py +0 -0
  175. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_pdf.py +0 -0
  176. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_prepared_statements.py +0 -0
  177. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_psycopg3_upgrade.py +0 -0
  178. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_query_cache.py +0 -0
  179. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_row_batch_update.py +0 -0
  180. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_row_cache_staleness.py +0 -0
  181. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_row_dirty_tracking.py +0 -0
  182. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_schema_migrations.py +0 -0
  183. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_security_hardening.py +0 -0
  184. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_spreadsheet_functions.py +0 -0
  185. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_sqs_per_record_transactions.py +0 -0
  186. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  187. {velocity_python-0.1.29 → velocity_python-0.1.31}/tests/test_table_alter.py +0 -0
  188. {velocity_python-0.1.29 → velocity_python-0.1.31}/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.29
3
+ Version: 0.1.31
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.29"
7
+ version = "0.1.31"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.29"
1
+ __version__ = version = "0.1.31"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -594,8 +594,7 @@ class DataServiceMixin:
594
594
  tag = "_" + tag
595
595
  return tag or "_col"
596
596
 
597
- @classmethod
598
- def _build_export_buffer(cls, result_format, headers, rows, filename_base):
597
+ def _build_export_buffer(self, result_format, headers, rows, filename_base):
599
598
  """
600
599
  Build a (bytes_buffer, filename, mime_type) tuple for the given
601
600
  result_format. Returns None if the format is not handled here.
@@ -627,7 +626,7 @@ class DataServiceMixin:
627
626
  for row in rows:
628
627
  row_el = ET.SubElement(root, "row")
629
628
  for header, value in zip(headers, row):
630
- col_el = ET.SubElement(row_el, cls._safe_xml_tag(header))
629
+ col_el = ET.SubElement(row_el, self._safe_xml_tag(header))
631
630
  col_el.text = "" if value is None else str(value)
632
631
  xml_bytes = ET.tostring(root, encoding="unicode").encode("utf-8")
633
632
  return xml_bytes, f"{filename_base}.xml", "application/xml"
@@ -1186,6 +1186,52 @@ class Table:
1186
1186
  raise Exception("sql_only is not supported for list queries")
1187
1187
  return self.select(*args, **kwds).all()
1188
1188
 
1189
+ def stream(
1190
+ self,
1191
+ columns=None,
1192
+ where=None,
1193
+ orderby=None,
1194
+ groupby=None,
1195
+ having=None,
1196
+ fetch_size: int = 5_000,
1197
+ ):
1198
+ """Stream a large result set via a PostgreSQL server-side cursor.
1199
+
1200
+ Unlike :meth:`select`, this never buffers the entire result in memory.
1201
+ Rows are fetched from the database *fetch_size* at a time via
1202
+ ``DECLARE … CURSOR FOR`` / ``FETCH N`` / ``CLOSE``.
1203
+
1204
+ Must be used as a context manager::
1205
+
1206
+ with tx.table("transactions").stream(where={"year": 2026}) as cur:
1207
+ for result in cur:
1208
+ headers = result.headers # column names, first batch
1209
+ rows = result.as_list().all()
1210
+
1211
+ Args:
1212
+ columns: Column expression(s) to SELECT (default ``*``).
1213
+ where: Filter dict or raw SQL fragment, same as :meth:`select`.
1214
+ orderby: ORDER BY expression, same as :meth:`select`.
1215
+ groupby: GROUP BY expression.
1216
+ having: HAVING clause.
1217
+ fetch_size: Rows per round-trip (default 5 000).
1218
+
1219
+ Returns:
1220
+ A :class:`~velocity.db.core.transaction.ServerCursor` context
1221
+ manager whose iterator yields :class:`~velocity.db.core.result.Result`
1222
+ objects, one per batch.
1223
+ """
1224
+ sql, vals = self.sql.select(
1225
+ self.tx,
1226
+ columns=columns,
1227
+ table=self.name,
1228
+ where=where,
1229
+ orderby=orderby,
1230
+ groupby=groupby,
1231
+ having=having,
1232
+ )
1233
+ return self.tx.stream(sql, vals, fetch_size=fetch_size)
1234
+
1189
1235
  def query(
1190
1236
  self,
1191
1237
  columns=None,
@@ -2,6 +2,7 @@ import logging
2
2
  import os
3
3
  import time as _time
4
4
  import traceback
5
+ import uuid
5
6
  from collections import OrderedDict
6
7
 
7
8
  from velocity.db.core.row import Row
@@ -87,6 +88,71 @@ def _summarize_sql(sql, limit=None):
87
88
  return compact
88
89
 
89
90
 
91
+ class ServerCursor:
92
+ """Context manager and iterator for streaming large result sets via a
93
+ PostgreSQL server-side cursor (``DECLARE … CURSOR FOR`` / ``FETCH N`` /
94
+ ``CLOSE``).
95
+
96
+ Do not instantiate directly — use :meth:`Transaction.stream` or
97
+ :meth:`Table.stream` instead.
98
+
99
+ Each iteration yields a :class:`Result` containing up to *fetch_size* rows.
100
+ When the result set is exhausted the loop ends, and the cursor is
101
+ ``CLOSE``d automatically on ``__exit__``, even when an exception is raised
102
+ inside the ``with`` block.
103
+
104
+ Example::
105
+
106
+ with tx.stream(sql, vals, fetch_size=5000) as cursor:
107
+ for result in cursor:
108
+ headers = result.headers # column names, first batch
109
+ rows = result.as_list().all()
110
+ """
111
+
112
+ def __init__(self, tx: "Transaction", sql: str, vals=None, fetch_size: int = 5_000):
113
+ self._tx = tx
114
+ self._sql = sql
115
+ self._vals = vals
116
+ self._fetch_size = fetch_size
117
+ self._name = f"vc_{uuid.uuid4().hex[:8]}"
118
+ self._open = False
119
+
120
+ # ── context manager ───────────────────────────────────────────────────────
121
+
122
+ def __enter__(self) -> "ServerCursor":
123
+ self._tx.execute(
124
+ f"DECLARE {self._name} CURSOR FOR {self._sql}",
125
+ self._vals,
126
+ )
127
+ self._open = True
128
+ return self
129
+
130
+ def __exit__(self, *args) -> bool:
131
+ if self._open:
132
+ try:
133
+ self._tx.execute(f"CLOSE {self._name}")
134
+ except Exception:
135
+ pass
136
+ self._open = False
137
+ return False # do not suppress exceptions
138
+
139
+ # ── iterator ──────────────────────────────────────────────────────────────
140
+
141
+ def __iter__(self) -> "ServerCursor":
142
+ return self
143
+
144
+ def __next__(self) -> Result:
145
+ """Fetch the next batch from the server-side cursor.
146
+
147
+ Returns a :class:`Result`; raises :class:`StopIteration` when the
148
+ result set is exhausted.
149
+ """
150
+ result = self._tx.execute(f"FETCH {self._fetch_size} FROM {self._name}")
151
+ if not result: # Result.__bool__ pre-fetches one row; False → empty
152
+ raise StopIteration
153
+ return result
154
+
155
+
90
156
  class Transaction:
91
157
  """
92
158
  Encapsulates a single transaction in the database (connection + commit/rollback).
@@ -302,6 +368,25 @@ class Transaction:
302
368
  """
303
369
  return self._execute(sql, parms, cursor=self.cursor())
304
370
 
371
+ def stream(self, sql: str, vals=None, fetch_size: int = 5_000) -> ServerCursor:
372
+ """Return a :class:`ServerCursor` for streaming a large result set
373
+ without buffering all rows in memory at once.
374
+
375
+ The cursor uses PostgreSQL ``DECLARE … CURSOR FOR`` / ``FETCH N`` /
376
+ ``CLOSE`` and must be used as a context manager::
377
+
378
+ with tx.stream(sql, vals, fetch_size=5000) as cursor:
379
+ for result in cursor:
380
+ headers = result.headers
381
+ rows = result.as_list().all()
382
+
383
+ Args:
384
+ sql: A SELECT statement (with ``%s`` placeholders if needed).
385
+ vals: Positional parameters matching the placeholders, or None.
386
+ fetch_size: Rows fetched per round-trip (default 5 000).
387
+ """
388
+ return ServerCursor(self, sql, vals, fetch_size=fetch_size)
389
+
305
390
  def commit(self):
306
391
  """
307
392
  Commits the current transaction if there's an open connection.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.29
3
+ Version: 0.1.31
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
@@ -178,6 +178,7 @@ tests/test_row_cache_staleness.py
178
178
  tests/test_row_dirty_tracking.py
179
179
  tests/test_schema_migrations.py
180
180
  tests/test_security_hardening.py
181
+ tests/test_server_cursor.py
181
182
  tests/test_spreadsheet_functions.py
182
183
  tests/test_sqs_per_record_transactions.py
183
184
  tests/test_sys_modified_count_postgres_demo.py
@@ -0,0 +1,168 @@
1
+ """
2
+ Tests for ServerCursor / Transaction.stream() / Table.stream().
3
+ """
4
+
5
+ import pytest
6
+ from unittest.mock import MagicMock, call, patch
7
+
8
+ from velocity.db.core.transaction import ServerCursor
9
+ from velocity.db.core.result import Result
10
+
11
+
12
+ # ── helpers ───────────────────────────────────────────────────────────────────
13
+
14
+ def _make_tx(execute_side_effects=None):
15
+ """Return a mock Transaction whose execute() returns the given side effects."""
16
+ tx = MagicMock()
17
+ if execute_side_effects is not None:
18
+ tx.execute.side_effect = execute_side_effects
19
+ return tx
20
+
21
+
22
+ def _result(rows, headers=None):
23
+ """Return a mock Result that iterates over rows and has the given headers."""
24
+ r = MagicMock(spec=Result)
25
+ r.__bool__ = MagicMock(return_value=bool(rows))
26
+ r.headers = headers or [f"col{i}" for i in range(len(rows[0]) if rows else 0)]
27
+ r.as_list.return_value.all.return_value = list(rows)
28
+ return r
29
+
30
+
31
+ def _empty_result():
32
+ """Return a falsy mock Result (no rows)."""
33
+ r = MagicMock(spec=Result)
34
+ r.__bool__ = MagicMock(return_value=False)
35
+ return r
36
+
37
+
38
+ # ── ServerCursor unit tests ───────────────────────────────────────────────────
39
+
40
+ class TestServerCursorContextManager:
41
+ def test_enter_issues_declare(self):
42
+ tx = _make_tx()
43
+ cursor = ServerCursor(tx, "SELECT 1", fetch_size=100)
44
+ cursor.__enter__()
45
+ declare_call = tx.execute.call_args_list[0]
46
+ assert "DECLARE" in declare_call[0][0]
47
+ assert "CURSOR FOR" in declare_call[0][0]
48
+ assert "SELECT 1" in declare_call[0][0]
49
+
50
+ def test_exit_issues_close(self):
51
+ tx = _make_tx()
52
+ cursor = ServerCursor(tx, "SELECT 1")
53
+ cursor._open = True
54
+ cursor._name = "vc_test1234"
55
+ cursor.__exit__(None, None, None)
56
+ close_call = tx.execute.call_args_list[-1]
57
+ assert "CLOSE vc_test1234" in close_call[0][0]
58
+
59
+ def test_exit_not_called_when_not_open(self):
60
+ tx = _make_tx()
61
+ cursor = ServerCursor(tx, "SELECT 1")
62
+ cursor._open = False
63
+ cursor.__exit__(None, None, None)
64
+ tx.execute.assert_not_called()
65
+
66
+ def test_exit_swallows_close_errors(self):
67
+ tx = _make_tx()
68
+ tx.execute.side_effect = Exception("connection lost")
69
+ cursor = ServerCursor(tx, "SELECT 1")
70
+ cursor._open = True
71
+ # should not raise
72
+ cursor.__exit__(None, None, None)
73
+
74
+ def test_exit_returns_false(self):
75
+ tx = _make_tx()
76
+ cursor = ServerCursor(tx, "SELECT 1")
77
+ assert cursor.__exit__(None, None, None) is False
78
+
79
+ def test_unique_cursor_names(self):
80
+ tx = _make_tx()
81
+ names = {ServerCursor(tx, "SELECT 1")._name for _ in range(50)}
82
+ assert len(names) == 50
83
+
84
+
85
+ class TestServerCursorIteration:
86
+ def test_yields_result_per_batch(self):
87
+ batch1 = _result([(1, "a"), (2, "b")])
88
+ batch2 = _empty_result()
89
+ tx = _make_tx(execute_side_effects=[MagicMock(), batch1, batch2]) # DECLARE, FETCH, FETCH
90
+
91
+ results = []
92
+ with ServerCursor(tx, "SELECT id, name FROM t", fetch_size=2) as cur:
93
+ for result in cur:
94
+ results.append(result)
95
+
96
+ assert results == [batch1]
97
+
98
+ def test_multiple_batches(self):
99
+ b1 = _result([(i,) for i in range(5)])
100
+ b2 = _result([(i,) for i in range(5, 10)])
101
+ empty = _empty_result()
102
+ tx = _make_tx(execute_side_effects=[MagicMock(), b1, b2, empty])
103
+
104
+ results = []
105
+ with ServerCursor(tx, "SELECT id FROM t", fetch_size=5) as cur:
106
+ for result in cur:
107
+ results.append(result)
108
+
109
+ assert results == [b1, b2]
110
+
111
+ def test_empty_table_yields_nothing(self):
112
+ empty = _empty_result()
113
+ tx = _make_tx(execute_side_effects=[MagicMock(), empty])
114
+
115
+ results = []
116
+ with ServerCursor(tx, "SELECT id FROM t") as cur:
117
+ for result in cur:
118
+ results.append(result)
119
+
120
+ assert results == []
121
+
122
+ def test_close_called_on_exception(self):
123
+ batch = _result([(1,)])
124
+ tx = _make_tx(execute_side_effects=[MagicMock(), batch])
125
+ cursor_name = None
126
+
127
+ try:
128
+ with ServerCursor(tx, "SELECT 1") as cur:
129
+ cursor_name = cur._name
130
+ for _ in cur:
131
+ raise RuntimeError("mid-loop error")
132
+ except RuntimeError:
133
+ pass
134
+
135
+ last_call = tx.execute.call_args_list[-1][0][0]
136
+ assert f"CLOSE {cursor_name}" in last_call
137
+
138
+ def test_fetch_uses_correct_size(self):
139
+ empty = _empty_result()
140
+ tx = _make_tx(execute_side_effects=[MagicMock(), empty])
141
+
142
+ with ServerCursor(tx, "SELECT 1", fetch_size=1234) as cur:
143
+ for _ in cur:
144
+ pass
145
+
146
+ fetch_call = tx.execute.call_args_list[1][0][0]
147
+ assert "FETCH 1234" in fetch_call
148
+
149
+ def test_vals_passed_to_declare(self):
150
+ tx = _make_tx()
151
+ cursor = ServerCursor(tx, "SELECT * FROM t WHERE id = %s", vals=(42,))
152
+ cursor.__enter__()
153
+ declare_call = tx.execute.call_args_list[0]
154
+ assert declare_call[0][1] == (42,)
155
+
156
+
157
+ # ── Transaction.stream() ──────────────────────────────────────────────────────
158
+
159
+ class TestTransactionStream:
160
+ def test_returns_server_cursor(self):
161
+ from velocity.db.core.transaction import Transaction
162
+ tx = MagicMock(spec=Transaction)
163
+ # call the real stream() via unbound method
164
+ sc = Transaction.stream(tx, "SELECT 1", (1, 2), fetch_size=100)
165
+ assert isinstance(sc, ServerCursor)
166
+ assert sc._sql == "SELECT 1"
167
+ assert sc._vals == (1, 2)
168
+ assert sc._fetch_size == 100