velocity-python 0.1.125__tar.gz → 0.1.127__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 (312) hide show
  1. {velocity_python-0.1.125 → velocity_python-0.1.127}/PKG-INFO +1 -1
  2. {velocity_python-0.1.125 → velocity_python-0.1.127}/pyproject.toml +1 -1
  3. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/jsonproxy.py +32 -0
  5. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/maintenance.py +11 -6
  6. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/postgres/__init__.py +5 -0
  7. velocity_python-0.1.127/src/velocity/db/tunnel.py +49 -0
  8. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/PKG-INFO +1 -1
  9. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  10. velocity_python-0.1.127/tests/test_db_tunnel.py +127 -0
  11. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_json_columns.py +57 -0
  12. {velocity_python-0.1.125 → velocity_python-0.1.127}/LICENSE +0 -0
  13. {velocity_python-0.1.125 → velocity_python-0.1.127}/README.md +0 -0
  14. {velocity_python-0.1.125 → velocity_python-0.1.127}/setup.cfg +0 -0
  15. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/__init__.py +0 -0
  16. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/amplify.py +0 -0
  17. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/amplify_build.py +0 -0
  18. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/__init__.py +0 -0
  19. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/backfill.py +0 -0
  20. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/indexing.py +0 -0
  21. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/references.py +0 -0
  22. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/service.py +0 -0
  23. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/assets/usage_index.py +0 -0
  24. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/blobs.py +0 -0
  25. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/__init__.py +0 -0
  26. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/base.py +0 -0
  27. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/dispatch.py +0 -0
  28. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/infra.py +0 -0
  29. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/jobs.py +0 -0
  30. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/provisioning.py +0 -0
  31. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/retention.py +0 -0
  32. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/security.py +0 -0
  33. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/capabilities/single_session.py +0 -0
  34. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/__init__.py +0 -0
  35. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/config.py +0 -0
  36. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/operations.py +0 -0
  37. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/policies.py +0 -0
  38. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/reconcile.py +0 -0
  39. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/deploy/targets.py +0 -0
  40. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/dirty_pipeline.py +0 -0
  41. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/email.py +0 -0
  42. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/facets.py +0 -0
  43. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/feature_flags.py +0 -0
  44. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/__init__.py +0 -0
  45. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/base_handler.py +0 -0
  46. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/context.py +0 -0
  47. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/context_factory.py +0 -0
  48. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/exceptions.py +0 -0
  49. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  50. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/masquerade.py +0 -0
  51. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  52. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  53. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  54. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  55. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  56. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/perf.py +0 -0
  57. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/response.py +0 -0
  58. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  59. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/job_lifecycle.py +0 -0
  60. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/job_results.py +0 -0
  61. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/mirror.py +0 -0
  62. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/ops.py +0 -0
  63. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/s3.py +0 -0
  64. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/sms.py +0 -0
  65. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/ssm_config.py +0 -0
  66. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/__init__.py +0 -0
  67. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  68. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  69. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  70. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  71. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  72. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_email.py +0 -0
  73. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  74. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_response.py +0 -0
  75. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/aws/tests/test_sms.py +0 -0
  76. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/cache.py +0 -0
  77. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/__init__.py +0 -0
  78. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/__init__.py +0 -0
  79. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/async_support.py +0 -0
  80. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/column.py +0 -0
  81. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/database.py +0 -0
  82. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/decorators.py +0 -0
  83. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/engine.py +0 -0
  84. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/lazy_engine.py +0 -0
  85. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/result.py +0 -0
  86. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/row.py +0 -0
  87. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/sequence.py +0 -0
  88. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/table.py +0 -0
  89. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/transaction.py +0 -0
  90. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/core/view.py +0 -0
  91. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/exceptions.py +0 -0
  92. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/migrations.py +0 -0
  93. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/__init__.py +0 -0
  94. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/base/__init__.py +0 -0
  95. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/base/initializer.py +0 -0
  96. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/base/operators.py +0 -0
  97. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/base/sql.py +0 -0
  98. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/base/types.py +0 -0
  99. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/mysql/__init__.py +0 -0
  100. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/mysql/operators.py +0 -0
  101. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/mysql/reserved.py +0 -0
  102. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/mysql/sql.py +0 -0
  103. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/mysql/types.py +0 -0
  104. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/postgres/operators.py +0 -0
  105. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/postgres/reserved.py +0 -0
  106. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/postgres/sql.py +0 -0
  107. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/postgres/types.py +0 -0
  108. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  109. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlite/operators.py +0 -0
  110. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  111. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlite/sql.py +0 -0
  112. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlite/types.py +0 -0
  113. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  114. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  115. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  116. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  117. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/sqlserver/types.py +0 -0
  118. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/servers/tablehelper.py +0 -0
  119. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/__init__.py +0 -0
  120. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/common_db_test.py +0 -0
  121. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/__init__.py +0 -0
  122. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/common.py +0 -0
  123. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/conftest.py +0 -0
  124. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_column.py +0 -0
  125. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  126. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_database.py +0 -0
  127. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  128. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  129. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  130. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_result.py +0 -0
  131. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_row.py +0 -0
  132. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  133. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  134. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  135. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  136. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  137. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_table.py +0 -0
  138. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  139. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  140. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/sql/__init__.py +0 -0
  141. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/sql/common.py +0 -0
  142. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  143. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  144. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  145. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_db_utils.py +0 -0
  146. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_postgres.py +0 -0
  147. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  148. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  149. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_result_caching.py +0 -0
  150. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  151. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  152. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  153. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  154. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_sql_builder.py +0 -0
  155. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_tablehelper.py +0 -0
  156. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/tests/test_view_helper.py +0 -0
  157. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/db/utils.py +0 -0
  158. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/logging.py +0 -0
  159. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/__init__.py +0 -0
  160. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/conv/__init__.py +0 -0
  161. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/conv/iconv.py +0 -0
  162. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/conv/oconv.py +0 -0
  163. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/db.py +0 -0
  164. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/export.py +0 -0
  165. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/format.py +0 -0
  166. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/mail.py +0 -0
  167. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/merge.py +0 -0
  168. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/pdf.py +0 -0
  169. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/__init__.py +0 -0
  170. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_db.py +0 -0
  171. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_fix.py +0 -0
  172. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_format.py +0 -0
  173. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_iconv.py +0 -0
  174. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_mail_send.py +0 -0
  175. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_merge.py +0 -0
  176. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_oconv.py +0 -0
  177. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_original_error.py +0 -0
  178. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tests/test_timer.py +0 -0
  179. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/timer.py +0 -0
  180. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/misc/tools.py +0 -0
  181. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/__init__.py +0 -0
  182. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/core/__init__.py +0 -0
  183. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/core/capabilities.py +0 -0
  184. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/core/store.py +0 -0
  185. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/core/where.py +0 -0
  186. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/exceptions.py +0 -0
  187. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/__init__.py +0 -0
  188. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/base/__init__.py +0 -0
  189. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  190. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  191. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  192. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  193. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  194. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  195. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  196. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  197. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  198. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  199. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  200. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  201. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/__init__.py +0 -0
  202. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/authorizenet_adapter.py +0 -0
  203. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/authorizenet_mirror.py +0 -0
  204. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/base_adapter.py +0 -0
  205. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/braintree_adapter.py +0 -0
  206. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/braintree_mirror.py +0 -0
  207. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/charge_rules.py +0 -0
  208. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/stripe_adapter.py +0 -0
  209. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity/payment/stripe_mirror.py +0 -0
  210. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  211. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/entry_points.txt +0 -0
  212. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/requires.txt +0 -0
  213. {velocity_python-0.1.125 → velocity_python-0.1.127}/src/velocity_python.egg-info/top_level.txt +0 -0
  214. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_access_error_403.py +0 -0
  215. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_activity_tracking_aborted_tx.py +0 -0
  216. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_amplify_build.py +0 -0
  217. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_asset_indexing.py +0 -0
  218. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_asset_references.py +0 -0
  219. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_assets_service.py +0 -0
  220. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_async_support.py +0 -0
  221. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_batch_operations.py +0 -0
  222. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_blob_store.py +0 -0
  223. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_capabilities.py +0 -0
  224. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_capability_catalog.py +0 -0
  225. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_cognito_user_cache.py +0 -0
  226. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_column_tx_arg.py +0 -0
  227. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_concurrency_safety.py +0 -0
  228. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_connection_pool.py +0 -0
  229. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_connection_resilience.py +0 -0
  230. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_context_flags_loader.py +0 -0
  231. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_context_job_descriptions.py +0 -0
  232. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_data_service_query_cap.py +0 -0
  233. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_data_service_renumber.py +0 -0
  234. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_data_service_save_as.py +0 -0
  235. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_db_credentials_ssm_cascade.py +0 -0
  236. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_db_maintenance.py +0 -0
  237. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_decorators.py +0 -0
  238. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_delete_hook_fk_blocked.py +0 -0
  239. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_dirty_pipeline_fast_path.py +0 -0
  240. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_email_processing.py +0 -0
  241. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_enqueue_metric_namespace.py +0 -0
  242. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_enqueue_send_failures.py +0 -0
  243. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_facets.py +0 -0
  244. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_feature_flags.py +0 -0
  245. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_get_cognito_user_provider.py +0 -0
  246. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_http_handler_rollback.py +0 -0
  247. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_iconv_money_to_cents.py +0 -0
  248. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_identifier_injection_guard.py +0 -0
  249. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_job_handler.py +0 -0
  250. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_job_lifecycle.py +0 -0
  251. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_job_results.py +0 -0
  252. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_jsonb_dict_adapter.py +0 -0
  253. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_lambda_handler.py +0 -0
  254. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_lambda_handler_auth.py +0 -0
  255. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_lambda_handler_masquerade.py +0 -0
  256. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_lambda_handler_user_cache.py +0 -0
  257. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_lazy_engine.py +0 -0
  258. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_masquerade_grant.py +0 -0
  259. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_mirror.py +0 -0
  260. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_mixins_import.py +0 -0
  261. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_n_plus_one.py +0 -0
  262. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_dynamodb.py +0 -0
  263. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_memory.py +0 -0
  264. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_mongodb.py +0 -0
  265. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_redis.py +0 -0
  266. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_translation.py +0 -0
  267. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_nosql_where.py +0 -0
  268. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_observability.py +0 -0
  269. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_on_commit.py +0 -0
  270. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_ops.py +0 -0
  271. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_authorizenet_adapter.py +0 -0
  272. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_braintree_adapter.py +0 -0
  273. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_braintree_mirror.py +0 -0
  274. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_mirror_empty_table.py +0 -0
  275. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_profile_sorting.py +0 -0
  276. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_payment_stripe_adapter.py +0 -0
  277. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_pdf.py +0 -0
  278. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_prepared_statements.py +0 -0
  279. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_psycopg3_upgrade.py +0 -0
  280. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_query_cache.py +0 -0
  281. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_query_missing_table_unlocked.py +0 -0
  282. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_read_cache.py +0 -0
  283. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_response.py +0 -0
  284. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_restricted_direct_tables.py +0 -0
  285. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_result_returning.py +0 -0
  286. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_retry_side_effect_guard.py +0 -0
  287. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_return_default_safety.py +0 -0
  288. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_row_batch_update.py +0 -0
  289. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_row_cache_staleness.py +0 -0
  290. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_s3_bucket_config.py +0 -0
  291. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_schema_migrations.py +0 -0
  292. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_security_capability.py +0 -0
  293. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_security_hardening.py +0 -0
  294. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_server_cursor.py +0 -0
  295. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_session_control.py +0 -0
  296. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_single_autocommit_safety.py +0 -0
  297. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_single_session_capability.py +0 -0
  298. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_spreadsheet_functions.py +0 -0
  299. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_sqlite_backend.py +0 -0
  300. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_sqs_per_record_transactions.py +0 -0
  301. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_ssm_config.py +0 -0
  302. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_store_user_data.py +0 -0
  303. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  304. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_table_alter.py +0 -0
  305. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_table_find_cache.py +0 -0
  306. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_table_purge.py +0 -0
  307. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_transaction_class_wrapping.py +0 -0
  308. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_transaction_commit_and_ownership.py +0 -0
  309. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_transaction_edge_cases.py +0 -0
  310. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_where_clause_validation.py +0 -0
  311. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_write_hook_create_flow.py +0 -0
  312. {velocity_python-0.1.125 → velocity_python-0.1.127}/tests/test_write_hook_empty_strings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.125
3
+ Version: 0.1.127
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.125"
7
+ version = "0.1.127"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.125"
1
+ __version__ = version = "0.1.127"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -68,6 +68,22 @@ class BoundList(list):
68
68
  super().__init__(_wrap(v, ctrl) for v in iterable)
69
69
  self._ctrl = ctrl
70
70
 
71
+ # -- copies are plain values ---------------------------------------
72
+ # A copy of a bound value is a plain list with no link to the row.
73
+ # Without these, copy.deepcopy rebuilt the proxy through the pickle
74
+ # path and refilled it with __setitem__ — one flush (an UPDATE) per
75
+ # nested container for a read-only copy, or an AttributeError when the
76
+ # rebuilt controller had no flush (2026-09-28, found by a dance forms
77
+ # migration deep-copying a definition read from a JSONB column).
78
+ def __copy__(self):
79
+ return unwrap(self)
80
+
81
+ def __deepcopy__(self, memo):
82
+ return unwrap(self)
83
+
84
+ def __reduce__(self):
85
+ return (list, (unwrap(self),))
86
+
71
87
  # -- mutators ------------------------------------------------------
72
88
  def __setitem__(self, index, value):
73
89
  if isinstance(index, slice):
@@ -131,6 +147,22 @@ class BoundDict(dict):
131
147
  super().__init__({k: _wrap(v, ctrl) for k, v in mapping.items()})
132
148
  self._ctrl = ctrl
133
149
 
150
+ # -- copies are plain values ---------------------------------------
151
+ # A copy of a bound value is a plain dict with no link to the row.
152
+ # Without these, copy.deepcopy rebuilt the proxy through the pickle
153
+ # path and refilled it with __setitem__ — one flush (an UPDATE) per
154
+ # nested container for a read-only copy, or an AttributeError when the
155
+ # rebuilt controller had no flush (2026-09-28, found by a dance forms
156
+ # migration deep-copying a definition read from a JSONB column).
157
+ def __copy__(self):
158
+ return unwrap(self)
159
+
160
+ def __deepcopy__(self, memo):
161
+ return unwrap(self)
162
+
163
+ def __reduce__(self):
164
+ return (dict, (unwrap(self),))
165
+
134
166
  # -- mutators ------------------------------------------------------
135
167
  def __setitem__(self, key, value):
136
168
  super().__setitem__(key, _wrap(value, self._ctrl))
@@ -33,6 +33,7 @@ import os
33
33
  from typing import Iterable, List, Optional
34
34
 
35
35
  from velocity.db.servers import postgres
36
+ from velocity.db.tunnel import apply_db_tunnel
36
37
 
37
38
  SNAPSHOT_INFIX = "-snapshot-"
38
39
  TS_FORMAT = "%Y%m%d-%H%M%S" # sortable: lexical order == chronological order
@@ -136,12 +137,16 @@ class DatabaseMaintenance:
136
137
  return _run()
137
138
 
138
139
  def _conn_params(self) -> dict:
139
- return {
140
- "host": self._config.get("host") or os.environ.get("DBHost"),
141
- "port": self._config.get("port") or os.environ.get("DBPort"),
142
- "user": self._config.get("user") or os.environ.get("DBUser"),
143
- "password": self._config.get("password") or os.environ.get("DBPassword"),
144
- }
140
+ # the pg_dump/pg_restore/psql runs follow DB_TUNNEL like the engine does
141
+ return apply_db_tunnel(
142
+ {
143
+ "host": self._config.get("host") or os.environ.get("DBHost"),
144
+ "port": self._config.get("port") or os.environ.get("DBPort"),
145
+ "user": self._config.get("user") or os.environ.get("DBUser"),
146
+ "password": self._config.get("password")
147
+ or os.environ.get("DBPassword"),
148
+ }
149
+ )
145
150
 
146
151
  # ------------------------------------------------------------------ guards
147
152
  def _guard(self, db: str, action: str) -> None:
@@ -2,6 +2,7 @@ import os
2
2
  from ..base.initializer import BaseInitializer
3
3
  from velocity.db.core import engine
4
4
  from velocity.aws.ssm_config import getenv as _getenv
5
+ from velocity.db.tunnel import apply_db_tunnel
5
6
 
6
7
 
7
8
  # Default TCP keepalive settings for PostgreSQL connections.
@@ -60,6 +61,10 @@ class PostgreSQLInitializer(BaseInitializer):
60
61
  base_config, config, **kwargs
61
62
  )
62
63
 
64
+ # DB_TUNNEL (operator laptops): every connection goes to the local end
65
+ # of a tunnel to a private server. See velocity.db.tunnel.
66
+ apply_db_tunnel(final_config)
67
+
63
68
  # Apply TCP keepalive defaults (user config takes precedence).
64
69
  for key, default_val in _DEFAULT_KEEPALIVE.items():
65
70
  final_config.setdefault(key, default_val)
@@ -0,0 +1,49 @@
1
+ """``DB_TUNNEL``: reach a private database through a local tunnel end.
2
+
3
+ An operator on a laptop cannot reach a database that has no public address;
4
+ a tunnel (SSH ``-L``, an SSM port forward, ...) exposes it on a local port.
5
+ Setting ``DB_TUNNEL=127.0.0.1:15433`` points every velocity PostgreSQL
6
+ connection in the process -- engines from ``postgres.initialize()`` and the
7
+ ``pg_dump``/``pg_restore``/``psql`` runs of ``DatabaseMaintenance`` -- at that
8
+ local end instead of the configured host. Host and port only: the database
9
+ name and credentials still come from wherever they were configured.
10
+
11
+ A tunnel forwards to ONE server, so the override applies to every connection,
12
+ including an explicit ``config={"host": ...}``: under a tunnel, no other host
13
+ is reachable anyway. ``sslmode=require`` still encrypts end to end;
14
+ ``verify-full`` would reject the local name, so don't combine the two.
15
+
16
+ Read from ``os.environ`` only (never SSM): it describes this process's
17
+ network path, not the application's configuration.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ from typing import Mapping, Optional, Tuple
24
+
25
+ DB_TUNNEL_VAR = "DB_TUNNEL"
26
+
27
+
28
+ def db_tunnel(environ: Optional[Mapping[str, str]] = None) -> Optional[Tuple[str, str]]:
29
+ """``(host, port)`` from ``DB_TUNNEL``, or ``None`` when it is unset/blank.
30
+
31
+ Raises ``ValueError`` for anything that is not ``host:port`` -- a typo must
32
+ not silently fall back to the unreachable real host."""
33
+ value = (os.environ if environ is None else environ).get(DB_TUNNEL_VAR, "")
34
+ value = (value or "").strip()
35
+ if not value:
36
+ return None
37
+ host, _, port = value.rpartition(":")
38
+ if not host or not port.isdigit():
39
+ raise ValueError(f"{DB_TUNNEL_VAR} must be host:port, got {value!r}")
40
+ return host, port
41
+
42
+
43
+ def apply_db_tunnel(config: dict, environ: Optional[Mapping[str, str]] = None) -> dict:
44
+ """Replace ``config['host']``/``config['port']`` with the tunnel's local end
45
+ when ``DB_TUNNEL`` is set. Mutates and returns ``config``."""
46
+ tunnel = db_tunnel(environ)
47
+ if tunnel:
48
+ config["host"], config["port"] = tunnel
49
+ return config
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.125
3
+ Version: 0.1.127
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
@@ -69,6 +69,7 @@ src/velocity/db/__init__.py
69
69
  src/velocity/db/exceptions.py
70
70
  src/velocity/db/maintenance.py
71
71
  src/velocity/db/migrations.py
72
+ src/velocity/db/tunnel.py
72
73
  src/velocity/db/utils.py
73
74
  src/velocity/db/core/__init__.py
74
75
  src/velocity/db/core/async_support.py
@@ -229,6 +230,7 @@ tests/test_data_service_renumber.py
229
230
  tests/test_data_service_save_as.py
230
231
  tests/test_db_credentials_ssm_cascade.py
231
232
  tests/test_db_maintenance.py
233
+ tests/test_db_tunnel.py
232
234
  tests/test_decorators.py
233
235
  tests/test_delete_hook_fk_blocked.py
234
236
  tests/test_dirty_pipeline_fast_path.py
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env python3
2
+ """``DB_TUNNEL=host:port`` points every PostgreSQL connection in the process at
3
+ a local tunnel end: engines from ``postgres.initialize()`` and the
4
+ ``pg_dump``/``pg_restore`` runs of ``DatabaseMaintenance``. Host and port only;
5
+ a malformed value fails loudly instead of falling back to the real host."""
6
+
7
+ import os
8
+ import unittest
9
+ from unittest.mock import MagicMock, patch
10
+
11
+ from velocity.db import maintenance
12
+ from velocity.db.servers import postgres as postgres_server
13
+ from velocity.db.tunnel import DB_TUNNEL_VAR, apply_db_tunnel, db_tunnel
14
+
15
+ FAKE = {
16
+ "DBDatabase": "app_db",
17
+ "DBHost": "private.rds.example.com",
18
+ "DBPort": "5433",
19
+ "DBUser": "app_user",
20
+ "DBPassword": "app_pass",
21
+ }
22
+
23
+
24
+ def _no_tunnel():
25
+ env = dict(os.environ)
26
+ env.pop(DB_TUNNEL_VAR, None)
27
+ return patch.dict(os.environ, env, clear=True)
28
+
29
+
30
+ class TestParse(unittest.TestCase):
31
+ def test_unset_or_blank_is_none(self):
32
+ self.assertIsNone(db_tunnel({}))
33
+ self.assertIsNone(db_tunnel({DB_TUNNEL_VAR: " "}))
34
+
35
+ def test_host_and_port(self):
36
+ self.assertEqual(
37
+ db_tunnel({DB_TUNNEL_VAR: " 127.0.0.1:15433 "}), ("127.0.0.1", "15433")
38
+ )
39
+
40
+ def test_malformed_raises(self):
41
+ for bad in ("localhost", ":15433", "127.0.0.1:", "127.0.0.1:abc"):
42
+ with self.subTest(bad=bad), self.assertRaises(ValueError):
43
+ db_tunnel({DB_TUNNEL_VAR: bad})
44
+
45
+ def test_apply_replaces_host_and_port_only(self):
46
+ config = {"host": "h", "port": "1", "user": "u", "dbname": "d"}
47
+ apply_db_tunnel(config, {DB_TUNNEL_VAR: "127.0.0.1:15433"})
48
+ self.assertEqual(
49
+ config,
50
+ {"host": "127.0.0.1", "port": "15433", "user": "u", "dbname": "d"},
51
+ )
52
+
53
+ def test_apply_without_tunnel_leaves_config(self):
54
+ config = {"host": "h", "port": "1"}
55
+ self.assertEqual(apply_db_tunnel(config, {}), {"host": "h", "port": "1"})
56
+
57
+
58
+ class TestPostgresInitialize(unittest.TestCase):
59
+ def _initialize(self, config=None):
60
+ captured = {}
61
+
62
+ def fake_engine(driver, cfg, sql, **kwargs):
63
+ captured.update(cfg)
64
+ return MagicMock()
65
+
66
+ with (
67
+ patch.object(postgres_server, "_getenv", lambda key: FAKE.get(key)),
68
+ patch.object(postgres_server.engine, "Engine", fake_engine),
69
+ ):
70
+ postgres_server.PostgreSQLInitializer.initialize(config)
71
+ return captured
72
+
73
+ def test_without_tunnel_uses_configured_host(self):
74
+ with _no_tunnel():
75
+ cfg = self._initialize()
76
+ self.assertEqual(
77
+ (cfg["host"], cfg["port"]), ("private.rds.example.com", "5433")
78
+ )
79
+
80
+ def test_tunnel_overrides_host_and_port_keeps_credentials(self):
81
+ with patch.dict(os.environ, {DB_TUNNEL_VAR: "127.0.0.1:15433"}):
82
+ cfg = self._initialize()
83
+ self.assertEqual((cfg["host"], cfg["port"]), ("127.0.0.1", "15433"))
84
+ self.assertEqual(cfg["dbname"], "app_db")
85
+ self.assertEqual(cfg["user"], "app_user")
86
+ self.assertEqual(cfg["password"], "app_pass")
87
+
88
+ def test_tunnel_overrides_an_explicit_config_host(self):
89
+ # a tunnel reaches one server; any other host is unreachable anyway
90
+ with patch.dict(os.environ, {DB_TUNNEL_VAR: "127.0.0.1:15433"}):
91
+ cfg = self._initialize({"host": "other.example.com", "port": "5432"})
92
+ self.assertEqual((cfg["host"], cfg["port"]), ("127.0.0.1", "15433"))
93
+
94
+ def test_malformed_tunnel_fails_before_connecting(self):
95
+ with patch.dict(os.environ, {DB_TUNNEL_VAR: "localhost"}):
96
+ with self.assertRaises(ValueError):
97
+ self._initialize()
98
+
99
+
100
+ class TestMaintenanceTools(unittest.TestCase):
101
+ def test_pg_dump_follows_the_tunnel(self):
102
+ m = maintenance.DatabaseMaintenance(
103
+ config={"host": "private.rds.example.com", "port": "5433", "user": "u"},
104
+ engine=MagicMock(),
105
+ )
106
+ with (
107
+ patch.dict(os.environ, {DB_TUNNEL_VAR: "127.0.0.1:15433"}),
108
+ patch.object(m, "_run_tool") as run,
109
+ ):
110
+ m.backup_to_file("app_db", "/tmp/x.dump")
111
+ args = run.call_args[0][0]
112
+ self.assertEqual(args[args.index("-h") + 1], "127.0.0.1")
113
+ self.assertEqual(args[args.index("-p") + 1], "15433")
114
+
115
+ def test_pg_dump_without_tunnel_uses_configured_host(self):
116
+ m = maintenance.DatabaseMaintenance(
117
+ config={"host": "private.rds.example.com", "port": "5433", "user": "u"},
118
+ engine=MagicMock(),
119
+ )
120
+ with _no_tunnel(), patch.object(m, "_run_tool") as run:
121
+ m.backup_to_file("app_db", "/tmp/x.dump")
122
+ args = run.call_args[0][0]
123
+ self.assertEqual(args[args.index("-h") + 1], "private.rds.example.com")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ unittest.main()
@@ -423,3 +423,60 @@ class TestRowJsonTextDecode(unittest.TestCase):
423
423
 
424
424
  if __name__ == "__main__":
425
425
  unittest.main()
426
+
427
+
428
+ class TestProxyCopies(unittest.TestCase):
429
+ """A copy of a bound JSON value is a plain value: copying never
430
+ touches the row (2026-09-28)."""
431
+
432
+ def _bound(self):
433
+ from velocity.db.core import jsonproxy
434
+
435
+ writes = []
436
+ value = {
437
+ "pages": [{"ui": [{"copy": "x"}], "skippable": {"label": "s"}}],
438
+ "tags": ["a", ["b"]],
439
+ }
440
+ return jsonproxy.bind(value, writes.append), value, writes
441
+
442
+ def _assert_plain(self, value):
443
+ self.assertIn(type(value), (dict, list))
444
+ items = value.values() if isinstance(value, dict) else value
445
+ for item in items:
446
+ if isinstance(item, (dict, list)):
447
+ self._assert_plain(item)
448
+
449
+ def test_deepcopy_and_copy_are_plain_and_write_nothing(self):
450
+ import copy
451
+
452
+ bound, original, writes = self._bound()
453
+ deep = copy.deepcopy(bound)
454
+ shallow = copy.copy(bound)
455
+ self.assertEqual(deep, original)
456
+ self.assertEqual(shallow, original)
457
+ self._assert_plain(deep)
458
+ self._assert_plain(shallow)
459
+ self.assertEqual(writes, [])
460
+ # the copy is independent of the row
461
+ deep["pages"][0]["skippable"]["label"] = "changed"
462
+ self.assertEqual(bound["pages"][0]["skippable"]["label"], "s")
463
+ self.assertEqual(writes, [])
464
+ # nested proxies copy plain too
465
+ self._assert_plain(copy.deepcopy(bound["pages"]))
466
+ self._assert_plain(copy.copy(bound["tags"]))
467
+ self.assertEqual(writes, [])
468
+
469
+ def test_pickle_round_trip_is_plain(self):
470
+ import pickle
471
+
472
+ bound, original, writes = self._bound()
473
+ back = pickle.loads(pickle.dumps(bound))
474
+ self.assertEqual(back, original)
475
+ self._assert_plain(back)
476
+ self.assertEqual(writes, [])
477
+
478
+ def test_mutation_still_flushes(self):
479
+ bound, _original, writes = self._bound()
480
+ bound["pages"][0]["skippable"]["label"] = "t"
481
+ self.assertEqual(len(writes), 1)
482
+ self.assertEqual(writes[0]["pages"][0]["skippable"]["label"], "t")