velocity-python 0.1.116__tar.gz → 0.1.117__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 (307) hide show
  1. {velocity_python-0.1.116/src/velocity_python.egg-info → velocity_python-0.1.117}/PKG-INFO +1 -1
  2. {velocity_python-0.1.116 → velocity_python-0.1.117}/pyproject.toml +1 -1
  3. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/__init__.py +1 -1
  4. velocity_python-0.1.117/src/velocity/aws/facets.py +314 -0
  5. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/mixins/data_service.py +5 -1
  6. {velocity_python-0.1.116 → velocity_python-0.1.117/src/velocity_python.egg-info}/PKG-INFO +1 -1
  7. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity_python.egg-info/SOURCES.txt +2 -0
  8. velocity_python-0.1.117/tests/test_facets.py +361 -0
  9. {velocity_python-0.1.116 → velocity_python-0.1.117}/LICENSE +0 -0
  10. {velocity_python-0.1.116 → velocity_python-0.1.117}/README.md +0 -0
  11. {velocity_python-0.1.116 → velocity_python-0.1.117}/setup.cfg +0 -0
  12. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/__init__.py +0 -0
  13. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/amplify.py +0 -0
  14. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/amplify_build.py +0 -0
  15. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/__init__.py +0 -0
  16. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/backfill.py +0 -0
  17. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/indexing.py +0 -0
  18. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/references.py +0 -0
  19. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/service.py +0 -0
  20. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/assets/usage_index.py +0 -0
  21. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/blobs.py +0 -0
  22. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/__init__.py +0 -0
  23. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/base.py +0 -0
  24. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/dispatch.py +0 -0
  25. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/infra.py +0 -0
  26. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/jobs.py +0 -0
  27. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/provisioning.py +0 -0
  28. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/retention.py +0 -0
  29. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/security.py +0 -0
  30. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/capabilities/single_session.py +0 -0
  31. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/__init__.py +0 -0
  32. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/config.py +0 -0
  33. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/operations.py +0 -0
  34. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/policies.py +0 -0
  35. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/reconcile.py +0 -0
  36. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/deploy/targets.py +0 -0
  37. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/dirty_pipeline.py +0 -0
  38. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/email.py +0 -0
  39. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/feature_flags.py +0 -0
  40. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/__init__.py +0 -0
  41. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/base_handler.py +0 -0
  42. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/context.py +0 -0
  43. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/context_factory.py +0 -0
  44. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/exceptions.py +0 -0
  45. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  46. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/masquerade.py +0 -0
  47. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  48. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  49. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  50. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  51. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/perf.py +0 -0
  52. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/response.py +0 -0
  53. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  54. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/job_lifecycle.py +0 -0
  55. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/job_results.py +0 -0
  56. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/mirror.py +0 -0
  57. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/ops.py +0 -0
  58. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/s3.py +0 -0
  59. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/sms.py +0 -0
  60. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/ssm_config.py +0 -0
  61. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/__init__.py +0 -0
  62. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  63. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  64. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  65. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  66. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  67. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_email.py +0 -0
  68. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  69. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_response.py +0 -0
  70. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/aws/tests/test_sms.py +0 -0
  71. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/cache.py +0 -0
  72. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/__init__.py +0 -0
  73. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/__init__.py +0 -0
  74. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/async_support.py +0 -0
  75. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/column.py +0 -0
  76. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/database.py +0 -0
  77. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/decorators.py +0 -0
  78. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/engine.py +0 -0
  79. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/jsonproxy.py +0 -0
  80. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/lazy_engine.py +0 -0
  81. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/result.py +0 -0
  82. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/row.py +0 -0
  83. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/sequence.py +0 -0
  84. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/table.py +0 -0
  85. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/transaction.py +0 -0
  86. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/core/view.py +0 -0
  87. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/exceptions.py +0 -0
  88. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/maintenance.py +0 -0
  89. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/migrations.py +0 -0
  90. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/__init__.py +0 -0
  91. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/base/__init__.py +0 -0
  92. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/base/initializer.py +0 -0
  93. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/base/operators.py +0 -0
  94. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/base/sql.py +0 -0
  95. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/base/types.py +0 -0
  96. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/mysql/__init__.py +0 -0
  97. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/mysql/operators.py +0 -0
  98. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/mysql/reserved.py +0 -0
  99. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/mysql/sql.py +0 -0
  100. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/mysql/types.py +0 -0
  101. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/postgres/__init__.py +0 -0
  102. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/postgres/operators.py +0 -0
  103. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/postgres/reserved.py +0 -0
  104. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/postgres/sql.py +0 -0
  105. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/postgres/types.py +0 -0
  106. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  107. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlite/operators.py +0 -0
  108. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  109. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlite/sql.py +0 -0
  110. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlite/types.py +0 -0
  111. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  112. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  113. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  114. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  115. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/sqlserver/types.py +0 -0
  116. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/servers/tablehelper.py +0 -0
  117. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/__init__.py +0 -0
  118. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/common_db_test.py +0 -0
  119. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/__init__.py +0 -0
  120. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/common.py +0 -0
  121. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/conftest.py +0 -0
  122. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_column.py +0 -0
  123. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  124. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_database.py +0 -0
  125. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  126. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  127. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  128. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_result.py +0 -0
  129. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_row.py +0 -0
  130. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  131. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  132. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  133. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  134. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  135. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_table.py +0 -0
  136. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  137. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  138. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/sql/__init__.py +0 -0
  139. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/sql/common.py +0 -0
  140. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  141. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  142. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  143. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_db_utils.py +0 -0
  144. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_postgres.py +0 -0
  145. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  146. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  147. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_result_caching.py +0 -0
  148. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  149. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  150. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  151. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  152. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_sql_builder.py +0 -0
  153. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_tablehelper.py +0 -0
  154. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/tests/test_view_helper.py +0 -0
  155. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/db/utils.py +0 -0
  156. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/logging.py +0 -0
  157. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/__init__.py +0 -0
  158. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/conv/__init__.py +0 -0
  159. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/conv/iconv.py +0 -0
  160. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/conv/oconv.py +0 -0
  161. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/db.py +0 -0
  162. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/export.py +0 -0
  163. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/format.py +0 -0
  164. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/mail.py +0 -0
  165. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/merge.py +0 -0
  166. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/pdf.py +0 -0
  167. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/__init__.py +0 -0
  168. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_db.py +0 -0
  169. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_fix.py +0 -0
  170. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_format.py +0 -0
  171. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_iconv.py +0 -0
  172. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_mail_send.py +0 -0
  173. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_merge.py +0 -0
  174. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_oconv.py +0 -0
  175. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_original_error.py +0 -0
  176. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tests/test_timer.py +0 -0
  177. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/timer.py +0 -0
  178. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/misc/tools.py +0 -0
  179. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/__init__.py +0 -0
  180. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/core/__init__.py +0 -0
  181. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/core/capabilities.py +0 -0
  182. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/core/store.py +0 -0
  183. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/core/where.py +0 -0
  184. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/exceptions.py +0 -0
  185. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/__init__.py +0 -0
  186. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/base/__init__.py +0 -0
  187. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  188. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  189. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  190. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  191. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  192. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  193. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  194. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  195. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  196. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  197. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  198. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  199. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/__init__.py +0 -0
  200. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/authorizenet_adapter.py +0 -0
  201. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/authorizenet_mirror.py +0 -0
  202. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/base_adapter.py +0 -0
  203. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/braintree_adapter.py +0 -0
  204. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/braintree_mirror.py +0 -0
  205. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/charge_rules.py +0 -0
  206. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/stripe_adapter.py +0 -0
  207. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity/payment/stripe_mirror.py +0 -0
  208. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  209. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity_python.egg-info/entry_points.txt +0 -0
  210. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity_python.egg-info/requires.txt +0 -0
  211. {velocity_python-0.1.116 → velocity_python-0.1.117}/src/velocity_python.egg-info/top_level.txt +0 -0
  212. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_access_error_403.py +0 -0
  213. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_activity_tracking_aborted_tx.py +0 -0
  214. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_amplify_build.py +0 -0
  215. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_asset_indexing.py +0 -0
  216. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_asset_references.py +0 -0
  217. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_assets_service.py +0 -0
  218. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_async_support.py +0 -0
  219. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_batch_operations.py +0 -0
  220. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_blob_store.py +0 -0
  221. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_capabilities.py +0 -0
  222. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_capability_catalog.py +0 -0
  223. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_cognito_user_cache.py +0 -0
  224. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_column_tx_arg.py +0 -0
  225. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_concurrency_safety.py +0 -0
  226. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_connection_pool.py +0 -0
  227. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_connection_resilience.py +0 -0
  228. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_context_flags_loader.py +0 -0
  229. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_context_job_descriptions.py +0 -0
  230. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_data_service_query_cap.py +0 -0
  231. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_data_service_renumber.py +0 -0
  232. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_data_service_save_as.py +0 -0
  233. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_db_credentials_ssm_cascade.py +0 -0
  234. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_db_maintenance.py +0 -0
  235. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_decorators.py +0 -0
  236. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_delete_hook_fk_blocked.py +0 -0
  237. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_dirty_pipeline_fast_path.py +0 -0
  238. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_email_processing.py +0 -0
  239. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_enqueue_send_failures.py +0 -0
  240. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_feature_flags.py +0 -0
  241. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_get_cognito_user_provider.py +0 -0
  242. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_http_handler_rollback.py +0 -0
  243. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_iconv_money_to_cents.py +0 -0
  244. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_identifier_injection_guard.py +0 -0
  245. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_job_handler.py +0 -0
  246. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_job_lifecycle.py +0 -0
  247. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_job_results.py +0 -0
  248. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_json_columns.py +0 -0
  249. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_jsonb_dict_adapter.py +0 -0
  250. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_lambda_handler.py +0 -0
  251. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_lambda_handler_auth.py +0 -0
  252. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_lambda_handler_masquerade.py +0 -0
  253. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_lambda_handler_user_cache.py +0 -0
  254. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_lazy_engine.py +0 -0
  255. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_masquerade_grant.py +0 -0
  256. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_mirror.py +0 -0
  257. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_mixins_import.py +0 -0
  258. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_n_plus_one.py +0 -0
  259. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_dynamodb.py +0 -0
  260. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_memory.py +0 -0
  261. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_mongodb.py +0 -0
  262. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_redis.py +0 -0
  263. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_translation.py +0 -0
  264. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_nosql_where.py +0 -0
  265. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_observability.py +0 -0
  266. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_on_commit.py +0 -0
  267. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_ops.py +0 -0
  268. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_authorizenet_adapter.py +0 -0
  269. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_braintree_adapter.py +0 -0
  270. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_braintree_mirror.py +0 -0
  271. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_mirror_empty_table.py +0 -0
  272. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_profile_sorting.py +0 -0
  273. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_payment_stripe_adapter.py +0 -0
  274. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_pdf.py +0 -0
  275. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_prepared_statements.py +0 -0
  276. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_psycopg3_upgrade.py +0 -0
  277. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_query_cache.py +0 -0
  278. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_query_missing_table_unlocked.py +0 -0
  279. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_read_cache.py +0 -0
  280. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_response.py +0 -0
  281. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_restricted_direct_tables.py +0 -0
  282. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_result_returning.py +0 -0
  283. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_retry_side_effect_guard.py +0 -0
  284. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_return_default_safety.py +0 -0
  285. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_row_batch_update.py +0 -0
  286. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_row_cache_staleness.py +0 -0
  287. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_schema_migrations.py +0 -0
  288. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_security_capability.py +0 -0
  289. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_security_hardening.py +0 -0
  290. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_server_cursor.py +0 -0
  291. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_session_control.py +0 -0
  292. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_single_autocommit_safety.py +0 -0
  293. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_single_session_capability.py +0 -0
  294. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_spreadsheet_functions.py +0 -0
  295. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_sqlite_backend.py +0 -0
  296. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_sqs_per_record_transactions.py +0 -0
  297. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_ssm_config.py +0 -0
  298. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_store_user_data.py +0 -0
  299. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  300. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_table_alter.py +0 -0
  301. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_table_find_cache.py +0 -0
  302. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_table_purge.py +0 -0
  303. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_transaction_class_wrapping.py +0 -0
  304. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_transaction_commit_and_ownership.py +0 -0
  305. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_transaction_edge_cases.py +0 -0
  306. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_where_clause_validation.py +0 -0
  307. {velocity_python-0.1.116 → velocity_python-0.1.117}/tests/test_write_hook_create_flow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.116
3
+ Version: 0.1.117
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.1.116"
7
+ version = "0.1.117"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.116"
1
+ __version__ = version = "0.1.117"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -0,0 +1,314 @@
1
+ """Structured grid facets: the client says WHAT it selected, this builds the SQL.
2
+
3
+ A server-side grid used to filter by sending SQL it had built in the browser
4
+ (``params.where``). Facets — the pick-lists and write-in boxes on a grid's
5
+ toolbar — are the first filter to move off that path: the client sends the
6
+ selection as data (``params.facets``) and :func:`build_where` turns it into a
7
+ parameterized predicate here, with the field names whitelisted as plain
8
+ identifiers and every value bound as a parameter. ``params.where`` keeps
9
+ working unchanged alongside it (the toolbar search and the column filters
10
+ still arrive that way); the two are AND'ed by :func:`apply_to_params`.
11
+
12
+ The wire shape is one list under ``params.facets``::
13
+
14
+ [
15
+ {"field": "has_attachments", "kind": "values", "values": ["true"]},
16
+ {"field": "label_ids", "kind": "contains", "values": ["INBOX", "SENT"]},
17
+ {"field": "deleted_at", "kind": "presence", "value": "present"},
18
+ {"field": "internal_date", "kind": "since", "value": "30d"},
19
+ {"field": "from_domain", "kind": "text", "value": "example.com",
20
+ "match": "contains"},
21
+ ]
22
+
23
+ Facets AND together; the values of one facet OR together. Kinds:
24
+
25
+ ``values`` ``field IN (…)`` — a pick-list over a column's own values (or a
26
+ lookup table's ids). The default when ``kind`` is missing.
27
+ ``contains`` membership in a JSON array column: ``field @> '["x"]'::jsonb``,
28
+ OR'd per value. For ``jsonb`` columns only.
29
+ ``presence`` ``value`` is ``present`` or ``absent``: NULL *or empty string*
30
+ counts as absent, whatever the column type
31
+ (``NULLIF(field::text, '')``).
32
+ ``since`` a period ending now: ``today``, ``Nd`` (``7d``, ``30d`` …, up to
33
+ 3660 days), ``year`` (this calendar year). Evaluated in the
34
+ database's session time zone.
35
+ ``text`` a write-in, case-insensitive, against ``field::text``:
36
+ ``match`` is ``contains`` (default), ``equals`` or ``starts``.
37
+ The user's ``%`` and ``_`` are literal.
38
+
39
+ Anything malformed — an unknown kind, a field that is not a bare identifier,
40
+ too many or too long values — raises :class:`FacetError` rather than being
41
+ dropped: a silently ignored facet would show rows as filtered when they are
42
+ not. Strings are bound as parameters and PostgreSQL infers their type from the
43
+ column (a ``'3'`` parameter compares to a bigint, ``'true'`` to a boolean), so
44
+ no column types are needed here.
45
+ """
46
+
47
+ from __future__ import annotations
48
+
49
+ import re
50
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
51
+
52
+ # The SQL emitted here is PostgreSQL's (jsonb ``@>``, ``make_interval``,
53
+ # ``ILIKE``), and importing the postgres SQL module is what fills in
54
+ # TableHelper's reserved-word list, so a column called ``order`` is quoted.
55
+ import velocity.db.servers.postgres.sql # noqa: F401
56
+ from velocity.db.servers.tablehelper import TableHelper
57
+
58
+ PARAM_KEY = "facets"
59
+
60
+ KINDS = ("values", "contains", "presence", "since", "text")
61
+ MATCHES = ("contains", "equals", "starts")
62
+ PRESENCE = ("present", "absent")
63
+
64
+ MAX_FACETS = 40
65
+ MAX_VALUES = 200
66
+ MAX_VALUE_CHARS = 500
67
+ MAX_DAYS = 3660
68
+
69
+ _IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
70
+ _DAYS = re.compile(r"^(\d{1,4})d$")
71
+ _LIKE_SPECIAL = re.compile(r"([\\%_])")
72
+
73
+ Predicate = Tuple[str, Tuple[Any, ...]]
74
+
75
+
76
+ class FacetError(ValueError):
77
+ """A facet the client sent cannot be turned into SQL."""
78
+
79
+
80
+ # ── validation ────────────────────────────────────────────────────────────────
81
+
82
+
83
+ def _field(entry: Dict[str, Any]) -> str:
84
+ field = entry.get("field")
85
+ if not isinstance(field, str) or not _IDENTIFIER.match(field):
86
+ raise FacetError(f"facet field must be a column name, got {field!r}")
87
+ return field
88
+
89
+
90
+ def _scalar(value: Any, field: str) -> str:
91
+ if isinstance(value, bool):
92
+ return "true" if value else "false"
93
+ if isinstance(value, (int, float)) and not isinstance(value, bool):
94
+ return str(value)
95
+ if not isinstance(value, str):
96
+ raise FacetError(f"facet {field}: values must be strings, got {value!r}")
97
+ if len(value) > MAX_VALUE_CHARS:
98
+ raise FacetError(
99
+ f"facet {field}: a value is longer than {MAX_VALUE_CHARS} characters"
100
+ )
101
+ return value
102
+
103
+
104
+ def _values(entry: Dict[str, Any], field: str) -> List[str]:
105
+ raw = entry.get("values")
106
+ if raw is None and "value" in entry:
107
+ raw = [entry["value"]]
108
+ if not isinstance(raw, (list, tuple)):
109
+ raise FacetError(f"facet {field}: 'values' must be a list")
110
+ if len(raw) > MAX_VALUES:
111
+ raise FacetError(f"facet {field}: more than {MAX_VALUES} values")
112
+ seen, out = set(), []
113
+ for item in raw:
114
+ value = _scalar(item, field)
115
+ if value == "" or value in seen:
116
+ continue
117
+ seen.add(value)
118
+ out.append(value)
119
+ return out
120
+
121
+
122
+ def _one(entry: Dict[str, Any], field: str) -> str:
123
+ raw = entry.get("value")
124
+ if raw is None:
125
+ values = entry.get("values")
126
+ raw = values[0] if isinstance(values, (list, tuple)) and values else None
127
+ if raw is None:
128
+ return ""
129
+ return _scalar(raw, field).strip()
130
+
131
+
132
+ # ── one facet -> one predicate ────────────────────────────────────────────────
133
+
134
+
135
+ def _kind_values(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
136
+ values = _values(entry, field)
137
+ if not values:
138
+ return None
139
+ if len(values) == 1:
140
+ return f"{column} = %s", (values[0],)
141
+ return f"{column} IN ({', '.join(['%s'] * len(values))})", tuple(values)
142
+
143
+
144
+ def _kind_contains(
145
+ column: str, entry: Dict[str, Any], field: str
146
+ ) -> Optional[Predicate]:
147
+ import json
148
+
149
+ values = _values(entry, field)
150
+ if not values:
151
+ return None
152
+ parts = [f"{column} @> %s::jsonb" for _ in values]
153
+ params = tuple(json.dumps([value]) for value in values)
154
+ return f"({' OR '.join(parts)})", params
155
+
156
+
157
+ def _kind_presence(
158
+ column: str, entry: Dict[str, Any], field: str
159
+ ) -> Optional[Predicate]:
160
+ value = _one(entry, field).lower()
161
+ if not value:
162
+ return None
163
+ if value not in PRESENCE:
164
+ raise FacetError(f"facet {field}: presence must be one of {PRESENCE}")
165
+ test = "IS NOT NULL" if value == "present" else "IS NULL"
166
+ return f"NULLIF({column}::text, '') {test}", ()
167
+
168
+
169
+ def _kind_since(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
170
+ value = _one(entry, field).lower()
171
+ if not value:
172
+ return None
173
+ if value == "today":
174
+ return f"{column} >= date_trunc('day', now())", ()
175
+ if value == "year":
176
+ return f"{column} >= date_trunc('year', now())", ()
177
+ match = _DAYS.match(value)
178
+ if not match:
179
+ raise FacetError(f"facet {field}: period must be today, <N>d or year")
180
+ days = int(match.group(1))
181
+ if not 1 <= days <= MAX_DAYS:
182
+ raise FacetError(f"facet {field}: period must be 1 to {MAX_DAYS} days")
183
+ return f"{column} >= now() - make_interval(days => %s)", (days,)
184
+
185
+
186
+ def _kind_text(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
187
+ value = _one(entry, field)
188
+ if not value:
189
+ return None
190
+ match = str(entry.get("match") or "contains").lower()
191
+ if match not in MATCHES:
192
+ raise FacetError(f"facet {field}: match must be one of {MATCHES}")
193
+ escaped = _LIKE_SPECIAL.sub(r"\\\1", value)
194
+ pattern = {
195
+ "contains": f"%{escaped}%",
196
+ "starts": f"{escaped}%",
197
+ "equals": escaped,
198
+ }[match]
199
+ return f"{column}::text ILIKE %s", (pattern,)
200
+
201
+
202
+ _BUILDERS = {
203
+ "values": _kind_values,
204
+ "contains": _kind_contains,
205
+ "presence": _kind_presence,
206
+ "since": _kind_since,
207
+ "text": _kind_text,
208
+ }
209
+
210
+
211
+ def predicate(entry: Any) -> Optional[Predicate]:
212
+ """``(sql, params)`` for one facet entry, or None when it selects nothing."""
213
+ if not isinstance(entry, dict):
214
+ raise FacetError(f"a facet must be an object, got {type(entry).__name__}")
215
+ field = _field(entry)
216
+ kind = str(entry.get("kind") or "values").lower()
217
+ if kind not in _BUILDERS:
218
+ raise FacetError(f"facet {field}: unknown kind {kind!r}")
219
+ column = TableHelper.quote(field)
220
+ return _BUILDERS[kind](column, entry, field)
221
+
222
+
223
+ def build_where(facets: Any) -> Optional[Predicate]:
224
+ """``(sql, params)`` AND-ing every facet, or None when none selects anything."""
225
+ if facets in (None, "", [], ()):
226
+ return None
227
+ if not isinstance(facets, (list, tuple)):
228
+ raise FacetError("'facets' must be a list")
229
+ if len(facets) > MAX_FACETS:
230
+ raise FacetError(f"more than {MAX_FACETS} facets")
231
+ parts: List[str] = []
232
+ params: List[Any] = []
233
+ for entry in facets:
234
+ built = predicate(entry)
235
+ if built is None:
236
+ continue
237
+ sql, values = built
238
+ parts.append(sql)
239
+ params.extend(values)
240
+ if not parts:
241
+ return None
242
+ return " AND ".join(parts), tuple(params)
243
+
244
+
245
+ # ── folding into a query's params ─────────────────────────────────────────────
246
+
247
+
248
+ def _existing_predicates(tx, table: str, where: Any) -> List[Predicate]:
249
+ """The caller's ``where`` as ``(sql, params)`` predicates, whatever its shape."""
250
+ if where in (None, "", {}, [], ()):
251
+ return []
252
+ if isinstance(where, str):
253
+ clause = where.strip()
254
+ if clause.upper().startswith("WHERE "):
255
+ clause = clause[6:].strip()
256
+ if not clause:
257
+ return []
258
+ # The string travelled without parameters, so its literal ``%`` (an
259
+ # ``ilike '%x%'`` from the toolbar search) never met the driver's
260
+ # placeholder parser. Joined to a parameterized predicate it does:
261
+ # every literal ``%`` must become ``%%`` (found on demo, 2026-09-21).
262
+ return [(f"({clause.replace('%', '%%')})", ())]
263
+ if isinstance(where, dict):
264
+ helper = TableHelper(tx, table)
265
+ return [
266
+ (
267
+ sql,
268
+ (
269
+ ()
270
+ if value is None
271
+ else (value if isinstance(value, tuple) else (value,))
272
+ ),
273
+ )
274
+ for sql, value in (
275
+ helper.make_predicate(key, val) for key, val in where.items()
276
+ )
277
+ ]
278
+ if isinstance(where, (list, tuple)):
279
+ out: List[Predicate] = []
280
+ for item in where:
281
+ if not isinstance(item, (list, tuple)) or len(item) != 2:
282
+ raise FacetError(f"where predicate must be (sql, params), got {item!r}")
283
+ sql, value = item
284
+ out.append(
285
+ (
286
+ sql,
287
+ (
288
+ ()
289
+ if value is None
290
+ else (value if isinstance(value, tuple) else (value,))
291
+ ),
292
+ )
293
+ )
294
+ return out
295
+ raise FacetError(f"unsupported where shape: {type(where).__name__}")
296
+
297
+
298
+ def apply_to_params(tx, table: str, params: Optional[Dict[str, Any]]) -> Dict[str, Any]:
299
+ """Fold ``params['facets']`` into ``params['where']``; drop the key.
300
+
301
+ Returns a new dict. With no facets the params come back unchanged (the
302
+ same ``where`` object), so callers without facets see no difference.
303
+ """
304
+ if not isinstance(params, dict) or PARAM_KEY not in params:
305
+ return params if isinstance(params, dict) else {}
306
+ out = dict(params)
307
+ facets = out.pop(PARAM_KEY)
308
+ built = build_where(facets)
309
+ if built is None:
310
+ return out
311
+ predicates = _existing_predicates(tx, table, out.get("where"))
312
+ predicates.append(built)
313
+ out["where"] = predicates
314
+ return out
@@ -16,6 +16,7 @@ import xml.etree.ElementTree as ET
16
16
  from io import BytesIO, StringIO
17
17
 
18
18
  from velocity.aws import dirty_pipeline, ssm_config
19
+ from velocity.aws import facets
19
20
  from velocity.aws.handlers.exceptions import AlertError
20
21
  from velocity.db.exceptions import DbDataIntegrityError
21
22
  from velocity.misc import export
@@ -409,7 +410,10 @@ class DataServiceMixin:
409
410
  def query_hook(self, tx, table, payload, context):
410
411
  self._call_rwx_hook("before_query", "common", tx, table, payload, context)
411
412
  self._call_rwx_hook("before_query", table, tx, table, payload, context)
412
- params = payload.get("params", {})
413
+ # Structured facet selections (params.facets) become a parameterized
414
+ # predicate AND'ed onto params.where — here, once, so the rows, the
415
+ # count and every later use of ``params`` see the same filter.
416
+ params = facets.apply_to_params(tx, table, payload.get("params", {}))
413
417
  result_format = payload.get("result_format")
414
418
 
415
419
  # Clear any previous swallowed-error details so we only report errors
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.116
3
+ Version: 0.1.117
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
@@ -10,6 +10,7 @@ src/velocity/aws/amplify_build.py
10
10
  src/velocity/aws/blobs.py
11
11
  src/velocity/aws/dirty_pipeline.py
12
12
  src/velocity/aws/email.py
13
+ src/velocity/aws/facets.py
13
14
  src/velocity/aws/feature_flags.py
14
15
  src/velocity/aws/job_lifecycle.py
15
16
  src/velocity/aws/job_results.py
@@ -233,6 +234,7 @@ tests/test_delete_hook_fk_blocked.py
233
234
  tests/test_dirty_pipeline_fast_path.py
234
235
  tests/test_email_processing.py
235
236
  tests/test_enqueue_send_failures.py
237
+ tests/test_facets.py
236
238
  tests/test_feature_flags.py
237
239
  tests/test_get_cognito_user_provider.py
238
240
  tests/test_http_handler_rollback.py