velocity-python 0.3.0__tar.gz → 0.3.1__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 (439) hide show
  1. {velocity_python-0.3.0 → velocity_python-0.3.1}/PKG-INFO +1 -1
  2. {velocity_python-0.3.0 → velocity_python-0.3.1}/pyproject.toml +1 -1
  3. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/security.py +118 -43
  5. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/lambda_handler.py +12 -4
  6. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/audit.py +9 -0
  7. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/engine.py +35 -9
  8. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/transaction.py +16 -2
  9. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/PKG-INFO +1 -1
  10. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  11. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_connection_pool.py +75 -0
  12. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_masquerade.py +88 -1
  13. velocity_python-0.3.1/tests/test_record_event_postgres.py +245 -0
  14. velocity_python-0.3.1/tests/test_record_event_transaction.py +409 -0
  15. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_capability.py +20 -4
  16. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_user_not_provisioned.py +2 -1
  17. {velocity_python-0.3.0 → velocity_python-0.3.1}/LICENSE +0 -0
  18. {velocity_python-0.3.0 → velocity_python-0.3.1}/README.md +0 -0
  19. {velocity_python-0.3.0 → velocity_python-0.3.1}/setup.cfg +0 -0
  20. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/__init__.py +0 -0
  21. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/amplify.py +0 -0
  22. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/__init__.py +0 -0
  23. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/backfill.py +0 -0
  24. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/indexing.py +0 -0
  25. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/references.py +0 -0
  26. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/service.py +0 -0
  27. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/assets/usage_index.py +0 -0
  28. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/authz.py +0 -0
  29. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/blobs.py +0 -0
  30. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/__init__.py +0 -0
  31. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/base.py +0 -0
  32. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/dispatch.py +0 -0
  33. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/infra.py +0 -0
  34. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/jobs.py +0 -0
  35. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/provisioning.py +0 -0
  36. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/retention.py +0 -0
  37. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/capabilities/single_session.py +0 -0
  38. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/__init__.py +0 -0
  39. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/ci_gate.py +0 -0
  40. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/config.py +0 -0
  41. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/frontend_checks.py +0 -0
  42. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/operations.py +0 -0
  43. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/policies.py +0 -0
  44. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/reconcile.py +0 -0
  45. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/deploy/targets.py +0 -0
  46. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/dirty_pipeline.py +0 -0
  47. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/email.py +0 -0
  48. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/facets.py +0 -0
  49. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/failures.py +0 -0
  50. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/feature_flags.py +0 -0
  51. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/__init__.py +0 -0
  52. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/base_handler.py +0 -0
  53. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/context.py +0 -0
  54. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/context_factory.py +0 -0
  55. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/exceptions.py +0 -0
  56. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/masquerade.py +0 -0
  57. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  58. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
  59. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
  60. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
  61. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
  62. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  63. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  64. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
  65. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
  66. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  67. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
  68. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  69. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/perf.py +0 -0
  70. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/response.py +0 -0
  71. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  72. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/identity.py +0 -0
  73. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/job_lifecycle.py +0 -0
  74. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/job_results.py +0 -0
  75. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/mirror.py +0 -0
  76. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/ops.py +0 -0
  77. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/permissions.py +0 -0
  78. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/queue_defaults.py +0 -0
  79. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/s3.py +0 -0
  80. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/scheduler.py +0 -0
  81. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/schema.py +0 -0
  82. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/sms.py +0 -0
  83. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/ssm_config.py +0 -0
  84. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/__init__.py +0 -0
  85. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  86. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
  87. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  88. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
  89. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  90. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_email.py +0 -0
  91. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  92. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_response.py +0 -0
  93. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tests/test_sms.py +0 -0
  94. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/tiles.py +0 -0
  95. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/aws/user_data.py +0 -0
  96. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/cache.py +0 -0
  97. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/__init__.py +0 -0
  98. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/_pgddl.py +0 -0
  99. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/__init__.py +0 -0
  100. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/async_support.py +0 -0
  101. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/column.py +0 -0
  102. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/database.py +0 -0
  103. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/decorators.py +0 -0
  104. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/jsonproxy.py +0 -0
  105. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/lazy_engine.py +0 -0
  106. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/result.py +0 -0
  107. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/row.py +0 -0
  108. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/sequence.py +0 -0
  109. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/table.py +0 -0
  110. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/core/view.py +0 -0
  111. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/effective.py +0 -0
  112. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/exceptions.py +0 -0
  113. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/fragments.py +0 -0
  114. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/integrity.py +0 -0
  115. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/maintenance.py +0 -0
  116. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/migrations.py +0 -0
  117. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/__init__.py +0 -0
  118. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/__init__.py +0 -0
  119. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/initializer.py +0 -0
  120. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/operators.py +0 -0
  121. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/sql.py +0 -0
  122. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/base/types.py +0 -0
  123. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/__init__.py +0 -0
  124. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/operators.py +0 -0
  125. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/reserved.py +0 -0
  126. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/sql.py +0 -0
  127. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/mysql/types.py +0 -0
  128. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/__init__.py +0 -0
  129. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/operators.py +0 -0
  130. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/reserved.py +0 -0
  131. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/sql.py +0 -0
  132. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/postgres/types.py +0 -0
  133. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  134. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/operators.py +0 -0
  135. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  136. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/sql.py +0 -0
  137. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlite/types.py +0 -0
  138. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  139. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  140. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  141. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  142. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/sqlserver/types.py +0 -0
  143. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/servers/tablehelper.py +0 -0
  144. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/__init__.py +0 -0
  145. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/common_db_test.py +0 -0
  146. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/__init__.py +0 -0
  147. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/common.py +0 -0
  148. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/conftest.py +0 -0
  149. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_column.py +0 -0
  150. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  151. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_database.py +0 -0
  152. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  153. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  154. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  155. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_integrity_triggers.py +0 -0
  156. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_result.py +0 -0
  157. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_row.py +0 -0
  158. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  159. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  160. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  161. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  162. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  163. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_table.py +0 -0
  164. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  165. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  166. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/__init__.py +0 -0
  167. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/common.py +0 -0
  168. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  169. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  170. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  171. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_db_utils.py +0 -0
  172. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_postgres.py +0 -0
  173. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  174. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  175. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_result_caching.py +0 -0
  176. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  177. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  178. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  179. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  180. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_sql_builder.py +0 -0
  181. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_tablehelper.py +0 -0
  182. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tests/test_view_helper.py +0 -0
  183. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/tunnel.py +0 -0
  184. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/db/utils.py +0 -0
  185. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/hooks.py +0 -0
  186. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/logging.py +0 -0
  187. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/__init__.py +0 -0
  188. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/__init__.py +0 -0
  189. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/iconv.py +0 -0
  190. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/conv/oconv.py +0 -0
  191. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/db.py +0 -0
  192. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/export.py +0 -0
  193. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/format.py +0 -0
  194. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/mail.py +0 -0
  195. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/merge.py +0 -0
  196. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/pdf.py +0 -0
  197. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/__init__.py +0 -0
  198. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_db.py +0 -0
  199. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_fix.py +0 -0
  200. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_format.py +0 -0
  201. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_iconv.py +0 -0
  202. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_mail_send.py +0 -0
  203. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_merge.py +0 -0
  204. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_oconv.py +0 -0
  205. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_original_error.py +0 -0
  206. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tests/test_timer.py +0 -0
  207. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/timer.py +0 -0
  208. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/misc/tools.py +0 -0
  209. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/__init__.py +0 -0
  210. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/__init__.py +0 -0
  211. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/capabilities.py +0 -0
  212. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/store.py +0 -0
  213. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/core/where.py +0 -0
  214. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/exceptions.py +0 -0
  215. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/__init__.py +0 -0
  216. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/base/__init__.py +0 -0
  217. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  218. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  219. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  220. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  221. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  222. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  223. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  224. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  225. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  226. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  227. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  228. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  229. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/__init__.py +0 -0
  230. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/_mirror_core.py +0 -0
  231. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/authorizenet_adapter.py +0 -0
  232. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/authorizenet_mirror.py +0 -0
  233. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/base_adapter.py +0 -0
  234. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/braintree_adapter.py +0 -0
  235. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/braintree_mirror.py +0 -0
  236. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/charge_rules.py +0 -0
  237. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/money.py +0 -0
  238. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/plaid_api.py +0 -0
  239. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/plaid_webhook.py +0 -0
  240. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/redact.py +0 -0
  241. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/schema.py +0 -0
  242. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/stripe_adapter.py +0 -0
  243. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/payment/stripe_mirror.py +0 -0
  244. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/__init__.py +0 -0
  245. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/engine.py +0 -0
  246. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/expressions.py +0 -0
  247. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/facts.py +0 -0
  248. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/rules/storage.py +0 -0
  249. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/testing/__init__.py +0 -0
  250. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity/testing/fake_db.py +0 -0
  251. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  252. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/entry_points.txt +0 -0
  253. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/requires.txt +0 -0
  254. {velocity_python-0.3.0 → velocity_python-0.3.1}/src/velocity_python.egg-info/top_level.txt +0 -0
  255. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_access_error_403.py +0 -0
  256. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_action_names.py +0 -0
  257. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_activity_tracking_aborted_tx.py +0 -0
  258. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_amplify_redirects.py +0 -0
  259. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_asset_indexing.py +0 -0
  260. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_asset_references.py +0 -0
  261. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_assets_service.py +0 -0
  262. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_async_support.py +0 -0
  263. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_log_confidential.py +0 -0
  264. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_mixin.py +0 -0
  265. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_audit_registry_table_gate.py +0 -0
  266. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_authz.py +0 -0
  267. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_aws_package_hygiene.py +0 -0
  268. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_batch_operations.py +0 -0
  269. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_blob_store.py +0 -0
  270. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_business_objects.py +0 -0
  271. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capabilities.py +0 -0
  272. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capability_catalog.py +0 -0
  273. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_capability_dependencies.py +0 -0
  274. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_check_changelog.py +0 -0
  275. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ci_gate.py +0 -0
  276. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_errors_mixin.py +0 -0
  277. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_input_contract_export.py +0 -0
  278. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_sql_boundary.py +0 -0
  279. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_client_table_gate_case_and_pointers.py +0 -0
  280. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_cognito_client_lazy.py +0 -0
  281. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_cognito_user_cache.py +0 -0
  282. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_column_tx_arg.py +0 -0
  283. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_concurrency_safety.py +0 -0
  284. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_connection_resilience.py +0 -0
  285. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_enqueue_idempotency.py +0 -0
  286. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_flags_loader.py +0 -0
  287. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_job_descriptions.py +0 -0
  288. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_context_queue_job.py +0 -0
  289. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_conv_combine.py +0 -0
  290. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_owner_scope.py +0 -0
  291. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_query_cap.py +0 -0
  292. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_renumber.py +0 -0
  293. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_row_number.py +0 -0
  294. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_save_as.py +0 -0
  295. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_data_service_security_gates.py +0 -0
  296. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_audit_required.py +0 -0
  297. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_credentials_ssm_cascade.py +0 -0
  298. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_effective.py +0 -0
  299. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_integrity.py +0 -0
  300. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_maintenance.py +0 -0
  301. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_db_tunnel.py +0 -0
  302. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_declared_column_types.py +0 -0
  303. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_decorators.py +0 -0
  304. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_delete_hook_fk_blocked.py +0 -0
  305. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_config_fields.py +0 -0
  306. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_engine.py +0 -0
  307. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_frontend_checks.py +0 -0
  308. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_operations_queue.py +0 -0
  309. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_deploy_policies.py +0 -0
  310. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dirty_pipeline_consumer.py +0 -0
  311. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dirty_pipeline_fast_path.py +0 -0
  312. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_dlq_constant.py +0 -0
  313. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_docs_snippets.py +0 -0
  314. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_email_processing.py +0 -0
  315. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_enqueue_metric_namespace.py +0 -0
  316. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_enqueue_send_failures.py +0 -0
  317. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ensure_infrastructure.py +0 -0
  318. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_export_workbook.py +0 -0
  319. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_facets.py +0 -0
  320. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_facets_and_where.py +0 -0
  321. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_failures.py +0 -0
  322. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_feature_flags.py +0 -0
  323. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_get_cognito_user_provider.py +0 -0
  324. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_hooks.py +0 -0
  325. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_http_handler_rollback.py +0 -0
  326. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_iconv_money_to_cents.py +0 -0
  327. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_identifier_injection_guard.py +0 -0
  328. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_identity.py +0 -0
  329. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_handler.py +0 -0
  330. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_lifecycle.py +0 -0
  331. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_record_guard.py +0 -0
  332. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_record_guard_postgres.py +0 -0
  333. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_job_results.py +0 -0
  334. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_json_columns.py +0 -0
  335. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_jsonb_dict_adapter.py +0 -0
  336. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler.py +0 -0
  337. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_auth.py +0 -0
  338. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lambda_handler_user_cache.py +0 -0
  339. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_lazy_engine.py +0 -0
  340. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_masquerade_grant.py +0 -0
  341. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_merge_on_conflict.py +0 -0
  342. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mirror.py +0 -0
  343. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mirror_upsert_public.py +0 -0
  344. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_mixins_import.py +0 -0
  345. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_n_plus_one.py +0 -0
  346. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_dynamodb.py +0 -0
  347. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_memory.py +0 -0
  348. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_mongodb.py +0 -0
  349. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_redis.py +0 -0
  350. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_translation.py +0 -0
  351. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_nosql_where.py +0 -0
  352. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_observability.py +0 -0
  353. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_on_action_default.py +0 -0
  354. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_on_commit.py +0 -0
  355. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ops.py +0 -0
  356. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_audit_fixes.py +0 -0
  357. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_authorizenet_adapter.py +0 -0
  358. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_braintree_adapter.py +0 -0
  359. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_braintree_mirror.py +0 -0
  360. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_business_agnostic.py +0 -0
  361. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_charge_reference.py +0 -0
  362. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_extra_bounds.py +0 -0
  363. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_lazy_imports.py +0 -0
  364. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_mirror_empty_table.py +0 -0
  365. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_profile_sorting.py +0 -0
  366. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_payment_stripe_adapter.py +0 -0
  367. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pdf.py +0 -0
  368. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions.py +0 -0
  369. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_actions.py +0 -0
  370. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_notices.py +0 -0
  371. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_permissions_review.py +0 -0
  372. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_bulk_cache.py +0 -0
  373. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_capability_types.py +0 -0
  374. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_ensure_infrastructure.py +0 -0
  375. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_get_tables.py +0 -0
  376. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_permissions.py +0 -0
  377. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_pointer_joins.py +0 -0
  378. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_row_scope.py +0 -0
  379. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_search_export.py +0 -0
  380. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_tiles.py +0 -0
  381. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pgserver_user_data.py +0 -0
  382. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_plaid_api.py +0 -0
  383. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_plaid_webhook.py +0 -0
  384. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_pointer_join_sql.py +0 -0
  385. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_postgres_initializer_config.py +0 -0
  386. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_postgres_py_type.py +0 -0
  387. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_prepared_statements.py +0 -0
  388. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_psycopg3_upgrade.py +0 -0
  389. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_query_cache.py +0 -0
  390. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_query_missing_table_unlocked.py +0 -0
  391. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_raw_sql_prefix_policy.py +0 -0
  392. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_read_cache.py +0 -0
  393. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_readme_imports.py +0 -0
  394. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_response.py +0 -0
  395. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_restricted_direct_tables.py +0 -0
  396. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_result_returning.py +0 -0
  397. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_retry_side_effect_guard.py +0 -0
  398. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_return_default_safety.py +0 -0
  399. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_batch_update.py +0 -0
  400. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_cache_staleness.py +0 -0
  401. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_row_scoped_data_service.py +0 -0
  402. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_rules.py +0 -0
  403. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_rules_storage.py +0 -0
  404. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_bucket_config.py +0 -0
  405. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_list_client_files.py +0 -0
  406. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_s3_upload.py +0 -0
  407. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_scheduler.py +0 -0
  408. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_schema_lock_config_other_backends.py +0 -0
  409. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_schema_migrations.py +0 -0
  410. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_search_paging_export.py +0 -0
  411. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_events_table_gate.py +0 -0
  412. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_security_hardening.py +0 -0
  413. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_server_cursor.py +0 -0
  414. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_session_control.py +0 -0
  415. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_single_autocommit_safety.py +0 -0
  416. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_single_session_capability.py +0 -0
  417. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sms_twilio_signature.py +0 -0
  418. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_spreadsheet_functions.py +0 -0
  419. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sqlite_backend.py +0 -0
  420. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sqs_per_record_transactions.py +0 -0
  421. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ssm_config.py +0 -0
  422. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_ssm_config_require.py +0 -0
  423. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_store_user_data.py +0 -0
  424. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  425. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_alter.py +0 -0
  426. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_claim.py +0 -0
  427. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_find_cache.py +0 -0
  428. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_table_purge.py +0 -0
  429. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_testing_fake_db.py +0 -0
  430. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tiles.py +0 -0
  431. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_class_wrapping.py +0 -0
  432. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_commit_and_ownership.py +0 -0
  433. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_transaction_edge_cases.py +0 -0
  434. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tx_lifecycle_retry.py +0 -0
  435. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_tx_read_cache_and_bulk.py +0 -0
  436. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_user_data.py +0 -0
  437. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_where_clause_validation.py +0 -0
  438. {velocity_python-0.3.0 → velocity_python-0.3.1}/tests/test_write_hook_create_flow.py +0 -0
  439. {velocity_python-0.3.0 → velocity_python-0.3.1}/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.3.0
3
+ Version: 0.3.1
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.3.0"
7
+ version = "0.3.1"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.3.0"
1
+ __version__ = version = "0.3.1"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -19,7 +19,8 @@ rate limits, payload caps, and an audit trail of what was refused and why.
19
19
  Attach :class:`SecurityMixin` FIRST in a handler's bases; it runs in
20
20
  ``beforeAction`` ahead of auth and every action, and is a no-op unless the
21
21
  site's ``sys_capabilities`` row for ``security`` is enabled. A refusal raises
22
- :class:`AccessError` (a 403) after committing a ``sys_security_events`` row.
22
+ :class:`AccessError` (a 403) after recording a ``sys_security_events`` row
23
+ (committed on its own transaction, so the 403's rollback keeps it).
23
24
 
24
25
  ``sys_security_events`` itself is a CORE table (``SCHEMA``, in
25
26
  ``velocity.aws.schema``'s ``core`` group), not the capability's: it is
@@ -39,6 +40,7 @@ import logging
39
40
 
40
41
  from velocity.aws import feature_flags
41
42
  from velocity.cache import memo, read_cache
43
+ from velocity.db import _pgddl, audit
42
44
 
43
45
  from .base import (
44
46
  CIDR_LIST,
@@ -314,7 +316,13 @@ def enforce(tx, aws_event, context, *, now=None):
314
316
  guard, reason = refusal
315
317
  if truthy(config.get("security_block_logging_enabled")):
316
318
  record_event(
317
- tx, guard, reason, aws_event=aws_event, context=context, user_email=user
319
+ tx,
320
+ guard,
321
+ reason,
322
+ aws_event=aws_event,
323
+ context=context,
324
+ user_email=user,
325
+ actor=GUARD_AUDIT_ACTOR,
318
326
  )
319
327
  log.warning("security guard %s refused %s: %s", guard, facts["ip"], reason)
320
328
  # imported here, not at module top: the capabilities package must not
@@ -326,6 +334,15 @@ def enforce(tx, aws_event, context, *, now=None):
326
334
  return config
327
335
 
328
336
 
337
+ # A second connection waiting on a lock the caller's own transaction holds
338
+ # would wait forever (the database can't see it as a deadlock): bound it, so
339
+ # the event fails (and an audited decision is refused) instead of hanging.
340
+ EVENT_LOCK_TIMEOUT = "5s"
341
+ # The audit actor of a guard refusal: it is recorded in beforeAction, before
342
+ # any request actor is set.
343
+ GUARD_AUDIT_ACTOR = "system:security_guard"
344
+
345
+
329
346
  def record_event(
330
347
  tx,
331
348
  guard,
@@ -337,58 +354,116 @@ def record_event(
337
354
  user_sub=None,
338
355
  detail=None,
339
356
  occurred_at=None,
357
+ actor=None,
340
358
  ):
341
- """Append one ``sys_security_events`` row and commit it — an audit row
342
- must survive whatever the caller does next (typically raise). The one
343
- audit sink for the framework: the request guards, masquerade grants,
344
- sign-ins, and anything else security-relevant write here. ``user_sub`` is
345
- the identity provider's subject; ``detail`` a JSON-able dict of facts that
346
- have no column; ``occurred_at`` when it happened, if not now (an event
347
- relayed through a queue). Silent when the table is not provisioned in this site, and
348
- an audit failure never breaks the request. Returns True when a row was
349
- written.
359
+ """Append one ``sys_security_events`` row on its own short transaction.
360
+ The one audit sink for the framework: the request guards, masquerade
361
+ grants, sign-ins, privileged decisions (``authz.require``), and anything
362
+ else security-relevant write here. ``user_sub`` is the identity provider's
363
+ subject; ``detail`` a JSON-able dict of facts that have no column;
364
+ ``occurred_at`` when it happened, if not now (an event relayed through a
365
+ queue). Silent when the table is not provisioned in this site, and an
366
+ audit failure never breaks the request. Returns True only once the row is
367
+ committed.
368
+
369
+ ``tx`` is the caller's transaction and is only used for its engine: the
370
+ row is written and committed through ``tx.engine.side_transaction()``
371
+ (another connection, which never waits for the pool), so it survives whatever the caller does next (raise,
372
+ roll back) and the caller's transaction is left exactly as it was: its
373
+ writes stay uncommitted, and its ``SET LOCAL`` settings (the audit actor),
374
+ row locks and transaction-level advisory locks are kept. A ``tx`` with no
375
+ engine (a test double) gets the row written on itself, uncommitted. On
376
+ PostgreSQL the event transaction's audit actor (``velocity.actor``, for a
377
+ ``sys_security_events`` under the audit trigger's ``require_actor``) is
378
+ ``actor`` when given, else the one the caller set with
379
+ :func:`velocity.db.audit.set_audit_actor`. Callers that record before any
380
+ actor is set (the request guards, the masquerade grant) pass their own.
350
381
 
351
382
  A table created before ``user_sub``/``detail`` existed (a site whose schema
352
383
  lane has not run since) still records the row without them: on a locked
353
384
  schema an unknown column would otherwise lose the whole event."""
385
+ facts = dict(
386
+ aws_event=aws_event,
387
+ context=context,
388
+ user_email=user_email,
389
+ user_sub=user_sub,
390
+ detail=detail,
391
+ occurred_at=occurred_at,
392
+ )
354
393
  try:
355
- table = tx.table(EVENTS_TABLE)
356
- if not table.exists():
357
- return False
358
- facts = request_facts(aws_event)
359
- row = {
360
- "guard": guard,
361
- "reason": reason,
362
- "ip": facts["ip"],
363
- "country": facts["country"],
364
- "path": facts["path"],
365
- "action": _action(context) if context is not None else "",
366
- "user_email": user_email,
367
- "user_agent": facts["user_agent"][:512],
368
- }
369
- extra = {
370
- "user_sub": user_sub,
371
- "detail": detail,
372
- "occurred_at": occurred_at or datetime.datetime.now(datetime.timezone.utc),
373
- }
374
- extra = {k: v for k, v in extra.items() if v is not None}
375
- if extra:
376
- try:
377
- have = {c.lower() for c in table.sys_columns()}
378
- except Exception:
379
- # can't tell which newer columns exist: the event still lands
380
- # with the columns every version of the table has
381
- log.warning("security event %s: columns unknown", guard, exc_info=True)
382
- have = set()
383
- row.update({k: v for k, v in extra.items() if k in have})
384
- table.insert(row)
385
- tx.commit()
386
- return True
394
+ open_transaction = getattr(
395
+ getattr(tx, "engine", None), "side_transaction", None
396
+ )
397
+ if not callable(open_transaction):
398
+ return _write_event(tx, guard, reason, **facts)
399
+ postgres = "postgres" in str(_pgddl.server_name(tx) or "").lower()
400
+ if postgres and not actor:
401
+ actor = _caller_actor(tx)
402
+ # the row is committed when the block exits; a commit failure raises
403
+ # out of it, so True is never returned for a row that isn't durable
404
+ with open_transaction() as event_tx:
405
+ if postgres:
406
+ # one round trip: the bounded lock wait, and the caller's audit
407
+ # actor (a sys_security_events under the audit trigger's
408
+ # require_actor still records)
409
+ event_tx.execute(
410
+ "SELECT set_config('lock_timeout', %s, true), "
411
+ "set_config(%s, %s, true)",
412
+ (EVENT_LOCK_TIMEOUT, audit.ACTOR_SETTING, actor or ""),
413
+ )
414
+ return _write_event(event_tx, guard, reason, **facts)
387
415
  except Exception:
388
416
  log.warning("security event %s not recorded", guard, exc_info=True)
389
417
  return False
390
418
 
391
419
 
420
+ def _caller_actor(tx):
421
+ """The audit actor :func:`velocity.db.audit.set_audit_actor` set on the
422
+ caller's transaction, or None. Read from the transaction object only:
423
+ running a statement on the caller's connection here could hit a dead
424
+ connection, and swallowing that error would lose the caller's
425
+ mid-transaction failure (R31) — its next write would land on a fresh
426
+ connection."""
427
+ value = getattr(tx, "_audit_actor", None)
428
+ return value if isinstance(value, str) and value else None
429
+
430
+
431
+ def _write_event(
432
+ tx, guard, reason, *, aws_event, context, user_email, user_sub, detail, occurred_at
433
+ ):
434
+ table = tx.table(EVENTS_TABLE)
435
+ if not table.exists():
436
+ return False
437
+ facts = request_facts(aws_event)
438
+ row = {
439
+ "guard": guard,
440
+ "reason": reason,
441
+ "ip": facts["ip"],
442
+ "country": facts["country"],
443
+ "path": facts["path"],
444
+ "action": _action(context) if context is not None else "",
445
+ "user_email": user_email,
446
+ "user_agent": facts["user_agent"][:512],
447
+ }
448
+ extra = {
449
+ "user_sub": user_sub,
450
+ "detail": detail,
451
+ "occurred_at": occurred_at or datetime.datetime.now(datetime.timezone.utc),
452
+ }
453
+ extra = {k: v for k, v in extra.items() if v is not None}
454
+ if extra:
455
+ try:
456
+ have = {c.lower() for c in table.sys_columns()}
457
+ except Exception:
458
+ # can't tell which newer columns exist: the event still lands
459
+ # with the columns every version of the table has
460
+ log.warning("security event %s: columns unknown", guard, exc_info=True)
461
+ have = set()
462
+ row.update({k: v for k, v in extra.items() if k in have})
463
+ table.insert(row)
464
+ return True
465
+
466
+
392
467
  def _user_email(context):
393
468
  try:
394
469
  session = context.session() or {}
@@ -8,7 +8,7 @@ from typing import Optional
8
8
 
9
9
  from velocity.aws import ssm_config
10
10
  from velocity.aws.handlers.base_handler import BaseHandler
11
- from velocity.aws.handlers.exceptions import UserNotProvisionedError
11
+ from velocity.aws.handlers.exceptions import AccessError, UserNotProvisionedError
12
12
  from velocity.aws.handlers.context_factory import ContextFactory
13
13
  from velocity.aws.handlers.response import Response
14
14
  from velocity.logging import configure_logging
@@ -116,11 +116,12 @@ class LambdaHandler(BaseHandler):
116
116
  )
117
117
  # Persist the audit the docstring promises: who acted as whom,
118
118
  # from where. Goes to the framework's one security audit sink
119
- # (sys_security_events, the Security Events page); silent when
120
- # that table is not provisioned here, never breaks the request.
119
+ # (sys_security_events, the Security Events page), on its own
120
+ # transaction: this request's tx stays open. Fail-closed, as
121
+ # authz.require is: no audit row, no masquerade.
121
122
  from velocity.aws.capabilities.security import record_event
122
123
 
123
- record_event(
124
+ recorded = record_event(
124
125
  tx,
125
126
  "masquerade",
126
127
  f"{grant['real_user']} acting as {grant['effective_user']} "
@@ -128,7 +129,14 @@ class LambdaHandler(BaseHandler):
128
129
  aws_event=self.aws_event,
129
130
  context=context,
130
131
  user_email=grant["real_user"],
132
+ # recorded before any request actor is set; the actor
133
+ # format AuditTrailMixin uses under masquerade
134
+ actor=f"{grant['effective_user']} as {grant['real_user']}",
131
135
  )
136
+ if not recorded:
137
+ raise AccessError(
138
+ "That masquerade could not be recorded, so it was refused."
139
+ )
132
140
 
133
141
  if masquerade or auth_mode == "none":
134
142
  # In masquerade mode the caller's own Cognito token is for a
@@ -172,11 +172,20 @@ def set_audit_actor(tx, actor: Optional[str]) -> None:
172
172
  explicit ``tx.commit()``. ``None`` / ``""`` clears it (the trigger then
173
173
  records ``db:<session_user>``). A no-op off PostgreSQL, where there is no
174
174
  trigger to read it.
175
+
176
+ The value is also kept on the transaction (``tx._audit_actor``, cleared at
177
+ its commit, rollback or connection release; not undone by a savepoint
178
+ rollback) so a side transaction can carry it over without querying this
179
+ connection (:func:`velocity.aws.capabilities.security.record_event`).
175
180
  """
176
181
  if not _pgddl.is_postgres(tx):
177
182
  return
178
183
  value = "" if actor is None else str(actor)
179
184
  tx.execute("SELECT set_config(%s, %s, true)", (ACTOR_SETTING, value))
185
+ try:
186
+ tx._audit_actor = value or None
187
+ except AttributeError: # a transaction double that refuses attributes
188
+ pass
180
189
 
181
190
 
182
191
  # ── trigger DDL ───────────────────────────────────────────────────────────
@@ -199,16 +199,27 @@ class ConnectionPool:
199
199
  # Public API
200
200
  # ------------------------------------------------------------------
201
201
 
202
- def getconn(self):
203
- """Borrow a connection from the pool (blocking if exhausted)."""
202
+ def getconn(self, *, wait=True):
203
+ """Borrow a connection from the pool (blocking if exhausted).
204
+
205
+ ``wait=False`` never waits: with no free slot it raises
206
+ ``DbConnectionError`` at once."""
204
207
  if self._closed:
205
208
  raise exceptions.DbConnectionError("Connection pool is closed")
206
209
 
207
210
  t0 = time.perf_counter()
208
- # R24 — bounded wait for a free slot. Without a timeout an exhausted
209
- # pool blocks until the surrounding Lambda/request itself times out,
210
- # which masks the real problem and wastes the whole invocation window.
211
- acquired = self._available.acquire(timeout=self._acquire_timeout)
211
+ if not wait:
212
+ if not self._available.acquire(blocking=False):
213
+ raise exceptions.DbConnectionError(
214
+ f"Connection pool exhausted (maxconn={self._maxconn}); "
215
+ f"not waiting for a slot."
216
+ )
217
+ acquired = True
218
+ else:
219
+ # R24 — bounded wait for a free slot. Without a timeout an exhausted
220
+ # pool blocks until the surrounding Lambda/request itself times out,
221
+ # which masks the real problem and wastes the whole invocation window.
222
+ acquired = self._available.acquire(timeout=self._acquire_timeout)
212
223
  if not acquired:
213
224
  raise exceptions.DbConnectionError(
214
225
  f"Connection pool exhausted: no connection available within "
@@ -478,16 +489,19 @@ class Engine:
478
489
  self.__pool = None
479
490
  self.__pool_enabled = False
480
491
 
481
- def connect(self):
492
+ def connect(self, *, wait=True):
482
493
  """
483
494
  Connects to the database and returns the connection object.
484
495
  If pooling is enabled, borrows from the pool.
485
496
  If the database is missing, tries to create it, then reconnect.
497
+
498
+ ``wait=False`` never waits for a pool slot: with none free right now
499
+ it opens a direct (unpooled) connection instead.
486
500
  """
487
501
  t0 = time.perf_counter()
488
502
  if self.__pool:
489
503
  try:
490
- conn = self.__pool.getconn()
504
+ conn = self.__pool.getconn(wait=wait)
491
505
  elapsed_ms = (time.perf_counter() - t0) * 1000
492
506
  logger.debug(
493
507
  "Engine.connect: obtained pooled connection (%.1f ms)", elapsed_ms
@@ -495,7 +509,7 @@ class Engine:
495
509
  return conn
496
510
  except exceptions.DbDatabaseMissingError:
497
511
  self.create_database()
498
- return self.__pool.getconn()
512
+ return self.__pool.getconn(wait=wait)
499
513
  except Exception:
500
514
  # Pool may have failed to connect — fall through to direct.
501
515
  pass
@@ -596,6 +610,18 @@ class Engine:
596
610
  # Never let adapter registration break connection establishment.
597
611
  pass
598
612
 
613
+ def side_transaction(self):
614
+ """A new :class:`Transaction` for a short write taken while another
615
+ transaction of this thread is open (an audit row that must commit on
616
+ its own, whatever the request's transaction does next). Use it as
617
+ ``with engine.side_transaction() as tx:``.
618
+
619
+ It never waits for the pool: it leases a free slot if one is free right
620
+ now, else opens a direct connection. Waiting could only end when the
621
+ caller's own transaction, which holds a slot, finishes — a pool of one
622
+ would stall for the acquire timeout, or forever with none."""
623
+ return Transaction(self, wait_for_pool=False)
624
+
599
625
  def transaction(self, func_or_cls=None):
600
626
  """
601
627
  Decorator that provides a Transaction. If `tx` is passed in, uses it; otherwise, creates a new one.
@@ -343,9 +343,17 @@ class Transaction:
343
343
  Encapsulates a single transaction in the database (connection + commit/rollback).
344
344
  """
345
345
 
346
- def __init__(self, engine, connection=None):
346
+ def __init__(self, engine, connection=None, *, wait_for_pool=True):
347
347
  self.engine = engine
348
348
  self.connection = connection
349
+ # False (Engine.side_transaction): lease a pool slot only if one is
350
+ # free now, else connect directly — never wait on the pool.
351
+ self._wait_for_pool = wait_for_pool
352
+ # The audit actor velocity.db.audit.set_audit_actor set on this
353
+ # transaction (velocity.actor is transaction-local, so it is cleared
354
+ # at commit, rollback and connection release). Read by code that must
355
+ # carry it to another transaction without querying this connection.
356
+ self._audit_actor = None
349
357
  self.__pg_types = {}
350
358
  # Track whether this connection came from the pool and should be returned.
351
359
  self.__pooled = False # set per leased connection (TX-7)
@@ -445,7 +453,10 @@ class Transaction:
445
453
  "or Row used after the block must be consumed inside it."
446
454
  )
447
455
  try:
448
- conn = self.engine.connect()
456
+ if getattr(self, "_wait_for_pool", True):
457
+ conn = self.engine.connect()
458
+ else:
459
+ conn = self.engine.connect(wait=False)
449
460
  except exceptions.DbException as e:
450
461
  if getattr(e, "tx", None) is None:
451
462
  e.tx = self
@@ -472,6 +483,7 @@ class Transaction:
472
483
  self.connection = None
473
484
  self.__pooled = False
474
485
  self._in_progress = False
486
+ self._audit_actor = None
475
487
  if conn is None:
476
488
  return
477
489
  returned = False
@@ -844,6 +856,7 @@ class Transaction:
844
856
  raise self._db_error(e)
845
857
  # Work is durably committed; the transaction is clean again.
846
858
  self._in_progress = False
859
+ self._audit_actor = None
847
860
  if self._query_count:
848
861
  _logger.debug(
849
862
  "Transaction commit: %d queries in %.1f ms",
@@ -934,6 +947,7 @@ class Transaction:
934
947
  self.connection.rollback()
935
948
  # Transaction is clean again — no uncommitted work pending.
936
949
  self._in_progress = False
950
+ self._audit_actor = None
937
951
 
938
952
  def is_aborted(self):
939
953
  """Return True if the connection is in a failed/aborted transaction.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.3.0
3
+ Version: 0.3.1
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
@@ -385,6 +385,8 @@ tests/test_query_missing_table_unlocked.py
385
385
  tests/test_raw_sql_prefix_policy.py
386
386
  tests/test_read_cache.py
387
387
  tests/test_readme_imports.py
388
+ tests/test_record_event_postgres.py
389
+ tests/test_record_event_transaction.py
388
390
  tests/test_response.py
389
391
  tests/test_restricted_direct_tables.py
390
392
  tests/test_result_returning.py
@@ -358,3 +358,78 @@ class TestAcquireTimeout:
358
358
  monkeypatch.setenv("VELOCITY_POOL_ACQUIRE_TIMEOUT", "7")
359
359
  eng = _make_engine()
360
360
  assert eng.pool._acquire_timeout == 7
361
+
362
+
363
+ class TestNoWaitAcquire:
364
+ """``wait=False`` (Engine.side_transaction): never wait on the pool."""
365
+
366
+ def test_exhausted_pool_refuses_at_once_without_waiting(self):
367
+ pool = ConnectionPool(
368
+ _make_connect_fn(), minconn=0, maxconn=1, validate=False, acquire_timeout=0
369
+ )
370
+ held = pool.getconn()
371
+ t0 = time.time()
372
+ with pytest.raises(exceptions.DbConnectionError):
373
+ pool.getconn(wait=False)
374
+ assert time.time() - t0 < 0.5
375
+ pool.putconn(held)
376
+ assert pool.getconn(wait=False) is held # a free slot is still used
377
+
378
+ def test_engine_connects_directly_when_no_slot_is_free(self):
379
+ eng = _make_engine(pool_enabled=True, pool_min=0, pool_max=1)
380
+ held = eng.connect()
381
+ direct = eng.connect(wait=False)
382
+ assert direct is not held and not eng.is_pooled_connection(direct)
383
+ eng.return_pooled_connection(held)
384
+
385
+ def test_side_transaction_does_not_wait_and_closes_its_direct_connection(self):
386
+ eng = _make_engine(pool_enabled=True, pool_min=0, pool_max=1)
387
+ with Transaction(eng) as outer:
388
+ outer.cursor()
389
+ t0 = time.time()
390
+ with eng.side_transaction() as side:
391
+ side.cursor()
392
+ conn = side.connection
393
+ assert not eng.is_pooled_connection(conn)
394
+ assert time.time() - t0 < 0.5
395
+ conn.close.assert_called()
396
+ assert eng.is_pooled_connection(outer.connection)
397
+
398
+ def test_concurrent_no_wait_acquires_keep_the_slot_count_balanced(self):
399
+ pool = ConnectionPool(
400
+ _make_connect_fn(), minconn=0, maxconn=2, validate=False, acquire_timeout=0
401
+ )
402
+ lock = threading.Lock()
403
+ state = {"held": 0, "peak": 0, "refused": 0, "leased": 0}
404
+
405
+ def worker():
406
+ for _ in range(200):
407
+ try:
408
+ conn = pool.getconn(wait=False)
409
+ except exceptions.DbConnectionError:
410
+ with lock:
411
+ state["refused"] += 1
412
+ continue
413
+ with lock:
414
+ state["held"] += 1
415
+ state["leased"] += 1
416
+ state["peak"] = max(state["peak"], state["held"])
417
+ time.sleep(0)
418
+ with lock:
419
+ state["held"] -= 1
420
+ pool.putconn(conn)
421
+
422
+ threads = [threading.Thread(target=worker) for _ in range(8)]
423
+ for t in threads:
424
+ t.start()
425
+ for t in threads:
426
+ t.join(timeout=10)
427
+ assert not any(t.is_alive() for t in threads)
428
+ assert state["peak"] <= 2 and state["leased"] > 0
429
+ assert state["leased"] + state["refused"] == 8 * 200
430
+ # balanced: exactly maxconn slots are free again, no more
431
+ a, b = pool.getconn(wait=False), pool.getconn(wait=False)
432
+ with pytest.raises(exceptions.DbConnectionError):
433
+ pool.getconn(wait=False)
434
+ pool.putconn(a)
435
+ pool.putconn(b)
@@ -1,5 +1,8 @@
1
1
  import unittest
2
+ from unittest.mock import patch
2
3
 
4
+ from velocity.aws.capabilities import security
5
+ from velocity.aws.handlers.exceptions import AccessError
3
6
  from velocity.aws.handlers.lambda_handler import LambdaHandler
4
7
  from velocity.aws.handlers.masquerade import mint_grant
5
8
 
@@ -188,7 +191,91 @@ class TestLambdaHandlerMasquerade(unittest.TestCase):
188
191
  self.assertEqual(event["user_email"], ADMIN)
189
192
  self.assertIn("acting as donor@example.com on donor_users", event["reason"])
190
193
  self.assertEqual(event["action"], "do-thing")
191
- self.assertEqual(tx.commits, 1) # the audit row is committed on its own
194
+ # the audit never commits the request's transaction (a real engine
195
+ # writes it on its own; see test_record_event_transaction)
196
+ self.assertEqual(tx.commits, 0)
197
+
198
+ def test_a_masquerade_that_cannot_be_recorded_is_refused(self):
199
+ # fail-closed like authz.require: no audit row, no masquerade (the
200
+ # table not provisioned, no connection, a write failure)
201
+ token = mint_grant(
202
+ SECRET,
203
+ real_user=ADMIN,
204
+ effective_user=TARGET,
205
+ pool="donor_users",
206
+ )
207
+ ctx = _Context(grant_token=token)
208
+ tx = _Tx(
209
+ {"donor@example.com": {"email_address": "donor@example.com", "sys_id": 7}}
210
+ )
211
+ h = _make_handler()
212
+ with patch.object(security, "record_event", return_value=False) as rec:
213
+ with self.assertRaisesRegex(AccessError, "could not be recorded"):
214
+ h.beforeAction(tx=tx, context=ctx)
215
+ rec.assert_called_once()
216
+ self.assertEqual(h.current_user, {})
217
+ self.assertEqual(tx.events, [])
218
+
219
+ def test_the_masquerade_event_names_its_own_audit_actor(self):
220
+ # recorded before AuditTrailMixin sets the request's actor: under an
221
+ # events table with require_actor it would otherwise be refused
222
+ token = mint_grant(
223
+ SECRET, real_user=ADMIN, effective_user=TARGET, pool="donor_users"
224
+ )
225
+ ctx = _Context(grant_token=token)
226
+ tx = _Tx(
227
+ {"donor@example.com": {"email_address": "donor@example.com", "sys_id": 7}}
228
+ )
229
+ with patch.object(security, "record_event", return_value=True) as rec:
230
+ _make_handler().beforeAction(tx=tx, context=ctx)
231
+ self.assertEqual(rec.call_args.kwargs["actor"], f"{TARGET} as {ADMIN}")
232
+
233
+
234
+ class TestRefusedMasqueradeThroughServe(unittest.TestCase):
235
+ """serve(): the refusal is a 403 with the reason, after a rollback."""
236
+
237
+ def test_a_masquerade_that_cannot_be_recorded_is_a_403(self):
238
+ import json
239
+ from unittest.mock import MagicMock
240
+
241
+ token = mint_grant(
242
+ SECRET, real_user=ADMIN, effective_user=TARGET, pool="donor_users"
243
+ )
244
+ aws_event = {
245
+ "headers": {"X-CC-Masquerade": token},
246
+ "queryStringParameters": {},
247
+ "body": json.dumps({"action": "do-thing", "payload": {}}),
248
+ }
249
+ h = _Handler(
250
+ aws_event=aws_event,
251
+ aws_context=type("C", (), {"aws_request_id": "rid"})(),
252
+ )
253
+ h.auth_mode = "required"
254
+ h.user_table = "donor_users"
255
+ ran = []
256
+ h.OnActionDoThing = lambda tx, context: ran.append(True)
257
+ tx = MagicMock()
258
+ with (
259
+ patch(
260
+ "velocity.aws.handlers.context.config_getenv",
261
+ side_effect=lambda name, default=None: (
262
+ SECRET if name == "MasqueradeSigningKey" else default
263
+ ),
264
+ ),
265
+ patch.object(security, "record_event", return_value=False),
266
+ ):
267
+ rendered = h.serve(tx)
268
+ self.assertEqual(rendered["statusCode"], 403)
269
+ body = json.loads(rendered["body"])
270
+ self.assertEqual(
271
+ body,
272
+ {
273
+ "ok": False,
274
+ "message": "That masquerade could not be recorded, so it was refused.",
275
+ },
276
+ )
277
+ tx.rollback.assert_called_once()
278
+ self.assertEqual(ran, [])
192
279
 
193
280
 
194
281
  if __name__ == "__main__":