pyrsql 0.0.4__tar.gz → 0.0.5__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 (191) hide show
  1. {pyrsql-0.0.4 → pyrsql-0.0.5}/PKG-INFO +14 -11
  2. {pyrsql-0.0.4 → pyrsql-0.0.5}/README.md +8 -4
  3. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/explanation/architecture.md +1 -1
  4. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/index.md +1 -1
  5. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/quickstart.md +1 -1
  6. pyrsql-0.0.5/docs/reference/operators.md +51 -0
  7. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/reference/options.md +4 -0
  8. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/query.md +9 -5
  9. pyrsql-0.0.5/pyproject.toml +233 -0
  10. pyrsql-0.0.4/pyproject.toml → pyrsql-0.0.5/pyproject.toml.orig +21 -19
  11. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/json/options.py +2 -2
  12. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/orm.py +1 -1
  13. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/translator.py +2 -2
  14. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/type_inference.py +9 -8
  15. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/lexer.py +6 -6
  16. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_async_pipeline.py +16 -2
  17. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_query_pipeline.py +58 -16
  18. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/conftest.py +2 -0
  19. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_conversion.py +1 -1
  20. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +4 -0
  21. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +1 -1
  22. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_resolver.py +1 -1
  23. pyrsql-0.0.5/tests/unit/orms/sqlalchemy/test_type_inference.py +43 -0
  24. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/parsing/test_lexer.py +37 -0
  25. pyrsql-0.0.4/docs/reference/operators.md +0 -37
  26. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/contributing.md +0 -0
  27. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/explanation/extensibility.md +0 -0
  28. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/reference/api.md +0 -0
  29. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/testing.md +0 -0
  30. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/async.md +0 -0
  31. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/custom-predicates.md +0 -0
  32. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/errors.md +0 -0
  33. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/fastapi-sqlalchemy.md +0 -0
  34. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/fastapi.md +0 -0
  35. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/field-policies.md +0 -0
  36. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/join-hints.md +0 -0
  37. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/json.md +0 -0
  38. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/page.md +0 -0
  39. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/sort.md +0 -0
  40. {pyrsql-0.0.4 → pyrsql-0.0.5}/docs/usage/value-conversion.md +0 -0
  41. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/__init__.py +0 -0
  42. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/__init__.py +0 -0
  43. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/__init__.py +0 -0
  44. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/config.py +0 -0
  45. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/criteria.py +0 -0
  46. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/dependency.py +0 -0
  47. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/errors.py +0 -0
  48. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/api.py +0 -0
  49. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/__init__.py +0 -0
  50. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/binding_policy.py +0 -0
  51. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/compiler.py +0 -0
  52. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/conversion.py +0 -0
  53. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/custom.py +0 -0
  54. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/field_policy.py +0 -0
  55. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/joins.py +0 -0
  56. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/json/__init__.py +0 -0
  57. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/json/path.py +0 -0
  58. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/json/query.py +0 -0
  59. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/json/values.py +0 -0
  60. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/options.py +0 -0
  61. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/page.py +0 -0
  62. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/procedure_policy.py +0 -0
  63. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/query.py +0 -0
  64. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/sort.py +0 -0
  65. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/source_issues.py +0 -0
  66. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/core/validation.py +0 -0
  67. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/__init__.py +0 -0
  68. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
  69. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +0 -0
  70. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +0 -0
  71. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +0 -0
  72. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +0 -0
  73. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -0
  74. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +0 -0
  75. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/__init__.py +0 -0
  76. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/base.py +0 -0
  77. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/__init__.py +0 -0
  78. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/coercion.py +0 -0
  79. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_page.py +0 -0
  80. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_query.py +0 -0
  81. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +0 -0
  82. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
  83. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/errors.py +0 -0
  84. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/introspection.py +0 -0
  85. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/json_path.py +0 -0
  86. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/resolver.py +0 -0
  87. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/sorter.py +0 -0
  88. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/statement.py +0 -0
  89. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/types.py +0 -0
  90. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/__init__.py +0 -0
  91. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/ast.py +0 -0
  92. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/errors.py +0 -0
  93. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/limits.py +0 -0
  94. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/operators.py +0 -0
  95. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/parser.py +0 -0
  96. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/source.py +0 -0
  97. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/parsing/tokens.py +0 -0
  98. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/py.typed +0 -0
  99. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/selector/__init__.py +0 -0
  100. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/selector/ast.py +0 -0
  101. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/selector/parser.py +0 -0
  102. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/semantic/__init__.py +0 -0
  103. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/semantic/binder.py +0 -0
  104. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/semantic/diagnostics.py +0 -0
  105. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/semantic/errors.py +0 -0
  106. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/__init__.py +0 -0
  107. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/ast.py +0 -0
  108. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/binder.py +0 -0
  109. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/diagnostics.py +0 -0
  110. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/errors.py +0 -0
  111. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/limits.py +0 -0
  112. {pyrsql-0.0.4 → pyrsql-0.0.5}/src/pyrsql/sorting/parser.py +0 -0
  113. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/__init__.py +0 -0
  114. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/conftest.py +0 -0
  115. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/__init__.py +0 -0
  116. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/__init__.py +0 -0
  117. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/test_async_dependency.py +0 -0
  118. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/test_dependency.py +0 -0
  119. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
  120. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
  121. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/test_async_integration.py +0 -0
  122. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
  123. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/functional/test_public_api.py +0 -0
  124. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/__init__.py +0 -0
  125. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/__init__.py +0 -0
  126. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/conftest.py +0 -0
  127. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
  128. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_sort_pipeline.py +0 -0
  129. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/__init__.py +0 -0
  130. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/conftest.py +0 -0
  131. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_conversion_bench.py +0 -0
  132. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_json_bench.py +0 -0
  133. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_json_path_builder_bench.py +0 -0
  134. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_parser_bench.py +0 -0
  135. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_path_resolver_bench.py +0 -0
  136. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_selector_bench.py +0 -0
  137. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_semantic_bench.py +0 -0
  138. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
  139. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/__init__.py +0 -0
  140. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/conftest.py +0 -0
  141. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_configuration_hardening.py +0 -0
  142. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +0 -0
  143. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_hpp.py +0 -0
  144. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_injection.py +0 -0
  145. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_limits.py +0 -0
  146. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_policies.py +0 -0
  147. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_resource_security.py +0 -0
  148. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_sqlalchemy_injection.py +0 -0
  149. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/security/test_sqlalchemy_jsonpath_security.py +0 -0
  150. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/__init__.py +0 -0
  151. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/__init__.py +0 -0
  152. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
  153. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/test_config.py +0 -0
  154. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/test_criteria.py +0 -0
  155. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/test_errors.py +0 -0
  156. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/conftest.py +0 -0
  157. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/__init__.py +0 -0
  158. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/conftest.py +0 -0
  159. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/json/__init__.py +0 -0
  160. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/json/test_options.py +0 -0
  161. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/json/test_path.py +0 -0
  162. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/json/test_query.py +0 -0
  163. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/json/test_values.py +0 -0
  164. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_custom.py +0 -0
  165. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_options.py +0 -0
  166. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_page.py +0 -0
  167. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_query.py +0 -0
  168. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/core/test_sort.py +0 -0
  169. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/__init__.py +0 -0
  170. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/fastapi/__init__.py +0 -0
  171. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
  172. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +0 -0
  173. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +0 -0
  174. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/__init__.py +0 -0
  175. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
  176. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
  177. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
  178. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
  179. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_statement.py +0 -0
  180. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/parsing/__init__.py +0 -0
  181. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/parsing/test_ast.py +0 -0
  182. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/parsing/test_parser.py +0 -0
  183. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/parsing/test_source.py +0 -0
  184. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/selector/__init__.py +0 -0
  185. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/selector/test_parser.py +0 -0
  186. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/semantic/__init__.py +0 -0
  187. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/semantic/test_binder.py +0 -0
  188. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/sorting/__init__.py +0 -0
  189. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/sorting/test_ast.py +0 -0
  190. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/sorting/test_binder.py +0 -0
  191. {pyrsql-0.0.4 → pyrsql-0.0.5}/tests/unit/sorting/test_parser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrsql
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: Compiler-oriented RSQL query engine for safe, typed, and extensible Python ORM-backed APIs.
5
5
  Keywords: rsql,orm,filter,pagination,sort,sqlalchemy,fastapi,rest
6
6
  License-Expression: MIT
@@ -9,7 +9,6 @@ Classifier: Environment :: Web Environment
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: Intended Audience :: Information Technology
11
11
  Classifier: Operating System :: OS Independent
12
- Classifier: Programming Language :: Python :: 3.10
13
12
  Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
@@ -20,11 +19,11 @@ Classifier: Topic :: Database
20
19
  Classifier: Topic :: Software Development :: Compilers
21
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
21
  Classifier: Typing :: Typed
23
- Requires-Dist: msgspec>=0.18,<1.0
24
- Requires-Dist: typing-extensions>=4.10.0 ; python_full_version < '3.11'
25
- Requires-Dist: fastapi>=0.110,<1.0 ; extra == 'fastapi'
26
- Requires-Dist: sqlalchemy>=2.0,<3.0 ; extra == 'sqlalchemy'
27
- Requires-Python: >=3.10
22
+ Requires-Dist: msgspec>=0.22.0,<1.0
23
+ Requires-Dist: fastapi>=0.150.0,<1.0 ; extra == 'fastapi'
24
+ Requires-Dist: sqlalchemy[asyncio]>=2.1,<3.0 ; extra == 'sqlalchemy'
25
+ Requires-Dist: typing-extensions>=4.10.0 ; extra == 'sqlalchemy'
26
+ Requires-Python: >=3.11
28
27
  Project-URL: Homepage, https://github.com/wskr00/pyrsql
29
28
  Project-URL: Repository, https://github.com/wskr00/pyrsql
30
29
  Project-URL: Issues, https://github.com/wskr00/pyrsql/issues
@@ -38,7 +37,7 @@ Description-Content-Type: text/markdown
38
37
  A compiler-oriented RSQL query engine for safe, typed, and extensible
39
38
  filtering, sorting, and pagination in Python APIs.
40
39
 
41
- [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14%20%7C%203.14t-blue?logo=python)](https://pypi.org/project/pyrsql/)
40
+ [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14%20%7C%203.14t-blue?logo=python)](https://pypi.org/project/pyrsql/)
42
41
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
43
42
 
44
43
  pyrsql compiles RSQL query strings into ORM-specific statement objects
@@ -51,9 +50,9 @@ For FastAPI applications using SQLAlchemy, the recommended entry point is
51
50
  declarative resources; the lower-level query, adapter, and ORM APIs remain
52
51
  available when more control is needed.
53
52
 
54
- **Current backends:** SQLAlchemy 2.0
55
- **Current framework adapters:** FastAPI
56
- **Planned:** Django ORM, SQLModel, Flask
53
+ - **Current backends:** SQLAlchemy 2.1+
54
+ - **Current framework adapters:** FastAPI
55
+ - **Planned:** Django ORM, SQLModel, Flask
57
56
 
58
57
  ## Why pyrsql?
59
58
 
@@ -276,6 +275,10 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
276
275
  - Strong typing
277
276
  - ORM-neutral public API
278
277
 
278
+ ## Acknowledgements
279
+
280
+ `pyrsql` was strongly inspired by [Jirutka's `rsql-parser`](https://github.com/jirutka/rsql-parser) and [PerplexHub's `rsql-jpa-specification`](https://github.com/perplexhub/rsql-jpa-specification), especially their RSQL syntax and filtering behavior. I am grateful to their maintainers and contributors for their work.
281
+
279
282
  ## License
280
283
 
281
284
  [MIT](LICENSE).
@@ -3,7 +3,7 @@
3
3
  A compiler-oriented RSQL query engine for safe, typed, and extensible
4
4
  filtering, sorting, and pagination in Python APIs.
5
5
 
6
- [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14%20%7C%203.14t-blue?logo=python)](https://pypi.org/project/pyrsql/)
6
+ [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14%20%7C%203.14t-blue?logo=python)](https://pypi.org/project/pyrsql/)
7
7
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
8
 
9
9
  pyrsql compiles RSQL query strings into ORM-specific statement objects
@@ -16,9 +16,9 @@ For FastAPI applications using SQLAlchemy, the recommended entry point is
16
16
  declarative resources; the lower-level query, adapter, and ORM APIs remain
17
17
  available when more control is needed.
18
18
 
19
- **Current backends:** SQLAlchemy 2.0
20
- **Current framework adapters:** FastAPI
21
- **Planned:** Django ORM, SQLModel, Flask
19
+ - **Current backends:** SQLAlchemy 2.1+
20
+ - **Current framework adapters:** FastAPI
21
+ - **Planned:** Django ORM, SQLModel, Flask
22
22
 
23
23
  ## Why pyrsql?
24
24
 
@@ -241,6 +241,10 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
241
241
  - Strong typing
242
242
  - ORM-neutral public API
243
243
 
244
+ ## Acknowledgements
245
+
246
+ `pyrsql` was strongly inspired by [Jirutka's `rsql-parser`](https://github.com/jirutka/rsql-parser) and [PerplexHub's `rsql-jpa-specification`](https://github.com/perplexhub/rsql-jpa-specification), especially their RSQL syntax and filtering behavior. I am grateful to their maintainers and contributors for their work.
247
+
244
248
  ## License
245
249
 
246
250
  [MIT](LICENSE).
@@ -28,7 +28,7 @@ The project is designed around four major responsibilities:
28
28
  - backend lowering: turn validated query objects into ORM-specific statements
29
29
  - optional framework adapters and integrations: expose the core in HTTP stacks
30
30
 
31
- The current production backend is SQLAlchemy 2.0. The current framework
31
+ The current production backend is SQLAlchemy 2.1+. The current framework
32
32
  adapter is FastAPI.
33
33
 
34
34
  ## Current Pipeline
@@ -45,7 +45,7 @@ I/O.
45
45
  ## Key features
46
46
 
47
47
  - **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
48
- - **Pluggable backends** - SQLAlchemy 2.0 today, Django ORM / SQLModel planned
48
+ - **Pluggable backends** - SQLAlchemy 2.1+ today, Django ORM / SQLModel planned
49
49
  - **Pluggable adapters** - FastAPI today, Flask planned
50
50
  - **20+ built-in operators** - `==`, `=like=`, `=in=`, `=bt=`, `=nn=`, etc.
51
51
  - **Custom operators** - define your own RSQL operators with per-ORM lowering
@@ -55,7 +55,7 @@ from pyrsql import Query
55
55
  Query.parse("name==demo") # equal
56
56
  Query.parse("age=gt=18") # greater than
57
57
  Query.parse("id=in=(1,2,3)") # IN
58
- Query.parse("name=like=demo*") # LIKE
58
+ Query.parse("name=like=demo") # contains: LIKE '%demo%'
59
59
  Query.parse("score=bt=(10,20)") # BETWEEN
60
60
  Query.parse("name==demo;age=gt=18") # AND
61
61
  Query.parse("name==demo,age=gt=18") # OR
@@ -0,0 +1,51 @@
1
+ # Operators
2
+
3
+ Pyrsql supports the base RSQL comparison operators and the extensions used by
4
+ PerplexHub's JPA specification.
5
+
6
+ ## Comparison operators
7
+
8
+ | Operator | Aliases | Arity | Description |
9
+ |----------|---------|-------|-------------|
10
+ | `==` | | 1 | Equal |
11
+ | `!=` | | 1 | Not equal |
12
+ | `=gt=` | `>` | 1 | Greater than |
13
+ | `=ge=` | `>=` | 1 | Greater or equal |
14
+ | `=lt=` | `<` | 1 | Less than |
15
+ | `=le=` | `<=` | 1 | Less or equal |
16
+ | `=in=` | | 1..100 | IN (comma-separated args) |
17
+ | `=out=` | | 1..100 | NOT IN |
18
+ | `=like=` | `=ke=` | 1 | Contains match (`LIKE '%value%'`) |
19
+ | `=notlike=` | `=nk=` | 1 | Negated contains match |
20
+ | `=ilike=` | `=ik=` | 1 | Case-insensitive contains match |
21
+ | `=inotlike=` | `=ni=` | 1 | Negated case-insensitive contains match |
22
+ | `=ic=` | `=icase=` | 1 | Case-insensitive equality |
23
+ | `=bt=` | `=between=` | 2 | BETWEEN (exactly 2 args) |
24
+ | `=nb=` | `=notbetween=` | 2 | NOT BETWEEN (exactly 2 args) |
25
+ | `=na=` | `=isnull=`, `=null=` | 0..1 | IS NULL |
26
+ | `=nn=` | `=notnull=`, `=isnotnull=` | 0..1 | IS NOT NULL |
27
+
28
+ For `=like=`, `=notlike=`, `=ilike=`, and `=inotlike=`, pyrsql surrounds the
29
+ argument with `%` to match anywhere in the value. These operators use SQL
30
+ `LIKE` pattern rules: `%` matches any number of characters, `_` matches one,
31
+ and `*` is literal. The `like_escape_character` option controls SQL pattern
32
+ escaping.
33
+
34
+ For string-column `==` and `!=`, `*` is the wildcard and any `^` marker
35
+ requests case-insensitive matching. Quoting an argument does not disable these
36
+ markers. Use `=in=(*)` to match a literal asterisk.
37
+
38
+ ## Logical operators
39
+
40
+ | Operator | Aliases | Description |
41
+ |----------|---------|-------------|
42
+ | `;` | `and` | AND (higher precedence) |
43
+ | `,` | `or` | OR (lower precedence) |
44
+
45
+ ## Sort modifiers
46
+
47
+ | Modifier | Example | Description |
48
+ |----------|---------|-------------|
49
+ | `asc` | `name,asc` | Ascending (default) |
50
+ | `desc` | `name,desc` | Descending |
51
+ | `ic` | `name,desc,ic` | Ignore case |
@@ -26,6 +26,10 @@
26
26
 
27
27
  Notes:
28
28
 
29
+ - `strict_equality=True` makes string `==` and `!=` compare markers such as
30
+ `*` and `^` literally; quoting alone does not disable them
31
+ - `like_escape_character` applies to LIKE-based operators, including equality
32
+ comparisons that contain `*`
29
33
  - configuration containers are normalized into immutable views
30
34
  - procedure allow/block rules are compiled as regex policies and evaluated with
31
35
  `fullmatch`, not partial match
@@ -29,10 +29,10 @@ Query.parse("name=nn=") # IS NOT NULL
29
29
  ## Text matching
30
30
 
31
31
  ```python
32
- Query.parse("name=like=demo*") # LIKE
32
+ Query.parse("name=like=demo") # contains: LIKE '%demo%'
33
33
  Query.parse("name=notlike=demo") # NOT LIKE
34
- Query.parse("name=ic=demo*") # ILIKE (case-insensitive)
35
- Query.parse("name=ilike=DEMO") # ILIKE alias
34
+ Query.parse("name=ic=DEMO") # case-insensitive equality
35
+ Query.parse("name=ilike=DEMO") # case-insensitive contains
36
36
  ```
37
37
 
38
38
  ## Range
@@ -54,15 +54,19 @@ Query.parse("(name==demo;age=gt=18),status==active") # grouping
54
54
 
55
55
  ```python
56
56
  Query.parse("name=='de\\'mo'") # quoted string with escape
57
- Query.parse("name=='*not a wildcard*'") # literal asterisks inside quotes
57
+ Query.parse('name=="Kill Bill"') # double-quoted argument
58
+ Query.parse("name=='*demo*'") # quotes do not disable equality wildcards
58
59
  ```
59
60
 
60
61
  ## Wildcard matching in equality
61
62
 
62
- By default, `*` in equality expressions is treated as a `LIKE` wildcard:
63
+ By default, `*` in string equality expressions is treated as a `LIKE`
64
+ wildcard, and `^` makes the equality case-insensitive:
63
65
 
64
66
  ```python
65
67
  Query.parse("name==*demo*") # becomes LIKE '%demo%'
68
+ Query.parse("name==^*DEMO*") # case-insensitive LIKE '%DEMO%'
69
+ Query.parse("name=in=(*)") # exact match for a literal asterisk
66
70
  ```
67
71
 
68
72
  Disable with strict mode:
@@ -0,0 +1,233 @@
1
+ [project]
2
+ name = "pyrsql"
3
+ version = "0.0.5"
4
+ description = "Compiler-oriented RSQL query engine for safe, typed, and extensible Python ORM-backed APIs."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ keywords = [
9
+ "rsql",
10
+ "orm",
11
+ "filter",
12
+ "pagination",
13
+ "sort",
14
+ "sqlalchemy",
15
+ "fastapi",
16
+ "rest",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 4 - Beta",
20
+ "Environment :: Web Environment",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Information Technology",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ "Programming Language :: Python :: Free Threading",
29
+ "Framework :: FastAPI",
30
+ "Topic :: Database",
31
+ "Topic :: Software Development :: Compilers",
32
+ "Topic :: Software Development :: Libraries :: Python Modules",
33
+ "Typing :: Typed",
34
+ ]
35
+ dependencies = ["msgspec>=0.22.0,<1.0"]
36
+
37
+ [project.optional-dependencies]
38
+ sqlalchemy = [
39
+ "sqlalchemy[asyncio]>=2.1,<3.0",
40
+ "typing-extensions>=4.10.0",
41
+ ]
42
+ fastapi = ["fastapi>=0.150.0,<1.0"]
43
+
44
+ [project.urls]
45
+ Homepage = "https://github.com/wskr00/pyrsql"
46
+ Repository = "https://github.com/wskr00/pyrsql"
47
+ Issues = "https://github.com/wskr00/pyrsql/issues"
48
+ Documentation = "https://wskr00.github.io/pyrsql/"
49
+
50
+ [dependency-groups]
51
+ lint = ["ruff>=0.15.21"]
52
+ test = [
53
+ "pytest>=9.1.1",
54
+ "httpx>=0.28.1",
55
+ "pytest-cov>=7.1.0",
56
+ "aiosqlite>=0.21.0,<0.22.0",
57
+ "sqlalchemy[asyncio]>=2.1,<3.0",
58
+ "typing-extensions>=4.10.0",
59
+ ]
60
+ typing = ["mypy>=2.2.0"]
61
+ docs = [
62
+ "mkdocs-material>=9.7.6",
63
+ "mkdocstrings[python]>=1.0.5",
64
+ ]
65
+
66
+ [[dependency-groups.dev]]
67
+ include-group = "lint"
68
+
69
+ [[dependency-groups.dev]]
70
+ include-group = "test"
71
+
72
+ [[dependency-groups.dev]]
73
+ include-group = "typing"
74
+
75
+ [[dependency-groups.dev]]
76
+ include-group = "docs"
77
+
78
+ [build-system]
79
+ requires = ["uv_build>=0.13.1,<0.14.0"]
80
+ build-backend = "uv_build"
81
+
82
+ [tool.uv]
83
+ managed = true
84
+
85
+ [tool.uv.build-backend]
86
+ source-include = [
87
+ "docs/**",
88
+ "tests/**",
89
+ "src/pyrsql/py.typed",
90
+ ]
91
+
92
+ [tool.ruff]
93
+ line-length = 80
94
+ target-version = "py311"
95
+ src = [
96
+ "src",
97
+ "tests",
98
+ ]
99
+ fix = true
100
+
101
+ [tool.ruff.lint]
102
+ select = ["ALL"]
103
+ preview = true
104
+ fixable = ["ALL"]
105
+ unfixable = ["ERA"]
106
+ ignore = [
107
+ "CPY001",
108
+ "EM101",
109
+ "EM102",
110
+ "TRY003",
111
+ "COM812",
112
+ "ISC001",
113
+ "C901",
114
+ "PLR0913",
115
+ "PLR0911",
116
+ "PLR0912",
117
+ ]
118
+ future-annotations = true
119
+
120
+ [tool.ruff.lint.pydocstyle]
121
+ convention = "google"
122
+
123
+ [tool.ruff.lint.flake8-annotations]
124
+ mypy-init-return = true
125
+ suppress-dummy-args = true
126
+
127
+ [tool.ruff.lint.flake8-bugbear]
128
+ extend-immutable-calls = [
129
+ "fastapi.Depends",
130
+ "fastapi.Query",
131
+ "fastapi.Path",
132
+ "fastapi.Header",
133
+ "fastapi.Cookie",
134
+ "fastapi.Body",
135
+ "fastapi.Form",
136
+ "fastapi.File",
137
+ "fastapi.Security",
138
+ ]
139
+
140
+ [tool.ruff.lint.flake8-type-checking]
141
+ exempt-modules = [
142
+ "typing",
143
+ "typing_extensions",
144
+ ]
145
+ strict = false
146
+ runtime-evaluated-base-classes = ["sqlalchemy.orm.DeclarativeBase"]
147
+ runtime-evaluated-decorators = [
148
+ "fastapi.FastAPI.get",
149
+ "fastapi.FastAPI.post",
150
+ "fastapi.FastAPI.put",
151
+ "fastapi.FastAPI.patch",
152
+ "fastapi.FastAPI.delete",
153
+ "fastapi.FastAPI.options",
154
+ "fastapi.FastAPI.head",
155
+ "fastapi.FastAPI.trace",
156
+ "fastapi.FastAPI.api_route",
157
+ "fastapi.APIRouter.get",
158
+ "fastapi.APIRouter.post",
159
+ "fastapi.APIRouter.put",
160
+ "fastapi.APIRouter.patch",
161
+ "fastapi.APIRouter.delete",
162
+ "fastapi.APIRouter.options",
163
+ "fastapi.APIRouter.head",
164
+ "fastapi.APIRouter.trace",
165
+ "fastapi.APIRouter.api_route",
166
+ ]
167
+
168
+ [tool.ruff.lint.isort]
169
+ known-first-party = ["pyrsql"]
170
+ combine-as-imports = true
171
+ force-sort-within-sections = true
172
+
173
+ [tool.ruff.lint.flake8-pytest-style]
174
+ fixture-parentheses = false
175
+ mark-parentheses = false
176
+
177
+ [tool.ruff.lint.per-file-ignores]
178
+ "tests/**/*.py" = [
179
+ "ANN",
180
+ "ARG",
181
+ "B008",
182
+ "DOC201",
183
+ "DOC501",
184
+ "DOC502",
185
+ "D",
186
+ "E402",
187
+ "FBT",
188
+ "PLR2004",
189
+ "S101",
190
+ "SLF001",
191
+ ]
192
+
193
+ [tool.ruff.format]
194
+ quote-style = "double"
195
+ indent-style = "space"
196
+ line-ending = "auto"
197
+ docstring-code-format = true
198
+ docstring-code-line-length = 80
199
+
200
+ [tool.pytest.ini_options]
201
+ testpaths = ["tests"]
202
+ pythonpath = ["src"]
203
+ addopts = ["--import-mode=importlib"]
204
+ markers = [
205
+ "unit: isolated tests",
206
+ "integration: interaction between components",
207
+ "functional: user-visible behavior",
208
+ "security: adversarial safety and injection-resistance tests",
209
+ "performance: benchmark tests",
210
+ "sqlalchemy: SQLAlchemy tests",
211
+ "fastapi: FastAPI tests",
212
+ ]
213
+
214
+ [tool.mypy]
215
+ python_version = "3.11"
216
+ files = ["src"]
217
+ exclude = ["^tests/"]
218
+ strict = true
219
+ warn_unused_configs = true
220
+ show_error_codes = true
221
+ pretty = true
222
+
223
+ [tool.coverage.run]
224
+ source = ["pyrsql"]
225
+ branch = true
226
+
227
+ [tool.coverage.report]
228
+ show_missing = true
229
+ exclude_lines = [
230
+ "pragma: no cover",
231
+ "if TYPE_CHECKING:",
232
+ "raise NotImplementedError",
233
+ ]
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "pyrsql"
3
- version = "0.0.4"
3
+ version = "0.0.5"
4
4
  description = "Compiler-oriented RSQL query engine for safe, typed, and extensible Python ORM-backed APIs."
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
6
+ requires-python = ">=3.11"
7
7
  license = "MIT"
8
8
  keywords = ["rsql", "orm", "filter", "pagination", "sort", "sqlalchemy", "fastapi", "rest"]
9
9
  classifiers = [
@@ -12,7 +12,6 @@ classifiers = [
12
12
  "Intended Audience :: Developers",
13
13
  "Intended Audience :: Information Technology",
14
14
  "Operating System :: OS Independent",
15
- "Programming Language :: Python :: 3.10",
16
15
  "Programming Language :: Python :: 3.11",
17
16
  "Programming Language :: Python :: 3.12",
18
17
  "Programming Language :: Python :: 3.13",
@@ -25,13 +24,15 @@ classifiers = [
25
24
  "Typing :: Typed",
26
25
  ]
27
26
  dependencies = [
28
- "msgspec>=0.18,<1.0",
29
- "typing-extensions>=4.10.0; python_version < '3.11'",
27
+ "msgspec>=0.22.0,<1.0",
30
28
  ]
31
29
 
32
30
  [project.optional-dependencies]
33
- sqlalchemy = ["sqlalchemy>=2.0,<3.0"]
34
- fastapi = ["fastapi>=0.110,<1.0"]
31
+ sqlalchemy = [
32
+ "sqlalchemy[asyncio]>=2.1,<3.0",
33
+ "typing-extensions>=4.10.0",
34
+ ]
35
+ fastapi = ["fastapi>=0.150.0,<1.0"]
35
36
 
36
37
  [project.urls]
37
38
  Homepage = "https://github.com/wskr00/pyrsql"
@@ -47,23 +48,24 @@ dev = [
47
48
  { include-group = "docs" },
48
49
  ]
49
50
  lint = [
50
- "ruff>=0.11.0",
51
+ "ruff>=0.15.21",
51
52
  ]
52
53
  test = [
53
- "pytest>=8.2.0",
54
- "httpx>=0.27.0",
55
- "pytest-cov>=5.0.0",
56
- "aiosqlite>=0.20.0",
57
- "sqlalchemy[asyncio]>=2.0,<3.0",
54
+ "pytest>=9.1.1",
55
+ "httpx>=0.28.1",
56
+ "pytest-cov>=7.1.0",
57
+ "aiosqlite>=0.21.0,<0.22.0",
58
+ "sqlalchemy[asyncio]>=2.1,<3.0",
59
+ "typing-extensions>=4.10.0",
58
60
  ]
59
- typing = ["mypy>=2.0.0"]
61
+ typing = ["mypy>=2.2.0"]
60
62
  docs = [
61
- "mkdocs-material>=9.5",
62
- "mkdocstrings[python]>=0.27",
63
+ "mkdocs-material>=9.7.6",
64
+ "mkdocstrings[python]>=1.0.5",
63
65
  ]
64
66
 
65
67
  [build-system]
66
- requires = ["uv_build>=0.11.6,<0.12.0"]
68
+ requires = ["uv_build>=0.13.1,<0.14.0"]
67
69
  build-backend = "uv_build"
68
70
 
69
71
  [tool.uv]
@@ -71,7 +73,7 @@ managed = true
71
73
 
72
74
  [tool.ruff]
73
75
  line-length = 80
74
- target-version = "py310"
76
+ target-version = "py311"
75
77
  src = ["src", "tests"]
76
78
  fix = true
77
79
 
@@ -192,7 +194,7 @@ markers = [
192
194
  ]
193
195
 
194
196
  [tool.mypy]
195
- python_version = "3.10"
197
+ python_version = "3.11"
196
198
  files = ["src"]
197
199
  exclude = ["^tests/"]
198
200
  strict = true
@@ -3,7 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Mapping
6
- from enum import Enum
6
+ from enum import StrEnum
7
7
  import re
8
8
  from types import MappingProxyType
9
9
  from typing import TypeAlias
@@ -14,7 +14,7 @@ _SQL_IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
14
14
  SortFieldTypeMap: TypeAlias = Mapping[str, "JSONSortScalarType"]
15
15
 
16
16
 
17
- class JSONSortScalarType(str, Enum):
17
+ class JSONSortScalarType(StrEnum):
18
18
  """Supported scalar cast strategies for JSON sort expressions."""
19
19
 
20
20
  TEXT = "text"
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
23
23
 
24
24
 
25
25
  class SQLAlchemyORM(base.ORM):
26
- """ORM adapter for SQLAlchemy 2.0 integration."""
26
+ """ORM adapter for SQLAlchemy 2.1+ integration."""
27
27
 
28
28
  __slots__ = ("_sort_translator", "_translator")
29
29
 
@@ -490,8 +490,8 @@ class SQLAlchemyExpressionTranslator:
490
490
  if options.strict_equality:
491
491
  return expression == value
492
492
 
493
- ignore_case = value.startswith("^")
494
- normalized_value = value[1:] if ignore_case else value
493
+ ignore_case = "^" in value
494
+ normalized_value = value.replace("^", "") if ignore_case else value
495
495
  if "*" in normalized_value:
496
496
  pattern = normalized_value.replace("*", "%")
497
497
  return self._build_pattern_predicate(
@@ -22,18 +22,12 @@ def infer_sql_function_python_type(
22
22
  ) -> type[Any] | None:
23
23
  """Infers the Python type for one SQL function expression.
24
24
 
25
- This helper first asks SQLAlchemy for the concrete expression type when an
26
- expression is available, then falls back to cheap, explicit heuristics for
27
- a small set of well-known functions.
25
+ This helper prefers explicit return types for known functions and argument
26
+ types for ``coalesce``, then asks SQLAlchemy for the expression type.
28
27
 
29
28
  Returns:
30
29
  The inferred Python type, or ``None`` when unknown.
31
30
  """
32
- if function_expression is not None:
33
- try:
34
- return function_expression.type.python_type
35
- except (AttributeError, NotImplementedError):
36
- pass
37
31
  normalized_name = function_name.lower()
38
32
  known_return_type = _KNOWN_FUNCTION_RETURN_TYPES.get(normalized_name)
39
33
  if known_return_type is not None:
@@ -42,6 +36,13 @@ def infer_sql_function_python_type(
42
36
  for argument_type in argument_types:
43
37
  if argument_type is not None:
44
38
  return argument_type
39
+ if function_expression is not None:
40
+ try:
41
+ inferred_type = function_expression.type.python_type
42
+ except (AttributeError, NotImplementedError):
43
+ pass
44
+ else:
45
+ return None if inferred_type is object else inferred_type
45
46
  return None
46
47
 
47
48
 
@@ -84,13 +84,13 @@ class Lexer:
84
84
  return self._single_char_token(TokenKind.COMMA)
85
85
  case ";":
86
86
  return self._single_char_token(TokenKind.SEMICOLON)
87
- case "'":
88
- return self._consume_quoted_text()
87
+ case "'" | '"':
88
+ return self._consume_quoted_text(current_char)
89
89
  case _:
90
90
  return self._consume_unquoted_text()
91
91
 
92
- def _consume_quoted_text(self) -> Token:
93
- """Consumes a single-quoted text token.
92
+ def _consume_quoted_text(self, quote_character: str) -> Token:
93
+ """Consumes a single- or double-quoted text token.
94
94
 
95
95
  Returns:
96
96
  The consumed quoted token.
@@ -103,7 +103,7 @@ class Lexer:
103
103
  characters: list[str] = []
104
104
  while not self._is_at_end():
105
105
  current_char = self._advance()
106
- if current_char == "'":
106
+ if current_char == quote_character:
107
107
  lexeme = "".join(characters)
108
108
  self._enforce_argument_length(lexeme, start)
109
109
  return self._make_token(TokenKind.QUOTED_TEXT, lexeme, start)
@@ -132,7 +132,7 @@ class Lexer:
132
132
  current_char = self._peek()
133
133
  if current_char.isspace():
134
134
  break
135
- if current_char in "(),;":
135
+ if current_char in "(),;'\"!~<>":
136
136
  break
137
137
  if self._match_operator_spelling(current_char) is not None:
138
138
  break