velocity-python 0.1.38__tar.gz → 0.1.39__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 (189) hide show
  1. {velocity_python-0.1.38/src/velocity_python.egg-info → velocity_python-0.1.39}/PKG-INFO +1 -1
  2. {velocity_python-0.1.38 → velocity_python-0.1.39}/pyproject.toml +1 -1
  3. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/table.py +104 -7
  5. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/base/sql.py +4 -0
  6. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/mysql/sql.py +17 -2
  7. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/postgres/sql.py +32 -10
  8. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlite/sql.py +11 -1
  9. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlserver/sql.py +17 -2
  10. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_schema_locking.py +65 -0
  11. {velocity_python-0.1.38 → velocity_python-0.1.39/src/velocity_python.egg-info}/PKG-INFO +1 -1
  12. {velocity_python-0.1.38 → velocity_python-0.1.39}/LICENSE +0 -0
  13. {velocity_python-0.1.38 → velocity_python-0.1.39}/README.md +0 -0
  14. {velocity_python-0.1.38 → velocity_python-0.1.39}/setup.cfg +0 -0
  15. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/__init__.py +0 -0
  16. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/amplify.py +0 -0
  17. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/amplify_build.py +0 -0
  18. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/__init__.py +0 -0
  19. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/backfill.py +0 -0
  20. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/indexing.py +0 -0
  21. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/references.py +0 -0
  22. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/service.py +0 -0
  23. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/assets/usage_index.py +0 -0
  24. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/dirty_pipeline.py +0 -0
  25. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/__init__.py +0 -0
  26. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/base_handler.py +0 -0
  27. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/context.py +0 -0
  28. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/context_factory.py +0 -0
  29. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/exceptions.py +0 -0
  30. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  31. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  32. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  33. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  34. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/perf.py +0 -0
  35. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/response.py +0 -0
  36. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  37. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/s3.py +0 -0
  38. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/ssm_config.py +0 -0
  39. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/tests/__init__.py +0 -0
  40. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  41. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  42. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/aws/tests/test_response.py +0 -0
  43. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/__init__.py +0 -0
  44. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/__init__.py +0 -0
  45. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/async_support.py +0 -0
  46. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/column.py +0 -0
  47. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/database.py +0 -0
  48. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/decorators.py +0 -0
  49. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/engine.py +0 -0
  50. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/result.py +0 -0
  51. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/row.py +0 -0
  52. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/sequence.py +0 -0
  53. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/transaction.py +0 -0
  54. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/core/view.py +0 -0
  55. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/exceptions.py +0 -0
  56. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/migrations.py +0 -0
  57. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/__init__.py +0 -0
  58. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/base/__init__.py +0 -0
  59. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/base/initializer.py +0 -0
  60. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/base/operators.py +0 -0
  61. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/base/types.py +0 -0
  62. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/mysql/__init__.py +0 -0
  63. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/mysql/operators.py +0 -0
  64. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/mysql/reserved.py +0 -0
  65. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/mysql/types.py +0 -0
  66. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/postgres/__init__.py +0 -0
  67. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/postgres/operators.py +0 -0
  68. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/postgres/reserved.py +0 -0
  69. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/postgres/types.py +0 -0
  70. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  71. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlite/operators.py +0 -0
  72. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  73. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlite/types.py +0 -0
  74. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  75. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  76. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  77. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/sqlserver/types.py +0 -0
  78. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/servers/tablehelper.py +0 -0
  79. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/__init__.py +0 -0
  80. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/common_db_test.py +0 -0
  81. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/__init__.py +0 -0
  82. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/common.py +0 -0
  83. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_column.py +0 -0
  84. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  85. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_database.py +0 -0
  86. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  87. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  88. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  89. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_result.py +0 -0
  90. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_row.py +0 -0
  91. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  92. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  93. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  94. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  95. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_table.py +0 -0
  96. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  97. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  98. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/sql/__init__.py +0 -0
  99. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/sql/common.py +0 -0
  100. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  101. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  102. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  103. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_db_utils.py +0 -0
  104. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_postgres.py +0 -0
  105. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  106. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  107. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_result_caching.py +0 -0
  108. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  109. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  110. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  111. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  112. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_sql_builder.py +0 -0
  113. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_tablehelper.py +0 -0
  114. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/tests/test_view_helper.py +0 -0
  115. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/db/utils.py +0 -0
  116. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/logging.py +0 -0
  117. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/__init__.py +0 -0
  118. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/conv/__init__.py +0 -0
  119. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/conv/iconv.py +0 -0
  120. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/conv/oconv.py +0 -0
  121. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/db.py +0 -0
  122. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/export.py +0 -0
  123. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/format.py +0 -0
  124. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/mail.py +0 -0
  125. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/merge.py +0 -0
  126. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/pdf.py +0 -0
  127. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/__init__.py +0 -0
  128. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_db.py +0 -0
  129. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_fix.py +0 -0
  130. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_format.py +0 -0
  131. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_iconv.py +0 -0
  132. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_merge.py +0 -0
  133. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_oconv.py +0 -0
  134. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_original_error.py +0 -0
  135. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tests/test_timer.py +0 -0
  136. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/timer.py +0 -0
  137. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/misc/tools.py +0 -0
  138. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/__init__.py +0 -0
  139. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/authorizenet_adapter.py +0 -0
  140. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/authorizenet_mirror.py +0 -0
  141. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/base_adapter.py +0 -0
  142. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/braintree_adapter.py +0 -0
  143. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/braintree_mirror.py +0 -0
  144. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/charge_rules.py +0 -0
  145. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/stripe_adapter.py +0 -0
  146. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity/payment/stripe_mirror.py +0 -0
  147. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity_python.egg-info/SOURCES.txt +0 -0
  148. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  149. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity_python.egg-info/entry_points.txt +0 -0
  150. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity_python.egg-info/requires.txt +0 -0
  151. {velocity_python-0.1.38 → velocity_python-0.1.39}/src/velocity_python.egg-info/top_level.txt +0 -0
  152. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_amplify_build.py +0 -0
  153. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_asset_indexing.py +0 -0
  154. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_asset_references.py +0 -0
  155. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_assets_service.py +0 -0
  156. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_async_support.py +0 -0
  157. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_batch_operations.py +0 -0
  158. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_concurrency_safety.py +0 -0
  159. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_connection_pool.py +0 -0
  160. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_connection_resilience.py +0 -0
  161. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_decorators.py +0 -0
  162. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_dirty_pipeline_fast_path.py +0 -0
  163. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_email_processing.py +0 -0
  164. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_iconv_money_to_cents.py +0 -0
  165. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_lambda_handler.py +0 -0
  166. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_lambda_handler_auth.py +0 -0
  167. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_mixins_import.py +0 -0
  168. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_n_plus_one.py +0 -0
  169. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_observability.py +0 -0
  170. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_payment_authorizenet_adapter.py +0 -0
  171. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_payment_braintree_adapter.py +0 -0
  172. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_payment_braintree_mirror.py +0 -0
  173. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_payment_profile_sorting.py +0 -0
  174. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_payment_stripe_adapter.py +0 -0
  175. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_pdf.py +0 -0
  176. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_prepared_statements.py +0 -0
  177. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_psycopg3_upgrade.py +0 -0
  178. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_query_cache.py +0 -0
  179. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_row_batch_update.py +0 -0
  180. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_row_cache_staleness.py +0 -0
  181. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_row_dirty_tracking.py +0 -0
  182. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_schema_migrations.py +0 -0
  183. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_security_hardening.py +0 -0
  184. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_server_cursor.py +0 -0
  185. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_spreadsheet_functions.py +0 -0
  186. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_sqs_per_record_transactions.py +0 -0
  187. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  188. {velocity_python-0.1.38 → velocity_python-0.1.39}/tests/test_table_alter.py +0 -0
  189. {velocity_python-0.1.38 → velocity_python-0.1.39}/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.38
3
+ Version: 0.1.39
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.38"
7
+ version = "0.1.39"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.38"
1
+ __version__ = version = "0.1.39"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -155,6 +155,12 @@ def _normalize_identifier(value):
155
155
  return str(value or "").strip().strip('"').lower()
156
156
 
157
157
 
158
+ def _normalize_referential_action(value):
159
+ if not value:
160
+ return "NO ACTION"
161
+ return " ".join(str(value).strip().upper().split())
162
+
163
+
158
164
  def _split_sql_expressions(text):
159
165
  expressions = []
160
166
  current = []
@@ -267,12 +273,14 @@ def _normalize_index_specs(indexes, *, unique_default=False):
267
273
  direction = definition.get("direction")
268
274
  where = definition.get("where")
269
275
  lower = definition.get("lower")
276
+ name = definition.get("name")
270
277
  else:
271
278
  columns = definition
272
279
  unique = unique_default
273
280
  direction = None
274
281
  where = None
275
282
  lower = None
283
+ name = None
276
284
 
277
285
  if isinstance(columns, str):
278
286
  columns = [column.strip() for column in columns.split(",") if column.strip()]
@@ -288,6 +296,7 @@ def _normalize_index_specs(indexes, *, unique_default=False):
288
296
  "direction": direction,
289
297
  "where": where,
290
298
  "lower": lower,
299
+ "name": _normalize_identifier(name) if name else None,
291
300
  }
292
301
  )
293
302
 
@@ -315,6 +324,9 @@ def _normalize_foreign_key_specs(foreign_keys):
315
324
  raise ValueError("Foreign key definition requires ref_table")
316
325
 
317
326
  ref_columns = definition.get("ref_columns") or definition.get("key_to_columns") or "sys_id"
327
+ name = definition.get("name")
328
+ on_delete = _normalize_referential_action(definition.get("on_delete"))
329
+ on_update = _normalize_referential_action(definition.get("on_update"))
318
330
 
319
331
  if isinstance(columns, str):
320
332
  columns = [column.strip() for column in columns.split(",") if column.strip()]
@@ -337,6 +349,9 @@ def _normalize_foreign_key_specs(foreign_keys):
337
349
  "ref_columns": tuple(
338
350
  _normalize_identifier(column) for column in ref_columns
339
351
  ),
352
+ "name": _normalize_identifier(name) if name else None,
353
+ "on_delete": on_delete,
354
+ "on_update": on_update,
340
355
  }
341
356
  )
342
357
 
@@ -430,7 +445,14 @@ class Table:
430
445
 
431
446
  @return_default(None, (exceptions.DbObjectExistsError,))
432
447
  def create_index(
433
- self, columns, unique=False, direction=None, where=None, lower=None, **kwds
448
+ self,
449
+ columns,
450
+ unique=False,
451
+ direction=None,
452
+ where=None,
453
+ lower=None,
454
+ name=None,
455
+ **kwds,
434
456
  ):
435
457
  """
436
458
  Creates an index on the given columns. Returns None on success, or `None` if the index already exists.
@@ -445,6 +467,7 @@ class Table:
445
467
  direction=direction,
446
468
  where=where,
447
469
  lower=lower,
470
+ name=name,
448
471
  )
449
472
  if kwds.get("sql_only", False):
450
473
  return sql, vals
@@ -484,6 +507,7 @@ class Table:
484
507
  "direction": definition.get("direction"),
485
508
  "where": definition.get("where"),
486
509
  "lower": definition.get("lower"),
510
+ "name": definition.get("name"),
487
511
  }
488
512
  else:
489
513
  columns = definition
@@ -492,6 +516,7 @@ class Table:
492
516
  "direction": None,
493
517
  "where": None,
494
518
  "lower": None,
519
+ "name": None,
495
520
  }
496
521
 
497
522
  if isinstance(columns, str):
@@ -558,6 +583,7 @@ class Table:
558
583
  direction=spec["direction"],
559
584
  where=spec["where"],
560
585
  lower=spec["lower"],
586
+ name=spec["name"],
561
587
  sql_only=True,
562
588
  )
563
589
  return _parse_index_signature(sql)
@@ -594,6 +620,8 @@ class Table:
594
620
  _normalize_identifier(item["referenced_column_name"])
595
621
  for item in ordered
596
622
  ),
623
+ "on_delete": _normalize_referential_action(first.get("delete_rule")),
624
+ "on_update": _normalize_referential_action(first.get("update_rule")),
597
625
  }
598
626
  )
599
627
  return signatures
@@ -710,6 +738,7 @@ class Table:
710
738
  summary["skipped_indexes"].append(
711
739
  {
712
740
  "columns": list(spec["columns"]),
741
+ "name": spec["name"],
713
742
  "reason": message,
714
743
  }
715
744
  )
@@ -719,6 +748,7 @@ class Table:
719
748
  summary["skipped_indexes"].append(
720
749
  {
721
750
  "columns": list(spec["columns"]),
751
+ "name": spec["name"],
722
752
  "reason": "create_missing_indexes is disabled",
723
753
  }
724
754
  )
@@ -732,11 +762,13 @@ class Table:
732
762
  direction=spec["direction"],
733
763
  where=spec["where"],
734
764
  lower=spec["lower"],
765
+ name=spec["name"],
735
766
  )
736
767
  summary["created_indexes"].append(
737
768
  {
738
769
  "columns": list(spec["columns"]),
739
770
  "unique": spec["unique"],
771
+ "name": spec["name"],
740
772
  }
741
773
  )
742
774
  existing_indexes = self._existing_index_signatures()
@@ -750,6 +782,7 @@ class Table:
750
782
  summary["skipped_indexes"].append(
751
783
  {
752
784
  "columns": list(spec["columns"]),
785
+ "name": spec["name"],
753
786
  "reason": message,
754
787
  }
755
788
  )
@@ -763,12 +796,40 @@ class Table:
763
796
  if existing["columns"] == spec["columns"]
764
797
  and existing["ref_table"] == spec["ref_table"]
765
798
  and existing["ref_columns"] == spec["ref_columns"]
799
+ and existing["on_delete"] == spec["on_delete"]
800
+ and existing["on_update"] == spec["on_update"]
766
801
  ),
767
802
  None,
768
803
  )
769
804
  if exact_match:
770
805
  continue
771
806
 
807
+ name_conflict = None
808
+ if spec["name"]:
809
+ name_conflict = next(
810
+ (
811
+ existing
812
+ for existing in existing_foreign_keys
813
+ if existing["constraint_name"] == spec["name"]
814
+ ),
815
+ None,
816
+ )
817
+ if name_conflict:
818
+ message = (
819
+ f"Foreign key '{spec['name']}' on table '{self.name}' exists "
820
+ "with a different definition."
821
+ )
822
+ if on_existing_conflicts == "raise":
823
+ raise exceptions.DbSchemaConflictError(message)
824
+ summary["skipped_foreign_keys"].append(
825
+ {
826
+ "columns": list(spec["columns"]),
827
+ "name": spec["name"],
828
+ "reason": message,
829
+ }
830
+ )
831
+ continue
832
+
772
833
  column_conflict = next(
773
834
  (
774
835
  existing
@@ -780,13 +841,14 @@ class Table:
780
841
  if column_conflict:
781
842
  message = (
782
843
  f"Foreign key on table '{self.name}' for columns {spec['columns']} "
783
- "exists with a different target."
844
+ "exists with a different target or referential action."
784
845
  )
785
846
  if on_existing_conflicts == "raise":
786
847
  raise exceptions.DbSchemaConflictError(message)
787
848
  summary["skipped_foreign_keys"].append(
788
849
  {
789
850
  "columns": list(spec["columns"]),
851
+ "name": spec["name"],
790
852
  "reason": message,
791
853
  }
792
854
  )
@@ -796,6 +858,7 @@ class Table:
796
858
  summary["skipped_foreign_keys"].append(
797
859
  {
798
860
  "columns": list(spec["columns"]),
861
+ "name": spec["name"],
799
862
  "reason": "create_missing_foreign_keys is disabled",
800
863
  }
801
864
  )
@@ -806,12 +869,18 @@ class Table:
806
869
  list(spec["columns"]),
807
870
  spec["ref_table"],
808
871
  list(spec["ref_columns"]),
872
+ name=spec["name"],
873
+ on_delete=spec["on_delete"],
874
+ on_update=spec["on_update"],
809
875
  )
810
876
  summary["created_foreign_keys"].append(
811
877
  {
812
878
  "columns": list(spec["columns"]),
813
879
  "ref_table": spec["ref_table"],
814
880
  "ref_columns": list(spec["ref_columns"]),
881
+ "name": spec["name"],
882
+ "on_delete": spec["on_delete"],
883
+ "on_update": spec["on_update"],
815
884
  }
816
885
  )
817
886
  existing_foreign_keys = self._existing_foreign_key_signatures()
@@ -1124,25 +1193,53 @@ class Table:
1124
1193
 
1125
1194
  @return_default()
1126
1195
  def create_foreign_key(
1127
- self, columns, key_to_table, key_to_columns="sys_id", **kwds
1196
+ self,
1197
+ columns,
1198
+ key_to_table,
1199
+ key_to_columns="sys_id",
1200
+ name=None,
1201
+ on_delete=None,
1202
+ on_update=None,
1203
+ **kwds,
1128
1204
  ):
1129
1205
  """
1130
1206
  Creates a foreign key referencing `key_to_table(key_to_columns)`.
1131
1207
  """
1132
1208
  sql, vals = self.sql.create_foreign_key(
1133
- self.name, columns, key_to_table, key_to_columns
1209
+ self.name,
1210
+ columns,
1211
+ key_to_table,
1212
+ key_to_columns,
1213
+ name=name,
1214
+ on_delete=on_delete,
1215
+ on_update=on_update,
1134
1216
  )
1135
1217
  if kwds.get("sql_only", False):
1136
1218
  return sql, vals
1137
1219
  _ddl_logger.warning("DDL CREATE FOREIGN KEY on %s columns=%s ref=%s(%s)", self.name, columns, key_to_table, key_to_columns)
1138
1220
  return self.tx.execute(sql, vals, cursor=self.cursor())
1139
1221
 
1140
- def drop_foreign_key(self, columns, key_to_table, key_to_columns="sys_id", **kwds):
1222
+ def drop_foreign_key(
1223
+ self,
1224
+ columns,
1225
+ key_to_table,
1226
+ key_to_columns="sys_id",
1227
+ name=None,
1228
+ on_delete=None,
1229
+ on_update=None,
1230
+ **kwds,
1231
+ ):
1141
1232
  """
1142
1233
  Drops the specified foreign key constraint.
1143
1234
  """
1144
- sql, vals = self.sql.create_foreign_key(
1145
- self.name, columns, key_to_table, key_to_columns
1235
+ sql, vals = self.sql.drop_foreign_key(
1236
+ self.name,
1237
+ columns,
1238
+ key_to_table,
1239
+ key_to_columns,
1240
+ name=name,
1241
+ on_delete=on_delete,
1242
+ on_update=on_update,
1146
1243
  )
1147
1244
  if kwds.get("sql_only", False):
1148
1245
  return sql, vals
@@ -407,6 +407,8 @@ class BaseSQLDialect(ABC):
407
407
  key_to_columns: List[str],
408
408
  name: Optional[str] = None,
409
409
  schema: Optional[str] = None,
410
+ on_delete: Optional[str] = None,
411
+ on_update: Optional[str] = None,
410
412
  ) -> str:
411
413
  """Generate CREATE FOREIGN KEY statement."""
412
414
  pass
@@ -421,6 +423,8 @@ class BaseSQLDialect(ABC):
421
423
  key_to_columns: Optional[List[str]] = None,
422
424
  name: Optional[str] = None,
423
425
  schema: Optional[str] = None,
426
+ on_delete: Optional[str] = None,
427
+ on_update: Optional[str] = None,
424
428
  ) -> str:
425
429
  """Generate DROP FOREIGN KEY statement."""
426
430
  pass
@@ -654,7 +654,15 @@ END;
654
654
 
655
655
  @classmethod
656
656
  def create_foreign_key(
657
- cls, table, columns, key_to_table, key_to_columns, name=None, schema=None
657
+ cls,
658
+ table,
659
+ columns,
660
+ key_to_table,
661
+ key_to_columns,
662
+ name=None,
663
+ schema=None,
664
+ on_delete=None,
665
+ on_update=None,
658
666
  ):
659
667
  if name is None:
660
668
  name = f"fk_{table}_{'_'.join(columns)}"
@@ -662,12 +670,17 @@ END;
662
670
  col_list = ", ".join(quote(col) for col in columns)
663
671
  ref_col_list = ", ".join(quote(col) for col in key_to_columns)
664
672
 
665
- return f"""
673
+ sql = f"""
666
674
  ALTER TABLE {quote(table)}
667
675
  ADD CONSTRAINT {quote(name)}
668
676
  FOREIGN KEY ({col_list})
669
677
  REFERENCES {quote(key_to_table)} ({ref_col_list})
670
678
  """
679
+ if on_delete:
680
+ sql += f" ON DELETE {str(on_delete).upper()}"
681
+ if on_update:
682
+ sql += f" ON UPDATE {str(on_update).upper()}"
683
+ return sql
671
684
 
672
685
  @classmethod
673
686
  def drop_foreign_key(
@@ -678,6 +691,8 @@ END;
678
691
  key_to_columns=None,
679
692
  name=None,
680
693
  schema=None,
694
+ on_delete=None,
695
+ on_update=None,
681
696
  ):
682
697
  if name is None:
683
698
  name = f"fk_{table}_{'_'.join(columns)}"
@@ -1461,6 +1461,8 @@ class SQL(BaseSQLDialect):
1461
1461
  , KCU2.CONSTRAINT_SCHEMA AS "REFERENCED_TABLE_SCHEMA"
1462
1462
  , KCU2.TABLE_NAME AS "REFERENCED_TABLE_NAME"
1463
1463
  , KCU2.COLUMN_NAME AS "REFERENCED_COLUMN_NAME"
1464
+ , RC.UPDATE_RULE AS "UPDATE_RULE"
1465
+ , RC.DELETE_RULE AS "DELETE_RULE"
1464
1466
  FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
1465
1467
  JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU1
1466
1468
  ON KCU1.CONSTRAINT_CATALOG = RC.CONSTRAINT_CATALOG
@@ -1495,7 +1497,15 @@ class SQL(BaseSQLDialect):
1495
1497
 
1496
1498
  @classmethod
1497
1499
  def create_foreign_key(
1498
- cls, table, columns, key_to_table, key_to_columns, name=None, schema=None
1500
+ cls,
1501
+ table,
1502
+ columns,
1503
+ key_to_table,
1504
+ key_to_columns,
1505
+ name=None,
1506
+ schema=None,
1507
+ on_delete=None,
1508
+ on_update=None,
1499
1509
  ):
1500
1510
  if "." not in table and schema:
1501
1511
  table = f"{schema}.{table}"
@@ -1510,7 +1520,15 @@ class SQL(BaseSQLDialect):
1510
1520
  m.update(key_to_table.encode("utf-8"))
1511
1521
  m.update(" ".join(key_to_columns).encode("utf-8"))
1512
1522
  name = f"FK_{m.hexdigest()}"
1513
- sql = f"ALTER TABLE {table} ADD CONSTRAINT {name} FOREIGN KEY ({','.join(columns)}) REFERENCES {key_to_table} ({','.join(key_to_columns)});"
1523
+ sql = (
1524
+ f"ALTER TABLE {table} ADD CONSTRAINT {name} FOREIGN KEY ({','.join(columns)}) "
1525
+ f"REFERENCES {key_to_table} ({','.join(key_to_columns)})"
1526
+ )
1527
+ if on_delete:
1528
+ sql += f" ON DELETE {str(on_delete).upper()}"
1529
+ if on_update:
1530
+ sql += f" ON UPDATE {str(on_update).upper()}"
1531
+ sql += ";"
1514
1532
  sql = sqlparse.format(sql, reindent=True, keyword_case="upper")
1515
1533
  return sql, tuple()
1516
1534
 
@@ -1523,6 +1541,8 @@ class SQL(BaseSQLDialect):
1523
1541
  key_to_columns=None,
1524
1542
  name=None,
1525
1543
  schema=None,
1544
+ on_delete=None,
1545
+ on_update=None,
1526
1546
  ):
1527
1547
  if "." not in table and schema:
1528
1548
  table = f"{schema}.{table}"
@@ -1576,10 +1596,11 @@ class SQL(BaseSQLDialect):
1576
1596
  "",
1577
1597
  columns.replace(" ", "").replace(",", "_").replace('"', ""),
1578
1598
  )
1579
- if trigram:
1580
- sql.append(f"IDX__TRGM_{table.replace('.', '_')}_{trigram}__{name}".upper())
1581
- else:
1582
- sql.append(f"IDX__{table.replace('.', '_')}__{name}".upper())
1599
+ if trigram:
1600
+ name = f"IDX__TRGM_{table.replace('.', '_')}_{trigram}__{name}".upper()
1601
+ else:
1602
+ name = f"IDX__{table.replace('.', '_')}__{name}".upper()
1603
+ sql.append(name)
1583
1604
  sql.append("ON")
1584
1605
  sql.append(TableHelper.quote(tablename))
1585
1606
 
@@ -1633,10 +1654,11 @@ class SQL(BaseSQLDialect):
1633
1654
  "",
1634
1655
  columns.replace(" ", "").replace(",", "_").replace('"', ""),
1635
1656
  )
1636
- if trigram:
1637
- sql.append(f"IDX__TRGM_{table.replace('.', '_')}_{trigram.upper()}__{name}")
1638
- else:
1639
- sql.append(f"IDX__{table.replace('.', '_')}__{name}")
1657
+ if trigram:
1658
+ name = f"IDX__TRGM_{table.replace('.', '_')}_{trigram.upper()}__{name}"
1659
+ else:
1660
+ name = f"IDX__{table.replace('.', '_')}__{name}"
1661
+ sql.append(name)
1640
1662
 
1641
1663
  sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
1642
1664
  return sql, tuple()
@@ -593,7 +593,15 @@ END;
593
593
 
594
594
  @classmethod
595
595
  def create_foreign_key(
596
- cls, table, columns, key_to_table, key_to_columns, name=None, schema=None
596
+ cls,
597
+ table,
598
+ columns,
599
+ key_to_table,
600
+ key_to_columns,
601
+ name=None,
602
+ schema=None,
603
+ on_delete=None,
604
+ on_update=None,
597
605
  ):
598
606
  # SQLite foreign keys must be defined at table creation time
599
607
  return "-- SQLite foreign keys must be defined at table creation"
@@ -607,6 +615,8 @@ END;
607
615
  key_to_columns=None,
608
616
  name=None,
609
617
  schema=None,
618
+ on_delete=None,
619
+ on_update=None,
610
620
  ):
611
621
  return "-- SQLite foreign keys must be dropped by recreating table"
612
622
 
@@ -733,7 +733,15 @@ END;
733
733
 
734
734
  @classmethod
735
735
  def create_foreign_key(
736
- cls, table, columns, key_to_table, key_to_columns, name=None, schema=None
736
+ cls,
737
+ table,
738
+ columns,
739
+ key_to_table,
740
+ key_to_columns,
741
+ name=None,
742
+ schema=None,
743
+ on_delete=None,
744
+ on_update=None,
737
745
  ):
738
746
  if name is None:
739
747
  name = f"FK_{table}_{'_'.join(columns)}"
@@ -741,12 +749,17 @@ END;
741
749
  col_list = ", ".join(quote(col) for col in columns)
742
750
  ref_col_list = ", ".join(quote(col) for col in key_to_columns)
743
751
 
744
- return f"""
752
+ sql = f"""
745
753
  ALTER TABLE {quote(table)}
746
754
  ADD CONSTRAINT {quote(name)}
747
755
  FOREIGN KEY ({col_list})
748
756
  REFERENCES {quote(key_to_table)} ({ref_col_list})
749
757
  """
758
+ if on_delete:
759
+ sql += f" ON DELETE {str(on_delete).upper()}"
760
+ if on_update:
761
+ sql += f" ON UPDATE {str(on_update).upper()}"
762
+ return sql
750
763
 
751
764
  @classmethod
752
765
  def drop_foreign_key(
@@ -757,6 +770,8 @@ END;
757
770
  key_to_columns=None,
758
771
  name=None,
759
772
  schema=None,
773
+ on_delete=None,
774
+ on_update=None,
760
775
  ):
761
776
  if name is None:
762
777
  name = f"FK_{table}_{'_'.join(columns)}"
@@ -253,6 +253,71 @@ class TestSchemaLocking(unittest.TestCase):
253
253
 
254
254
  run()
255
255
 
256
+ def test_ensure_schema_supports_named_indexes_and_foreign_keys(self):
257
+ """ensure_schema should preserve explicit names and referential actions."""
258
+ unlocked_engine = postgres.initialize(
259
+ database=test_db, schema_locked=False, pool_enabled=False
260
+ )
261
+
262
+ @unlocked_engine.transaction
263
+ def run(tx):
264
+ tx.table("schema_departments").ensure_schema(columns={"name": str})
265
+
266
+ summary = tx.table("schema_memberships").ensure_schema(
267
+ columns={
268
+ "email": str,
269
+ "status": str,
270
+ "department_id": int,
271
+ },
272
+ unique_indexes=[
273
+ {"columns": ["email"], "name": "schema_memberships_email_uq"}
274
+ ],
275
+ indexes=[
276
+ {"columns": ["status"], "name": "schema_memberships_status_idx"}
277
+ ],
278
+ foreign_keys=[
279
+ {
280
+ "columns": ["department_id"],
281
+ "ref_table": "schema_departments",
282
+ "ref_columns": ["sys_id"],
283
+ "name": "schema_memberships_department_fk",
284
+ "on_delete": "cascade",
285
+ }
286
+ ],
287
+ create_missing_foreign_keys=True,
288
+ )
289
+
290
+ self.assertEqual(summary["created_indexes"][0]["name"], "schema_memberships_email_uq")
291
+ self.assertEqual(summary["created_foreign_keys"][0]["name"], "schema_memberships_department_fk")
292
+ self.assertEqual(summary["created_foreign_keys"][0]["on_delete"], "CASCADE")
293
+
294
+ index_names = {
295
+ row["indexname"].lower()
296
+ for row in tx.execute(
297
+ """
298
+ SELECT indexname
299
+ FROM pg_indexes
300
+ WHERE schemaname = 'public' AND tablename = %s
301
+ """,
302
+ ["schema_memberships"],
303
+ ).as_dict().all()
304
+ }
305
+ self.assertIn("schema_memberships_email_uq", index_names)
306
+ self.assertIn("schema_memberships_status_idx", index_names)
307
+
308
+ fk_rules = tx.execute(
309
+ """
310
+ SELECT constraint_name, delete_rule
311
+ FROM information_schema.referential_constraints
312
+ WHERE constraint_name = %s
313
+ """,
314
+ ["schema_memberships_department_fk"],
315
+ ).as_dict().all()
316
+ self.assertEqual(len(fk_rules), 1)
317
+ self.assertEqual(fk_rules[0]["delete_rule"], "CASCADE")
318
+
319
+ run()
320
+
256
321
  def test_ensure_schema_raises_schema_conflict_for_duplicate_unique_rows(self):
257
322
  """ensure_schema should raise a schema conflict when unique index backfill finds duplicates."""
258
323
  unlocked_engine = postgres.initialize(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.38
3
+ Version: 0.1.39
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