pyrsql 0.0.2__tar.gz → 0.0.4__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 (226) hide show
  1. {pyrsql-0.0.2 → pyrsql-0.0.4}/PKG-INFO +74 -18
  2. {pyrsql-0.0.2 → pyrsql-0.0.4}/README.md +72 -16
  3. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/contributing.md +1 -1
  4. pyrsql-0.0.4/docs/explanation/architecture.md +244 -0
  5. pyrsql-0.0.4/docs/explanation/extensibility.md +82 -0
  6. pyrsql-0.0.4/docs/index.md +76 -0
  7. pyrsql-0.0.4/docs/quickstart.md +181 -0
  8. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/reference/api.md +1 -3
  9. pyrsql-0.0.4/docs/reference/options.md +107 -0
  10. pyrsql-0.0.4/docs/testing.md +184 -0
  11. pyrsql-0.0.4/docs/usage/async.md +138 -0
  12. pyrsql-0.0.4/docs/usage/errors.md +213 -0
  13. pyrsql-0.0.4/docs/usage/fastapi-sqlalchemy.md +166 -0
  14. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/fastapi.md +49 -4
  15. {pyrsql-0.0.2 → pyrsql-0.0.4}/pyproject.toml +8 -13
  16. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/__init__.py +2 -6
  17. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/__init__.py +5 -1
  18. pyrsql-0.0.4/src/pyrsql/adapters/fastapi/config.py +84 -0
  19. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/criteria.py +3 -22
  20. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/dependency.py +142 -67
  21. pyrsql-0.0.4/src/pyrsql/adapters/fastapi/errors.py +342 -0
  22. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/api.py +3 -3
  23. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/__init__.py +0 -8
  24. pyrsql-0.0.4/src/pyrsql/core/binding_policy.py +78 -0
  25. pyrsql-0.0.4/src/pyrsql/core/compiler.py +15 -0
  26. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/conversion.py +184 -92
  27. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/custom.py +10 -2
  28. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/field_policy.py +67 -21
  29. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/__init__.py +1 -6
  30. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/options.py +62 -25
  31. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/path.py +28 -21
  32. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/query.py +7 -16
  33. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/values.py +27 -6
  34. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/options.py +153 -227
  35. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/page.py +30 -37
  36. pyrsql-0.0.4/src/pyrsql/core/procedure_policy.py +173 -0
  37. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/query.py +20 -60
  38. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/sort.py +19 -61
  39. pyrsql-0.0.4/src/pyrsql/core/source_issues.py +64 -0
  40. pyrsql-0.0.4/src/pyrsql/core/validation.py +16 -0
  41. pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +17 -0
  42. pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +215 -0
  43. pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +150 -0
  44. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +101 -135
  45. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -7
  46. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +93 -130
  47. pyrsql-0.0.4/src/pyrsql/orms/__init__.py +11 -0
  48. pyrsql-0.0.4/src/pyrsql/orms/base.py +58 -0
  49. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/__init__.py +1 -1
  50. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/coercion.py +53 -13
  51. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_page.py +9 -8
  52. pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/compiled.py → pyrsql-0.0.4/src/pyrsql/orms/sqlalchemy/compiled_query.py +4 -4
  53. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +6 -6
  54. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/errors.py +13 -1
  55. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/introspection.py +74 -53
  56. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/json_path.py +55 -60
  57. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/orm.py +19 -21
  58. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/resolver.py +112 -57
  59. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/sorter.py +41 -39
  60. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/statement.py +35 -13
  61. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/translator.py +69 -64
  62. pyrsql-0.0.4/src/pyrsql/orms/sqlalchemy/type_inference.py +56 -0
  63. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/types.py +5 -8
  64. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/__init__.py +2 -8
  65. pyrsql-0.0.4/src/pyrsql/parsing/ast.py +73 -0
  66. pyrsql-0.0.4/src/pyrsql/parsing/errors.py +19 -0
  67. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/lexer.py +34 -39
  68. pyrsql-0.0.4/src/pyrsql/parsing/limits.py +19 -0
  69. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/operators.py +16 -65
  70. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/parser.py +26 -18
  71. pyrsql-0.0.4/src/pyrsql/parsing/source.py +29 -0
  72. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/selector/__init__.py +1 -6
  73. pyrsql-0.0.4/src/pyrsql/selector/ast.py +31 -0
  74. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/selector/parser.py +87 -27
  75. pyrsql-0.0.4/src/pyrsql/semantic/binder.py +143 -0
  76. pyrsql-0.0.4/src/pyrsql/semantic/diagnostics.py +9 -0
  77. pyrsql-0.0.4/src/pyrsql/semantic/errors.py +39 -0
  78. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/ast.py +4 -14
  79. pyrsql-0.0.4/src/pyrsql/sorting/binder.py +112 -0
  80. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/diagnostics.py +2 -0
  81. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/errors.py +4 -2
  82. pyrsql-0.0.4/src/pyrsql/sorting/limits.py +16 -0
  83. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/parser.py +35 -14
  84. pyrsql-0.0.4/tests/__init__.py +1 -0
  85. pyrsql-0.0.4/tests/functional/fastapi_adapter/test_async_dependency.py +114 -0
  86. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/test_dependency.py +33 -5
  87. pyrsql-0.0.4/tests/functional/fastapi_sqlalchemy/test_async_integration.py +234 -0
  88. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/test_public_api.py +3 -7
  89. pyrsql-0.0.4/tests/integration/sqlalchemy/test_async_pipeline.py +104 -0
  90. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_query_pipeline.py +49 -0
  91. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_sort_pipeline.py +27 -0
  92. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_path_resolver_bench.py +0 -1
  93. pyrsql-0.0.4/tests/security/__init__.py +1 -0
  94. pyrsql-0.0.4/tests/security/conftest.py +109 -0
  95. pyrsql-0.0.4/tests/security/test_configuration_hardening.py +30 -0
  96. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +101 -0
  97. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_hpp.py +106 -0
  98. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_injection.py +49 -0
  99. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_limits.py +215 -0
  100. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_policies.py +183 -0
  101. pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_resource_security.py +54 -0
  102. pyrsql-0.0.4/tests/security/test_sqlalchemy_injection.py +77 -0
  103. pyrsql-0.0.4/tests/security/test_sqlalchemy_jsonpath_security.py +70 -0
  104. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/conftest.py +11 -16
  105. pyrsql-0.0.4/tests/unit/adapters/fastapi_adapter/test_config.py +39 -0
  106. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/test_criteria.py +5 -35
  107. pyrsql-0.0.4/tests/unit/adapters/fastapi_adapter/test_errors.py +30 -0
  108. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/conftest.py +0 -5
  109. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_options.py +14 -0
  110. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_path.py +7 -18
  111. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_query.py +13 -22
  112. pyrsql-0.0.4/tests/unit/core/json/test_values.py +72 -0
  113. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_conversion.py +82 -0
  114. pyrsql-0.0.4/tests/unit/core/test_custom.py +22 -0
  115. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_options.py +81 -2
  116. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_page.py +3 -5
  117. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_query.py +3 -3
  118. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_sort.py +3 -3
  119. pyrsql-0.0.4/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +97 -0
  120. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +70 -119
  121. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_resolver.py +115 -8
  122. pyrsql-0.0.4/tests/unit/orms/sqlalchemy/test_statement.py +61 -0
  123. pyrsql-0.0.4/tests/unit/parsing/test_ast.py +66 -0
  124. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/test_parser.py +27 -26
  125. pyrsql-0.0.4/tests/unit/parsing/test_source.py +22 -0
  126. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/selector/test_parser.py +28 -7
  127. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/semantic/test_binder.py +39 -18
  128. pyrsql-0.0.4/tests/unit/sorting/test_ast.py +26 -0
  129. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/sorting/test_binder.py +32 -9
  130. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/sorting/test_parser.py +18 -6
  131. pyrsql-0.0.2/docs/explanation/architecture.md +0 -352
  132. pyrsql-0.0.2/docs/explanation/extensibility.md +0 -72
  133. pyrsql-0.0.2/docs/index.md +0 -36
  134. pyrsql-0.0.2/docs/quickstart.md +0 -71
  135. pyrsql-0.0.2/docs/reference/options.md +0 -66
  136. pyrsql-0.0.2/docs/testing.md +0 -152
  137. pyrsql-0.0.2/docs/usage/fastapi-sqlalchemy.md +0 -85
  138. pyrsql-0.0.2/src/pyrsql/adapters/fastapi/config.py +0 -130
  139. pyrsql-0.0.2/src/pyrsql/adapters/fastapi/errors.py +0 -218
  140. pyrsql-0.0.2/src/pyrsql/core/compiler.py +0 -127
  141. pyrsql-0.0.2/src/pyrsql/core/procedure_policy.py +0 -89
  142. pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +0 -11
  143. pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +0 -114
  144. pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +0 -83
  145. pyrsql-0.0.2/src/pyrsql/ir/__init__.py +0 -28
  146. pyrsql-0.0.2/src/pyrsql/ir/page.py +0 -40
  147. pyrsql-0.0.2/src/pyrsql/ir/query.py +0 -181
  148. pyrsql-0.0.2/src/pyrsql/ir/sort.py +0 -50
  149. pyrsql-0.0.2/src/pyrsql/orms/__init__.py +0 -15
  150. pyrsql-0.0.2/src/pyrsql/orms/base.py +0 -108
  151. pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/type_inference.py +0 -35
  152. pyrsql-0.0.2/src/pyrsql/parsing/ast.py +0 -123
  153. pyrsql-0.0.2/src/pyrsql/parsing/diagnostics.py +0 -29
  154. pyrsql-0.0.2/src/pyrsql/parsing/errors.py +0 -47
  155. pyrsql-0.0.2/src/pyrsql/parsing/limits.py +0 -36
  156. pyrsql-0.0.2/src/pyrsql/parsing/source.py +0 -83
  157. pyrsql-0.0.2/src/pyrsql/selector/ast.py +0 -86
  158. pyrsql-0.0.2/src/pyrsql/semantic/binder.py +0 -237
  159. pyrsql-0.0.2/src/pyrsql/semantic/diagnostics.py +0 -29
  160. pyrsql-0.0.2/src/pyrsql/semantic/errors.py +0 -69
  161. pyrsql-0.0.2/src/pyrsql/sorting/binder.py +0 -184
  162. pyrsql-0.0.2/src/pyrsql/sorting/limits.py +0 -27
  163. pyrsql-0.0.2/tests/unit/adapters/fastapi_adapter/test_config.py +0 -109
  164. pyrsql-0.0.2/tests/unit/core/json/test_values.py +0 -31
  165. pyrsql-0.0.2/tests/unit/ir/__init__.py +0 -1
  166. pyrsql-0.0.2/tests/unit/ir/test_page.py +0 -32
  167. pyrsql-0.0.2/tests/unit/ir/test_query.py +0 -124
  168. pyrsql-0.0.2/tests/unit/ir/test_sort.py +0 -72
  169. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/reference/operators.md +0 -0
  170. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/custom-predicates.md +0 -0
  171. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/field-policies.md +0 -0
  172. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/join-hints.md +0 -0
  173. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/json.md +0 -0
  174. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/page.md +0 -0
  175. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/query.md +0 -0
  176. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/sort.md +0 -0
  177. {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/value-conversion.md +0 -0
  178. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/__init__.py +0 -0
  179. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/joins.py +0 -0
  180. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/__init__.py +0 -0
  181. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
  182. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
  183. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/tokens.py +0 -0
  184. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/py.typed +0 -0
  185. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/semantic/__init__.py +0 -0
  186. {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/__init__.py +0 -0
  187. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/conftest.py +0 -0
  188. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/__init__.py +0 -0
  189. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/__init__.py +0 -0
  190. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
  191. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
  192. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
  193. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/__init__.py +0 -0
  194. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/__init__.py +0 -0
  195. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/conftest.py +0 -0
  196. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
  197. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/__init__.py +0 -0
  198. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/conftest.py +0 -0
  199. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_conversion_bench.py +0 -0
  200. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_json_bench.py +0 -0
  201. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_json_path_builder_bench.py +0 -0
  202. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_parser_bench.py +0 -0
  203. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_selector_bench.py +0 -0
  204. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_semantic_bench.py +0 -0
  205. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
  206. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/__init__.py +0 -0
  207. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/__init__.py +0 -0
  208. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
  209. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/conftest.py +0 -0
  210. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/__init__.py +0 -0
  211. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/__init__.py +0 -0
  212. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/__init__.py +0 -0
  213. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi/__init__.py +0 -0
  214. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
  215. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +0 -0
  216. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/__init__.py +0 -0
  217. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
  218. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
  219. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
  220. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +0 -0
  221. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
  222. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/__init__.py +0 -0
  223. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/test_lexer.py +0 -0
  224. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/selector/__init__.py +0 -0
  225. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/semantic/__init__.py +0 -0
  226. {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/sorting/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrsql
3
- Version: 0.0.2
3
+ Version: 0.0.4
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
@@ -14,12 +14,12 @@ Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: Free Threading
17
18
  Classifier: Framework :: FastAPI
18
19
  Classifier: Topic :: Database
19
20
  Classifier: Topic :: Software Development :: Compilers
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Classifier: Typing :: Typed
22
- Requires-Dist: ciso8601>=2.3,<3.0
23
23
  Requires-Dist: msgspec>=0.18,<1.0
24
24
  Requires-Dist: typing-extensions>=4.10.0 ; python_full_version < '3.11'
25
25
  Requires-Dist: fastapi>=0.110,<1.0 ; extra == 'fastapi'
@@ -38,13 +38,18 @@ Description-Content-Type: text/markdown
38
38
  A compiler-oriented RSQL query engine for safe, typed, and extensible
39
39
  filtering, sorting, and pagination in Python APIs.
40
40
 
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-blue?logo=python)](https://pypi.org/project/pyrsql/)
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/)
42
42
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
43
43
 
44
44
  pyrsql compiles RSQL query strings into ORM-specific statement objects
45
- through a language frontend, semantic binder, logical IR, and pluggable
46
- backend lowering - making it easy to expose complex query capabilities in
47
- your API without coupling to a specific ORM or framework.
45
+ through a language frontend, semantic binding, and pluggable backend
46
+ lowering - making it easy to expose complex query capabilities in your API
47
+ without coupling to a specific ORM or framework.
48
+
49
+ For FastAPI applications using SQLAlchemy, the recommended entry point is
50
+ `FastAPISQLAlchemyIntegration`. It provides route-ready dependencies and
51
+ declarative resources; the lower-level query, adapter, and ORM APIs remain
52
+ available when more control is needed.
48
53
 
49
54
  **Current backends:** SQLAlchemy 2.0
50
55
  **Current framework adapters:** FastAPI
@@ -53,9 +58,10 @@ your API without coupling to a specific ORM or framework.
53
58
  ## Why pyrsql?
54
59
 
55
60
  Most API filtering libraries are tightly coupled to one ORM or one framework.
56
- pyrsql is built as a **compiler pipeline** - parsing, semantic analysis, and
57
- IR lowering are separate stages. Adding a new ORM backend means implementing
58
- one interface (`ORM`), not rewriting the parser or query language.
61
+ pyrsql is built as a **compiler pipeline**. Parsing, semantic analysis, and
62
+ backend lowering are separate stages. Adding a new ORM backend means
63
+ implementing one interface (`ORM`), not rewriting the parser or query
64
+ language.
59
65
 
60
66
  - **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
61
67
  - **Pluggable backends** - implement `compile_query` / `compile_sort` /
@@ -64,13 +70,49 @@ one interface (`ORM`), not rewriting the parser or query language.
64
70
  - **Custom operators** - define your own RSQL operators with per-ORM lowering
65
71
  - **Field policies** - whitelist, blacklist, aliases at global and per-model level
66
72
  - **Type-safe** - strict mypy, Google-style docstrings, immutable value objects
73
+ - **Performance-oriented internals** - immutable `msgspec` models across the
74
+ core pipeline and configuration objects
75
+ - **Security-oriented request handling** - parser/sort limits, structural
76
+ allowlists/blocklists, sanitized FastAPI error payloads
67
77
 
68
78
  ## Quickstart
69
79
 
70
80
  ```bash
71
- pip install pyrsql[sqlalchemy]
81
+ pip install pyrsql[fastapi,sqlalchemy]
72
82
  ```
73
83
 
84
+ ```python
85
+ from typing import Annotated, Any
86
+
87
+ from fastapi import Depends, FastAPI
88
+
89
+ from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
90
+
91
+ app = FastAPI()
92
+ integration = FastAPISQLAlchemyIntegration()
93
+ users = integration.resource(
94
+ User,
95
+ filterable_fields={"id", "name"},
96
+ sortable_fields={"name"},
97
+ default_sort="name,asc",
98
+ max_page_size=100,
99
+ )
100
+
101
+ @app.get("/users")
102
+ def list_users(
103
+ stmt: Annotated[Any, Depends(users.select_dependency())],
104
+ ):
105
+ return {"sql": str(stmt)}
106
+ ```
107
+
108
+ Clients can now use `filter`, `sort`, `page`, and `size` query parameters.
109
+ See [FastAPI + SQLAlchemy](https://wskr00.github.io/pyrsql/usage/fastapi-sqlalchemy/)
110
+ for count, pagination, custom base statements, error behavior, and async use.
111
+
112
+ ### Lower-level SQLAlchemy API
113
+
114
+ Use the direct API when you do not need a FastAPI route dependency:
115
+
74
116
  ```python
75
117
  import pyrsql
76
118
  from sqlalchemy import select
@@ -79,13 +121,13 @@ from pyrsql.orms.sqlalchemy import SQLAlchemyORM
79
121
  orm = SQLAlchemyORM()
80
122
  stmt = select(User)
81
123
 
82
- # Filter: name equals "demo", company name contains "acme"
83
- stmt = pyrsql.parse("name==demo;company.name==acme*").apply(stmt, User, orm=orm)
124
+ stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
125
+ stmt,
126
+ User,
127
+ orm=orm,
128
+ )
84
129
 
85
- # Sort: by name ascending, then company name descending
86
130
  stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
87
-
88
- # Paginate: page 0, 25 items per page
89
131
  stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
90
132
  ```
91
133
 
@@ -163,6 +205,9 @@ options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
163
205
 
164
206
  ### FastAPI Integration
165
207
 
208
+ Use this adapter directly when you need only parsed `RequestCriteria`; for
209
+ SQLAlchemy routes, prefer the integration below.
210
+
166
211
  ```python
167
212
  from fastapi import Depends, FastAPI
168
213
  from pyrsql.adapters.fastapi import criteria_dependency
@@ -176,10 +221,11 @@ def list_items(criteria = Depends(dependency)):
176
221
  ```
177
222
 
178
223
  - Auto-extracts `filter`, `sort`, `page`, `size` from query params
179
- - `HTTP 422` with structured diagnostics on parse/semantic errors
224
+ - Structured `HTTP 400` parse errors and `HTTP 422` semantic errors
180
225
  - OpenAPI examples from configuration
181
226
  - One-based paging support
182
227
  - Custom query parameter names
228
+ - Optional repeated sort parameters through `SortParameterFormat.REPEATED`
183
229
 
184
230
  ### FastAPI + SQLAlchemy Integration
185
231
 
@@ -194,8 +240,17 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
194
240
  ```
195
241
 
196
242
  - `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
197
- - Declarative `resource()` with auto-generated OpenAPI examples
243
+ - Declarative `resource()` with auto-generated OpenAPI examples and field policies
198
244
  - `applier_dependency` for custom base statements
245
+ - Compatible with both sync `Session` and async `AsyncSession` execution
246
+ - FastAPI parse and page-validation failures become structured `HTTP 400` payloads
247
+ - FastAPI semantic and backend integration failures become structured `HTTP 422` payloads
248
+
249
+ ### Concurrency and Validation
250
+
251
+ - Shared base-select and ORM metadata caches are protected for free-threaded execution
252
+ - Async support is validated for adapter, ORM, and integration flows
253
+ - Dedicated async, free-threaded, and security test suites validate these flows
199
254
 
200
255
  ## Documentation
201
256
 
@@ -204,12 +259,13 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
204
259
  | Section | Description |
205
260
  |---------|-------------|
206
261
  | [Quickstart](https://wskr00.github.io/pyrsql/quickstart/) | One-minute primer |
207
- | [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, custom predicates |
262
+ | [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, async flows, custom predicates |
208
263
  | [API Reference](https://wskr00.github.io/pyrsql/reference/api/) | Auto-generated from docstrings |
209
264
  | [Operators](https://wskr00.github.io/pyrsql/reference/operators/) | Complete operator table |
210
265
  | [Options](https://wskr00.github.io/pyrsql/reference/options/) | QueryOptions, SortOptions, JSONOptions |
211
266
  | [Architecture](https://wskr00.github.io/pyrsql/explanation/architecture/) | Pipeline, modules, design |
212
267
  | [Extensibility](https://wskr00.github.io/pyrsql/explanation/extensibility/) | Adding backends and adapters |
268
+ | [Testing](https://wskr00.github.io/pyrsql/testing/) | Test layers, async, security, free-threaded validation |
213
269
  | [Contributing](https://wskr00.github.io/pyrsql/contributing/) | Setup, workflow, standards |
214
270
 
215
271
  ## Development Principles
@@ -3,13 +3,18 @@
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-blue?logo=python)](https://pypi.org/project/pyrsql/)
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/)
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
10
- through a language frontend, semantic binder, logical IR, and pluggable
11
- backend lowering - making it easy to expose complex query capabilities in
12
- your API without coupling to a specific ORM or framework.
10
+ through a language frontend, semantic binding, and pluggable backend
11
+ lowering - making it easy to expose complex query capabilities in your API
12
+ without coupling to a specific ORM or framework.
13
+
14
+ For FastAPI applications using SQLAlchemy, the recommended entry point is
15
+ `FastAPISQLAlchemyIntegration`. It provides route-ready dependencies and
16
+ declarative resources; the lower-level query, adapter, and ORM APIs remain
17
+ available when more control is needed.
13
18
 
14
19
  **Current backends:** SQLAlchemy 2.0
15
20
  **Current framework adapters:** FastAPI
@@ -18,9 +23,10 @@ your API without coupling to a specific ORM or framework.
18
23
  ## Why pyrsql?
19
24
 
20
25
  Most API filtering libraries are tightly coupled to one ORM or one framework.
21
- pyrsql is built as a **compiler pipeline** - parsing, semantic analysis, and
22
- IR lowering are separate stages. Adding a new ORM backend means implementing
23
- one interface (`ORM`), not rewriting the parser or query language.
26
+ pyrsql is built as a **compiler pipeline**. Parsing, semantic analysis, and
27
+ backend lowering are separate stages. Adding a new ORM backend means
28
+ implementing one interface (`ORM`), not rewriting the parser or query
29
+ language.
24
30
 
25
31
  - **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
26
32
  - **Pluggable backends** - implement `compile_query` / `compile_sort` /
@@ -29,13 +35,49 @@ one interface (`ORM`), not rewriting the parser or query language.
29
35
  - **Custom operators** - define your own RSQL operators with per-ORM lowering
30
36
  - **Field policies** - whitelist, blacklist, aliases at global and per-model level
31
37
  - **Type-safe** - strict mypy, Google-style docstrings, immutable value objects
38
+ - **Performance-oriented internals** - immutable `msgspec` models across the
39
+ core pipeline and configuration objects
40
+ - **Security-oriented request handling** - parser/sort limits, structural
41
+ allowlists/blocklists, sanitized FastAPI error payloads
32
42
 
33
43
  ## Quickstart
34
44
 
35
45
  ```bash
36
- pip install pyrsql[sqlalchemy]
46
+ pip install pyrsql[fastapi,sqlalchemy]
37
47
  ```
38
48
 
49
+ ```python
50
+ from typing import Annotated, Any
51
+
52
+ from fastapi import Depends, FastAPI
53
+
54
+ from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
55
+
56
+ app = FastAPI()
57
+ integration = FastAPISQLAlchemyIntegration()
58
+ users = integration.resource(
59
+ User,
60
+ filterable_fields={"id", "name"},
61
+ sortable_fields={"name"},
62
+ default_sort="name,asc",
63
+ max_page_size=100,
64
+ )
65
+
66
+ @app.get("/users")
67
+ def list_users(
68
+ stmt: Annotated[Any, Depends(users.select_dependency())],
69
+ ):
70
+ return {"sql": str(stmt)}
71
+ ```
72
+
73
+ Clients can now use `filter`, `sort`, `page`, and `size` query parameters.
74
+ See [FastAPI + SQLAlchemy](https://wskr00.github.io/pyrsql/usage/fastapi-sqlalchemy/)
75
+ for count, pagination, custom base statements, error behavior, and async use.
76
+
77
+ ### Lower-level SQLAlchemy API
78
+
79
+ Use the direct API when you do not need a FastAPI route dependency:
80
+
39
81
  ```python
40
82
  import pyrsql
41
83
  from sqlalchemy import select
@@ -44,13 +86,13 @@ from pyrsql.orms.sqlalchemy import SQLAlchemyORM
44
86
  orm = SQLAlchemyORM()
45
87
  stmt = select(User)
46
88
 
47
- # Filter: name equals "demo", company name contains "acme"
48
- stmt = pyrsql.parse("name==demo;company.name==acme*").apply(stmt, User, orm=orm)
89
+ stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
90
+ stmt,
91
+ User,
92
+ orm=orm,
93
+ )
49
94
 
50
- # Sort: by name ascending, then company name descending
51
95
  stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
52
-
53
- # Paginate: page 0, 25 items per page
54
96
  stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
55
97
  ```
56
98
 
@@ -128,6 +170,9 @@ options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
128
170
 
129
171
  ### FastAPI Integration
130
172
 
173
+ Use this adapter directly when you need only parsed `RequestCriteria`; for
174
+ SQLAlchemy routes, prefer the integration below.
175
+
131
176
  ```python
132
177
  from fastapi import Depends, FastAPI
133
178
  from pyrsql.adapters.fastapi import criteria_dependency
@@ -141,10 +186,11 @@ def list_items(criteria = Depends(dependency)):
141
186
  ```
142
187
 
143
188
  - Auto-extracts `filter`, `sort`, `page`, `size` from query params
144
- - `HTTP 422` with structured diagnostics on parse/semantic errors
189
+ - Structured `HTTP 400` parse errors and `HTTP 422` semantic errors
145
190
  - OpenAPI examples from configuration
146
191
  - One-based paging support
147
192
  - Custom query parameter names
193
+ - Optional repeated sort parameters through `SortParameterFormat.REPEATED`
148
194
 
149
195
  ### FastAPI + SQLAlchemy Integration
150
196
 
@@ -159,8 +205,17 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
159
205
  ```
160
206
 
161
207
  - `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
162
- - Declarative `resource()` with auto-generated OpenAPI examples
208
+ - Declarative `resource()` with auto-generated OpenAPI examples and field policies
163
209
  - `applier_dependency` for custom base statements
210
+ - Compatible with both sync `Session` and async `AsyncSession` execution
211
+ - FastAPI parse and page-validation failures become structured `HTTP 400` payloads
212
+ - FastAPI semantic and backend integration failures become structured `HTTP 422` payloads
213
+
214
+ ### Concurrency and Validation
215
+
216
+ - Shared base-select and ORM metadata caches are protected for free-threaded execution
217
+ - Async support is validated for adapter, ORM, and integration flows
218
+ - Dedicated async, free-threaded, and security test suites validate these flows
164
219
 
165
220
  ## Documentation
166
221
 
@@ -169,12 +224,13 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
169
224
  | Section | Description |
170
225
  |---------|-------------|
171
226
  | [Quickstart](https://wskr00.github.io/pyrsql/quickstart/) | One-minute primer |
172
- | [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, custom predicates |
227
+ | [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, async flows, custom predicates |
173
228
  | [API Reference](https://wskr00.github.io/pyrsql/reference/api/) | Auto-generated from docstrings |
174
229
  | [Operators](https://wskr00.github.io/pyrsql/reference/operators/) | Complete operator table |
175
230
  | [Options](https://wskr00.github.io/pyrsql/reference/options/) | QueryOptions, SortOptions, JSONOptions |
176
231
  | [Architecture](https://wskr00.github.io/pyrsql/explanation/architecture/) | Pipeline, modules, design |
177
232
  | [Extensibility](https://wskr00.github.io/pyrsql/explanation/extensibility/) | Adding backends and adapters |
233
+ | [Testing](https://wskr00.github.io/pyrsql/testing/) | Test layers, async, security, free-threaded validation |
178
234
  | [Contributing](https://wskr00.github.io/pyrsql/contributing/) | Setup, workflow, standards |
179
235
 
180
236
  ## Development Principles
@@ -23,7 +23,7 @@ uv run pytest # tests
23
23
  - strict mypy type checking
24
24
  - ruff for linting and formatting
25
25
  - pytest with `unit`, `integration`, `functional`, `performance` layers
26
- - ORM-neutral core - zero ORM imports in `pyrsql.core`, `pyrsql.ir`, `pyrsql.parsing`, `pyrsql.semantic`
26
+ - ORM-neutral core - zero ORM imports in `pyrsql.core`, `pyrsql.parsing`, `pyrsql.semantic`, `pyrsql.sorting`
27
27
 
28
28
  ## Test layers
29
29
 
@@ -0,0 +1,244 @@
1
+ # pyrsql Architecture
2
+
3
+ ## Identity
4
+
5
+ `pyrsql` is a compiler-oriented RSQL query engine. It turns RSQL-like filter
6
+ and sort expressions into ORM-specific statement updates through a staged
7
+ pipeline:
8
+
9
+ ```text
10
+ query text
11
+ -> parsing
12
+ -> selector AST
13
+ -> semantic binding
14
+ -> backend lowering
15
+ -> ORM-specific statement
16
+ ```
17
+
18
+ The core is deliberately ORM-neutral. Adding a new backend means implementing
19
+ the `ORM` interface, not rewriting the parser, selector grammar, or semantic
20
+ rules.
21
+
22
+ ## Purpose
23
+
24
+ The project is designed around four major responsibilities:
25
+
26
+ - language frontend: parse text into syntax trees
27
+ - semantic binding: resolve fields, functions, and access policies
28
+ - backend lowering: turn validated query objects into ORM-specific statements
29
+ - optional framework adapters and integrations: expose the core in HTTP stacks
30
+
31
+ The current production backend is SQLAlchemy 2.0. The current framework
32
+ adapter is FastAPI.
33
+
34
+ ## Current Pipeline
35
+
36
+ More concretely, the runtime flow looks like this:
37
+
38
+ ```text
39
+ RSQL string
40
+ -> parsing AST
41
+ -> selector nodes
42
+ -> SemanticBinder / SortBinder
43
+ -> Query / Sort / PageRequest application
44
+ -> SQLAlchemyORM lowering
45
+ -> SQLAlchemy Select
46
+ ```
47
+
48
+ For pagination-only flows, `PageRequest` goes directly to backend lowering.
49
+
50
+ ## Module Boundaries
51
+
52
+ The project is split into these main modules:
53
+
54
+ - `parsing`
55
+ - `selector`
56
+ - `semantic`
57
+ - `sorting`
58
+ - `core`
59
+ - `orms`
60
+ - `adapters`
61
+ - `integrations`
62
+ - `api`
63
+
64
+ ### `parsing`
65
+
66
+ Owns:
67
+
68
+ - source spans
69
+ - tokens
70
+ - parser AST
71
+ - parser limits
72
+ - parse diagnostics and errors
73
+
74
+ ### `selector`
75
+
76
+ Owns selector syntax only:
77
+
78
+ - `FieldSelector`
79
+ - `LiteralSelector`
80
+ - `FunctionSelector`
81
+
82
+ It does not own semantic meaning.
83
+
84
+ ### `semantic`
85
+
86
+ Owns semantic binding:
87
+
88
+ - field policy checks
89
+ - function policy checks
90
+ - field mapping
91
+ - semantic diagnostics and errors
92
+ - normalization for backend lowering
93
+
94
+ ### `sorting`
95
+
96
+ Owns sort syntax and sort binding.
97
+
98
+ ### `core`
99
+
100
+ Owns user-facing ORM-agnostic objects:
101
+
102
+ - `Query`, `Sort`, `PageRequest`
103
+ - `QueryOptions`, `SortOptions`
104
+ - `FieldPolicySet`, `ProcedureAccessPolicy`
105
+ - `CustomPredicateDefinition`
106
+ - `ValueConverterRegistry`, `FieldValueConverterSet`
107
+ - `JSONOptions`, `JSONSortScalarType`
108
+ - `JSONPath`, `JSONPathComparison`
109
+ - `JSONScalarNormalizer`, `JSONScalarValue`
110
+ - `JoinHint`
111
+
112
+ The `core/json/` package owns JSON-aware comparison models and value
113
+ normalization, keeping JSON semantics ORM-neutral.
114
+
115
+ ### `orms`
116
+
117
+ Owns backend lowering.
118
+
119
+ Current production backend:
120
+
121
+ - `orms.sqlalchemy`
122
+
123
+ Its job is:
124
+
125
+ - consume normalized query, sort, and page objects
126
+ - resolve model metadata
127
+ - coerce values
128
+ - lower to SQLAlchemy statements
129
+
130
+ It should not redo semantic interpretation already handled upstream.
131
+
132
+ For PostgreSQL JSON/JSONB lowering, the current design separates:
133
+
134
+ - whole-document JSON predicates
135
+ - nested JSON path predicates
136
+ - JSON sort extraction rules
137
+
138
+ This keeps the backend close to SQLAlchemy and PostgreSQL primitives instead of
139
+ forcing every JSON case through one generic lowering path.
140
+
141
+ ### `adapters`
142
+
143
+ Own framework-level request and error adaptation.
144
+
145
+ Current adapter:
146
+
147
+ - `adapters.fastapi`
148
+
149
+ It is responsible for:
150
+
151
+ - extracting query params
152
+ - building `RequestCriteria`
153
+ - translating pyrsql failures into framework-native HTTP errors
154
+ - adding OpenAPI metadata
155
+
156
+ ### `integrations`
157
+
158
+ Own stack-specific orchestration across adapter + backend.
159
+
160
+ Current integration:
161
+
162
+ - `integrations.fastapi.sqlalchemy`
163
+
164
+ It is responsible for:
165
+
166
+ - request criteria application helpers
167
+ - route-ready dependencies
168
+ - declarative resource configuration
169
+ - stack-specific DX
170
+
171
+ ### `api`
172
+
173
+ Owns the smallest public helper surface:
174
+
175
+ - `parse(...)`
176
+ - `compile(...)`
177
+ - `apply(...)`
178
+
179
+ ## Extensibility
180
+
181
+ ### Adding a new ORM backend
182
+
183
+ Implement `pyrsql.orms.base.ORM`:
184
+
185
+ ```python
186
+ class ORM(ABC):
187
+ def compile_query(self, query: Query) -> CompiledArtifact: ...
188
+ def compile_sort(self, sort: Sort) -> CompiledArtifact: ...
189
+ def compile_page_request(self, page_request: PageRequest) -> CompiledArtifact: ...
190
+ ```
191
+
192
+ Each method receives one ORM-neutral core object and returns a compiled object
193
+ with an `apply(target, model)` method.
194
+
195
+ ### Adding a new framework adapter
196
+
197
+ Implement `pyrsql.adapters.*` to extract query parameters from requests and
198
+ translate pyrsql failures into framework-native HTTP responses.
199
+
200
+ The FastAPI adapter is the reference implementation.
201
+
202
+ ## Design Principles
203
+
204
+ The project follows:
205
+
206
+ - object-oriented design
207
+ - SOLID
208
+ - strong typing
209
+ - backend-independent core semantics
210
+ - performance-oriented data modeling
211
+ - explicit concurrency boundaries
212
+
213
+ ### Practical interpretation
214
+
215
+ - semantic work should happen before the ORM backend
216
+ - ORMs should do lowering, not semantic analysis
217
+ - adapters should do transport adaptation, not query semantics
218
+ - integrations should provide DX, not execution frameworks
219
+
220
+ ## Performance Direction
221
+
222
+ The project uses `msgspec` where it improves compactness and immutable data
223
+ modeling:
224
+
225
+ - diagnostics
226
+ - parser/source structures
227
+ - adapter and integration payloads
228
+ - core query/sort/options/value-policy models
229
+
230
+ Other performance principles:
231
+
232
+ - cache repeated base statements when safe
233
+ - avoid repeating semantic work in the ORM backend
234
+ - keep shared cache lock scope small in concurrency-sensitive paths
235
+
236
+ ## Async and Free-Threaded Support
237
+
238
+ These are related but separate concerns:
239
+
240
+ - async support means generated SQLAlchemy statements work with both `Session`
241
+ and `AsyncSession`
242
+ - free-threaded support means shared mutable caches are synchronized explicitly
243
+
244
+ The core query pipeline stays synchronous and ORM-neutral in both cases.
@@ -0,0 +1,82 @@
1
+ # Extensibility
2
+
3
+ `pyrsql` is designed to support multiple ORM backends and framework adapters.
4
+ The core (`pyrsql.core`, `pyrsql.parsing`, `pyrsql.semantic`,
5
+ `pyrsql.sorting`) has zero dependencies on any ORM or framework.
6
+
7
+ ## Adding a new ORM backend
8
+
9
+ Implement the `ORM` abstract base class:
10
+
11
+ ```python
12
+ from pyrsql.core.compiler import CompiledArtifact
13
+ from pyrsql.orms.base import ORM
14
+
15
+ class MyORM(ORM):
16
+ def compile_query(self, query: Query) -> CompiledArtifact:
17
+ ...
18
+
19
+ def compile_sort(self, sort: Sort) -> CompiledArtifact:
20
+ ...
21
+
22
+ def compile_page_request(self, page_request: PageRequest) -> CompiledArtifact:
23
+ ...
24
+ ```
25
+
26
+ Each `compile_*` method receives one ORM-neutral core object (`Query`, `Sort`,
27
+ or `PageRequest`) and returns a compiled object with an `apply(target, model)`
28
+ method.
29
+
30
+ The current reference implementation is `SQLAlchemyORM` in
31
+ `pyrsql.orms.sqlalchemy`.
32
+
33
+ ## Adding a new framework adapter
34
+
35
+ Create a module under `pyrsql.adapters` that:
36
+
37
+ 1. extracts query parameters from requests
38
+ 2. builds `RequestCriteria`
39
+ 3. translates parse, semantic, and page-validation failures into
40
+ framework-appropriate error responses
41
+
42
+ The FastAPI adapter (`pyrsql.adapters.fastapi`) is the reference:
43
+
44
+ - `FastAPICriteriaConfig` holds public parameter names and defaults
45
+ - `criteria_dependency()` returns a FastAPI dependency callable
46
+
47
+ ## Backend contract in detail
48
+
49
+ ### `CompiledArtifact`
50
+
51
+ ```python
52
+ class CompiledArtifact(Protocol):
53
+ def apply(self, target: _TargetT, model: type[_ModelT]) -> _TargetT: ...
54
+ ```
55
+
56
+ Receives an ORM-specific target and model class, then returns the modified
57
+ target with query conditions applied.
58
+
59
+ The same contract applies to query, sort, and pagination artifacts such as
60
+ `LIMIT` and `OFFSET`.
61
+
62
+ ## Integration layer
63
+
64
+ Adapters and ORMs can stay independent. The `integrations/` package is where
65
+ stack-specific DX belongs.
66
+
67
+ For example, `integrations.fastapi.sqlalchemy` is allowed to know both:
68
+
69
+ - the FastAPI adapter contract
70
+ - the SQLAlchemy backend contract
71
+
72
+ That is the right place for:
73
+
74
+ - route-ready dependencies
75
+ - paginated statement bundles
76
+ - OpenAPI example generation tied to SQLAlchemy model metadata
77
+
78
+ ## Planned backends and adapters
79
+
80
+ - **Django ORM** - compile to Django `QuerySet`
81
+ - **SQLModel** - likely reuse most SQLAlchemy lowering
82
+ - **Flask** - request adapter for `request.args`