pyrsql 0.0.3__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.
- {pyrsql-0.0.3 → pyrsql-0.0.5}/PKG-INFO +64 -20
- {pyrsql-0.0.3 → pyrsql-0.0.5}/README.md +58 -13
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/explanation/architecture.md +4 -5
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/explanation/extensibility.md +9 -17
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/index.md +28 -10
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/quickstart.md +40 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/reference/api.md +1 -2
- pyrsql-0.0.5/docs/reference/operators.md +51 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/reference/options.md +4 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/testing.md +0 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/fastapi-sqlalchemy.md +20 -4
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/fastapi.md +23 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/query.md +9 -5
- pyrsql-0.0.5/pyproject.toml +233 -0
- pyrsql-0.0.3/pyproject.toml → pyrsql-0.0.5/pyproject.toml.orig +21 -19
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/__init__.py +0 -6
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/__init__.py +5 -1
- pyrsql-0.0.5/src/pyrsql/adapters/fastapi/config.py +84 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/criteria.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/dependency.py +130 -53
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/adapters/fastapi/errors.py +9 -14
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/api.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/__init__.py +0 -8
- pyrsql-0.0.5/src/pyrsql/core/compiler.py +15 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/conversion.py +2 -3
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/json/options.py +2 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/options.py +41 -153
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/page.py +4 -9
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/query.py +4 -8
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/sort.py +4 -8
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/source_issues.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +4 -16
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +21 -103
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +10 -80
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/__init__.py +0 -6
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/base.py +5 -19
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_page.py +3 -4
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_query.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/introspection.py +4 -16
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/json_path.py +6 -9
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/orm.py +11 -34
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/resolver.py +1 -17
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/sorter.py +7 -15
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/translator.py +5 -18
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/type_inference.py +14 -16
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/types.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/__init__.py +1 -4
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/ast.py +4 -20
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/errors.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/lexer.py +13 -35
- pyrsql-0.0.5/src/pyrsql/parsing/limits.py +19 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/operators.py +1 -92
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/parser.py +8 -15
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/source.py +0 -23
- pyrsql-0.0.5/src/pyrsql/selector/ast.py +31 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/selector/parser.py +34 -72
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/semantic/binder.py +0 -12
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/binder.py +0 -6
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/errors.py +1 -1
- pyrsql-0.0.5/src/pyrsql/sorting/limits.py +16 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/parser.py +3 -36
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/test_dependency.py +27 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/test_public_api.py +3 -7
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_async_pipeline.py +16 -2
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_query_pipeline.py +58 -16
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_hpp.py +21 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/conftest.py +2 -0
- pyrsql-0.0.5/tests/unit/adapters/fastapi_adapter/test_config.py +39 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/test_criteria.py +4 -13
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/conftest.py +0 -5
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_conversion.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_options.py +2 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_page.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_query.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_sort.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +4 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +0 -182
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_resolver.py +1 -1
- pyrsql-0.0.5/tests/unit/orms/sqlalchemy/test_type_inference.py +43 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/parsing/test_lexer.py +37 -32
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/parsing/test_parser.py +0 -72
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/parsing/test_source.py +1 -6
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/selector/test_parser.py +10 -88
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/sorting/test_parser.py +0 -62
- pyrsql-0.0.3/docs/reference/operators.md +0 -37
- pyrsql-0.0.3/src/pyrsql/adapters/fastapi/config.py +0 -169
- pyrsql-0.0.3/src/pyrsql/core/compiler.py +0 -45
- pyrsql-0.0.3/src/pyrsql/parsing/diagnostics.py +0 -9
- pyrsql-0.0.3/src/pyrsql/parsing/limits.py +0 -48
- pyrsql-0.0.3/src/pyrsql/parsing/normalization.py +0 -51
- pyrsql-0.0.3/src/pyrsql/selector/ast.py +0 -45
- pyrsql-0.0.3/src/pyrsql/sorting/limits.py +0 -33
- pyrsql-0.0.3/tests/unit/adapters/fastapi_adapter/test_config.py +0 -111
- pyrsql-0.0.3/tests/unit/parsing/test_operators.py +0 -105
- pyrsql-0.0.3/tests/unit/selector/test_ast.py +0 -30
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/contributing.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/async.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/custom-predicates.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/errors.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/field-policies.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/join-hints.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/json.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/page.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/sort.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/docs/usage/value-conversion.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/adapters/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/binding_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/field_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/joins.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/json/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/json/path.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/json/query.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/json/values.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/procedure_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/core/validation.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/coercion.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/orms/sqlalchemy/statement.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/parsing/tokens.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/py.typed +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/selector/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/semantic/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/semantic/diagnostics.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/semantic/errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/src/pyrsql/sorting/diagnostics.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_adapter/test_async_dependency.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/test_async_integration.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/integration/sqlalchemy/test_sort_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_conversion_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_json_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_json_path_builder_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_parser_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_path_resolver_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_selector_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_semantic_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_configuration_hardening.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_injection.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_limits.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_policies.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_fastapi_sqlalchemy_resource_security.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_sqlalchemy_injection.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/security/test_sqlalchemy_jsonpath_security.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/adapters/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/adapters/fastapi_adapter/test_errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/json/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/json/test_options.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/json/test_path.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/json/test_query.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/json/test_values.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/core/test_custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/orms/sqlalchemy/test_statement.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/parsing/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/parsing/test_ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/selector/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/semantic/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/semantic/test_binder.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/sorting/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/sorting/test_ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.5}/tests/unit/sorting/test_binder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyrsql
|
|
3
|
-
Version: 0.0.
|
|
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.
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Python: >=3.
|
|
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
|
-
[](https://pypi.org/project/pyrsql/)
|
|
42
41
|
[](LICENSE)
|
|
43
42
|
|
|
44
43
|
pyrsql compiles RSQL query strings into ORM-specific statement objects
|
|
@@ -46,9 +45,14 @@ through a language frontend, semantic binding, and pluggable backend
|
|
|
46
45
|
lowering - making it easy to expose complex query capabilities in your API
|
|
47
46
|
without coupling to a specific ORM or framework.
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
For FastAPI applications using SQLAlchemy, the recommended entry point is
|
|
49
|
+
`FastAPISQLAlchemyIntegration`. It provides route-ready dependencies and
|
|
50
|
+
declarative resources; the lower-level query, adapter, and ORM APIs remain
|
|
51
|
+
available when more control is needed.
|
|
52
|
+
|
|
53
|
+
- **Current backends:** SQLAlchemy 2.1+
|
|
54
|
+
- **Current framework adapters:** FastAPI
|
|
55
|
+
- **Planned:** Django ORM, SQLModel, Flask
|
|
52
56
|
|
|
53
57
|
## Why pyrsql?
|
|
54
58
|
|
|
@@ -73,9 +77,41 @@ language.
|
|
|
73
77
|
## Quickstart
|
|
74
78
|
|
|
75
79
|
```bash
|
|
76
|
-
pip install pyrsql[sqlalchemy]
|
|
80
|
+
pip install pyrsql[fastapi,sqlalchemy]
|
|
77
81
|
```
|
|
78
82
|
|
|
83
|
+
```python
|
|
84
|
+
from typing import Annotated, Any
|
|
85
|
+
|
|
86
|
+
from fastapi import Depends, FastAPI
|
|
87
|
+
|
|
88
|
+
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
89
|
+
|
|
90
|
+
app = FastAPI()
|
|
91
|
+
integration = FastAPISQLAlchemyIntegration()
|
|
92
|
+
users = integration.resource(
|
|
93
|
+
User,
|
|
94
|
+
filterable_fields={"id", "name"},
|
|
95
|
+
sortable_fields={"name"},
|
|
96
|
+
default_sort="name,asc",
|
|
97
|
+
max_page_size=100,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@app.get("/users")
|
|
101
|
+
def list_users(
|
|
102
|
+
stmt: Annotated[Any, Depends(users.select_dependency())],
|
|
103
|
+
):
|
|
104
|
+
return {"sql": str(stmt)}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Clients can now use `filter`, `sort`, `page`, and `size` query parameters.
|
|
108
|
+
See [FastAPI + SQLAlchemy](https://wskr00.github.io/pyrsql/usage/fastapi-sqlalchemy/)
|
|
109
|
+
for count, pagination, custom base statements, error behavior, and async use.
|
|
110
|
+
|
|
111
|
+
### Lower-level SQLAlchemy API
|
|
112
|
+
|
|
113
|
+
Use the direct API when you do not need a FastAPI route dependency:
|
|
114
|
+
|
|
79
115
|
```python
|
|
80
116
|
import pyrsql
|
|
81
117
|
from sqlalchemy import select
|
|
@@ -84,13 +120,13 @@ from pyrsql.orms.sqlalchemy import SQLAlchemyORM
|
|
|
84
120
|
orm = SQLAlchemyORM()
|
|
85
121
|
stmt = select(User)
|
|
86
122
|
|
|
87
|
-
|
|
88
|
-
stmt
|
|
123
|
+
stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
|
|
124
|
+
stmt,
|
|
125
|
+
User,
|
|
126
|
+
orm=orm,
|
|
127
|
+
)
|
|
89
128
|
|
|
90
|
-
# Sort: by name ascending, then company name descending
|
|
91
129
|
stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
|
|
92
|
-
|
|
93
|
-
# Paginate: page 0, 25 items per page
|
|
94
130
|
stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
|
|
95
131
|
```
|
|
96
132
|
|
|
@@ -168,6 +204,9 @@ options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
|
|
|
168
204
|
|
|
169
205
|
### FastAPI Integration
|
|
170
206
|
|
|
207
|
+
Use this adapter directly when you need only parsed `RequestCriteria`; for
|
|
208
|
+
SQLAlchemy routes, prefer the integration below.
|
|
209
|
+
|
|
171
210
|
```python
|
|
172
211
|
from fastapi import Depends, FastAPI
|
|
173
212
|
from pyrsql.adapters.fastapi import criteria_dependency
|
|
@@ -181,10 +220,11 @@ def list_items(criteria = Depends(dependency)):
|
|
|
181
220
|
```
|
|
182
221
|
|
|
183
222
|
- Auto-extracts `filter`, `sort`, `page`, `size` from query params
|
|
184
|
-
- `HTTP
|
|
223
|
+
- Structured `HTTP 400` parse errors and `HTTP 422` semantic errors
|
|
185
224
|
- OpenAPI examples from configuration
|
|
186
225
|
- One-based paging support
|
|
187
226
|
- Custom query parameter names
|
|
227
|
+
- Optional repeated sort parameters through `SortParameterFormat.REPEATED`
|
|
188
228
|
|
|
189
229
|
### FastAPI + SQLAlchemy Integration
|
|
190
230
|
|
|
@@ -199,7 +239,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
199
239
|
```
|
|
200
240
|
|
|
201
241
|
- `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
|
|
202
|
-
- Declarative `resource()` with auto-generated OpenAPI examples
|
|
242
|
+
- Declarative `resource()` with auto-generated OpenAPI examples and field policies
|
|
203
243
|
- `applier_dependency` for custom base statements
|
|
204
244
|
- Compatible with both sync `Session` and async `AsyncSession` execution
|
|
205
245
|
- FastAPI parse and page-validation failures become structured `HTTP 400` payloads
|
|
@@ -207,7 +247,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
207
247
|
|
|
208
248
|
### Concurrency and Validation
|
|
209
249
|
|
|
210
|
-
- Shared
|
|
250
|
+
- Shared base-select and ORM metadata caches are protected for free-threaded execution
|
|
211
251
|
- Async support is validated for adapter, ORM, and integration flows
|
|
212
252
|
- Dedicated async, free-threaded, and security test suites validate these flows
|
|
213
253
|
|
|
@@ -235,6 +275,10 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
|
|
|
235
275
|
- Strong typing
|
|
236
276
|
- ORM-neutral public API
|
|
237
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
|
+
|
|
238
282
|
## License
|
|
239
283
|
|
|
240
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
|
-
[](https://pypi.org/project/pyrsql/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
9
|
pyrsql compiles RSQL query strings into ORM-specific statement objects
|
|
@@ -11,9 +11,14 @@ through a language frontend, semantic binding, and pluggable backend
|
|
|
11
11
|
lowering - making it easy to expose complex query capabilities in your API
|
|
12
12
|
without coupling to a specific ORM or framework.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
18
|
+
|
|
19
|
+
- **Current backends:** SQLAlchemy 2.1+
|
|
20
|
+
- **Current framework adapters:** FastAPI
|
|
21
|
+
- **Planned:** Django ORM, SQLModel, Flask
|
|
17
22
|
|
|
18
23
|
## Why pyrsql?
|
|
19
24
|
|
|
@@ -38,9 +43,41 @@ language.
|
|
|
38
43
|
## Quickstart
|
|
39
44
|
|
|
40
45
|
```bash
|
|
41
|
-
pip install pyrsql[sqlalchemy]
|
|
46
|
+
pip install pyrsql[fastapi,sqlalchemy]
|
|
42
47
|
```
|
|
43
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
|
+
|
|
44
81
|
```python
|
|
45
82
|
import pyrsql
|
|
46
83
|
from sqlalchemy import select
|
|
@@ -49,13 +86,13 @@ from pyrsql.orms.sqlalchemy import SQLAlchemyORM
|
|
|
49
86
|
orm = SQLAlchemyORM()
|
|
50
87
|
stmt = select(User)
|
|
51
88
|
|
|
52
|
-
|
|
53
|
-
stmt
|
|
89
|
+
stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
|
|
90
|
+
stmt,
|
|
91
|
+
User,
|
|
92
|
+
orm=orm,
|
|
93
|
+
)
|
|
54
94
|
|
|
55
|
-
# Sort: by name ascending, then company name descending
|
|
56
95
|
stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
|
|
57
|
-
|
|
58
|
-
# Paginate: page 0, 25 items per page
|
|
59
96
|
stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
|
|
60
97
|
```
|
|
61
98
|
|
|
@@ -133,6 +170,9 @@ options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
|
|
|
133
170
|
|
|
134
171
|
### FastAPI Integration
|
|
135
172
|
|
|
173
|
+
Use this adapter directly when you need only parsed `RequestCriteria`; for
|
|
174
|
+
SQLAlchemy routes, prefer the integration below.
|
|
175
|
+
|
|
136
176
|
```python
|
|
137
177
|
from fastapi import Depends, FastAPI
|
|
138
178
|
from pyrsql.adapters.fastapi import criteria_dependency
|
|
@@ -146,10 +186,11 @@ def list_items(criteria = Depends(dependency)):
|
|
|
146
186
|
```
|
|
147
187
|
|
|
148
188
|
- Auto-extracts `filter`, `sort`, `page`, `size` from query params
|
|
149
|
-
- `HTTP
|
|
189
|
+
- Structured `HTTP 400` parse errors and `HTTP 422` semantic errors
|
|
150
190
|
- OpenAPI examples from configuration
|
|
151
191
|
- One-based paging support
|
|
152
192
|
- Custom query parameter names
|
|
193
|
+
- Optional repeated sort parameters through `SortParameterFormat.REPEATED`
|
|
153
194
|
|
|
154
195
|
### FastAPI + SQLAlchemy Integration
|
|
155
196
|
|
|
@@ -164,7 +205,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
164
205
|
```
|
|
165
206
|
|
|
166
207
|
- `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
|
|
167
|
-
- Declarative `resource()` with auto-generated OpenAPI examples
|
|
208
|
+
- Declarative `resource()` with auto-generated OpenAPI examples and field policies
|
|
168
209
|
- `applier_dependency` for custom base statements
|
|
169
210
|
- Compatible with both sync `Session` and async `AsyncSession` execution
|
|
170
211
|
- FastAPI parse and page-validation failures become structured `HTTP 400` payloads
|
|
@@ -172,7 +213,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
172
213
|
|
|
173
214
|
### Concurrency and Validation
|
|
174
215
|
|
|
175
|
-
- Shared
|
|
216
|
+
- Shared base-select and ORM metadata caches are protected for free-threaded execution
|
|
176
217
|
- Async support is validated for adapter, ORM, and integration flows
|
|
177
218
|
- Dedicated async, free-threaded, and security test suites validate these flows
|
|
178
219
|
|
|
@@ -200,6 +241,10 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
|
|
|
200
241
|
- Strong typing
|
|
201
242
|
- ORM-neutral public API
|
|
202
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
|
+
|
|
203
248
|
## License
|
|
204
249
|
|
|
205
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.
|
|
31
|
+
The current production backend is SQLAlchemy 2.1+. The current framework
|
|
32
32
|
adapter is FastAPI.
|
|
33
33
|
|
|
34
34
|
## Current Pipeline
|
|
@@ -108,7 +108,6 @@ Owns user-facing ORM-agnostic objects:
|
|
|
108
108
|
- `JSONPath`, `JSONPathComparison`
|
|
109
109
|
- `JSONScalarNormalizer`, `JSONScalarValue`
|
|
110
110
|
- `JoinHint`
|
|
111
|
-
- `CompilationResult`, `SortCompilationResult`, `PageCompilationResult`
|
|
112
111
|
|
|
113
112
|
The `core/json/` package owns JSON-aware comparison models and value
|
|
114
113
|
normalization, keeping JSON semantics ORM-neutral.
|
|
@@ -185,9 +184,9 @@ Implement `pyrsql.orms.base.ORM`:
|
|
|
185
184
|
|
|
186
185
|
```python
|
|
187
186
|
class ORM(ABC):
|
|
188
|
-
def compile_query(self, query: Query) ->
|
|
189
|
-
def compile_sort(self, sort: Sort) ->
|
|
190
|
-
def compile_page_request(self, page_request: PageRequest) ->
|
|
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: ...
|
|
191
190
|
```
|
|
192
191
|
|
|
193
192
|
Each method receives one ORM-neutral core object and returns a compiled object
|
|
@@ -9,20 +9,17 @@ The core (`pyrsql.core`, `pyrsql.parsing`, `pyrsql.semantic`,
|
|
|
9
9
|
Implement the `ORM` abstract base class:
|
|
10
10
|
|
|
11
11
|
```python
|
|
12
|
-
from pyrsql.
|
|
12
|
+
from pyrsql.core.compiler import CompiledArtifact
|
|
13
|
+
from pyrsql.orms.base import ORM
|
|
13
14
|
|
|
14
15
|
class MyORM(ORM):
|
|
15
|
-
|
|
16
|
-
def name(self) -> str:
|
|
17
|
-
return "my-orm"
|
|
18
|
-
|
|
19
|
-
def compile_query(self, query: Query) -> CompiledQuery:
|
|
16
|
+
def compile_query(self, query: Query) -> CompiledArtifact:
|
|
20
17
|
...
|
|
21
18
|
|
|
22
|
-
def compile_sort(self, sort: Sort) ->
|
|
19
|
+
def compile_sort(self, sort: Sort) -> CompiledArtifact:
|
|
23
20
|
...
|
|
24
21
|
|
|
25
|
-
def compile_page_request(self, page_request: PageRequest) ->
|
|
22
|
+
def compile_page_request(self, page_request: PageRequest) -> CompiledArtifact:
|
|
26
23
|
...
|
|
27
24
|
```
|
|
28
25
|
|
|
@@ -49,23 +46,18 @@ The FastAPI adapter (`pyrsql.adapters.fastapi`) is the reference:
|
|
|
49
46
|
|
|
50
47
|
## Backend contract in detail
|
|
51
48
|
|
|
52
|
-
### `
|
|
49
|
+
### `CompiledArtifact`
|
|
53
50
|
|
|
54
51
|
```python
|
|
55
|
-
class
|
|
52
|
+
class CompiledArtifact(Protocol):
|
|
56
53
|
def apply(self, target: _TargetT, model: type[_ModelT]) -> _TargetT: ...
|
|
57
54
|
```
|
|
58
55
|
|
|
59
56
|
Receives an ORM-specific target and model class, then returns the modified
|
|
60
57
|
target with query conditions applied.
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Same contract as `CompiledQuery`, but applies sort ordering.
|
|
65
|
-
|
|
66
|
-
### `CompiledPageRequest`
|
|
67
|
-
|
|
68
|
-
Same contract, but applies pagination semantics such as `LIMIT` and `OFFSET`.
|
|
59
|
+
The same contract applies to query, sort, and pagination artifacts such as
|
|
60
|
+
`LIMIT` and `OFFSET`.
|
|
69
61
|
|
|
70
62
|
## Integration layer
|
|
71
63
|
|
|
@@ -10,24 +10,42 @@ capabilities in your API without coupling to a specific ORM or framework.
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
pip install pyrsql[sqlalchemy]
|
|
13
|
+
pip install pyrsql[fastapi,sqlalchemy]
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Recommended: FastAPI + SQLAlchemy
|
|
17
17
|
|
|
18
18
|
```python
|
|
19
|
-
import
|
|
20
|
-
from sqlalchemy import select
|
|
21
|
-
from pyrsql.orms.sqlalchemy import SQLAlchemyORM
|
|
19
|
+
from typing import Annotated, Any
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
from fastapi import Depends, FastAPI
|
|
22
|
+
|
|
23
|
+
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
24
|
+
|
|
25
|
+
app = FastAPI()
|
|
26
|
+
integration = FastAPISQLAlchemyIntegration()
|
|
27
|
+
users = integration.resource(
|
|
28
|
+
User,
|
|
29
|
+
filterable_fields={"id", "name"},
|
|
30
|
+
sortable_fields={"name"},
|
|
31
|
+
default_sort="name,asc",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@app.get("/users")
|
|
35
|
+
def list_users(
|
|
36
|
+
stmt: Annotated[Any, Depends(users.select_dependency())],
|
|
37
|
+
):
|
|
38
|
+
return {"sql": str(stmt)}
|
|
25
39
|
```
|
|
26
40
|
|
|
41
|
+
The integration parses request criteria, applies SQLAlchemy filters/sorting/
|
|
42
|
+
pagination, and exposes a route-ready dependency. It does not execute database
|
|
43
|
+
I/O.
|
|
44
|
+
|
|
27
45
|
## Key features
|
|
28
46
|
|
|
29
47
|
- **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
|
|
30
|
-
- **Pluggable backends** - SQLAlchemy 2.
|
|
48
|
+
- **Pluggable backends** - SQLAlchemy 2.1+ today, Django ORM / SQLModel planned
|
|
31
49
|
- **Pluggable adapters** - FastAPI today, Flask planned
|
|
32
50
|
- **20+ built-in operators** - `==`, `=like=`, `=in=`, `=bt=`, `=nn=`, etc.
|
|
33
51
|
- **Custom operators** - define your own RSQL operators with per-ORM lowering
|
|
@@ -43,11 +61,11 @@ stmt = pyrsql.parse("name==demo;age=gt=18").apply(select(User), User, orm=orm)
|
|
|
43
61
|
|
|
44
62
|
## Integration options
|
|
45
63
|
|
|
46
|
-
- **FastAPI adapter** - parse `filter`, `sort`, `page`, and `size` into
|
|
47
|
-
`RequestCriteria`
|
|
48
64
|
- **FastAPI + SQLAlchemy integration** - route-ready dependencies,
|
|
49
65
|
declarative `resource()` helpers, count/paginated bundles, and normalized
|
|
50
66
|
backend error translation
|
|
67
|
+
- **FastAPI adapter** - parse `filter`, `sort`, `page`, and `size` into
|
|
68
|
+
`RequestCriteria` when you need criteria without SQLAlchemy integration
|
|
51
69
|
|
|
52
70
|
## Next steps
|
|
53
71
|
|
|
@@ -9,6 +9,44 @@ pip install pyrsql[fastapi] # with FastAPI
|
|
|
9
9
|
pip install pyrsql[fastapi,sqlalchemy] # both
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
## Recommended: FastAPI + SQLAlchemy
|
|
13
|
+
|
|
14
|
+
For a FastAPI application backed by SQLAlchemy, start with the integration.
|
|
15
|
+
It owns the public query contract for one model and gives routes a ready-to-use
|
|
16
|
+
SQLAlchemy statement.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from typing import Annotated, Any
|
|
20
|
+
|
|
21
|
+
from fastapi import Depends, FastAPI
|
|
22
|
+
|
|
23
|
+
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
24
|
+
|
|
25
|
+
app = FastAPI()
|
|
26
|
+
integration = FastAPISQLAlchemyIntegration()
|
|
27
|
+
users = integration.resource(
|
|
28
|
+
User,
|
|
29
|
+
filterable_fields={"id", "name"},
|
|
30
|
+
sortable_fields={"name"},
|
|
31
|
+
default_sort="name,asc",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@app.get("/users")
|
|
35
|
+
def list_users(
|
|
36
|
+
stmt: Annotated[Any, Depends(users.select_dependency())],
|
|
37
|
+
):
|
|
38
|
+
return {"sql": str(stmt)}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Clients can use `filter`, `sort`, `page`, and `size`. The integration also
|
|
42
|
+
offers count and paginated dependencies. See [FastAPI + SQLAlchemy](usage/fastapi-sqlalchemy.md)
|
|
43
|
+
for the complete route API.
|
|
44
|
+
|
|
45
|
+
## Building blocks
|
|
46
|
+
|
|
47
|
+
The following sections show the lower-level APIs used by the integration. They
|
|
48
|
+
are useful for non-FastAPI applications or when you need direct control.
|
|
49
|
+
|
|
12
50
|
## Filter
|
|
13
51
|
|
|
14
52
|
```python
|
|
@@ -17,7 +55,7 @@ from pyrsql import Query
|
|
|
17
55
|
Query.parse("name==demo") # equal
|
|
18
56
|
Query.parse("age=gt=18") # greater than
|
|
19
57
|
Query.parse("id=in=(1,2,3)") # IN
|
|
20
|
-
Query.parse("name=like=demo
|
|
58
|
+
Query.parse("name=like=demo") # contains: LIKE '%demo%'
|
|
21
59
|
Query.parse("score=bt=(10,20)") # BETWEEN
|
|
22
60
|
Query.parse("name==demo;age=gt=18") # AND
|
|
23
61
|
Query.parse("name==demo,age=gt=18") # OR
|
|
@@ -83,7 +121,7 @@ Parse and page-validation errors become structured `HTTP 400` responses.
|
|
|
83
121
|
Semantic and backend integration errors become structured `HTTP 422`
|
|
84
122
|
responses.
|
|
85
123
|
|
|
86
|
-
##
|
|
124
|
+
## Direct integration dependency
|
|
87
125
|
|
|
88
126
|
```python
|
|
89
127
|
from typing import Annotated, Any
|
|
@@ -27,12 +27,11 @@
|
|
|
27
27
|
- ValueConverterRegistry
|
|
28
28
|
- JoinHint
|
|
29
29
|
- ORM
|
|
30
|
-
- CompilationResult
|
|
31
30
|
- DEFAULT_JSON_OPTIONS
|
|
32
31
|
|
|
33
32
|
## Subpackages
|
|
34
33
|
|
|
35
|
-
- `pyrsql.adapters.fastapi` - FastAPI adapter (FastAPICriteriaConfig, RequestCriteria, CriteriaDependency)
|
|
34
|
+
- `pyrsql.adapters.fastapi` - FastAPI adapter (FastAPICriteriaConfig, RequestCriteria, CriteriaDependency, SortParameterFormat)
|
|
36
35
|
- `pyrsql.integrations.fastapi` - FastAPI + SQLAlchemy (FastAPISQLAlchemyIntegration, FastAPISQLAlchemyResource)
|
|
37
36
|
- `pyrsql.orms.sqlalchemy` - SQLAlchemy backend (SQLAlchemyORM)
|
|
38
37
|
- `pyrsql.core` - ORM-neutral core types and options
|
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FastAPI + SQLAlchemy
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Recommended entry point
|
|
4
4
|
|
|
5
5
|
```python
|
|
6
6
|
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
@@ -21,6 +21,23 @@ The integration works with:
|
|
|
21
21
|
- synchronous SQLAlchemy execution through `Session`
|
|
22
22
|
- asynchronous SQLAlchemy execution through `AsyncSession`
|
|
23
23
|
|
|
24
|
+
For routes that expose one stable model contract, prefer a declarative
|
|
25
|
+
resource. It keeps field policies, default ordering, page limits, and OpenAPI
|
|
26
|
+
examples next to the model they describe.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
users = integration.resource(
|
|
30
|
+
User,
|
|
31
|
+
filterable_fields={"id", "name"},
|
|
32
|
+
sortable_fields={"name"},
|
|
33
|
+
default_sort="name,desc",
|
|
34
|
+
max_page_size=50,
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `FastAPISQLAlchemyIntegration` directly for dynamic models or when a
|
|
39
|
+
service layer needs to build statements outside a route dependency.
|
|
40
|
+
|
|
24
41
|
## Route-ready dependencies
|
|
25
42
|
|
|
26
43
|
```python
|
|
@@ -140,9 +157,8 @@ This avoids leaking raw SQLAlchemy exceptions as `500` responses.
|
|
|
140
157
|
|
|
141
158
|
## Free-threaded behavior
|
|
142
159
|
|
|
143
|
-
The integration caches
|
|
144
|
-
|
|
145
|
-
free-threaded execution.
|
|
160
|
+
The integration caches base `select(model)` statements per model. That shared
|
|
161
|
+
cache is published with explicit synchronization for free-threaded execution.
|
|
146
162
|
|
|
147
163
|
## Async note
|
|
148
164
|
|
|
@@ -40,6 +40,29 @@ config = FastAPICriteriaConfig(
|
|
|
40
40
|
dependency = criteria_dependency(config)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
## Repeated sort parameters
|
|
44
|
+
|
|
45
|
+
The default sort representation uses one semicolon-delimited parameter:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
?sort=name,asc;created_at,desc
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To accept one sort field per repeated query parameter, configure the adapter
|
|
52
|
+
with `SortParameterFormat.REPEATED`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from pyrsql.adapters.fastapi import SortParameterFormat
|
|
56
|
+
|
|
57
|
+
config = FastAPICriteriaConfig(
|
|
58
|
+
sort_parameter_format=SortParameterFormat.REPEATED,
|
|
59
|
+
)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
?sort=name,asc&sort=created_at,desc
|
|
64
|
+
```
|
|
65
|
+
|
|
43
66
|
## Class-based dependency
|
|
44
67
|
|
|
45
68
|
```python
|