velocity-python 0.1.118__tar.gz → 0.1.121__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.118 → velocity_python-0.1.121}/PKG-INFO +1 -1
  2. {velocity_python-0.1.118 → velocity_python-0.1.121}/pyproject.toml +1 -1
  3. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/facets.py +71 -11
  5. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/PKG-INFO +1 -1
  6. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_facets.py +86 -0
  7. {velocity_python-0.1.118 → velocity_python-0.1.121}/LICENSE +0 -0
  8. {velocity_python-0.1.118 → velocity_python-0.1.121}/README.md +0 -0
  9. {velocity_python-0.1.118 → velocity_python-0.1.121}/setup.cfg +0 -0
  10. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/__init__.py +0 -0
  11. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/amplify.py +0 -0
  12. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/amplify_build.py +0 -0
  13. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/__init__.py +0 -0
  14. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/backfill.py +0 -0
  15. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/indexing.py +0 -0
  16. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/references.py +0 -0
  17. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/service.py +0 -0
  18. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/assets/usage_index.py +0 -0
  19. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/blobs.py +0 -0
  20. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/__init__.py +0 -0
  21. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/base.py +0 -0
  22. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/dispatch.py +0 -0
  23. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/infra.py +0 -0
  24. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/jobs.py +0 -0
  25. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/provisioning.py +0 -0
  26. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/retention.py +0 -0
  27. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/security.py +0 -0
  28. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/capabilities/single_session.py +0 -0
  29. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/__init__.py +0 -0
  30. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/config.py +0 -0
  31. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/operations.py +0 -0
  32. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/policies.py +0 -0
  33. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/reconcile.py +0 -0
  34. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/deploy/targets.py +0 -0
  35. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/dirty_pipeline.py +0 -0
  36. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/email.py +0 -0
  37. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/feature_flags.py +0 -0
  38. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/__init__.py +0 -0
  39. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/base_handler.py +0 -0
  40. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/context.py +0 -0
  41. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/context_factory.py +0 -0
  42. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/exceptions.py +0 -0
  43. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  44. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/masquerade.py +0 -0
  45. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  46. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/mixins/data_service.py +0 -0
  47. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  48. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  49. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  50. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/perf.py +0 -0
  51. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/response.py +0 -0
  52. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  53. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/job_lifecycle.py +0 -0
  54. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/job_results.py +0 -0
  55. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/mirror.py +0 -0
  56. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/ops.py +0 -0
  57. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/s3.py +0 -0
  58. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/sms.py +0 -0
  59. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/ssm_config.py +0 -0
  60. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/__init__.py +0 -0
  61. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_amplify_build_policy_size.py +0 -0
  62. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_amplify_build_shim.py +0 -0
  63. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  64. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  65. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  66. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_email.py +0 -0
  67. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  68. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_response.py +0 -0
  69. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/aws/tests/test_sms.py +0 -0
  70. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/cache.py +0 -0
  71. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/__init__.py +0 -0
  72. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/__init__.py +0 -0
  73. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/async_support.py +0 -0
  74. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/column.py +0 -0
  75. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/database.py +0 -0
  76. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/decorators.py +0 -0
  77. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/engine.py +0 -0
  78. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/jsonproxy.py +0 -0
  79. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/lazy_engine.py +0 -0
  80. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/result.py +0 -0
  81. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/row.py +0 -0
  82. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/sequence.py +0 -0
  83. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/table.py +0 -0
  84. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/transaction.py +0 -0
  85. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/core/view.py +0 -0
  86. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/exceptions.py +0 -0
  87. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/maintenance.py +0 -0
  88. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/migrations.py +0 -0
  89. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/__init__.py +0 -0
  90. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/base/__init__.py +0 -0
  91. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/base/initializer.py +0 -0
  92. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/base/operators.py +0 -0
  93. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/base/sql.py +0 -0
  94. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/base/types.py +0 -0
  95. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/mysql/__init__.py +0 -0
  96. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/mysql/operators.py +0 -0
  97. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/mysql/reserved.py +0 -0
  98. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/mysql/sql.py +0 -0
  99. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/mysql/types.py +0 -0
  100. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/postgres/__init__.py +0 -0
  101. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/postgres/operators.py +0 -0
  102. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/postgres/reserved.py +0 -0
  103. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/postgres/sql.py +0 -0
  104. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/postgres/types.py +0 -0
  105. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  106. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlite/operators.py +0 -0
  107. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  108. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlite/sql.py +0 -0
  109. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlite/types.py +0 -0
  110. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  111. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  112. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  113. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  114. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/sqlserver/types.py +0 -0
  115. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/servers/tablehelper.py +0 -0
  116. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/__init__.py +0 -0
  117. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/common_db_test.py +0 -0
  118. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/__init__.py +0 -0
  119. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/common.py +0 -0
  120. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/conftest.py +0 -0
  121. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_column.py +0 -0
  122. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  123. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_database.py +0 -0
  124. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  125. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  126. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  127. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_result.py +0 -0
  128. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_row.py +0 -0
  129. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  130. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  131. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  132. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  133. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  134. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_table.py +0 -0
  135. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  136. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  137. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/sql/__init__.py +0 -0
  138. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/sql/common.py +0 -0
  139. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  140. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  141. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  142. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_db_utils.py +0 -0
  143. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_postgres.py +0 -0
  144. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  145. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  146. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_result_caching.py +0 -0
  147. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  148. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  149. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  150. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  151. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_sql_builder.py +0 -0
  152. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_tablehelper.py +0 -0
  153. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/tests/test_view_helper.py +0 -0
  154. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/db/utils.py +0 -0
  155. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/logging.py +0 -0
  156. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/__init__.py +0 -0
  157. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/conv/__init__.py +0 -0
  158. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/conv/iconv.py +0 -0
  159. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/conv/oconv.py +0 -0
  160. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/db.py +0 -0
  161. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/export.py +0 -0
  162. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/format.py +0 -0
  163. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/mail.py +0 -0
  164. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/merge.py +0 -0
  165. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/pdf.py +0 -0
  166. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/__init__.py +0 -0
  167. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_db.py +0 -0
  168. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_fix.py +0 -0
  169. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_format.py +0 -0
  170. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_iconv.py +0 -0
  171. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_mail_send.py +0 -0
  172. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_merge.py +0 -0
  173. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_oconv.py +0 -0
  174. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_original_error.py +0 -0
  175. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tests/test_timer.py +0 -0
  176. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/timer.py +0 -0
  177. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/misc/tools.py +0 -0
  178. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/__init__.py +0 -0
  179. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/core/__init__.py +0 -0
  180. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/core/capabilities.py +0 -0
  181. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/core/store.py +0 -0
  182. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/core/where.py +0 -0
  183. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/exceptions.py +0 -0
  184. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/__init__.py +0 -0
  185. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/base/__init__.py +0 -0
  186. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  187. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  188. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  189. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  190. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  191. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  192. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  193. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  194. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  195. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  196. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  197. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  198. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/__init__.py +0 -0
  199. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/authorizenet_adapter.py +0 -0
  200. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/authorizenet_mirror.py +0 -0
  201. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/base_adapter.py +0 -0
  202. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/braintree_adapter.py +0 -0
  203. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/braintree_mirror.py +0 -0
  204. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/charge_rules.py +0 -0
  205. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/stripe_adapter.py +0 -0
  206. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity/payment/stripe_mirror.py +0 -0
  207. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/SOURCES.txt +0 -0
  208. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  209. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/entry_points.txt +0 -0
  210. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/requires.txt +0 -0
  211. {velocity_python-0.1.118 → velocity_python-0.1.121}/src/velocity_python.egg-info/top_level.txt +0 -0
  212. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_access_error_403.py +0 -0
  213. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_activity_tracking_aborted_tx.py +0 -0
  214. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_amplify_build.py +0 -0
  215. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_asset_indexing.py +0 -0
  216. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_asset_references.py +0 -0
  217. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_assets_service.py +0 -0
  218. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_async_support.py +0 -0
  219. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_batch_operations.py +0 -0
  220. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_blob_store.py +0 -0
  221. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_capabilities.py +0 -0
  222. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_capability_catalog.py +0 -0
  223. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_cognito_user_cache.py +0 -0
  224. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_column_tx_arg.py +0 -0
  225. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_concurrency_safety.py +0 -0
  226. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_connection_pool.py +0 -0
  227. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_connection_resilience.py +0 -0
  228. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_context_flags_loader.py +0 -0
  229. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_context_job_descriptions.py +0 -0
  230. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_data_service_query_cap.py +0 -0
  231. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_data_service_renumber.py +0 -0
  232. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_data_service_save_as.py +0 -0
  233. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_db_credentials_ssm_cascade.py +0 -0
  234. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_db_maintenance.py +0 -0
  235. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_decorators.py +0 -0
  236. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_delete_hook_fk_blocked.py +0 -0
  237. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_dirty_pipeline_fast_path.py +0 -0
  238. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_email_processing.py +0 -0
  239. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_enqueue_send_failures.py +0 -0
  240. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_feature_flags.py +0 -0
  241. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_get_cognito_user_provider.py +0 -0
  242. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_http_handler_rollback.py +0 -0
  243. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_iconv_money_to_cents.py +0 -0
  244. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_identifier_injection_guard.py +0 -0
  245. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_job_handler.py +0 -0
  246. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_job_lifecycle.py +0 -0
  247. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_job_results.py +0 -0
  248. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_json_columns.py +0 -0
  249. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_jsonb_dict_adapter.py +0 -0
  250. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_lambda_handler.py +0 -0
  251. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_lambda_handler_auth.py +0 -0
  252. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_lambda_handler_masquerade.py +0 -0
  253. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_lambda_handler_user_cache.py +0 -0
  254. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_lazy_engine.py +0 -0
  255. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_masquerade_grant.py +0 -0
  256. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_mirror.py +0 -0
  257. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_mixins_import.py +0 -0
  258. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_n_plus_one.py +0 -0
  259. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_dynamodb.py +0 -0
  260. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_memory.py +0 -0
  261. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_mongodb.py +0 -0
  262. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_redis.py +0 -0
  263. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_translation.py +0 -0
  264. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_nosql_where.py +0 -0
  265. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_observability.py +0 -0
  266. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_on_commit.py +0 -0
  267. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_ops.py +0 -0
  268. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_authorizenet_adapter.py +0 -0
  269. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_braintree_adapter.py +0 -0
  270. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_braintree_mirror.py +0 -0
  271. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_mirror_empty_table.py +0 -0
  272. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_profile_sorting.py +0 -0
  273. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_payment_stripe_adapter.py +0 -0
  274. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_pdf.py +0 -0
  275. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_prepared_statements.py +0 -0
  276. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_psycopg3_upgrade.py +0 -0
  277. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_query_cache.py +0 -0
  278. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_query_missing_table_unlocked.py +0 -0
  279. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_read_cache.py +0 -0
  280. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_response.py +0 -0
  281. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_restricted_direct_tables.py +0 -0
  282. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_result_returning.py +0 -0
  283. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_retry_side_effect_guard.py +0 -0
  284. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_return_default_safety.py +0 -0
  285. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_row_batch_update.py +0 -0
  286. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_row_cache_staleness.py +0 -0
  287. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_schema_migrations.py +0 -0
  288. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_security_capability.py +0 -0
  289. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_security_hardening.py +0 -0
  290. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_server_cursor.py +0 -0
  291. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_session_control.py +0 -0
  292. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_single_autocommit_safety.py +0 -0
  293. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_single_session_capability.py +0 -0
  294. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_spreadsheet_functions.py +0 -0
  295. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_sqlite_backend.py +0 -0
  296. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_sqs_per_record_transactions.py +0 -0
  297. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_ssm_config.py +0 -0
  298. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_store_user_data.py +0 -0
  299. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  300. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_table_alter.py +0 -0
  301. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_table_find_cache.py +0 -0
  302. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_table_purge.py +0 -0
  303. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_transaction_class_wrapping.py +0 -0
  304. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_transaction_commit_and_ownership.py +0 -0
  305. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_transaction_edge_cases.py +0 -0
  306. {velocity_python-0.1.118 → velocity_python-0.1.121}/tests/test_where_clause_validation.py +0 -0
  307. {velocity_python-0.1.118 → velocity_python-0.1.121}/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.118
3
+ Version: 0.1.121
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.118"
7
+ version = "0.1.121"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.1.118"
1
+ __version__ = version = "0.1.121"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -18,6 +18,8 @@ The wire shape is one list under ``params.facets``::
18
18
  {"field": "internal_date", "kind": "since", "value": "30d"},
19
19
  {"field": "from_domain", "kind": "text", "value": "example.com",
20
20
  "match": "contains"},
21
+ {"field": "from_domain", "kind": "domain", "values": ["example.com"],
22
+ "exclude": true},
21
23
  ]
22
24
 
23
25
  Facets AND together; the values of one facet OR together. Kinds:
@@ -33,10 +35,21 @@ Facets AND together; the values of one facet OR together. Kinds:
33
35
  3660 days), ``year`` (this calendar year). Evaluated in the
34
36
  database's session time zone.
35
37
  ``text`` a write-in, case-insensitive, against ``field::text``:
36
- ``match`` is ``contains`` (default), ``equals`` or ``starts``.
38
+ ``match`` is ``contains`` (default), ``equals``, ``starts`` or
39
+ ``words`` — every whitespace-separated word of the term must
40
+ appear, in any order ("school rock" finds "School of Rock").
37
41
  The user's ``%`` and ``_`` are literal. With ``fields`` (a list
38
42
  of columns) the term is matched against each, OR'd — one box
39
- that searches subject *or* snippet.
43
+ that searches subject *or* snippet; for ``words`` each word may
44
+ be in either column.
45
+ ``domain`` a domain and every subdomain of it: each value (``example.com``)
46
+ matches ``field`` equal to it or ending in ``.example.com``,
47
+ case-insensitively; values OR together. "This top-level
48
+ domain", from a cell that reads ``em1.mail.example.com``.
49
+
50
+ Any facet may carry ``"exclude": true``: its predicate is negated, NULL-safe
51
+ — ``NOT COALESCE((…), false)`` — so a row the predicate cannot judge (the
52
+ column is NULL) is kept, not hidden. "Hide everything from this domain."
40
53
 
41
54
  Anything malformed — an unknown kind, a field that is not a bare identifier,
42
55
  too many or too long values — raises :class:`FacetError` rather than being
@@ -49,7 +62,7 @@ no column types are needed here.
49
62
  from __future__ import annotations
50
63
 
51
64
  import re
52
- from typing import Any, Dict, List, Optional, Sequence, Tuple
65
+ from typing import Any, Dict, List, Optional, Tuple
53
66
 
54
67
  # The SQL emitted here is PostgreSQL's (jsonb ``@>``, ``make_interval``,
55
68
  # ``ILIKE``), and importing the postgres SQL module is what fills in
@@ -59,8 +72,8 @@ from velocity.db.servers.tablehelper import TableHelper
59
72
 
60
73
  PARAM_KEY = "facets"
61
74
 
62
- KINDS = ("values", "contains", "presence", "since", "text")
63
- MATCHES = ("contains", "equals", "starts")
75
+ KINDS = ("values", "contains", "presence", "since", "text", "domain")
76
+ MATCHES = ("contains", "equals", "starts", "words")
64
77
  PRESENCE = ("present", "absent")
65
78
 
66
79
  MAX_FACETS = 40
@@ -214,17 +227,54 @@ def _kind_text(column: str, entry: Dict[str, Any], field: str) -> Optional[Predi
214
227
  match = str(entry.get("match") or "contains").lower()
215
228
  if match not in MATCHES:
216
229
  raise FacetError(f"facet {field}: match must be one of {MATCHES}")
230
+ columns = _columns(entry, column, field)
231
+
232
+ def like_any(pattern: str) -> Predicate:
233
+ if len(columns) == 1:
234
+ return f"{columns[0]}::text ILIKE %s", (pattern,)
235
+ parts = " OR ".join(f"{name}::text ILIKE %s" for name in columns)
236
+ return f"({parts})", tuple(pattern for _ in columns)
237
+
238
+ if match == "words":
239
+ words: List[str] = []
240
+ for word in value.split():
241
+ if word not in words:
242
+ words.append(word)
243
+ built = []
244
+ for word in words:
245
+ escaped_word = _LIKE_SPECIAL.sub(r"\\\1", word)
246
+ built.append(like_any(f"%{escaped_word}%"))
247
+ if len(built) == 1:
248
+ return built[0]
249
+ sql = " AND ".join(part for part, _ in built)
250
+ return f"({sql})", tuple(p for _, params in built for p in params)
251
+
217
252
  escaped = _LIKE_SPECIAL.sub(r"\\\1", value)
218
253
  pattern = {
219
254
  "contains": f"%{escaped}%",
220
255
  "starts": f"{escaped}%",
221
256
  "equals": escaped,
222
257
  }[match]
223
- columns = _columns(entry, column, field)
224
- if len(columns) == 1:
225
- return f"{columns[0]}::text ILIKE %s", (pattern,)
226
- parts = " OR ".join(f"{name}::text ILIKE %s" for name in columns)
227
- return f"({parts})", tuple(pattern for _ in columns)
258
+ return like_any(pattern)
259
+
260
+
261
+ def _kind_domain(column: str, entry: Dict[str, Any], field: str) -> Optional[Predicate]:
262
+ parts: List[str] = []
263
+ params: List[str] = []
264
+ for value in _values(entry, field):
265
+ domain = value.strip().lower().strip(".")
266
+ if not domain:
267
+ continue
268
+ if "@" in domain or any(ch.isspace() for ch in domain):
269
+ raise FacetError(f"facet {field}: {value!r} is not a domain")
270
+ escaped = _LIKE_SPECIAL.sub(r"\\\1", domain)
271
+ parts.append(f"({column}::text ILIKE %s OR {column}::text ILIKE %s)")
272
+ params.extend((escaped, f"%.{escaped}"))
273
+ if not parts:
274
+ return None
275
+ if len(parts) == 1:
276
+ return parts[0], tuple(params)
277
+ return f"({' OR '.join(parts)})", tuple(params)
228
278
 
229
279
 
230
280
  _BUILDERS = {
@@ -233,6 +283,7 @@ _BUILDERS = {
233
283
  "presence": _kind_presence,
234
284
  "since": _kind_since,
235
285
  "text": _kind_text,
286
+ "domain": _kind_domain,
236
287
  }
237
288
 
238
289
 
@@ -244,8 +295,17 @@ def predicate(entry: Any) -> Optional[Predicate]:
244
295
  kind = str(entry.get("kind") or "values").lower()
245
296
  if kind not in _BUILDERS:
246
297
  raise FacetError(f"facet {field}: unknown kind {kind!r}")
298
+ exclude = entry.get("exclude", False)
299
+ if not isinstance(exclude, bool):
300
+ raise FacetError(f"facet {field}: 'exclude' must be true or false")
247
301
  column = TableHelper.quote(field)
248
- return _BUILDERS[kind](column, entry, field)
302
+ built = _BUILDERS[kind](column, entry, field)
303
+ if built is None or not exclude:
304
+ return built
305
+ sql, params = built
306
+ # NULL-safe negation: a NULL column makes the inner predicate NULL, and
307
+ # plain NOT would drop the row; COALESCE keeps it.
308
+ return f"NOT COALESCE(({sql}), false)", params
249
309
 
250
310
 
251
311
  def build_where(facets: Any) -> Optional[Predicate]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.1.118
3
+ Version: 0.1.121
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
@@ -81,6 +81,39 @@ def test_text_matches_are_case_insensitive_and_escape_like_wildcards():
81
81
  facets.predicate({"field": "x", "kind": "text", "value": "a", "match": "regex"})
82
82
 
83
83
 
84
+ def test_words_match_requires_every_word_in_any_order():
85
+ assert facets.predicate(
86
+ {"field": "subject", "kind": "text", "value": "school rock", "match": "words"}
87
+ ) == (
88
+ "(subject::text ILIKE %s AND subject::text ILIKE %s)",
89
+ ("%school%", "%rock%"),
90
+ )
91
+ # one word is the plain contains form; repeats and extra whitespace collapse
92
+ assert facets.predicate(
93
+ {"field": "subject", "kind": "text", "value": " rock rock ", "match": "words"}
94
+ ) == ("subject::text ILIKE %s", ("%rock%",))
95
+ # over several columns each word may sit in either one
96
+ sql, params = facets.predicate(
97
+ {
98
+ "field": "subject",
99
+ "kind": "text",
100
+ "value": "school rock",
101
+ "match": "words",
102
+ "fields": ["subject", "snippet"],
103
+ }
104
+ )
105
+ assert sql == (
106
+ "((subject::text ILIKE %s OR snippet::text ILIKE %s) AND "
107
+ "(subject::text ILIKE %s OR snippet::text ILIKE %s))"
108
+ )
109
+ assert params == ("%school%", "%school%", "%rock%", "%rock%")
110
+ # the user's wildcards stay literal per word
111
+ _sql, params = facets.predicate(
112
+ {"field": "s", "kind": "text", "value": "50%_off now", "match": "words"}
113
+ )
114
+ assert params == ("%50\\%\\_off%", "%now%")
115
+
116
+
84
117
  def test_text_over_several_fields_ors_one_term_across_them():
85
118
  sql, params = facets.predicate(
86
119
  {
@@ -114,6 +147,58 @@ def test_text_over_several_fields_ors_one_term_across_them():
114
147
  )
115
148
 
116
149
 
150
+ def test_domain_matches_the_domain_and_every_subdomain_case_insensitively():
151
+ assert facets.predicate(
152
+ {"field": "from_domain", "kind": "domain", "values": ["Example.com"]}
153
+ ) == (
154
+ "(from_domain::text ILIKE %s OR from_domain::text ILIKE %s)",
155
+ ("example.com", "%.example.com"),
156
+ )
157
+ sql, params = facets.predicate(
158
+ {"field": "from_domain", "kind": "domain", "values": ["a.com", ".b.org."]}
159
+ )
160
+ assert sql == (
161
+ "((from_domain::text ILIKE %s OR from_domain::text ILIKE %s) OR "
162
+ "(from_domain::text ILIKE %s OR from_domain::text ILIKE %s))"
163
+ )
164
+ assert params == ("a.com", "%.a.com", "b.org", "%.b.org")
165
+ # LIKE wildcards in a value are literal characters
166
+ _sql, params = facets.predicate(
167
+ {"field": "d", "kind": "domain", "values": ["100%_x.com"]}
168
+ )
169
+ assert params == ("100\\%\\_x.com", "%.100\\%\\_x.com")
170
+
171
+
172
+ def test_domain_refuses_an_address_or_whitespace_and_ignores_blanks():
173
+ for bad in ("user@example.com", "two words.com", "a\tb.com"):
174
+ with pytest.raises(facets.FacetError):
175
+ facets.predicate({"field": "d", "kind": "domain", "values": [bad]})
176
+ assert facets.predicate({"field": "d", "kind": "domain", "values": ["", ".", " "]}) is None
177
+
178
+
179
+ def test_exclude_negates_null_safely_for_any_kind():
180
+ assert facets.predicate(
181
+ {"field": "from_address", "values": ["test@example.com"], "exclude": True}
182
+ ) == ("NOT COALESCE((from_address = %s), false)", ("test@example.com",))
183
+ assert facets.predicate(
184
+ {"field": "from_domain", "kind": "domain", "values": ["y.com"], "exclude": True}
185
+ ) == (
186
+ "NOT COALESCE(((from_domain::text ILIKE %s OR from_domain::text ILIKE %s)), false)",
187
+ ("y.com", "%.y.com"),
188
+ )
189
+ assert facets.predicate(
190
+ {"field": "subject", "kind": "text", "value": "spam", "exclude": True}
191
+ ) == ("NOT COALESCE((subject::text ILIKE %s), false)", ("%spam%",))
192
+ # the flag is explicit: false and absent are the plain predicate
193
+ plain = facets.predicate({"field": "a", "values": ["1"]})
194
+ assert facets.predicate({"field": "a", "values": ["1"], "exclude": False}) == plain
195
+ # nothing selected excludes nothing
196
+ assert facets.predicate({"field": "a", "values": [], "exclude": True}) is None
197
+ for bad in ("true", 1, None):
198
+ with pytest.raises(facets.FacetError):
199
+ facets.predicate({"field": "a", "values": ["1"], "exclude": bad})
200
+
201
+
117
202
  def test_an_empty_selection_is_no_predicate_not_an_error():
118
203
  for entry in (
119
204
  {"field": "a", "values": []},
@@ -122,6 +207,7 @@ def test_an_empty_selection_is_no_predicate_not_an_error():
122
207
  {"field": "a", "kind": "presence", "value": ""},
123
208
  {"field": "a", "kind": "since", "value": None},
124
209
  {"field": "a", "kind": "text", "value": " "},
210
+ {"field": "a", "kind": "domain", "values": []},
125
211
  ):
126
212
  assert facets.predicate(entry) is None
127
213
  assert facets.build_where([{"field": "a", "values": []}]) is None