velocity-python 0.2.9__tar.gz → 0.3.0__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 (441) hide show
  1. {velocity_python-0.2.9 → velocity_python-0.3.0}/PKG-INFO +5 -2
  2. {velocity_python-0.2.9 → velocity_python-0.3.0}/pyproject.toml +10 -2
  3. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/__init__.py +8 -0
  5. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/base.py +62 -0
  6. velocity_python-0.3.0/src/velocity/aws/capabilities/dispatch.py +124 -0
  7. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/jobs.py +41 -16
  8. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/provisioning.py +72 -5
  9. velocity_python-0.3.0/src/velocity/aws/dirty_pipeline.py +624 -0
  10. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/context.py +315 -16
  11. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/__init__.py +8 -0
  12. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/audit_trail.py +288 -83
  13. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/job_handler.py +2 -16
  14. velocity_python-0.3.0/src/velocity/aws/job_lifecycle.py +486 -0
  15. velocity_python-0.3.0/src/velocity/aws/scheduler.py +567 -0
  16. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/schema.py +7 -0
  17. velocity_python-0.3.0/src/velocity/db/_pgddl.py +162 -0
  18. velocity_python-0.3.0/src/velocity/db/audit.py +784 -0
  19. velocity_python-0.3.0/src/velocity/db/effective.py +408 -0
  20. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/exceptions.py +28 -0
  21. velocity_python-0.3.0/src/velocity/db/integrity.py +782 -0
  22. velocity_python-0.3.0/src/velocity/db/tests/postgres/test_integrity_triggers.py +514 -0
  23. velocity_python-0.3.0/src/velocity/hooks.py +526 -0
  24. velocity_python-0.3.0/src/velocity/rules/__init__.py +111 -0
  25. velocity_python-0.3.0/src/velocity/rules/engine.py +857 -0
  26. velocity_python-0.3.0/src/velocity/rules/expressions.py +451 -0
  27. velocity_python-0.3.0/src/velocity/rules/facts.py +331 -0
  28. velocity_python-0.3.0/src/velocity/rules/storage.py +391 -0
  29. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/PKG-INFO +5 -2
  30. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/SOURCES.txt +26 -0
  31. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/requires.txt +5 -1
  32. velocity_python-0.3.0/tests/test_audit_log_confidential.py +41 -0
  33. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_audit_mixin.py +4 -1
  34. velocity_python-0.3.0/tests/test_audit_registry_table_gate.py +79 -0
  35. velocity_python-0.3.0/tests/test_capability_dependencies.py +319 -0
  36. velocity_python-0.3.0/tests/test_context_enqueue_idempotency.py +574 -0
  37. velocity_python-0.3.0/tests/test_db_audit_required.py +982 -0
  38. velocity_python-0.3.0/tests/test_db_effective.py +390 -0
  39. velocity_python-0.3.0/tests/test_db_integrity.py +724 -0
  40. velocity_python-0.3.0/tests/test_dirty_pipeline_consumer.py +645 -0
  41. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_ensure_infrastructure.py +14 -1
  42. velocity_python-0.3.0/tests/test_hooks.py +360 -0
  43. velocity_python-0.3.0/tests/test_job_lifecycle.py +182 -0
  44. velocity_python-0.3.0/tests/test_job_record_guard.py +443 -0
  45. velocity_python-0.3.0/tests/test_job_record_guard_postgres.py +198 -0
  46. velocity_python-0.3.0/tests/test_rules.py +868 -0
  47. velocity_python-0.3.0/tests/test_rules_storage.py +353 -0
  48. velocity_python-0.3.0/tests/test_scheduler.py +594 -0
  49. velocity_python-0.2.9/src/velocity/aws/capabilities/dispatch.py +0 -61
  50. velocity_python-0.2.9/src/velocity/aws/dirty_pipeline.py +0 -121
  51. velocity_python-0.2.9/src/velocity/aws/job_lifecycle.py +0 -71
  52. velocity_python-0.2.9/tests/test_job_lifecycle.py +0 -119
  53. {velocity_python-0.2.9 → velocity_python-0.3.0}/LICENSE +0 -0
  54. {velocity_python-0.2.9 → velocity_python-0.3.0}/README.md +0 -0
  55. {velocity_python-0.2.9 → velocity_python-0.3.0}/setup.cfg +0 -0
  56. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/__init__.py +0 -0
  57. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/amplify.py +0 -0
  58. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/__init__.py +0 -0
  59. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/backfill.py +0 -0
  60. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/indexing.py +0 -0
  61. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/references.py +0 -0
  62. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/service.py +0 -0
  63. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/assets/usage_index.py +0 -0
  64. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/authz.py +0 -0
  65. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/blobs.py +0 -0
  66. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/infra.py +0 -0
  67. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/retention.py +0 -0
  68. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/security.py +0 -0
  69. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/capabilities/single_session.py +0 -0
  70. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/__init__.py +0 -0
  71. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/ci_gate.py +0 -0
  72. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/config.py +0 -0
  73. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/frontend_checks.py +0 -0
  74. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/operations.py +0 -0
  75. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/policies.py +0 -0
  76. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/reconcile.py +0 -0
  77. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/deploy/targets.py +0 -0
  78. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/email.py +0 -0
  79. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/facets.py +0 -0
  80. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/failures.py +0 -0
  81. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/feature_flags.py +0 -0
  82. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/__init__.py +0 -0
  83. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/base_handler.py +0 -0
  84. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/context_factory.py +0 -0
  85. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/exceptions.py +0 -0
  86. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  87. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/masquerade.py +0 -0
  88. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
  89. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
  90. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
  91. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  92. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
  93. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
  94. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  95. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/tiles_actions.py +0 -0
  96. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  97. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/perf.py +0 -0
  98. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/response.py +0 -0
  99. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  100. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/identity.py +0 -0
  101. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/job_results.py +0 -0
  102. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/mirror.py +0 -0
  103. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/ops.py +0 -0
  104. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/permissions.py +0 -0
  105. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/queue_defaults.py +0 -0
  106. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/s3.py +0 -0
  107. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/sms.py +0 -0
  108. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/ssm_config.py +0 -0
  109. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/__init__.py +0 -0
  110. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  111. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
  112. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  113. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
  114. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  115. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_email.py +0 -0
  116. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  117. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_response.py +0 -0
  118. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tests/test_sms.py +0 -0
  119. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/tiles.py +0 -0
  120. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/aws/user_data.py +0 -0
  121. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/cache.py +0 -0
  122. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/__init__.py +0 -0
  123. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/__init__.py +0 -0
  124. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/async_support.py +0 -0
  125. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/column.py +0 -0
  126. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/database.py +0 -0
  127. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/decorators.py +0 -0
  128. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/engine.py +0 -0
  129. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/jsonproxy.py +0 -0
  130. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/lazy_engine.py +0 -0
  131. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/result.py +0 -0
  132. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/row.py +0 -0
  133. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/sequence.py +0 -0
  134. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/table.py +0 -0
  135. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/transaction.py +0 -0
  136. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/core/view.py +0 -0
  137. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/fragments.py +0 -0
  138. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/maintenance.py +0 -0
  139. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/migrations.py +0 -0
  140. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/__init__.py +0 -0
  141. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/base/__init__.py +0 -0
  142. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/base/initializer.py +0 -0
  143. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/base/operators.py +0 -0
  144. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/base/sql.py +0 -0
  145. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/base/types.py +0 -0
  146. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/mysql/__init__.py +0 -0
  147. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/mysql/operators.py +0 -0
  148. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/mysql/reserved.py +0 -0
  149. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/mysql/sql.py +0 -0
  150. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/mysql/types.py +0 -0
  151. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/postgres/__init__.py +0 -0
  152. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/postgres/operators.py +0 -0
  153. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/postgres/reserved.py +0 -0
  154. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/postgres/sql.py +0 -0
  155. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/postgres/types.py +0 -0
  156. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  157. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlite/operators.py +0 -0
  158. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  159. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlite/sql.py +0 -0
  160. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlite/types.py +0 -0
  161. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  162. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  163. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  164. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  165. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/sqlserver/types.py +0 -0
  166. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/servers/tablehelper.py +0 -0
  167. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/__init__.py +0 -0
  168. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/common_db_test.py +0 -0
  169. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/__init__.py +0 -0
  170. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/common.py +0 -0
  171. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/conftest.py +0 -0
  172. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_column.py +0 -0
  173. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  174. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_database.py +0 -0
  175. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  176. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  177. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  178. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_result.py +0 -0
  179. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_row.py +0 -0
  180. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  181. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  182. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  183. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  184. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  185. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_table.py +0 -0
  186. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  187. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  188. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/sql/__init__.py +0 -0
  189. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/sql/common.py +0 -0
  190. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  191. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  192. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  193. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_db_utils.py +0 -0
  194. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_postgres.py +0 -0
  195. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  196. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  197. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_result_caching.py +0 -0
  198. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  199. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  200. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  201. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  202. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_sql_builder.py +0 -0
  203. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_tablehelper.py +0 -0
  204. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tests/test_view_helper.py +0 -0
  205. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/tunnel.py +0 -0
  206. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/db/utils.py +0 -0
  207. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/logging.py +0 -0
  208. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/__init__.py +0 -0
  209. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/conv/__init__.py +0 -0
  210. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/conv/iconv.py +0 -0
  211. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/conv/oconv.py +0 -0
  212. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/db.py +0 -0
  213. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/export.py +0 -0
  214. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/format.py +0 -0
  215. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/mail.py +0 -0
  216. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/merge.py +0 -0
  217. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/pdf.py +0 -0
  218. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/__init__.py +0 -0
  219. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_db.py +0 -0
  220. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_fix.py +0 -0
  221. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_format.py +0 -0
  222. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_iconv.py +0 -0
  223. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_mail_send.py +0 -0
  224. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_merge.py +0 -0
  225. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_oconv.py +0 -0
  226. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_original_error.py +0 -0
  227. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tests/test_timer.py +0 -0
  228. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/timer.py +0 -0
  229. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/misc/tools.py +0 -0
  230. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/__init__.py +0 -0
  231. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/core/__init__.py +0 -0
  232. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/core/capabilities.py +0 -0
  233. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/core/store.py +0 -0
  234. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/core/where.py +0 -0
  235. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/exceptions.py +0 -0
  236. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/__init__.py +0 -0
  237. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/base/__init__.py +0 -0
  238. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  239. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  240. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  241. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  242. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  243. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  244. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  245. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  246. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  247. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  248. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  249. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  250. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/__init__.py +0 -0
  251. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/_mirror_core.py +0 -0
  252. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/authorizenet_adapter.py +0 -0
  253. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/authorizenet_mirror.py +0 -0
  254. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/base_adapter.py +0 -0
  255. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/braintree_adapter.py +0 -0
  256. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/braintree_mirror.py +0 -0
  257. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/charge_rules.py +0 -0
  258. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/money.py +0 -0
  259. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/plaid_api.py +0 -0
  260. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/plaid_webhook.py +0 -0
  261. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/redact.py +0 -0
  262. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/schema.py +0 -0
  263. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/stripe_adapter.py +0 -0
  264. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/payment/stripe_mirror.py +0 -0
  265. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/testing/__init__.py +0 -0
  266. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity/testing/fake_db.py +0 -0
  267. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  268. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/entry_points.txt +0 -0
  269. {velocity_python-0.2.9 → velocity_python-0.3.0}/src/velocity_python.egg-info/top_level.txt +0 -0
  270. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_access_error_403.py +0 -0
  271. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_action_names.py +0 -0
  272. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_activity_tracking_aborted_tx.py +0 -0
  273. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_amplify_redirects.py +0 -0
  274. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_asset_indexing.py +0 -0
  275. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_asset_references.py +0 -0
  276. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_assets_service.py +0 -0
  277. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_async_support.py +0 -0
  278. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_authz.py +0 -0
  279. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_aws_package_hygiene.py +0 -0
  280. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_batch_operations.py +0 -0
  281. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_blob_store.py +0 -0
  282. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_business_objects.py +0 -0
  283. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_capabilities.py +0 -0
  284. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_capability_catalog.py +0 -0
  285. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_check_changelog.py +0 -0
  286. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_ci_gate.py +0 -0
  287. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_client_errors_mixin.py +0 -0
  288. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_client_input_contract_export.py +0 -0
  289. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_client_sql_boundary.py +0 -0
  290. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_client_table_gate_case_and_pointers.py +0 -0
  291. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_cognito_client_lazy.py +0 -0
  292. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_cognito_user_cache.py +0 -0
  293. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_column_tx_arg.py +0 -0
  294. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_concurrency_safety.py +0 -0
  295. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_connection_pool.py +0 -0
  296. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_connection_resilience.py +0 -0
  297. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_context_flags_loader.py +0 -0
  298. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_context_job_descriptions.py +0 -0
  299. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_context_queue_job.py +0 -0
  300. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_conv_combine.py +0 -0
  301. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_owner_scope.py +0 -0
  302. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_query_cap.py +0 -0
  303. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_renumber.py +0 -0
  304. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_row_number.py +0 -0
  305. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_save_as.py +0 -0
  306. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_data_service_security_gates.py +0 -0
  307. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_db_credentials_ssm_cascade.py +0 -0
  308. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_db_maintenance.py +0 -0
  309. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_db_tunnel.py +0 -0
  310. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_declared_column_types.py +0 -0
  311. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_decorators.py +0 -0
  312. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_delete_hook_fk_blocked.py +0 -0
  313. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_deploy_config_fields.py +0 -0
  314. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_deploy_engine.py +0 -0
  315. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_deploy_frontend_checks.py +0 -0
  316. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_deploy_operations_queue.py +0 -0
  317. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_deploy_policies.py +0 -0
  318. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_dirty_pipeline_fast_path.py +0 -0
  319. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_dlq_constant.py +0 -0
  320. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_docs_snippets.py +0 -0
  321. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_email_processing.py +0 -0
  322. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_enqueue_metric_namespace.py +0 -0
  323. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_enqueue_send_failures.py +0 -0
  324. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_export_workbook.py +0 -0
  325. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_facets.py +0 -0
  326. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_facets_and_where.py +0 -0
  327. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_failures.py +0 -0
  328. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_feature_flags.py +0 -0
  329. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_get_cognito_user_provider.py +0 -0
  330. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_http_handler_rollback.py +0 -0
  331. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_iconv_money_to_cents.py +0 -0
  332. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_identifier_injection_guard.py +0 -0
  333. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_identity.py +0 -0
  334. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_job_handler.py +0 -0
  335. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_job_results.py +0 -0
  336. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_json_columns.py +0 -0
  337. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_jsonb_dict_adapter.py +0 -0
  338. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_lambda_handler.py +0 -0
  339. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_lambda_handler_auth.py +0 -0
  340. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_lambda_handler_masquerade.py +0 -0
  341. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_lambda_handler_user_cache.py +0 -0
  342. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_lazy_engine.py +0 -0
  343. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_masquerade_grant.py +0 -0
  344. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_merge_on_conflict.py +0 -0
  345. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_mirror.py +0 -0
  346. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_mirror_upsert_public.py +0 -0
  347. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_mixins_import.py +0 -0
  348. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_n_plus_one.py +0 -0
  349. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_dynamodb.py +0 -0
  350. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_memory.py +0 -0
  351. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_mongodb.py +0 -0
  352. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_redis.py +0 -0
  353. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_translation.py +0 -0
  354. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_nosql_where.py +0 -0
  355. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_observability.py +0 -0
  356. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_on_action_default.py +0 -0
  357. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_on_commit.py +0 -0
  358. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_ops.py +0 -0
  359. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_audit_fixes.py +0 -0
  360. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_authorizenet_adapter.py +0 -0
  361. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_braintree_adapter.py +0 -0
  362. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_braintree_mirror.py +0 -0
  363. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_business_agnostic.py +0 -0
  364. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_charge_reference.py +0 -0
  365. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_extra_bounds.py +0 -0
  366. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_lazy_imports.py +0 -0
  367. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_mirror_empty_table.py +0 -0
  368. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_profile_sorting.py +0 -0
  369. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_payment_stripe_adapter.py +0 -0
  370. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pdf.py +0 -0
  371. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_permissions.py +0 -0
  372. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_permissions_actions.py +0 -0
  373. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_permissions_notices.py +0 -0
  374. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_permissions_review.py +0 -0
  375. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_bulk_cache.py +0 -0
  376. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_capability_types.py +0 -0
  377. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_ensure_infrastructure.py +0 -0
  378. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_get_tables.py +0 -0
  379. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_permissions.py +0 -0
  380. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_pointer_joins.py +0 -0
  381. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_row_scope.py +0 -0
  382. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_search_export.py +0 -0
  383. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_tiles.py +0 -0
  384. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pgserver_user_data.py +0 -0
  385. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_plaid_api.py +0 -0
  386. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_plaid_webhook.py +0 -0
  387. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_pointer_join_sql.py +0 -0
  388. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_postgres_initializer_config.py +0 -0
  389. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_postgres_py_type.py +0 -0
  390. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_prepared_statements.py +0 -0
  391. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_psycopg3_upgrade.py +0 -0
  392. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_query_cache.py +0 -0
  393. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_query_missing_table_unlocked.py +0 -0
  394. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_raw_sql_prefix_policy.py +0 -0
  395. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_read_cache.py +0 -0
  396. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_readme_imports.py +0 -0
  397. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_response.py +0 -0
  398. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_restricted_direct_tables.py +0 -0
  399. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_result_returning.py +0 -0
  400. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_retry_side_effect_guard.py +0 -0
  401. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_return_default_safety.py +0 -0
  402. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_row_batch_update.py +0 -0
  403. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_row_cache_staleness.py +0 -0
  404. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_row_scoped_data_service.py +0 -0
  405. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_s3_bucket_config.py +0 -0
  406. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_s3_list_client_files.py +0 -0
  407. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_s3_upload.py +0 -0
  408. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_schema_lock_config_other_backends.py +0 -0
  409. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_schema_migrations.py +0 -0
  410. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_search_paging_export.py +0 -0
  411. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_security_capability.py +0 -0
  412. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_security_events_table_gate.py +0 -0
  413. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_security_hardening.py +0 -0
  414. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_server_cursor.py +0 -0
  415. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_session_control.py +0 -0
  416. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_single_autocommit_safety.py +0 -0
  417. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_single_session_capability.py +0 -0
  418. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_sms_twilio_signature.py +0 -0
  419. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_spreadsheet_functions.py +0 -0
  420. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_sqlite_backend.py +0 -0
  421. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_sqs_per_record_transactions.py +0 -0
  422. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_ssm_config.py +0 -0
  423. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_ssm_config_require.py +0 -0
  424. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_store_user_data.py +0 -0
  425. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  426. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_table_alter.py +0 -0
  427. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_table_claim.py +0 -0
  428. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_table_find_cache.py +0 -0
  429. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_table_purge.py +0 -0
  430. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_testing_fake_db.py +0 -0
  431. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_tiles.py +0 -0
  432. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_transaction_class_wrapping.py +0 -0
  433. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_transaction_commit_and_ownership.py +0 -0
  434. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_transaction_edge_cases.py +0 -0
  435. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_tx_lifecycle_retry.py +0 -0
  436. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_tx_read_cache_and_bulk.py +0 -0
  437. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_user_data.py +0 -0
  438. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_user_not_provisioned.py +0 -0
  439. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_where_clause_validation.py +0 -0
  440. {velocity_python-0.2.9 → velocity_python-0.3.0}/tests/test_write_hook_create_flow.py +0 -0
  441. {velocity_python-0.2.9 → velocity_python-0.3.0}/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.9
3
+ Version: 0.3.0
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
@@ -57,8 +57,10 @@ Requires-Dist: braintree<5,>=4.30.0; extra == "payment"
57
57
  Requires-Dist: xmltodict<2,>=0.13.0; extra == "payment"
58
58
  Provides-Extra: plaid
59
59
  Requires-Dist: ecdsa>=0.19.0; extra == "plaid"
60
+ Provides-Extra: rules
61
+ Requires-Dist: cel-python<0.6,>=0.5.0; extra == "rules"
60
62
  Provides-Extra: all
61
- Requires-Dist: velocity-python[aws,excel,http,payment,pdf,plaid,postgres,templates]; extra == "all"
63
+ Requires-Dist: velocity-python[aws,excel,http,payment,pdf,plaid,postgres,rules,templates]; extra == "all"
62
64
  Provides-Extra: dev
63
65
  Requires-Dist: pytest>=8.0.0; extra == "dev"
64
66
  Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
@@ -81,6 +83,7 @@ Requires-Dist: xmltodict<2,>=0.13.0; extra == "test"
81
83
  Requires-Dist: authorizenet>=1.1.3; extra == "test"
82
84
  Requires-Dist: ecdsa>=0.19.0; extra == "test"
83
85
  Requires-Dist: openpyxl>=3.1.0; extra == "test"
86
+ Requires-Dist: cel-python<0.6,>=0.5.0; extra == "test"
84
87
  Requires-Dist: redis>=5.0.0; extra == "test"
85
88
  Requires-Dist: fakeredis>=2.23.0; extra == "test"
86
89
  Requires-Dist: pymongo>=4.6.0; extra == "test"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.2.9"
7
+ version = "0.3.0"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -101,8 +101,15 @@ payment = [
101
101
  plaid = [
102
102
  "ecdsa>=0.19.0",
103
103
  ]
104
+ # Expression conditions for velocity.rules (CEL, pure Python; pulls lark,
105
+ # pendulum and google-re2). Decision tables need nothing extra. Tested on
106
+ # cel-python 0.5.x; raise the bound only after `make test-unit` passes on the
107
+ # next minor (pre-1.0, so a minor may change the API).
108
+ rules = [
109
+ "cel-python>=0.5.0,<0.6",
110
+ ]
104
111
  all = [
105
- "velocity-python[postgres,aws,excel,templates,http,payment,plaid,pdf]",
112
+ "velocity-python[postgres,aws,excel,templates,http,payment,plaid,pdf,rules]",
106
113
  ]
107
114
  dev = [
108
115
  "pytest>=8.0.0",
@@ -130,6 +137,7 @@ test = [
130
137
  "authorizenet>=1.1.3",
131
138
  "ecdsa>=0.19.0",
132
139
  "openpyxl>=3.1.0",
140
+ "cel-python>=0.5.0,<0.6",
133
141
  # NoSQL adapter unit tests run against in-process fakes (real clients for
134
142
  # the wire formats, fakes for the servers). ES/Cassandra/Couchbase have
135
143
  # translation-only unit tests and need no driver here.
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.2.9"
1
+ __version__ = version = "0.3.0"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -13,10 +13,13 @@ from .base import ( # noqa: F401
13
13
  POLICY_KINDS,
14
14
  REGISTRY,
15
15
  Capability,
16
+ CapabilityDependencyError,
16
17
  capability,
17
18
  catalog,
19
+ dependency_order,
18
20
  describe,
19
21
  get,
22
+ missing_dependencies,
20
23
  load_config,
21
24
  policy_field,
22
25
  resolve_config,
@@ -30,6 +33,7 @@ from .dispatch import ( # noqa: F401
30
33
  )
31
34
  from .provisioning import ( # noqa: F401
32
35
  as_bool,
36
+ check_dependencies,
33
37
  provision,
34
38
  row_enabled,
35
39
  trigger_key,
@@ -47,15 +51,19 @@ from .security import SecurityMixin # noqa: E402,F401
47
51
  __all__ = [
48
52
  "Capability",
49
53
  "CapabilityCatalogMixin",
54
+ "CapabilityDependencyError",
50
55
  "CapabilityDispatcher",
51
56
  "POLICY_KINDS",
52
57
  "REGISTRY",
53
58
  "as_bool",
54
59
  "capability",
55
60
  "catalog",
61
+ "check_dependencies",
62
+ "dependency_order",
56
63
  "describe",
57
64
  "get",
58
65
  "load_config",
66
+ "missing_dependencies",
59
67
  "policy_field",
60
68
  "provision",
61
69
  "resolve_config",
@@ -72,6 +72,11 @@ COUNTRY_CODE = re.compile(r"^[A-Z]{2}$")
72
72
  REGISTRY: dict = {}
73
73
 
74
74
 
75
+ class CapabilityDependencyError(ValueError):
76
+ """A capability is enabled without one it requires, requires an unknown
77
+ one, or the requirements form a cycle."""
78
+
79
+
75
80
  def policy_field(name, kind, label, help="", **extra) -> dict:
76
81
  """One entry of a capability's ``policy`` schema. ``extra`` carries
77
82
  kind-specific hints an editor may use (``options`` for a choice,
@@ -92,6 +97,7 @@ class Capability:
92
97
  mixins: tuple = () # handler mixins the site attaches at runtime
93
98
  applies_to: tuple = ("demo", "production") # default stages; registry can force
94
99
  policy: tuple = () # (policy_field(...), ...) — the per-site config shape
100
+ requires_capabilities: tuple = () # names that must be enabled alongside
95
101
 
96
102
  def handle(self, action, tx, context, config):
97
103
  """Do the work for one scheduled/queued ``action``. Return a JSON-able
@@ -119,6 +125,61 @@ def get(name):
119
125
  raise KeyError(f"unknown capability {name!r}; registered: {known}") from None
120
126
 
121
127
 
128
+ def requirements(name) -> tuple:
129
+ """The capabilities ``name`` declares it requires (unknown name: KeyError)."""
130
+ return tuple(getattr(get(name), "requires_capabilities", ()) or ())
131
+
132
+
133
+ def dependency_order(names) -> list:
134
+ """``names`` plus everything they require (transitively), each after the
135
+ capabilities it requires; otherwise in the order given.
136
+
137
+ Raises :class:`CapabilityDependencyError` for a cycle (naming it) or a
138
+ requirement that is not a registered capability.
139
+ """
140
+ ordered: list = []
141
+ done: set = set()
142
+ visiting: list = []
143
+
144
+ def visit(name, needed_by=None):
145
+ if name in done:
146
+ return
147
+ if name in visiting:
148
+ cycle = visiting[visiting.index(name) :] + [name]
149
+ raise CapabilityDependencyError(
150
+ "capability requirements form a cycle: " + " -> ".join(cycle)
151
+ )
152
+ if name not in REGISTRY:
153
+ if needed_by is None:
154
+ get(name) # the usual "unknown capability" KeyError
155
+ raise CapabilityDependencyError(
156
+ f"capability {needed_by!r} requires {name!r}, which is not registered"
157
+ )
158
+ visiting.append(name)
159
+ for required in requirements(name):
160
+ visit(required, name)
161
+ visiting.pop()
162
+ done.add(name)
163
+ ordered.append(name)
164
+
165
+ for name in names:
166
+ visit(name)
167
+ return ordered
168
+
169
+
170
+ def missing_dependencies(enabled) -> dict:
171
+ """``{name: [required names not in enabled]}`` for each name in
172
+ ``enabled`` that lacks one; empty when every requirement is met."""
173
+ enabled = list(enabled)
174
+ present = set(enabled)
175
+ missing = {}
176
+ for name in enabled:
177
+ absent = [req for req in requirements(name) if req not in present]
178
+ if absent:
179
+ missing[name] = absent
180
+ return missing
181
+
182
+
122
183
  def resolve_config(cap, flags=None, row_config=None) -> dict:
123
184
  """defaults <- feature flags (declared knobs only) <- site registry config."""
124
185
  config = dict(cap.defaults)
@@ -148,6 +209,7 @@ def describe(cap) -> dict:
148
209
  ],
149
210
  "flags": list(cap.defaults),
150
211
  "policy": [dict(field) for field in cap.policy],
212
+ "requires": list(cap.requires_capabilities),
151
213
  }
152
214
 
153
215
 
@@ -0,0 +1,124 @@
1
+ """Runtime dispatch: route the ``run-capability`` queue action to a capability.
2
+
3
+ A site's queue handler mixes in :class:`CapabilityDispatcher`; the scheduler
4
+ stubs seeded by :mod:`provision` enqueue ``run-capability`` with
5
+ ``{"capability": name, "action": action}`` and this routes it. Config is
6
+ resolved locally — the site's own ``sys_capabilities`` row plus its feature
7
+ flags — so the runtime never reaches into the control plane. A capability
8
+ runs only with an enabled row of its own and enabled rows for every
9
+ capability it requires (``requires_capabilities``, transitively); otherwise
10
+ it is skipped and the skip is reported (:mod:`velocity.aws.failures`).
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+
17
+ from velocity.aws import failures, feature_flags
18
+
19
+ from .base import (
20
+ REGISTRY,
21
+ CapabilityDependencyError,
22
+ catalog,
23
+ dependency_order,
24
+ resolve_config,
25
+ )
26
+ from .provisioning import SITE_TABLE, row_enabled
27
+
28
+
29
+ def _skip(tx, name, action, reason, *, report=True):
30
+ """The skipped response; with ``report``, a failure event (scrubbed,
31
+ never raising) once the transaction commits — a skip that should not
32
+ happen must reach an operator, not just a queue response."""
33
+ if report:
34
+
35
+ def send():
36
+ failures.report(
37
+ None,
38
+ None,
39
+ source="queue",
40
+ action="run-capability",
41
+ extra={
42
+ "capability": name,
43
+ "capability_action": action,
44
+ "skipped": reason,
45
+ },
46
+ )
47
+
48
+ on_commit = getattr(tx, "on_commit", None)
49
+ if callable(on_commit):
50
+ on_commit(send)
51
+ else:
52
+ send()
53
+ return {"capability": name, "action": action, "skipped": reason}
54
+
55
+
56
+ def run_capability(tx, context, name, action):
57
+ """Resolve config and run one capability action. Returns the response body.
58
+
59
+ Runs only when the site has an enabled ``sys_capabilities`` row for the
60
+ capability (provisioning wrote it) and an enabled row for every
61
+ capability it requires, directly or through another requirement
62
+ (:func:`dependency_order`). Otherwise the action is skipped, and — except
63
+ for a plain "disabled for this site" — reported through
64
+ :mod:`velocity.aws.failures`: a scheduled run for a capability that was
65
+ never provisioned, or whose requirement is off, is a misconfiguration.
66
+ """
67
+ cap = REGISTRY.get(name)
68
+ if cap is None:
69
+ return {"error": f"unknown capability {name!r}", "capability": name}
70
+
71
+ site = tx.table(SITE_TABLE)
72
+ row = site.find({"name": name})
73
+ if not row:
74
+ return _skip(tx, name, action, "not provisioned for this site")
75
+ if not row_enabled(row):
76
+ return _skip(tx, name, action, "disabled for this site", report=False)
77
+ try:
78
+ # every requirement, transitively (a -> b -> c needs c on too)
79
+ chain = [n for n in dependency_order([name]) if n != name]
80
+ except (CapabilityDependencyError, KeyError) as exc:
81
+ return _skip(tx, name, action, f"dependency error: {exc}")
82
+ missing = []
83
+ for required in chain:
84
+ required_row = site.find({"name": required})
85
+ if not required_row or not row_enabled(required_row):
86
+ missing.append(required)
87
+ if missing:
88
+ return _skip(
89
+ tx, name, action, f"requires {', '.join(missing)}, not enabled here"
90
+ )
91
+
92
+ raw = row.get("config")
93
+ row_config = json.loads(raw) if isinstance(raw, str) and raw else (raw or {})
94
+ config = resolve_config(cap, flags=feature_flags.get(tx), row_config=row_config)
95
+
96
+ result = cap.handle(action, tx, context, config)
97
+ return {"capability": name, "action": action, "result": result}
98
+
99
+
100
+ class CapabilityDispatcher:
101
+ """Queue-handler mixin: ``run-capability`` -> the registered capability."""
102
+
103
+ def OnActionRunCapability(self, tx, context):
104
+ # Queue records and scheduled ticks only: a handler that mixes this in
105
+ # must not let an HTTP caller run a capability's actions.
106
+ from velocity.aws.dirty_pipeline import refuse_http
107
+
108
+ refuse_http(self, "run-capability")
109
+ payload = context.payload() or {}
110
+ body = run_capability(
111
+ tx, context, payload.get("capability"), payload.get("action")
112
+ )
113
+ context.response().set_body(body)
114
+
115
+
116
+ class CapabilityCatalogMixin:
117
+ """Web-handler mixin for the control plane: ``capability-catalog`` answers
118
+ with every registered capability described (``{"rows": [...]}`` — the
119
+ shape velocity-react's ApiSelect and option hooks consume), so the admin
120
+ UI's pick-list and policy editors come from the code, not a copy."""
121
+
122
+ def OnActionCapabilityCatalog(self, tx, context):
123
+ del tx
124
+ context.response().set_body({"rows": catalog()})
@@ -3,9 +3,11 @@
3
3
  ``JobHandler`` only discovers a stale job (worker died: Lambda timeout / OOM)
4
4
  when someone polls its status — an abandoned job nobody polls stays "Running"
5
5
  forever, and ``aws_job_activity`` is never pruned. This capability sweeps on
6
- a schedule with the SAME predicate the web tier uses
7
- (``velocity.aws.job_lifecycle.job_is_stale``): stale jobs are persisted as
8
- Failed with the standard message, and old activity rows are purged.
6
+ a schedule with the same rule the web tier uses (``Running``/``Initialized``
7
+ with no progress for the window), judged by the database clock
8
+ (:func:`stale_jobs`): stale jobs are persisted as Failed with the standard
9
+ message plus the status they expired from (``expired_from``), and old
10
+ activity rows are purged.
9
11
  """
10
12
 
11
13
  from __future__ import annotations
@@ -17,7 +19,6 @@ from velocity.aws.job_lifecycle import (
17
19
  JOB_ACTIVITY_TABLE,
18
20
  STALE_FAILURE,
19
21
  STALE_JOB_STATUSES,
20
- job_is_stale,
21
22
  )
22
23
 
23
24
  from .base import EVERY_15_MINUTES, Capability, capability
@@ -39,27 +40,51 @@ class Jobs(Capability):
39
40
  if action != "expire_stale":
40
41
  raise ValueError(f"jobs: unknown action {action!r}")
41
42
  now = datetime.datetime.now(datetime.timezone.utc)
42
- stale_after = datetime.timedelta(
43
- minutes=int(config.get("job_stale_minutes") or 16)
44
- )
43
+ minutes = int(config.get("job_stale_minutes") or 16)
45
44
  jobs = tx.table(JOB_ACTIVITY_TABLE)
46
45
 
47
46
  expired = 0
48
- for status in sorted(STALE_JOB_STATUSES):
49
- for job in jobs.select(where={"status": status}):
50
- if job_is_stale(job, now=now, stale_after=stale_after):
51
- jobs.update(
52
- {"status": "Failed", "message": json.dumps(STALE_FAILURE)},
53
- where={"sys_id": job["sys_id"]},
54
- )
55
- expired += 1
56
- _report_after_commit(tx, job, status)
47
+ for job in stale_jobs(tx, minutes):
48
+ status = job.get("status")
49
+ # the status in the where: a job that progressed since the select
50
+ # (or that another sweep expired) is left alone
51
+ changed = jobs.update(
52
+ {
53
+ "status": "Failed",
54
+ "message": json.dumps({**STALE_FAILURE, "expired_from": status}),
55
+ },
56
+ where={"sys_id": job["sys_id"], "status": status},
57
+ )
58
+ if changed:
59
+ expired += 1
60
+ _report_after_commit(tx, job, status)
57
61
 
58
62
  days = int(config.get("job_retention_days") or 30)
59
63
  purged = jobs.purge({"<sys_created": now - datetime.timedelta(days=days)})
60
64
  return {"expired": expired, "purged": purged}
61
65
 
62
66
 
67
+ def stale_jobs(tx, minutes):
68
+ """``Running``/``Initialized`` jobs with no progress for ``minutes``,
69
+ judged by the database clock: ``sys_modified`` is written by the row
70
+ trigger in the session's time zone, so it is compared with
71
+ ``LOCALTIMESTAMP`` there, never with a Python clock (which disagrees with
72
+ it whenever the database does not run in UTC)."""
73
+ statuses = sorted(STALE_JOB_STATUSES)
74
+ placeholders = ", ".join(["%s"] * len(statuses))
75
+ return (
76
+ tx.execute(
77
+ f"SELECT sys_id, job_id, action, status FROM {JOB_ACTIVITY_TABLE} "
78
+ f"WHERE status IN ({placeholders}) "
79
+ "AND sys_modified < LOCALTIMESTAMP - %s::interval "
80
+ "ORDER BY sys_id",
81
+ [*statuses, f"{int(minutes)} minutes"],
82
+ )
83
+ .as_dict()
84
+ .all()
85
+ )
86
+
87
+
63
88
  def _report_after_commit(tx, job, status):
64
89
  """A job whose worker died never reached onError, so nothing reported it:
65
90
  publish a ``source="job"`` failure event once the sweep commits."""
@@ -12,6 +12,12 @@ table — the runtime reads config from there, never from the control plane —
12
12
  only what is unset, never clobbering an edit), and (4) upserts one
13
13
  ``sys_task_triggers`` row + one ``program_objects`` stub per schedule.
14
14
 
15
+ Before anything is written, every capability this run leaves enabled must
16
+ have the capabilities it requires (``requires_capabilities``) enabled too —
17
+ in ``entries``, or (when ``entries`` does not name them) already enabled in
18
+ the site's ``sys_capabilities`` — else :class:`CapabilityDependencyError`.
19
+ Entries are then provisioned in dependency order (required ones first).
20
+
15
21
  Everything is keyed deterministically (``cap:<name>:<action>``), so running it
16
22
  on every deploy is idempotent — and that is the point: a demo refresh wipes
17
23
  the triggers, the next deploy puts them back. A disabled entry disables its
@@ -25,7 +31,13 @@ import json
25
31
 
26
32
  from velocity.aws import feature_flags
27
33
 
28
- from .base import get
34
+ from .base import (
35
+ REGISTRY,
36
+ CapabilityDependencyError,
37
+ dependency_order,
38
+ get,
39
+ requirements,
40
+ )
29
41
 
30
42
  SITE_TABLE = "sys_capabilities" # per-site materialised registry
31
43
  TRIGGERS_TABLE = "sys_task_triggers"
@@ -202,17 +214,72 @@ def program_source(cap_name: str, action: str) -> str:
202
214
  )
203
215
 
204
216
 
217
+ def _entry_active(entry, stage) -> bool:
218
+ cap = get(entry["name"])
219
+ enabled = as_bool(entry.get("enabled", True), default=False)
220
+ return enabled and (stage in cap.applies_to or bool(entry.get("force")))
221
+
222
+
223
+ def check_dependencies(tx, stage: str, entries) -> list:
224
+ """The entries in dependency order, after checking that every entry this
225
+ run leaves active has what it requires active too. A requirement
226
+ ``entries`` does not mention is looked up in the site's own
227
+ ``sys_capabilities`` (an earlier provision); one ``entries`` disables or
228
+ keeps out of ``stage`` is missing. Raises
229
+ :class:`CapabilityDependencyError` listing every gap, before any write."""
230
+ entries = list(entries or [])
231
+ by_name = {entry["name"]: entry for entry in entries}
232
+ active = {name for name, entry in by_name.items() if _entry_active(entry, stage)}
233
+ order = dependency_order(list(by_name)) # unknown names / cycles raise here
234
+ site = tx.table(SITE_TABLE)
235
+ problems = []
236
+ for name in order:
237
+ if name not in active:
238
+ continue
239
+ for required in requirements(name):
240
+ if required in active:
241
+ continue
242
+ if required in by_name:
243
+ entry = by_name[required]
244
+ on = as_bool(entry.get("enabled", True), default=False)
245
+ why = f"not for {stage}" if on else "disabled"
246
+ else:
247
+ row = site.find({"name": required})
248
+ if row and row_enabled(row):
249
+ continue
250
+ why = "not enabled for this site"
251
+ problems.append(f"{name} requires {required} ({why})")
252
+ # turning one off must not strand a capability left on from before
253
+ for name in by_name:
254
+ if name in active:
255
+ continue
256
+ for other, cap in sorted(REGISTRY.items()):
257
+ if other in by_name or name not in (cap.requires_capabilities or ()):
258
+ continue
259
+ row = site.find({"name": other})
260
+ if row and row_enabled(row):
261
+ problems.append(
262
+ f"{other} requires {name}, which this run turns off "
263
+ f"(disable {other} too, or keep {name})"
264
+ )
265
+ if problems:
266
+ raise CapabilityDependencyError(
267
+ "capability dependencies not met: " + "; ".join(problems)
268
+ )
269
+ return [by_name[name] for name in order if name in by_name]
270
+
271
+
205
272
  def provision(tx, stage: str, entries, *, now=None) -> list:
206
- """Materialise ``entries`` for ``stage``. Returns [(name, outcome), ...]."""
273
+ """Materialise ``entries`` for ``stage``. Returns [(name, outcome), ...]
274
+ in dependency order (required capabilities first)."""
207
275
  now = now or datetime.datetime.utcnow()
208
276
  tx.table(SITE_TABLE).ensure_schema(**SCHEMA[SITE_TABLE])
209
277
  report = []
210
- for entry in entries or []:
278
+ for entry in check_dependencies(tx, stage, entries):
211
279
  name = entry["name"]
212
280
  cap = get(name)
281
+ active = _entry_active(entry, stage)
213
282
  enabled = as_bool(entry.get("enabled", True), default=False)
214
- applies = stage in cap.applies_to or bool(entry.get("force"))
215
- active = enabled and applies
216
283
 
217
284
  tx.table(SITE_TABLE).merge(
218
285
  {