velocity-python 0.2.1__tar.gz → 0.2.2__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 (392) hide show
  1. {velocity_python-0.2.1/src/velocity_python.egg-info → velocity_python-0.2.2}/PKG-INFO +1 -1
  2. {velocity_python-0.2.1 → velocity_python-0.2.2}/pyproject.toml +1 -1
  3. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.2.2/src/velocity/aws/authz.py +428 -0
  5. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/data_service.py +98 -9
  6. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/web_handler.py +115 -9
  7. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/schema.py +1 -0
  8. velocity_python-0.2.2/src/velocity/aws/user_data.py +404 -0
  9. {velocity_python-0.2.1 → velocity_python-0.2.2/src/velocity_python.egg-info}/PKG-INFO +1 -1
  10. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity_python.egg-info/SOURCES.txt +6 -0
  11. velocity_python-0.2.2/tests/test_authz.py +474 -0
  12. velocity_python-0.2.2/tests/test_data_service_owner_scope.py +259 -0
  13. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_ensure_infrastructure.py +1 -0
  14. velocity_python-0.2.2/tests/test_pgserver_user_data.py +188 -0
  15. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_query_missing_table_unlocked.py +2 -0
  16. velocity_python-0.2.2/tests/test_user_data.py +494 -0
  17. {velocity_python-0.2.1 → velocity_python-0.2.2}/LICENSE +0 -0
  18. {velocity_python-0.2.1 → velocity_python-0.2.2}/README.md +0 -0
  19. {velocity_python-0.2.1 → velocity_python-0.2.2}/setup.cfg +0 -0
  20. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/__init__.py +0 -0
  21. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/amplify.py +0 -0
  22. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/__init__.py +0 -0
  23. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/backfill.py +0 -0
  24. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/indexing.py +0 -0
  25. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/references.py +0 -0
  26. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/service.py +0 -0
  27. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/assets/usage_index.py +0 -0
  28. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/blobs.py +0 -0
  29. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/__init__.py +0 -0
  30. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/base.py +0 -0
  31. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/dispatch.py +0 -0
  32. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/infra.py +0 -0
  33. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/jobs.py +0 -0
  34. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/provisioning.py +0 -0
  35. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/retention.py +0 -0
  36. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/security.py +0 -0
  37. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/capabilities/single_session.py +0 -0
  38. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/__init__.py +0 -0
  39. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/ci_gate.py +0 -0
  40. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/config.py +0 -0
  41. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/frontend_checks.py +0 -0
  42. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/operations.py +0 -0
  43. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/policies.py +0 -0
  44. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/reconcile.py +0 -0
  45. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/deploy/targets.py +0 -0
  46. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/dirty_pipeline.py +0 -0
  47. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/email.py +0 -0
  48. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/facets.py +0 -0
  49. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/failures.py +0 -0
  50. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/feature_flags.py +0 -0
  51. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/__init__.py +0 -0
  52. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/base_handler.py +0 -0
  53. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/context.py +0 -0
  54. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/context_factory.py +0 -0
  55. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/exceptions.py +0 -0
  56. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  57. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/masquerade.py +0 -0
  58. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  59. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
  60. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
  61. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
  62. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  63. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
  64. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  65. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/perf.py +0 -0
  66. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/response.py +0 -0
  67. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  68. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/job_lifecycle.py +0 -0
  69. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/job_results.py +0 -0
  70. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/mirror.py +0 -0
  71. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/ops.py +0 -0
  72. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/queue_defaults.py +0 -0
  73. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/s3.py +0 -0
  74. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/sms.py +0 -0
  75. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/ssm_config.py +0 -0
  76. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/__init__.py +0 -0
  77. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  78. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
  79. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  80. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
  81. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  82. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_email.py +0 -0
  83. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  84. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_response.py +0 -0
  85. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/aws/tests/test_sms.py +0 -0
  86. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/cache.py +0 -0
  87. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/__init__.py +0 -0
  88. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/__init__.py +0 -0
  89. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/async_support.py +0 -0
  90. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/column.py +0 -0
  91. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/database.py +0 -0
  92. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/decorators.py +0 -0
  93. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/engine.py +0 -0
  94. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/jsonproxy.py +0 -0
  95. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/lazy_engine.py +0 -0
  96. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/result.py +0 -0
  97. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/row.py +0 -0
  98. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/sequence.py +0 -0
  99. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/table.py +0 -0
  100. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/transaction.py +0 -0
  101. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/core/view.py +0 -0
  102. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/exceptions.py +0 -0
  103. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/fragments.py +0 -0
  104. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/maintenance.py +0 -0
  105. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/migrations.py +0 -0
  106. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/__init__.py +0 -0
  107. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/base/__init__.py +0 -0
  108. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/base/initializer.py +0 -0
  109. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/base/operators.py +0 -0
  110. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/base/sql.py +0 -0
  111. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/base/types.py +0 -0
  112. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/mysql/__init__.py +0 -0
  113. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/mysql/operators.py +0 -0
  114. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/mysql/reserved.py +0 -0
  115. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/mysql/sql.py +0 -0
  116. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/mysql/types.py +0 -0
  117. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/postgres/__init__.py +0 -0
  118. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/postgres/operators.py +0 -0
  119. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/postgres/reserved.py +0 -0
  120. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/postgres/sql.py +0 -0
  121. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/postgres/types.py +0 -0
  122. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  123. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlite/operators.py +0 -0
  124. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  125. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlite/sql.py +0 -0
  126. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlite/types.py +0 -0
  127. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  128. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  129. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  130. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  131. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/sqlserver/types.py +0 -0
  132. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/servers/tablehelper.py +0 -0
  133. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/__init__.py +0 -0
  134. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/common_db_test.py +0 -0
  135. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/__init__.py +0 -0
  136. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/common.py +0 -0
  137. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/conftest.py +0 -0
  138. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_column.py +0 -0
  139. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  140. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_database.py +0 -0
  141. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  142. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  143. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  144. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_result.py +0 -0
  145. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_row.py +0 -0
  146. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  147. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  148. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  149. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  150. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  151. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_table.py +0 -0
  152. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  153. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  154. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/sql/__init__.py +0 -0
  155. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/sql/common.py +0 -0
  156. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  157. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  158. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  159. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_db_utils.py +0 -0
  160. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_postgres.py +0 -0
  161. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  162. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  163. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_result_caching.py +0 -0
  164. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  165. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  166. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  167. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  168. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_sql_builder.py +0 -0
  169. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_tablehelper.py +0 -0
  170. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tests/test_view_helper.py +0 -0
  171. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/tunnel.py +0 -0
  172. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/db/utils.py +0 -0
  173. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/logging.py +0 -0
  174. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/__init__.py +0 -0
  175. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/conv/__init__.py +0 -0
  176. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/conv/iconv.py +0 -0
  177. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/conv/oconv.py +0 -0
  178. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/db.py +0 -0
  179. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/export.py +0 -0
  180. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/format.py +0 -0
  181. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/mail.py +0 -0
  182. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/merge.py +0 -0
  183. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/pdf.py +0 -0
  184. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/__init__.py +0 -0
  185. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_db.py +0 -0
  186. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_fix.py +0 -0
  187. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_format.py +0 -0
  188. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_iconv.py +0 -0
  189. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_mail_send.py +0 -0
  190. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_merge.py +0 -0
  191. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_oconv.py +0 -0
  192. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_original_error.py +0 -0
  193. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tests/test_timer.py +0 -0
  194. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/timer.py +0 -0
  195. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/misc/tools.py +0 -0
  196. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/__init__.py +0 -0
  197. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/core/__init__.py +0 -0
  198. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/core/capabilities.py +0 -0
  199. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/core/store.py +0 -0
  200. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/core/where.py +0 -0
  201. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/exceptions.py +0 -0
  202. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/__init__.py +0 -0
  203. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/base/__init__.py +0 -0
  204. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  205. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  206. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  207. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  208. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  209. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  210. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  211. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  212. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  213. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  214. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  215. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  216. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/__init__.py +0 -0
  217. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/_mirror_core.py +0 -0
  218. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/authorizenet_adapter.py +0 -0
  219. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/authorizenet_mirror.py +0 -0
  220. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/base_adapter.py +0 -0
  221. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/braintree_adapter.py +0 -0
  222. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/braintree_mirror.py +0 -0
  223. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/charge_rules.py +0 -0
  224. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/money.py +0 -0
  225. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/plaid_api.py +0 -0
  226. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/plaid_webhook.py +0 -0
  227. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/redact.py +0 -0
  228. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/schema.py +0 -0
  229. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/stripe_adapter.py +0 -0
  230. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/payment/stripe_mirror.py +0 -0
  231. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/testing/__init__.py +0 -0
  232. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity/testing/fake_db.py +0 -0
  233. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  234. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity_python.egg-info/entry_points.txt +0 -0
  235. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity_python.egg-info/requires.txt +0 -0
  236. {velocity_python-0.2.1 → velocity_python-0.2.2}/src/velocity_python.egg-info/top_level.txt +0 -0
  237. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_access_error_403.py +0 -0
  238. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_activity_tracking_aborted_tx.py +0 -0
  239. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_amplify_redirects.py +0 -0
  240. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_asset_indexing.py +0 -0
  241. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_asset_references.py +0 -0
  242. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_assets_service.py +0 -0
  243. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_async_support.py +0 -0
  244. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_audit_mixin.py +0 -0
  245. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_aws_package_hygiene.py +0 -0
  246. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_batch_operations.py +0 -0
  247. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_blob_store.py +0 -0
  248. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_capabilities.py +0 -0
  249. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_capability_catalog.py +0 -0
  250. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_check_changelog.py +0 -0
  251. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_ci_gate.py +0 -0
  252. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_client_errors_mixin.py +0 -0
  253. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_client_input_contract_export.py +0 -0
  254. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_client_sql_boundary.py +0 -0
  255. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_client_table_gate_case_and_pointers.py +0 -0
  256. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_cognito_user_cache.py +0 -0
  257. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_column_tx_arg.py +0 -0
  258. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_concurrency_safety.py +0 -0
  259. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_connection_pool.py +0 -0
  260. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_connection_resilience.py +0 -0
  261. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_context_flags_loader.py +0 -0
  262. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_context_job_descriptions.py +0 -0
  263. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_context_queue_job.py +0 -0
  264. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_conv_combine.py +0 -0
  265. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_data_service_query_cap.py +0 -0
  266. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_data_service_renumber.py +0 -0
  267. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_data_service_row_number.py +0 -0
  268. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_data_service_save_as.py +0 -0
  269. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_data_service_security_gates.py +0 -0
  270. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_db_credentials_ssm_cascade.py +0 -0
  271. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_db_maintenance.py +0 -0
  272. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_db_tunnel.py +0 -0
  273. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_declared_column_types.py +0 -0
  274. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_decorators.py +0 -0
  275. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_delete_hook_fk_blocked.py +0 -0
  276. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_deploy_config_fields.py +0 -0
  277. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_deploy_engine.py +0 -0
  278. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_deploy_frontend_checks.py +0 -0
  279. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_deploy_operations_queue.py +0 -0
  280. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_deploy_policies.py +0 -0
  281. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_dirty_pipeline_fast_path.py +0 -0
  282. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_dlq_constant.py +0 -0
  283. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_docs_snippets.py +0 -0
  284. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_email_processing.py +0 -0
  285. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_enqueue_metric_namespace.py +0 -0
  286. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_enqueue_send_failures.py +0 -0
  287. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_export_workbook.py +0 -0
  288. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_facets.py +0 -0
  289. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_facets_and_where.py +0 -0
  290. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_failures.py +0 -0
  291. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_feature_flags.py +0 -0
  292. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_get_cognito_user_provider.py +0 -0
  293. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_http_handler_rollback.py +0 -0
  294. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_iconv_money_to_cents.py +0 -0
  295. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_identifier_injection_guard.py +0 -0
  296. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_job_handler.py +0 -0
  297. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_job_lifecycle.py +0 -0
  298. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_job_results.py +0 -0
  299. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_json_columns.py +0 -0
  300. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_jsonb_dict_adapter.py +0 -0
  301. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_lambda_handler.py +0 -0
  302. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_lambda_handler_auth.py +0 -0
  303. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_lambda_handler_masquerade.py +0 -0
  304. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_lambda_handler_user_cache.py +0 -0
  305. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_lazy_engine.py +0 -0
  306. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_masquerade_grant.py +0 -0
  307. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_merge_on_conflict.py +0 -0
  308. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_mirror.py +0 -0
  309. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_mirror_upsert_public.py +0 -0
  310. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_mixins_import.py +0 -0
  311. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_n_plus_one.py +0 -0
  312. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_dynamodb.py +0 -0
  313. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_memory.py +0 -0
  314. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_mongodb.py +0 -0
  315. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_redis.py +0 -0
  316. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_translation.py +0 -0
  317. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_nosql_where.py +0 -0
  318. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_observability.py +0 -0
  319. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_on_action_default.py +0 -0
  320. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_on_commit.py +0 -0
  321. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_ops.py +0 -0
  322. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_audit_fixes.py +0 -0
  323. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_authorizenet_adapter.py +0 -0
  324. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_braintree_adapter.py +0 -0
  325. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_braintree_mirror.py +0 -0
  326. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_business_agnostic.py +0 -0
  327. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_charge_reference.py +0 -0
  328. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_extra_bounds.py +0 -0
  329. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_lazy_imports.py +0 -0
  330. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_mirror_empty_table.py +0 -0
  331. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_profile_sorting.py +0 -0
  332. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_payment_stripe_adapter.py +0 -0
  333. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pdf.py +0 -0
  334. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_bulk_cache.py +0 -0
  335. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_capability_types.py +0 -0
  336. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_ensure_infrastructure.py +0 -0
  337. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_get_tables.py +0 -0
  338. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_pointer_joins.py +0 -0
  339. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pgserver_search_export.py +0 -0
  340. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_plaid_api.py +0 -0
  341. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_plaid_webhook.py +0 -0
  342. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_pointer_join_sql.py +0 -0
  343. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_postgres_initializer_config.py +0 -0
  344. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_postgres_py_type.py +0 -0
  345. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_prepared_statements.py +0 -0
  346. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_psycopg3_upgrade.py +0 -0
  347. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_query_cache.py +0 -0
  348. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_raw_sql_prefix_policy.py +0 -0
  349. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_read_cache.py +0 -0
  350. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_readme_imports.py +0 -0
  351. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_response.py +0 -0
  352. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_restricted_direct_tables.py +0 -0
  353. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_result_returning.py +0 -0
  354. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_retry_side_effect_guard.py +0 -0
  355. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_return_default_safety.py +0 -0
  356. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_row_batch_update.py +0 -0
  357. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_row_cache_staleness.py +0 -0
  358. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_row_scoped_data_service.py +0 -0
  359. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_s3_bucket_config.py +0 -0
  360. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_s3_list_client_files.py +0 -0
  361. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_s3_upload.py +0 -0
  362. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_schema_lock_config_other_backends.py +0 -0
  363. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_schema_migrations.py +0 -0
  364. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_search_paging_export.py +0 -0
  365. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_security_capability.py +0 -0
  366. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_security_hardening.py +0 -0
  367. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_server_cursor.py +0 -0
  368. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_session_control.py +0 -0
  369. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_single_autocommit_safety.py +0 -0
  370. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_single_session_capability.py +0 -0
  371. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_sms_twilio_signature.py +0 -0
  372. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_spreadsheet_functions.py +0 -0
  373. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_sqlite_backend.py +0 -0
  374. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_sqs_per_record_transactions.py +0 -0
  375. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_ssm_config.py +0 -0
  376. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_ssm_config_require.py +0 -0
  377. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_store_user_data.py +0 -0
  378. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  379. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_table_alter.py +0 -0
  380. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_table_claim.py +0 -0
  381. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_table_find_cache.py +0 -0
  382. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_table_purge.py +0 -0
  383. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_testing_fake_db.py +0 -0
  384. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_transaction_class_wrapping.py +0 -0
  385. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_transaction_commit_and_ownership.py +0 -0
  386. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_transaction_edge_cases.py +0 -0
  387. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_tx_lifecycle_retry.py +0 -0
  388. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_tx_read_cache_and_bulk.py +0 -0
  389. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_user_not_provisioned.py +0 -0
  390. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_where_clause_validation.py +0 -0
  391. {velocity_python-0.2.1 → velocity_python-0.2.2}/tests/test_write_hook_create_flow.py +0 -0
  392. {velocity_python-0.2.1 → velocity_python-0.2.2}/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.1
3
+ Version: 0.2.2
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.1"
7
+ version = "0.2.2"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.2.1"
1
+ __version__ = version = "0.2.2"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,428 @@
1
+ """Who may do what, decided in one place.
2
+
3
+ One pure function answers every authorization question::
4
+
5
+ can(principal, action, resource, context) -> Decision
6
+
7
+ * **Deny by default.** Nothing is allowed unless a rule permits it.
8
+ * **A forbid beats any permit.**
9
+ * **An error can only take access away.** A permit rule whose condition
10
+ raises is skipped; a forbid rule whose condition raises forbids. Either
11
+ way the rule is named in ``Decision.errors`` and logged.
12
+ * **No I/O.** A decision is id comparisons and set lookups. Whoever already
13
+ loaded the row passes its owner in; this module never loads data.
14
+
15
+ Rules are data, declared beside the feature they govern and registered once
16
+ at import::
17
+
18
+ from velocity.aws import authz
19
+
20
+ RULES = (
21
+ authz.permit("notes.own", {"notes.read", "notes.write"}, when=authz.owner),
22
+ authz.permit("notes.read_any", "notes.read",
23
+ when=authz.holds("notes.read_any"), audit=True),
24
+ authz.forbid("notes.masquerade_write", "notes.write",
25
+ when=authz.masquerading),
26
+ )
27
+ authz.register(*RULES)
28
+
29
+ Permissions are plain names a principal holds. Code never checks a role:
30
+ a role is a bundle of permissions, and which bundle a user gets is the
31
+ site's decision (``principal_permissions`` on the handler).
32
+
33
+ **On a handler.** :func:`principal_for` builds the principal from the user
34
+ row the handler already loaded; :func:`decide` asks; :func:`require` asks,
35
+ refuses with a 403, and records the use of any rule marked ``audit`` in
36
+ ``sys_security_events`` *before* the access happens (no audit row, no
37
+ access). The handler attributes they read:
38
+
39
+ ========================== ================================================
40
+ ``authz_mode`` ``"off"`` (default), ``"log"`` or ``"enforce"``:
41
+ how the *existing* generic surfaces (the
42
+ DataService table gate) use the decision. New
43
+ actions written against :func:`require` are
44
+ always enforced.
45
+ ``authz_rules`` a handler's own rules, beside the registered ones
46
+ ``principal_permissions`` ``(current_user) -> iterable of names``; the
47
+ default grants none
48
+ ========================== ================================================
49
+
50
+ The request shape and the decision rule are those of AWS's Cedar policy
51
+ language, on purpose: the rules can move there without changing a caller.
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ import logging
57
+ from dataclasses import dataclass, field
58
+ from fnmatch import fnmatchcase
59
+ from typing import Any, Callable, Optional
60
+
61
+ from velocity.aws import ssm_config
62
+ from velocity.aws.handlers.exceptions import AccessError
63
+
64
+ logger = logging.getLogger(__name__)
65
+
66
+ PERMIT = "permit"
67
+ FORBID = "forbid"
68
+
69
+ OFF = "off"
70
+ LOG = "log"
71
+ ENFORCE = "enforce"
72
+ MODES = (OFF, LOG, ENFORCE)
73
+
74
+ # The action the DataService table gate asks about, and its resource type.
75
+ DATA_ACCESS = "data.access"
76
+ TABLE = "table"
77
+ SYSTEM_PREFIX = "sys_"
78
+
79
+ # The audit sink's ``guard`` value for a use of an ``audit`` rule.
80
+ PRIVILEGED_ACCESS = "privileged_access"
81
+
82
+
83
+ @dataclass(frozen=True)
84
+ class Principal:
85
+ """Who is asking. ``user_sys_id`` is the row id in the site's user table
86
+ (never an email). ``real_user`` is set in a masquerade session: the
87
+ administrator who is acting as this user."""
88
+
89
+ user_sys_id: Optional[int] = None
90
+ permissions: frozenset = frozenset()
91
+ tenant: Any = None
92
+ real_user: Any = None
93
+
94
+ def holds(self, permission):
95
+ return permission in self.permissions
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class Resource:
100
+ """The specific object: its type, id, owner (a user row id) and tenant."""
101
+
102
+ type: str
103
+ id: Any = None
104
+ owner: Optional[int] = None
105
+ tenant: Any = None
106
+
107
+
108
+ @dataclass(frozen=True)
109
+ class Rule:
110
+ name: str
111
+ effect: str
112
+ actions: frozenset
113
+ when: Optional[Callable] = None
114
+ audit: bool = False
115
+
116
+ def covers(self, action):
117
+ return any(
118
+ action == pattern or ("*" in pattern and fnmatchcase(action, pattern))
119
+ for pattern in self.actions
120
+ )
121
+
122
+
123
+ @dataclass(frozen=True)
124
+ class Decision:
125
+ """The answer. ``rule`` is the rule that decided (``None`` when nothing
126
+ permitted); ``audit`` is true when that rule's use must be recorded."""
127
+
128
+ allowed: bool
129
+ action: str
130
+ rule: Optional[str] = None
131
+ audit: bool = False
132
+ errors: tuple = field(default_factory=tuple)
133
+
134
+ def __bool__(self):
135
+ return self.allowed
136
+
137
+
138
+ def _rule(effect, name, actions, when, audit):
139
+ if not isinstance(name, str) or not name:
140
+ raise ValueError("A rule needs a name.")
141
+ if isinstance(actions, str):
142
+ actions = (actions,)
143
+ actions = frozenset(actions or ())
144
+ if not actions or not all(isinstance(a, str) and a for a in actions):
145
+ raise ValueError(f"Rule '{name}' names no action.")
146
+ if when is not None and not callable(when):
147
+ raise ValueError(f"Rule '{name}': 'when' must be callable.")
148
+ return Rule(name, effect, actions, when, bool(audit))
149
+
150
+
151
+ def permit(name, actions, *, when=None, audit=False):
152
+ """Allow ``actions`` (a name, or names; ``*`` is a wildcard) when
153
+ ``when(principal, action, resource, context)`` is true. ``audit=True``
154
+ marks a privileged rule: each use is recorded."""
155
+ return _rule(PERMIT, name, actions, when, audit)
156
+
157
+
158
+ def forbid(name, actions, *, when=None):
159
+ """Refuse ``actions`` when ``when(...)`` is true, whatever permits."""
160
+ return _rule(FORBID, name, actions, when, False)
161
+
162
+
163
+ # ── conditions ─────────────────────────────────────────────────────────────
164
+
165
+
166
+ def owner(principal, action, resource, context):
167
+ """The resource belongs to the principal."""
168
+ return (
169
+ principal.user_sys_id is not None
170
+ and resource is not None
171
+ and resource.owner is not None
172
+ and resource.owner == principal.user_sys_id
173
+ )
174
+
175
+
176
+ def masquerading(principal, action, resource, context):
177
+ """An administrator is acting as this user."""
178
+ return principal.real_user is not None or bool((context or {}).get("masquerade"))
179
+
180
+
181
+ def signed_in(principal, action, resource, context):
182
+ return principal.user_sys_id is not None
183
+
184
+
185
+ def holds(permission):
186
+ """The principal holds the named permission."""
187
+
188
+ def condition(principal, action, resource, context):
189
+ return principal.holds(permission)
190
+
191
+ return condition
192
+
193
+
194
+ def table(*names):
195
+ """The resource is one of these tables."""
196
+ wanted = frozenset(names)
197
+
198
+ def condition(principal, action, resource, context):
199
+ return resource is not None and resource.type == TABLE and resource.id in wanted
200
+
201
+ return condition
202
+
203
+
204
+ def all_of(*conditions):
205
+ def condition(principal, action, resource, context):
206
+ return all(c(principal, action, resource, context) for c in conditions)
207
+
208
+ return condition
209
+
210
+
211
+ def any_of(*conditions):
212
+ def condition(principal, action, resource, context):
213
+ return any(c(principal, action, resource, context) for c in conditions)
214
+
215
+ return condition
216
+
217
+
218
+ def _ordinary_table(principal, action, resource, context):
219
+ return (
220
+ resource is not None
221
+ and resource.type == TABLE
222
+ and isinstance(resource.id, str)
223
+ and not resource.id.startswith(SYSTEM_PREFIX)
224
+ )
225
+
226
+
227
+ # ── the registry ───────────────────────────────────────────────────────────
228
+
229
+ # Until each site declares rules per table, an ordinary table stays reachable
230
+ # through the generic Data actions exactly as the table gate, the rwx hooks
231
+ # and the tenant scope already allow. System tables (``sys_…``) get no such
232
+ # rule: they are refused unless the handler declares one.
233
+ BUILTIN_RULES = (permit("data.ordinary_tables", DATA_ACCESS, when=_ordinary_table),)
234
+
235
+ _REGISTERED = {}
236
+
237
+
238
+ def register(*rules):
239
+ """Add rules to the ones every handler evaluates. Registering the same
240
+ rule again is a no-op; a different rule under a taken name is an error."""
241
+ for rule in rules:
242
+ if not isinstance(rule, Rule):
243
+ raise ValueError("register() takes rules made by permit() or forbid().")
244
+ known = _REGISTERED.get(rule.name)
245
+ if known is not None and known != rule:
246
+ raise ValueError(f"An authorization rule named '{rule.name}' exists.")
247
+ _REGISTERED[rule.name] = rule
248
+
249
+
250
+ def registered():
251
+ """Every rule a handler evaluates before its own."""
252
+ return BUILTIN_RULES + tuple(_REGISTERED.values())
253
+
254
+
255
+ # ── the decision ───────────────────────────────────────────────────────────
256
+
257
+
258
+ def can(principal, action, resource=None, context=None, *, rules=None):
259
+ """Decide. See the module docstring for the rule."""
260
+ rules = registered() if rules is None else tuple(rules)
261
+ context = context or {}
262
+ permitted = None
263
+ errors = []
264
+ if not isinstance(principal, Principal) or not isinstance(action, str):
265
+ return Decision(False, str(action))
266
+ for rule in rules:
267
+ if not rule.covers(action):
268
+ continue
269
+ try:
270
+ matched = rule.when is None or bool(
271
+ rule.when(principal, action, resource, context)
272
+ )
273
+ except Exception:
274
+ logger.warning(
275
+ "authorization rule %s failed on %s", rule.name, action, exc_info=True
276
+ )
277
+ errors.append(rule.name)
278
+ # an error can only take access away
279
+ matched = rule.effect == FORBID
280
+ if not matched:
281
+ continue
282
+ if rule.effect == FORBID:
283
+ return Decision(False, action, rule.name, False, tuple(errors))
284
+ if permitted is None:
285
+ permitted = rule
286
+ if permitted is None:
287
+ return Decision(False, action, None, False, tuple(errors))
288
+ return Decision(True, action, permitted.name, permitted.audit, tuple(errors))
289
+
290
+
291
+ # ── on a handler ───────────────────────────────────────────────────────────
292
+
293
+
294
+ def mode_of(handler):
295
+ """``AuthzMode`` config first, then the handler's ``authz_mode``. Blank
296
+ is ``off``; a value that is not a mode is ``enforce`` (fail closed)."""
297
+ raw = None
298
+ try:
299
+ raw = ssm_config.getenv("AuthzMode", None)
300
+ except Exception:
301
+ raw = None
302
+ raw = str(raw or getattr(handler, "authz_mode", None) or OFF).strip().lower()
303
+ return raw if raw in MODES else ENFORCE
304
+
305
+
306
+ def _session(context):
307
+ try:
308
+ return context.session() or {}
309
+ except Exception:
310
+ return {}
311
+
312
+
313
+ def principal_for(handler, context=None):
314
+ """The principal for the request ``handler`` is serving, from the user
315
+ row it already loaded. No query."""
316
+ user = getattr(handler, "current_user", None) or {}
317
+ try:
318
+ user_sys_id = int(user.get("sys_id"))
319
+ except (TypeError, ValueError, AttributeError):
320
+ user_sys_id = None
321
+ if user_sys_id is not None and user_sys_id <= 0:
322
+ user_sys_id = None
323
+ permissions = ()
324
+ hook = getattr(handler, "principal_permissions", None)
325
+ if callable(hook) and user_sys_id is not None:
326
+ try:
327
+ permissions = hook(user) or ()
328
+ except Exception:
329
+ # no permissions is the safe answer
330
+ logger.warning("principal_permissions failed", exc_info=True)
331
+ permissions = ()
332
+ context = context if context is not None else getattr(handler, "context", None)
333
+ masquerade = _session(context).get("masquerade") if context is not None else None
334
+ real_user = None
335
+ if masquerade:
336
+ real_user = (
337
+ masquerade.get("real_user") if isinstance(masquerade, dict) else None
338
+ ) or True
339
+ return Principal(
340
+ user_sys_id=user_sys_id,
341
+ permissions=frozenset(p for p in permissions if isinstance(p, str)),
342
+ tenant=user.get("tenant") if isinstance(user, dict) else None,
343
+ real_user=real_user,
344
+ )
345
+
346
+
347
+ def rules_for(handler):
348
+ return registered() + tuple(getattr(handler, "authz_rules", None) or ())
349
+
350
+
351
+ def decide(handler, action, resource=None, context=None):
352
+ """The decision for the request ``handler`` is serving."""
353
+ principal = principal_for(handler, context)
354
+ facts = {"masquerade": principal.real_user is not None}
355
+ return can(principal, action, resource, facts, rules=rules_for(handler))
356
+
357
+
358
+ def require(handler, tx, context, action, resource=None):
359
+ """Decide and enforce, whatever ``authz_mode`` says: for actions written
360
+ against the policy. A denial raises :class:`AccessError` (403). A use of
361
+ an ``audit`` rule is recorded first, and refused when it cannot be."""
362
+ decision = decide(handler, action, resource, context)
363
+ if not decision.allowed:
364
+ logger.warning(
365
+ "authorization refused %s on %s (%s)",
366
+ action,
367
+ _describe(resource),
368
+ decision.rule or "no rule permits it",
369
+ )
370
+ raise AccessError("You do not have access to that.")
371
+ if decision.audit and not _record(handler, tx, context, decision, resource):
372
+ raise AccessError("That access could not be recorded, so it was refused.")
373
+ return decision
374
+
375
+
376
+ _WOULD_REFUSE = set()
377
+
378
+
379
+ def gate(handler, action, resource=None):
380
+ """The decision for an EXISTING generic surface, under ``authz_mode``:
381
+ ``off`` allows without asking, ``log`` asks and logs what it would
382
+ refuse (once per container per action and resource) and allows,
383
+ ``enforce`` returns the decision's answer."""
384
+ mode = mode_of(handler)
385
+ if mode == OFF:
386
+ return True
387
+ decision = decide(handler, action, resource)
388
+ if decision.allowed:
389
+ return True
390
+ if mode == LOG:
391
+ key = (action, getattr(resource, "type", None), getattr(resource, "id", None))
392
+ if key not in _WOULD_REFUSE:
393
+ _WOULD_REFUSE.add(key)
394
+ logger.warning(
395
+ "authz (log only) would refuse %s on %s (%s)",
396
+ action,
397
+ _describe(resource),
398
+ decision.rule or "no rule permits it",
399
+ )
400
+ return True
401
+ return False
402
+
403
+
404
+ def _describe(resource):
405
+ if resource is None:
406
+ return "-"
407
+ return f"{resource.type}:{resource.id}"
408
+
409
+
410
+ def _record(handler, tx, context, decision, resource):
411
+ # imported here: the capabilities package pulls in more than a pure
412
+ # decision module should load at import
413
+ from velocity.aws.capabilities.security import record_event
414
+
415
+ session = _session(context)
416
+ actor = (
417
+ (session.get("masquerade") or {}).get("real_user")
418
+ if isinstance(session.get("masquerade"), dict)
419
+ else None
420
+ ) or session.get("email_address")
421
+ return record_event(
422
+ tx,
423
+ PRIVILEGED_ACCESS,
424
+ f"{decision.action} on {_describe(resource)} by rule {decision.rule}",
425
+ aws_event=getattr(handler, "aws_event", None),
426
+ context=context,
427
+ user_email=actor,
428
+ )
@@ -13,7 +13,7 @@ import re
13
13
  from io import BytesIO
14
14
  from types import MappingProxyType
15
15
 
16
- from velocity.aws import dirty_pipeline, ssm_config
16
+ from velocity.aws import authz, dirty_pipeline, ssm_config
17
17
  from velocity.aws import facets
18
18
  from velocity.aws.handlers.exceptions import AccessError, AlertError, BadRequestError
19
19
  from velocity.aws.handlers.mixins.client_input import MODES, STRICT, ClientInputGuard
@@ -72,6 +72,9 @@ def _fk_blocked_delete_message(table, exc):
72
72
 
73
73
 
74
74
  _IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
75
+ # Owner-scoped tables already warned about under the legacy client SQL mode
76
+ # (once per container, not once per request).
77
+ _OWNER_SCOPE_WARNED = set()
75
78
 
76
79
 
77
80
  TEXT_TYPES = frozenset(
@@ -695,6 +698,44 @@ class DataServiceMixin:
695
698
  client_denied_tables = frozenset()
696
699
  client_allowed_tables = None
697
700
 
701
+ # The authorization decision point (velocity.aws.authz) at the client
702
+ # table gate. ``"off"`` (default): the gate is as above. ``"log"``: every
703
+ # table is also put to the rules and what they would refuse is logged,
704
+ # nothing is refused. ``"enforce"``: a table no rule permits is refused.
705
+ # The ``AuthzMode`` config key overrides this per environment. Out of the
706
+ # box the rules permit every ordinary table and NO system table
707
+ # (``sys_…``): a handler that serves one in a grid declares it, e.g.
708
+ # authz_rules = (authz.permit("flags.admin", authz.DATA_ACCESS,
709
+ # when=authz.all_of(authz.table("sys_feature_flags"),
710
+ # authz.holds("site.admin"))),)
711
+ # ``principal_permissions(current_user)`` says which permissions the
712
+ # signed-in user holds; the default is none.
713
+ authz_mode = authz.OFF
714
+ authz_rules = ()
715
+
716
+ def principal_permissions(self, current_user):
717
+ return ()
718
+
719
+ # Tables whose every row belongs to ONE signed-in user: ``{table: owner
720
+ # column}``. The owner column holds the user's sign-in email as the
721
+ # session carries it. Each hooked action (query, find, read, write,
722
+ # delete) confines the caller to their own rows, and the direct actions
723
+ # refuse the table — the rule ``tenant_scope`` gives a site, applied by
724
+ # the framework itself and resolved BEFORE the site's ``tenant_scope``,
725
+ # so an override that forgets ``super()`` cannot switch it off.
726
+ #
727
+ # ``user_data`` is here because the framework owns it: per-user settings
728
+ # written by ``store-user-data`` and read back by the browser through the
729
+ # generic ``query``, which until this rule let any signed-in user read or
730
+ # overwrite another user's rows by naming their ``userid``.
731
+ #
732
+ # Enforced under the strict client SQL mode. Under legacy mode the
733
+ # browser can send raw SQL around any row predicate, so nothing here can
734
+ # be a boundary: the rule is skipped and a warning is logged, leaving
735
+ # legacy behaviour exactly as it was. Set the attribute to ``{}`` to opt
736
+ # a handler out.
737
+ owner_scoped_tables = MappingProxyType({"user_data": "userid"})
738
+
698
739
  # How much SQL-shaped input the boundary accepts from the browser
699
740
  # (velocity.aws.handlers.mixins.client_input): "strict" (Contract C — dict
700
741
  # filters, plain columns, structured @@search) or "legacy" (string
@@ -795,7 +836,9 @@ class DataServiceMixin:
795
836
  if table in self.client_denied_tables:
796
837
  return False
797
838
  allowed = self.client_allowed_tables
798
- return allowed is None or table in allowed
839
+ if allowed is not None and table not in allowed:
840
+ return False
841
+ return authz.gate(self, authz.DATA_ACCESS, authz.Resource(authz.TABLE, table))
799
842
 
800
843
  def _assert_client_table(self, table, label="table"):
801
844
  """The table gate every client action passes (SEC 4.2)."""
@@ -914,9 +957,12 @@ class DataServiceMixin:
914
957
  )
915
958
 
916
959
  def _assert_direct_unscoped(self, table, context):
917
- """A tenant-scoped table is never reachable through a direct action
918
- (they cannot apply the scope)."""
919
- if self.tenant_scope(context, table) is not None:
960
+ """A tenant-scoped or owner-scoped table is never reachable through a
961
+ direct action (they cannot apply the scope)."""
962
+ if (
963
+ self._owner_scope(context, table) is not None
964
+ or self.tenant_scope(context, table) is not None
965
+ ):
920
966
  raise AlertError(
921
967
  f"Direct access to '{table}' is not permitted; "
922
968
  "use the standard data actions."
@@ -995,11 +1041,47 @@ class DataServiceMixin:
995
1041
  """
996
1042
  return None
997
1043
 
1044
+ def _owner_scope(self, context, table):
1045
+ """``(column, values)`` confining ``table`` to the signed-in user, or
1046
+ ``None`` when the table is not in :attr:`owner_scoped_tables` — or
1047
+ when this handler runs the legacy client SQL mode, where no row
1048
+ predicate is a boundary (see the attribute's comment).
1049
+
1050
+ The values are the session's email as it stands and lowercased: rows
1051
+ written before emails were normalized keep matching. The first value
1052
+ is the one stamped on a new row. A session without an email is
1053
+ refused."""
1054
+ column = (self.owner_scoped_tables or {}).get(table)
1055
+ if not column:
1056
+ return None
1057
+ if self.client_sql_mode_effective != STRICT:
1058
+ if table not in _OWNER_SCOPE_WARNED:
1059
+ _OWNER_SCOPE_WARNED.add(table)
1060
+ logger.warning(
1061
+ "%s rows are not confined to their owner: this handler "
1062
+ "runs the legacy client SQL mode, under which no row rule "
1063
+ "can be enforced. Move it to strict mode.",
1064
+ table,
1065
+ )
1066
+ return None
1067
+ session = context.session() if hasattr(context, "session") else None
1068
+ email = (session or {}).get("email_address")
1069
+ if not isinstance(email, str) or not email.strip():
1070
+ raise AlertError("Access denied: no signed-in user for this session.")
1071
+ email = email.strip()
1072
+ return column, tuple(dict.fromkeys((email, email.lower())))
1073
+
998
1074
  def _resolve_tenant_scope(self, tx, table, context):
999
1075
  """``(column, values)`` for ``table``, or ``None`` (unscoped). Fails
1000
1076
  closed: a scope under any client SQL mode but strict, a malformed
1001
- scope, an empty value, or a table without the column is refused."""
1002
- scope = self.tenant_scope(context, table)
1077
+ scope, an empty value, or a table without the column is refused.
1078
+
1079
+ A table in :attr:`owner_scoped_tables` takes the framework's owner
1080
+ scope; the site's :meth:`tenant_scope` is not consulted for it."""
1081
+ scope = self._owner_scope(context, table)
1082
+ owned = scope is not None
1083
+ if scope is None:
1084
+ scope = self.tenant_scope(context, table)
1003
1085
  if scope is None:
1004
1086
  return None
1005
1087
  # The predicate is only a boundary when the client cannot send raw
@@ -1030,7 +1112,12 @@ class DataServiceMixin:
1030
1112
  except Exception: # noqa: BLE001 — unknown schema is not scoped schema
1031
1113
  available = set()
1032
1114
  if column.lower() not in available:
1033
- raise AlertError(f"Access denied: {table} is not tenant-scoped.")
1115
+ # An owner-scoped table that does not exist yet (user_data on a
1116
+ # fresh site, before anyone has saved a setting) has no rows to
1117
+ # confine: keep the scope and let the read come back empty, as it
1118
+ # did before. A table that exists WITHOUT the column is refused.
1119
+ if not (owned and not available):
1120
+ raise AlertError(f"Access denied: {table} is not tenant-scoped.")
1034
1121
  return column, values
1035
1122
 
1036
1123
  @staticmethod
@@ -1066,10 +1153,12 @@ class DataServiceMixin:
1066
1153
  f"Access denied: {column} must be one of your own values."
1067
1154
  )
1068
1155
  if not keys and creating:
1069
- if len(values) != 1:
1156
+ if len(values) != 1 and table not in (self.owner_scoped_tables or {}):
1070
1157
  raise AlertError(
1071
1158
  f"Access denied: choose the {column} for this {table} record."
1072
1159
  )
1160
+ # an owner scope's values are one person's email in two spellings:
1161
+ # the first is the session's own
1073
1162
  incoming[column] = values[0]
1074
1163
 
1075
1164
  def _check_tenant_row(self, table, scope, row):