velocity-python 0.2.5__tar.gz → 0.2.7__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 (410) hide show
  1. {velocity_python-0.2.5/src/velocity_python.egg-info → velocity_python-0.2.7}/PKG-INFO +1 -1
  2. {velocity_python-0.2.5 → velocity_python-0.2.7}/pyproject.toml +1 -1
  3. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/failures.py +198 -46
  5. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/context.py +20 -4
  6. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/async_support.py +3 -13
  7. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/transaction.py +74 -18
  8. {velocity_python-0.2.5 → velocity_python-0.2.7/src/velocity_python.egg-info}/PKG-INFO +1 -1
  9. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  10. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_async_support.py +4 -2
  11. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_errors_mixin.py +5 -1
  12. velocity_python-0.2.7/tests/test_cognito_client_lazy.py +45 -0
  13. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_failures.py +117 -6
  14. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_observability.py +37 -6
  15. {velocity_python-0.2.5 → velocity_python-0.2.7}/LICENSE +0 -0
  16. {velocity_python-0.2.5 → velocity_python-0.2.7}/README.md +0 -0
  17. {velocity_python-0.2.5 → velocity_python-0.2.7}/setup.cfg +0 -0
  18. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/__init__.py +0 -0
  19. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/amplify.py +0 -0
  20. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/__init__.py +0 -0
  21. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/backfill.py +0 -0
  22. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/indexing.py +0 -0
  23. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/references.py +0 -0
  24. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/service.py +0 -0
  25. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/assets/usage_index.py +0 -0
  26. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/authz.py +0 -0
  27. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/blobs.py +0 -0
  28. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/__init__.py +0 -0
  29. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/base.py +0 -0
  30. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/dispatch.py +0 -0
  31. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/infra.py +0 -0
  32. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/jobs.py +0 -0
  33. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/provisioning.py +0 -0
  34. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/retention.py +0 -0
  35. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/security.py +0 -0
  36. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/capabilities/single_session.py +0 -0
  37. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/__init__.py +0 -0
  38. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/ci_gate.py +0 -0
  39. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/config.py +0 -0
  40. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/frontend_checks.py +0 -0
  41. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/operations.py +0 -0
  42. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/policies.py +0 -0
  43. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/reconcile.py +0 -0
  44. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/deploy/targets.py +0 -0
  45. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/dirty_pipeline.py +0 -0
  46. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/email.py +0 -0
  47. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/facets.py +0 -0
  48. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/feature_flags.py +0 -0
  49. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/__init__.py +0 -0
  50. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/base_handler.py +0 -0
  51. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/context_factory.py +0 -0
  52. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/exceptions.py +0 -0
  53. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  54. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/masquerade.py +0 -0
  55. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  56. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
  57. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
  58. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
  59. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
  60. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  61. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  62. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
  63. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
  64. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  65. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
  66. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  67. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/perf.py +0 -0
  68. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/response.py +0 -0
  69. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  70. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/identity.py +0 -0
  71. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/job_lifecycle.py +0 -0
  72. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/job_results.py +0 -0
  73. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/mirror.py +0 -0
  74. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/ops.py +0 -0
  75. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/permissions.py +0 -0
  76. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/queue_defaults.py +0 -0
  77. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/s3.py +0 -0
  78. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/schema.py +0 -0
  79. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/sms.py +0 -0
  80. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/ssm_config.py +0 -0
  81. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/__init__.py +0 -0
  82. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  83. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
  84. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  85. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
  86. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  87. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_email.py +0 -0
  88. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  89. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_response.py +0 -0
  90. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tests/test_sms.py +0 -0
  91. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/tiles.py +0 -0
  92. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/aws/user_data.py +0 -0
  93. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/cache.py +0 -0
  94. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/__init__.py +0 -0
  95. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/__init__.py +0 -0
  96. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/column.py +0 -0
  97. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/database.py +0 -0
  98. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/decorators.py +0 -0
  99. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/engine.py +0 -0
  100. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/jsonproxy.py +0 -0
  101. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/lazy_engine.py +0 -0
  102. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/result.py +0 -0
  103. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/row.py +0 -0
  104. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/sequence.py +0 -0
  105. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/table.py +0 -0
  106. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/core/view.py +0 -0
  107. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/exceptions.py +0 -0
  108. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/fragments.py +0 -0
  109. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/maintenance.py +0 -0
  110. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/migrations.py +0 -0
  111. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/__init__.py +0 -0
  112. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/__init__.py +0 -0
  113. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/initializer.py +0 -0
  114. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/operators.py +0 -0
  115. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/sql.py +0 -0
  116. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/base/types.py +0 -0
  117. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/__init__.py +0 -0
  118. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/operators.py +0 -0
  119. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/reserved.py +0 -0
  120. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/sql.py +0 -0
  121. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/mysql/types.py +0 -0
  122. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/__init__.py +0 -0
  123. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/operators.py +0 -0
  124. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/reserved.py +0 -0
  125. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/sql.py +0 -0
  126. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/postgres/types.py +0 -0
  127. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  128. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/operators.py +0 -0
  129. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  130. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/sql.py +0 -0
  131. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlite/types.py +0 -0
  132. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  133. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  134. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  135. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  136. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/sqlserver/types.py +0 -0
  137. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/servers/tablehelper.py +0 -0
  138. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/__init__.py +0 -0
  139. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/common_db_test.py +0 -0
  140. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/__init__.py +0 -0
  141. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/common.py +0 -0
  142. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/conftest.py +0 -0
  143. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_column.py +0 -0
  144. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  145. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_database.py +0 -0
  146. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  147. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  148. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  149. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_result.py +0 -0
  150. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_row.py +0 -0
  151. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  152. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  153. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  154. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  155. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  156. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_table.py +0 -0
  157. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  158. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  159. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/__init__.py +0 -0
  160. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/common.py +0 -0
  161. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  162. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  163. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  164. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_db_utils.py +0 -0
  165. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_postgres.py +0 -0
  166. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  167. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  168. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_result_caching.py +0 -0
  169. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  170. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  171. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  172. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  173. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_sql_builder.py +0 -0
  174. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_tablehelper.py +0 -0
  175. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tests/test_view_helper.py +0 -0
  176. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/tunnel.py +0 -0
  177. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/db/utils.py +0 -0
  178. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/logging.py +0 -0
  179. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/__init__.py +0 -0
  180. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/__init__.py +0 -0
  181. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/iconv.py +0 -0
  182. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/conv/oconv.py +0 -0
  183. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/db.py +0 -0
  184. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/export.py +0 -0
  185. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/format.py +0 -0
  186. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/mail.py +0 -0
  187. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/merge.py +0 -0
  188. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/pdf.py +0 -0
  189. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/__init__.py +0 -0
  190. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_db.py +0 -0
  191. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_fix.py +0 -0
  192. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_format.py +0 -0
  193. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_iconv.py +0 -0
  194. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_mail_send.py +0 -0
  195. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_merge.py +0 -0
  196. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_oconv.py +0 -0
  197. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_original_error.py +0 -0
  198. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tests/test_timer.py +0 -0
  199. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/timer.py +0 -0
  200. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/misc/tools.py +0 -0
  201. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/__init__.py +0 -0
  202. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/__init__.py +0 -0
  203. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/capabilities.py +0 -0
  204. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/store.py +0 -0
  205. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/core/where.py +0 -0
  206. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/exceptions.py +0 -0
  207. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/__init__.py +0 -0
  208. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/base/__init__.py +0 -0
  209. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  210. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  211. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  212. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  213. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  214. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  215. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  216. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  217. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  218. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  219. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  220. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  221. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/__init__.py +0 -0
  222. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/_mirror_core.py +0 -0
  223. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/authorizenet_adapter.py +0 -0
  224. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/authorizenet_mirror.py +0 -0
  225. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/base_adapter.py +0 -0
  226. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/braintree_adapter.py +0 -0
  227. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/braintree_mirror.py +0 -0
  228. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/charge_rules.py +0 -0
  229. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/money.py +0 -0
  230. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/plaid_api.py +0 -0
  231. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/plaid_webhook.py +0 -0
  232. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/redact.py +0 -0
  233. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/schema.py +0 -0
  234. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/stripe_adapter.py +0 -0
  235. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/payment/stripe_mirror.py +0 -0
  236. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/testing/__init__.py +0 -0
  237. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity/testing/fake_db.py +0 -0
  238. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  239. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/entry_points.txt +0 -0
  240. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/requires.txt +0 -0
  241. {velocity_python-0.2.5 → velocity_python-0.2.7}/src/velocity_python.egg-info/top_level.txt +0 -0
  242. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_access_error_403.py +0 -0
  243. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_action_names.py +0 -0
  244. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_activity_tracking_aborted_tx.py +0 -0
  245. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_amplify_redirects.py +0 -0
  246. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_asset_indexing.py +0 -0
  247. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_asset_references.py +0 -0
  248. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_assets_service.py +0 -0
  249. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_audit_mixin.py +0 -0
  250. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_authz.py +0 -0
  251. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_aws_package_hygiene.py +0 -0
  252. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_batch_operations.py +0 -0
  253. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_blob_store.py +0 -0
  254. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_business_objects.py +0 -0
  255. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_capabilities.py +0 -0
  256. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_capability_catalog.py +0 -0
  257. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_check_changelog.py +0 -0
  258. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ci_gate.py +0 -0
  259. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_input_contract_export.py +0 -0
  260. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_sql_boundary.py +0 -0
  261. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_client_table_gate_case_and_pointers.py +0 -0
  262. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_cognito_user_cache.py +0 -0
  263. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_column_tx_arg.py +0 -0
  264. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_concurrency_safety.py +0 -0
  265. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_connection_pool.py +0 -0
  266. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_connection_resilience.py +0 -0
  267. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_flags_loader.py +0 -0
  268. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_job_descriptions.py +0 -0
  269. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_context_queue_job.py +0 -0
  270. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_conv_combine.py +0 -0
  271. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_owner_scope.py +0 -0
  272. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_query_cap.py +0 -0
  273. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_renumber.py +0 -0
  274. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_row_number.py +0 -0
  275. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_save_as.py +0 -0
  276. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_data_service_security_gates.py +0 -0
  277. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_credentials_ssm_cascade.py +0 -0
  278. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_maintenance.py +0 -0
  279. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_db_tunnel.py +0 -0
  280. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_declared_column_types.py +0 -0
  281. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_decorators.py +0 -0
  282. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_delete_hook_fk_blocked.py +0 -0
  283. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_config_fields.py +0 -0
  284. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_engine.py +0 -0
  285. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_frontend_checks.py +0 -0
  286. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_operations_queue.py +0 -0
  287. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_deploy_policies.py +0 -0
  288. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_dirty_pipeline_fast_path.py +0 -0
  289. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_dlq_constant.py +0 -0
  290. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_docs_snippets.py +0 -0
  291. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_email_processing.py +0 -0
  292. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_enqueue_metric_namespace.py +0 -0
  293. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_enqueue_send_failures.py +0 -0
  294. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ensure_infrastructure.py +0 -0
  295. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_export_workbook.py +0 -0
  296. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_facets.py +0 -0
  297. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_facets_and_where.py +0 -0
  298. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_feature_flags.py +0 -0
  299. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_get_cognito_user_provider.py +0 -0
  300. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_http_handler_rollback.py +0 -0
  301. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_iconv_money_to_cents.py +0 -0
  302. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_identifier_injection_guard.py +0 -0
  303. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_identity.py +0 -0
  304. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_handler.py +0 -0
  305. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_lifecycle.py +0 -0
  306. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_job_results.py +0 -0
  307. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_json_columns.py +0 -0
  308. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_jsonb_dict_adapter.py +0 -0
  309. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler.py +0 -0
  310. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_auth.py +0 -0
  311. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_masquerade.py +0 -0
  312. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lambda_handler_user_cache.py +0 -0
  313. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_lazy_engine.py +0 -0
  314. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_masquerade_grant.py +0 -0
  315. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_merge_on_conflict.py +0 -0
  316. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mirror.py +0 -0
  317. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mirror_upsert_public.py +0 -0
  318. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_mixins_import.py +0 -0
  319. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_n_plus_one.py +0 -0
  320. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_dynamodb.py +0 -0
  321. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_memory.py +0 -0
  322. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_mongodb.py +0 -0
  323. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_redis.py +0 -0
  324. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_translation.py +0 -0
  325. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_nosql_where.py +0 -0
  326. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_on_action_default.py +0 -0
  327. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_on_commit.py +0 -0
  328. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ops.py +0 -0
  329. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_audit_fixes.py +0 -0
  330. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_authorizenet_adapter.py +0 -0
  331. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_braintree_adapter.py +0 -0
  332. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_braintree_mirror.py +0 -0
  333. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_business_agnostic.py +0 -0
  334. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_charge_reference.py +0 -0
  335. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_extra_bounds.py +0 -0
  336. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_lazy_imports.py +0 -0
  337. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_mirror_empty_table.py +0 -0
  338. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_profile_sorting.py +0 -0
  339. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_payment_stripe_adapter.py +0 -0
  340. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pdf.py +0 -0
  341. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions.py +0 -0
  342. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_actions.py +0 -0
  343. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_notices.py +0 -0
  344. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_permissions_review.py +0 -0
  345. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_bulk_cache.py +0 -0
  346. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_capability_types.py +0 -0
  347. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_ensure_infrastructure.py +0 -0
  348. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_get_tables.py +0 -0
  349. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_permissions.py +0 -0
  350. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_pointer_joins.py +0 -0
  351. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_row_scope.py +0 -0
  352. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_search_export.py +0 -0
  353. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_tiles.py +0 -0
  354. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pgserver_user_data.py +0 -0
  355. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_plaid_api.py +0 -0
  356. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_plaid_webhook.py +0 -0
  357. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_pointer_join_sql.py +0 -0
  358. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_postgres_initializer_config.py +0 -0
  359. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_postgres_py_type.py +0 -0
  360. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_prepared_statements.py +0 -0
  361. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_psycopg3_upgrade.py +0 -0
  362. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_query_cache.py +0 -0
  363. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_query_missing_table_unlocked.py +0 -0
  364. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_raw_sql_prefix_policy.py +0 -0
  365. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_read_cache.py +0 -0
  366. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_readme_imports.py +0 -0
  367. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_response.py +0 -0
  368. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_restricted_direct_tables.py +0 -0
  369. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_result_returning.py +0 -0
  370. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_retry_side_effect_guard.py +0 -0
  371. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_return_default_safety.py +0 -0
  372. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_batch_update.py +0 -0
  373. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_cache_staleness.py +0 -0
  374. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_row_scoped_data_service.py +0 -0
  375. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_bucket_config.py +0 -0
  376. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_list_client_files.py +0 -0
  377. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_s3_upload.py +0 -0
  378. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_schema_lock_config_other_backends.py +0 -0
  379. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_schema_migrations.py +0 -0
  380. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_search_paging_export.py +0 -0
  381. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_security_capability.py +0 -0
  382. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_security_hardening.py +0 -0
  383. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_server_cursor.py +0 -0
  384. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_session_control.py +0 -0
  385. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_single_autocommit_safety.py +0 -0
  386. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_single_session_capability.py +0 -0
  387. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sms_twilio_signature.py +0 -0
  388. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_spreadsheet_functions.py +0 -0
  389. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sqlite_backend.py +0 -0
  390. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sqs_per_record_transactions.py +0 -0
  391. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ssm_config.py +0 -0
  392. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_ssm_config_require.py +0 -0
  393. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_store_user_data.py +0 -0
  394. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  395. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_alter.py +0 -0
  396. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_claim.py +0 -0
  397. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_find_cache.py +0 -0
  398. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_table_purge.py +0 -0
  399. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_testing_fake_db.py +0 -0
  400. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tiles.py +0 -0
  401. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_class_wrapping.py +0 -0
  402. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_commit_and_ownership.py +0 -0
  403. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_transaction_edge_cases.py +0 -0
  404. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tx_lifecycle_retry.py +0 -0
  405. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_tx_read_cache_and_bulk.py +0 -0
  406. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_user_data.py +0 -0
  407. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_user_not_provisioned.py +0 -0
  408. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_where_clause_validation.py +0 -0
  409. {velocity_python-0.2.5 → velocity_python-0.2.7}/tests/test_write_hook_create_flow.py +0 -0
  410. {velocity_python-0.2.5 → velocity_python-0.2.7}/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.2.5
3
+ Version: 0.2.7
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.2.5"
7
+ version = "0.2.7"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.2.5"
1
+ __version__ = version = "0.2.7"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -61,6 +61,13 @@ logger = logging.getLogger(__name__)
61
61
  EVENT_TYPE = "velocity.failure"
62
62
  EVENT_VERSION = 1
63
63
  TOPIC_CONFIG_KEY = "FailureTopicArn"
64
+ # Where a person reads failures (a failure inbox screen); the email links it.
65
+ INBOX_CONFIG_KEY = "FailureInboxUrl"
66
+ # The site's IANA zone; the email shows when it happened there.
67
+ TIMEZONE_CONFIG_KEY = "SiteTimezone"
68
+ # SNS caps an email subject at 100 characters of plain ASCII.
69
+ EMAIL_SUBJECT_LIMIT = 100
70
+ SUMMARY_LIMIT = 300
64
71
  SOURCES = frozenset({"queue", "scheduler", "web", "webhook", "job"})
65
72
 
66
73
  MESSAGE_LIMIT = 1000
@@ -541,10 +548,17 @@ def build_event(
541
548
  "log_group": f"/aws/lambda/{function}" if function else None,
542
549
  "log_stream": os.environ.get("AWS_LAMBDA_LOG_STREAM_NAME") or None,
543
550
  "classification": None,
551
+ # for people (render_text / the alert email): where it failed, a
552
+ # one-line summary and what to do, from the exception's `summary` /
553
+ # `hint` attributes or `extra`
554
+ "where": frame or None,
555
+ "summary": _person_text(exc, extra, "summary", keys),
556
+ "hint": _person_text(exc, extra, "hint", keys),
544
557
  }
545
558
  if extra:
546
559
  event["extra"] = payload_excerpt(
547
- {k: v for k, v in extra.items() if k != "message"}, keys
560
+ {k: v for k, v in extra.items() if k not in ("message", "summary", "hint")},
561
+ keys,
548
562
  )
549
563
  event["classification"] = _run_classify(
550
564
  classify if classify is not None else _classify, exc, event
@@ -552,55 +566,162 @@ def build_event(
552
566
  return event
553
567
 
554
568
 
569
+ def _person_text(exc, extra, name, keys):
570
+ """An exception's ``summary``/``hint`` attribute, else ``extra[name]``;
571
+ scrubbed and trimmed, or None."""
572
+ value = getattr(exc, name, None) if exc is not None else None
573
+ if not value and extra:
574
+ value = extra.get(name)
575
+ if not value:
576
+ return None
577
+ return _truncate(scrub_text(" ".join(str(value).split()), keys), SUMMARY_LIMIT)
578
+
579
+
580
+ def _local_time(occurred_at: Optional[str]) -> str:
581
+ """``Oct 20, 2026, 11:18 AM MDT`` in the site's zone (``SiteTimezone``),
582
+ else UTC; the raw value when it is not a timestamp."""
583
+ if not occurred_at:
584
+ return ""
585
+ try:
586
+ when = datetime.datetime.fromisoformat(str(occurred_at).replace("Z", "+00:00"))
587
+ except ValueError:
588
+ return str(occurred_at)
589
+ zone_name = None
590
+ try:
591
+ zone_name = _config(TIMEZONE_CONFIG_KEY)
592
+ except Exception:
593
+ zone_name = None
594
+ if zone_name:
595
+ try:
596
+ from zoneinfo import ZoneInfo
597
+
598
+ when = when.astimezone(ZoneInfo(str(zone_name)))
599
+ except Exception:
600
+ when = when.astimezone(datetime.timezone.utc)
601
+ hour = when.strftime("%I").lstrip("0") or "12"
602
+ return f"{when.strftime('%b')} {when.day}, {when.year}, {hour}:{when.strftime('%M %p %Z')}"
603
+
604
+
605
+ def _cloudwatch_escape(value: str) -> str:
606
+ from urllib.parse import quote
607
+
608
+ return quote(quote(value, safe=""), safe="").replace("%", "$")
609
+
610
+
611
+ def log_link(event: Optional[Dict[str, Any]]) -> str:
612
+ """The CloudWatch console page of the event's log stream, or ""."""
613
+ if not event or not event.get("log_group"):
614
+ return ""
615
+ region = os.environ.get("AWS_REGION") # set in every Lambda
616
+ if not region:
617
+ return ""
618
+ url = (
619
+ f"https://{region}.console.aws.amazon.com/cloudwatch/home?region={region}"
620
+ f"#logsV2:log-groups/log-group/{_cloudwatch_escape(event['log_group'])}"
621
+ )
622
+ if event.get("log_stream"):
623
+ url += f"/log-events/{_cloudwatch_escape(event['log_stream'])}"
624
+ return url
625
+
626
+
627
+ def _handled(event: Dict[str, Any]) -> Optional[bool]:
628
+ extra = event.get("extra")
629
+ if isinstance(extra, dict) and "handled" in extra:
630
+ return bool(extra["handled"])
631
+ return None
632
+
633
+
634
+ _SOURCE_WORDS = {
635
+ "job": "a background job",
636
+ "queue": "a queued message",
637
+ "scheduler": "a scheduled task",
638
+ "request": "a request",
639
+ }
640
+
641
+
555
642
  def render_text(event: Optional[Dict[str, Any]]) -> str:
556
- """A plain-text rendering of an (already scrubbed) event, for email or
557
- chat fallbacks. Contains nothing the event does not."""
643
+ """The failure for a person (the alert email, fallbacks): what failed,
644
+ the error in words, what to do, when (site-local time), whether it was
645
+ handled, where to look. Nothing beyond the event, the configured inbox
646
+ link and a log link."""
558
647
  if not event:
559
648
  return ""
560
- lines = [
561
- f"{event.get('site') or '?'} {event.get('env') or '?'} "
562
- f"{event.get('source') or '?'} failure"
563
- + (f" [{event['classification']}]" if event.get("classification") else ""),
564
- "",
565
- ]
566
- for key in (
567
- "function",
568
- "action",
569
- "job_id",
570
- "request_id",
571
- "message_id",
572
- "receive_count",
573
- "exception_type",
574
- "message",
575
- "occurred_at",
576
- "fingerprint",
577
- "log_group",
578
- "log_stream",
649
+ site = event.get("site") or "?"
650
+ env = event.get("env") or "?"
651
+ exc_name = (event.get("exception_type") or "").rsplit(".", 1)[-1]
652
+ source = _SOURCE_WORDS.get(event.get("source"), event.get("source") or "something")
653
+ lines = [f"{site} ({env}): {source} failed", ""]
654
+
655
+ what = event.get("action") or event.get("source") or "?"
656
+ extra = event.get("extra") if isinstance(event.get("extra"), dict) else {}
657
+ details = [str(extra[k]) for k in ("capability",) if extra.get(k)]
658
+ subject = extra.get("subject")
659
+ lines.append(
660
+ f"What: {what}"
661
+ + (f" ({', '.join(details)})" if details else "")
662
+ + (f", for {subject}" if subject else "")
663
+ )
664
+ error = event.get("summary") or event.get("message") or exc_name or "?"
665
+ lines.append(f"Error: {error}" + (f" ({exc_name})" if exc_name else ""))
666
+ if event.get("hint"):
667
+ lines.append(f"To do: {event['hint']}")
668
+ if event.get("occurred_at"):
669
+ lines.append(f"When: {_local_time(event['occurred_at'])}")
670
+ handled = _handled(event)
671
+ if handled is True:
672
+ lines.append(
673
+ "Handled: yes; it was recorded and the work is tried again on its next run"
674
+ )
675
+ elif handled is False:
676
+ lines.append("Handled: no; the function stopped on it")
677
+ if event.get("classification"):
678
+ lines.append(f"Class: {event['classification']}")
679
+ if event.get("receive_count"):
680
+ lines.append(f"Attempt: {event['receive_count']}")
681
+ lines.append("")
682
+ if event.get("function"):
683
+ lines.append(f"Function: {event['function']}")
684
+ link = log_link(event)
685
+ if link:
686
+ lines.append(f"Log: {link}")
687
+ inbox = None
688
+ try:
689
+ inbox = _config(INBOX_CONFIG_KEY)
690
+ except Exception:
691
+ inbox = None
692
+ if inbox:
693
+ lines.append(f"Inbox: {inbox}")
694
+ lines.append("")
695
+ if event.get("where"):
696
+ lines.append(f"Where it failed: {event['where']}")
697
+ if (
698
+ event.get("summary")
699
+ and event.get("message")
700
+ and event["message"] != event["summary"]
579
701
  ):
580
- value = event.get(key)
581
- if value not in (None, ""):
582
- lines.append(f"{key}: {value}")
583
- for key in ("payload_excerpt", "extra"):
584
- if event.get(key):
585
- lines.append(f"{key}: {_render(event[key])}")
586
- if event.get("traceback"):
587
- lines.extend(["", "Traceback (scrubbed):", event["traceback"]])
702
+ lines.append(f"Raw message: {event['message']}")
703
+ if event.get("fingerprint"):
704
+ lines.append(f"Reference: {str(event['fingerprint'])[:12]}")
705
+ while lines and not lines[-1]:
706
+ lines.pop()
588
707
  return "\n".join(lines)
589
708
 
590
709
 
591
710
  def subject_line(event: Optional[Dict[str, Any]]) -> str:
592
- """A one-line summary (≤ 200 chars) for an alert subject."""
711
+ """One line for an alert subject: site, env, what failed and the error in
712
+ words. Plain ASCII, at most 100 characters (SNS's email subject rules)."""
593
713
  if not event:
594
714
  return "Failure"
595
715
  exc_name = (event.get("exception_type") or "Failure").rsplit(".", 1)[-1]
596
- parts = [
597
- f"{(event.get('env') or '?').title()} {event.get('site') or ''}".strip(),
598
- event.get("source") or "",
599
- event.get("action") or "",
600
- ]
601
- label = f" [{event['classification']}]" if event.get("classification") else ""
602
- head = " ".join(part for part in parts if part)
603
- return _truncate(f"{head}: {exc_name}{label}", 200)
716
+ what = event.get("action") or event.get("source") or "something"
717
+ error = event.get("summary") or event.get("message") or exc_name
718
+ text = (
719
+ f"{event.get('site') or '?'} {event.get('env') or '?'}: {what} failed: {error}"
720
+ )
721
+ text = " ".join(text.split()).encode("ascii", "ignore").decode("ascii")
722
+ if len(text) > EMAIL_SUBJECT_LIMIT:
723
+ text = text[: EMAIL_SUBJECT_LIMIT - 3].rstrip() + "..."
724
+ return text
604
725
 
605
726
 
606
727
  def _run_fallback(event: Dict[str, Any]) -> None:
@@ -701,13 +822,7 @@ def report(
701
822
  return None
702
823
 
703
824
  try:
704
- _client().publish(
705
- TopicArn=topic_arn,
706
- Message=_dumps(event),
707
- MessageAttributes={
708
- "velocity_event": {"DataType": "String", "StringValue": "failure"}
709
- },
710
- )
825
+ _client().publish(**publish_request(topic_arn, event))
711
826
  except Exception:
712
827
  logger.exception(
713
828
  "failures: publish to %s failed (fingerprint %s)",
@@ -718,6 +833,43 @@ def report(
718
833
  return event
719
834
 
720
835
 
836
+ # SNS refuses a message over 256 KB; the email text is dropped to a short
837
+ # form well before that (the inbox keeps the whole event).
838
+ _SNS_MESSAGE_BUDGET = 240 * 1024
839
+
840
+
841
+ def publish_request(topic_arn: str, event: Dict[str, Any]) -> Dict[str, Any]:
842
+ """The ``sns.publish`` arguments for one event. Subscribers each get their
843
+ own form (``MessageStructure="json"``): a Lambda (the failure inbox) and
844
+ every other protocol the event's JSON, unchanged; an ``email``
845
+ subscription the text a person reads (:func:`render_text`) under a
846
+ readable subject. The ``handled`` attribute ("true"/"false"/"unknown")
847
+ lets a subscription filter: the person's backstop email takes only
848
+ failures that were not handled."""
849
+ machine = _dumps(event)
850
+ person = render_text(event)
851
+ message = json.dumps({"default": machine, "email": person})
852
+ if len(message.encode("utf-8")) > _SNS_MESSAGE_BUDGET:
853
+ person = person[:4000] + "\n\n(shortened; the failure inbox has the rest)"
854
+ message = json.dumps({"default": machine, "email": person})
855
+ handled = _handled(event)
856
+ return {
857
+ "TopicArn": topic_arn,
858
+ "Message": message,
859
+ "MessageStructure": "json",
860
+ "Subject": subject_line(event),
861
+ "MessageAttributes": {
862
+ "velocity_event": {"DataType": "String", "StringValue": "failure"},
863
+ "handled": {
864
+ "DataType": "String",
865
+ "StringValue": (
866
+ "unknown" if handled is None else ("true" if handled else "false")
867
+ ),
868
+ },
869
+ },
870
+ }
871
+
872
+
721
873
  def is_enabled() -> bool:
722
874
  """Whether ``FailureTopicArn`` resolves (events will be published)."""
723
875
  return bool(_config(TOPIC_CONFIG_KEY))
@@ -27,7 +27,22 @@ from velocity.db.core.lazy_engine import LazyEngine
27
27
  engine = LazyEngine(velocity.db.postgres.initialize)
28
28
 
29
29
 
30
- cognito_client = boto3.client("cognito-idp")
30
+ # The Cognito client is built on first use, not at import: creating it loads
31
+ # Cognito's whole API model and resolves its endpoint (~13 MB and ~2 s on a
32
+ # 128 MB Lambda), which every function importing a handler paid — a
33
+ # Scheduler or queue worker that never signs anyone in included. Tests may
34
+ # replace this attribute; whatever it holds is what `_cognito()` returns.
35
+ cognito_client = None
36
+
37
+
38
+ def _cognito():
39
+ """The shared Cognito client, created on first use."""
40
+ global cognito_client
41
+ if cognito_client is None:
42
+ cognito_client = boto3.client("cognito-idp")
43
+ return cognito_client
44
+
45
+
31
46
  logger = get_logger("velocity.aws.handlers.context")
32
47
 
33
48
  # Cognito User Pool id shape, e.g. "us-east-1_aBcD1234". Used to distinguish a
@@ -1036,18 +1051,19 @@ class Context:
1036
1051
  # one Cognito round trip per user per UserCacheSeconds per container
1037
1052
  # (velocity.cache); a lookup that fails is never cached
1038
1053
  def resolve():
1054
+ client = _cognito()
1039
1055
  try:
1040
1056
  self.perf.start("cognito admin_get_user")
1041
- response = cognito_client.admin_get_user(
1057
+ response = client.admin_get_user(
1042
1058
  UserPoolId=user_pool_id,
1043
1059
  Username=user_sub,
1044
1060
  )
1045
1061
  self.perf.log("cognito admin_get_user")
1046
1062
  resolved_username = response.get("Username")
1047
- except cognito_client.exceptions.UserNotFoundException:
1063
+ except client.exceptions.UserNotFoundException:
1048
1064
  try:
1049
1065
  self.perf.start("cognito list_users")
1050
- list_response = cognito_client.list_users(
1066
+ list_response = client.list_users(
1051
1067
  UserPoolId=user_pool_id,
1052
1068
  Filter=f'sub = "{user_sub}"',
1053
1069
  Limit=1,
@@ -528,19 +528,9 @@ class AsyncTransaction:
528
528
 
529
529
  # R12 — Slow query logging
530
530
  if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
531
- from velocity.db.core.transaction import _summarize_sql
532
-
533
- sql_preview = _summarize_sql(sql)
534
- _logger.warning(
535
- "Slow async query: %.1f ms sql=%s",
536
- elapsed_ms,
537
- sql_preview,
538
- extra={
539
- "query_duration_ms": round(elapsed_ms, 1),
540
- "sql_preview": sql_preview,
541
- },
542
- stack_info=True,
543
- )
531
+ from velocity.db.core.transaction import _slow_query_log
532
+
533
+ _slow_query_log(_logger, sql, elapsed_ms)
544
534
 
545
535
  # R14 — N+1 detection / R33 — raw-write cache invalidation
546
536
  from velocity.db.core.transaction import _classify_sql, _extract_table_name
@@ -30,6 +30,15 @@ _DEFAULT_QUERY_CACHE_SIZE = int(os.environ.get("VELOCITY_QUERY_CACHE_SIZE", "100
30
30
  # Slow-query threshold in milliseconds (0 = disabled).
31
31
  _SLOW_QUERY_MS = int(os.environ.get("VELOCITY_SLOW_QUERY_MS", "500"))
32
32
  _SLOW_QUERY_SQL_CHARS = int(os.environ.get("VELOCITY_SLOW_QUERY_SQL_CHARS", "4000"))
33
+ # The slow-query line is for people: this much SQL, the caller's file:line,
34
+ # no traceback unless VELOCITY_SLOW_QUERY_STACK=1 asks for one. The full
35
+ # preview (VELOCITY_SLOW_QUERY_SQL_CHARS) still rides on the record's `extra`.
36
+ _SLOW_QUERY_LINE_CHARS = 200
37
+ _SLOW_QUERY_STACK = os.environ.get("VELOCITY_SLOW_QUERY_STACK", "").strip().lower() in (
38
+ "1",
39
+ "true",
40
+ "yes",
41
+ )
33
42
 
34
43
  # N+1 detection: warn when the same table is SELECTed more than this many
35
44
  # times within a single transaction. 0 = disabled. Only active when the
@@ -174,16 +183,79 @@ def _written_tables_from_sql(sql):
174
183
  return tags or None
175
184
 
176
185
 
186
+ # a run of three or more bind placeholders: "IN (%s, %s, %s, …)"
187
+ _PLACEHOLDER_RUN_RE = re.compile(r"(%s|\?)(?:\s*,\s*\1){2,}")
188
+
189
+
190
+ def _collapse_placeholders(text):
191
+ """``%s, %s, … (812 of them)`` -> ``%s × 812``."""
192
+
193
+ def collapse(match):
194
+ token = match.group(1)
195
+ return f"{token} × {match.group(0).count(token)}"
196
+
197
+ return _PLACEHOLDER_RUN_RE.sub(collapse, text)
198
+
199
+
177
200
  def _summarize_sql(sql, limit=None):
178
201
  if not sql:
179
202
  return None
180
- compact = " ".join(str(sql).split())
203
+ compact = _collapse_placeholders(" ".join(str(sql).split()))
181
204
  max_chars = _SLOW_QUERY_SQL_CHARS if limit is None else limit
182
205
  if max_chars and len(compact) > max_chars:
183
206
  return compact[:max_chars] + "... [truncated]"
184
207
  return compact
185
208
 
186
209
 
210
+ _VELOCITY_DIR = os.path.dirname(
211
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
212
+ )
213
+
214
+
215
+ def _caller_location():
216
+ """``file.py:line`` of the first stack frame outside velocity (the code
217
+ that ran the query), or ``None``."""
218
+ import traceback
219
+
220
+ for frame in reversed(traceback.extract_stack()[:-1]):
221
+ filename = os.path.abspath(frame.filename)
222
+ if filename.startswith(_VELOCITY_DIR + os.sep) or "<frozen" in frame.filename:
223
+ continue
224
+ return f"{os.path.basename(frame.filename)}:{frame.lineno}"
225
+ return None
226
+
227
+
228
+ def _slow_query_log(logger, sql, elapsed_ms, *, op=None, table=None):
229
+ """One readable line for a slow query: what, on which table, how long,
230
+ from where, and the start of the SQL (placeholders collapsed). The fields
231
+ for log tooling go in ``extra``; a traceback only with
232
+ VELOCITY_SLOW_QUERY_STACK=1."""
233
+ op = op or _classify_sql(sql)
234
+ table = table if table is not None else _extract_table_name(sql)
235
+ preview = _summarize_sql(sql)
236
+ line = _summarize_sql(sql, limit=_SLOW_QUERY_LINE_CHARS)
237
+ caller = _caller_location()
238
+ took = (
239
+ f"{elapsed_ms / 1000:.1f} s" if elapsed_ms >= 1000 else f"{elapsed_ms:.0f} ms"
240
+ )
241
+ logger.warning(
242
+ "Slow %s%s: %s%s — %s",
243
+ op or "query",
244
+ f" on {table}" if table else "",
245
+ took,
246
+ f" at {caller}" if caller else "",
247
+ line,
248
+ extra={
249
+ "query_duration_ms": round(elapsed_ms, 1),
250
+ "table_name": table,
251
+ "operation": op,
252
+ "sql_preview": preview,
253
+ "caller": caller,
254
+ },
255
+ stack_info=_SLOW_QUERY_STACK,
256
+ )
257
+
258
+
187
259
  def _is_prepare_kw_type_error(error):
188
260
  """True for the TypeError a DB-API driver raises when ``execute()`` has
189
261
  no ``prepare`` keyword (``unexpected keyword argument 'prepare'``)."""
@@ -650,23 +722,7 @@ class Transaction:
650
722
 
651
723
  # R12 — Slow query logging.
652
724
  if _SLOW_QUERY_MS and elapsed_ms > _SLOW_QUERY_MS:
653
- op = _classify_sql(sql)
654
- tbl = _extract_table_name(sql)
655
- sql_preview = _summarize_sql(sql)
656
- _logger.warning(
657
- "Slow query (%s): %.1f ms table=%s sql=%s",
658
- op,
659
- elapsed_ms,
660
- tbl,
661
- sql_preview,
662
- extra={
663
- "query_duration_ms": round(elapsed_ms, 1),
664
- "table_name": tbl,
665
- "operation": op,
666
- "sql_preview": sql_preview,
667
- },
668
- stack_info=True,
669
- )
725
+ _slow_query_log(_logger, sql, elapsed_ms)
670
726
 
671
727
  # R14 — N+1 detection (only when debug=True).
672
728
  if debug and _N_PLUS_1_THRESHOLD:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.2.5
3
+ Version: 0.2.7
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
@@ -254,6 +254,7 @@ tests/test_client_errors_mixin.py
254
254
  tests/test_client_input_contract_export.py
255
255
  tests/test_client_sql_boundary.py
256
256
  tests/test_client_table_gate_case_and_pointers.py
257
+ tests/test_cognito_client_lazy.py
257
258
  tests/test_cognito_user_cache.py
258
259
  tests/test_column_tx_arg.py
259
260
  tests/test_concurrency_safety.py
@@ -728,6 +728,8 @@ class TestAsyncSlowQueryLogging:
728
728
  assert len(slow_calls) >= 1
729
729
  args = slow_calls[0].args
730
730
  kwargs = slow_calls[0].kwargs
731
- assert args[2] == "SELECT * FROM large_table"
731
+ # the same one-line form as the sync path: op, table, SQL last
732
+ assert args[1:3] == ("SELECT", " on large_table")
733
+ assert args[-1] == "SELECT * FROM large_table"
732
734
  assert kwargs["extra"]["sql_preview"] == "SELECT * FROM large_table"
733
- assert kwargs["stack_info"] is True
735
+ assert kwargs["stack_info"] is False
@@ -696,7 +696,11 @@ def test_real_failures_report_builds_a_publishable_event():
696
696
  ):
697
697
  run(Handler(), {"errors": [error(message="token=abc123secret failed")]})
698
698
  client.publish.assert_called_once()
699
- event = json.loads(client.publish.call_args.kwargs["Message"])
699
+ # one body per subscriber: the inbox reads "default" (the event), a
700
+ # person's email reads "email"; neither may carry the secret
701
+ bodies = json.loads(client.publish.call_args.kwargs["Message"])
702
+ assert "abc123secret" not in bodies["email"]
703
+ event = json.loads(bodies["default"])
700
704
  assert event["source"] == client_errors.FAILURE_SOURCE
701
705
  assert event["action"] == "report-client-error"
702
706
  assert event["exception_type"].endswith("client_errors.ClientError")
@@ -0,0 +1,45 @@
1
+ """The Cognito client is created on first use, not when velocity's handler
2
+ context is imported: building it loads Cognito's API model (~13 MB, ~2 s on a
3
+ 128 MB Lambda) that a Scheduler or queue worker never needs."""
4
+
5
+ import importlib
6
+ import sys
7
+ from unittest import mock
8
+
9
+
10
+ def test_importing_the_context_creates_no_cognito_client():
11
+ name = "velocity.aws.handlers.context"
12
+ original = sys.modules.pop(name, None)
13
+ try:
14
+ with mock.patch("boto3.client") as client:
15
+ module = importlib.import_module(name)
16
+ cognito_calls = [
17
+ c for c in client.call_args_list if c.args[:1] == ("cognito-idp",)
18
+ ]
19
+ assert cognito_calls == []
20
+ assert module.cognito_client is None
21
+ finally:
22
+ # every other test keeps the module object it imported
23
+ if original is not None:
24
+ sys.modules[name] = original
25
+
26
+
27
+ def test_the_client_is_created_once_on_first_use():
28
+ from velocity.aws.handlers import context
29
+
30
+ with (
31
+ mock.patch.object(context, "cognito_client", None),
32
+ mock.patch("boto3.client") as client,
33
+ ):
34
+ first = context._cognito()
35
+ second = context._cognito()
36
+ client.assert_called_once_with("cognito-idp")
37
+ assert first is second
38
+
39
+
40
+ def test_a_client_put_in_place_is_the_one_used():
41
+ from velocity.aws.handlers import context
42
+
43
+ fake = object()
44
+ with mock.patch.object(context, "cognito_client", fake):
45
+ assert context._cognito() is fake