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.
- {pyrsql-0.0.2 → pyrsql-0.0.4}/PKG-INFO +74 -18
- {pyrsql-0.0.2 → pyrsql-0.0.4}/README.md +72 -16
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/contributing.md +1 -1
- pyrsql-0.0.4/docs/explanation/architecture.md +244 -0
- pyrsql-0.0.4/docs/explanation/extensibility.md +82 -0
- pyrsql-0.0.4/docs/index.md +76 -0
- pyrsql-0.0.4/docs/quickstart.md +181 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/reference/api.md +1 -3
- pyrsql-0.0.4/docs/reference/options.md +107 -0
- pyrsql-0.0.4/docs/testing.md +184 -0
- pyrsql-0.0.4/docs/usage/async.md +138 -0
- pyrsql-0.0.4/docs/usage/errors.md +213 -0
- pyrsql-0.0.4/docs/usage/fastapi-sqlalchemy.md +166 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/fastapi.md +49 -4
- {pyrsql-0.0.2 → pyrsql-0.0.4}/pyproject.toml +8 -13
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/__init__.py +2 -6
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/__init__.py +5 -1
- pyrsql-0.0.4/src/pyrsql/adapters/fastapi/config.py +84 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/criteria.py +3 -22
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/dependency.py +142 -67
- pyrsql-0.0.4/src/pyrsql/adapters/fastapi/errors.py +342 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/api.py +3 -3
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/__init__.py +0 -8
- pyrsql-0.0.4/src/pyrsql/core/binding_policy.py +78 -0
- pyrsql-0.0.4/src/pyrsql/core/compiler.py +15 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/conversion.py +184 -92
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/custom.py +10 -2
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/field_policy.py +67 -21
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/__init__.py +1 -6
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/options.py +62 -25
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/path.py +28 -21
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/query.py +7 -16
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/json/values.py +27 -6
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/options.py +153 -227
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/page.py +30 -37
- pyrsql-0.0.4/src/pyrsql/core/procedure_policy.py +173 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/query.py +20 -60
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/sort.py +19 -61
- pyrsql-0.0.4/src/pyrsql/core/source_issues.py +64 -0
- pyrsql-0.0.4/src/pyrsql/core/validation.py +16 -0
- pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +17 -0
- pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +215 -0
- pyrsql-0.0.4/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +150 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +101 -135
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -7
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +93 -130
- pyrsql-0.0.4/src/pyrsql/orms/__init__.py +11 -0
- pyrsql-0.0.4/src/pyrsql/orms/base.py +58 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/__init__.py +1 -1
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/coercion.py +53 -13
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_page.py +9 -8
- pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/compiled.py → pyrsql-0.0.4/src/pyrsql/orms/sqlalchemy/compiled_query.py +4 -4
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +6 -6
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/errors.py +13 -1
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/introspection.py +74 -53
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/json_path.py +55 -60
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/orm.py +19 -21
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/resolver.py +112 -57
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/sorter.py +41 -39
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/statement.py +35 -13
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/translator.py +69 -64
- pyrsql-0.0.4/src/pyrsql/orms/sqlalchemy/type_inference.py +56 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/types.py +5 -8
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/__init__.py +2 -8
- pyrsql-0.0.4/src/pyrsql/parsing/ast.py +73 -0
- pyrsql-0.0.4/src/pyrsql/parsing/errors.py +19 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/lexer.py +34 -39
- pyrsql-0.0.4/src/pyrsql/parsing/limits.py +19 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/operators.py +16 -65
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/parser.py +26 -18
- pyrsql-0.0.4/src/pyrsql/parsing/source.py +29 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/selector/__init__.py +1 -6
- pyrsql-0.0.4/src/pyrsql/selector/ast.py +31 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/selector/parser.py +87 -27
- pyrsql-0.0.4/src/pyrsql/semantic/binder.py +143 -0
- pyrsql-0.0.4/src/pyrsql/semantic/diagnostics.py +9 -0
- pyrsql-0.0.4/src/pyrsql/semantic/errors.py +39 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/ast.py +4 -14
- pyrsql-0.0.4/src/pyrsql/sorting/binder.py +112 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/diagnostics.py +2 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/errors.py +4 -2
- pyrsql-0.0.4/src/pyrsql/sorting/limits.py +16 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/parser.py +35 -14
- pyrsql-0.0.4/tests/__init__.py +1 -0
- pyrsql-0.0.4/tests/functional/fastapi_adapter/test_async_dependency.py +114 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/test_dependency.py +33 -5
- pyrsql-0.0.4/tests/functional/fastapi_sqlalchemy/test_async_integration.py +234 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/test_public_api.py +3 -7
- pyrsql-0.0.4/tests/integration/sqlalchemy/test_async_pipeline.py +104 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_query_pipeline.py +49 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_sort_pipeline.py +27 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_path_resolver_bench.py +0 -1
- pyrsql-0.0.4/tests/security/__init__.py +1 -0
- pyrsql-0.0.4/tests/security/conftest.py +109 -0
- pyrsql-0.0.4/tests/security/test_configuration_hardening.py +30 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +101 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_hpp.py +106 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_injection.py +49 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_limits.py +215 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_policies.py +183 -0
- pyrsql-0.0.4/tests/security/test_fastapi_sqlalchemy_resource_security.py +54 -0
- pyrsql-0.0.4/tests/security/test_sqlalchemy_injection.py +77 -0
- pyrsql-0.0.4/tests/security/test_sqlalchemy_jsonpath_security.py +70 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/conftest.py +11 -16
- pyrsql-0.0.4/tests/unit/adapters/fastapi_adapter/test_config.py +39 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/test_criteria.py +5 -35
- pyrsql-0.0.4/tests/unit/adapters/fastapi_adapter/test_errors.py +30 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/conftest.py +0 -5
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_options.py +14 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_path.py +7 -18
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/test_query.py +13 -22
- pyrsql-0.0.4/tests/unit/core/json/test_values.py +72 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_conversion.py +82 -0
- pyrsql-0.0.4/tests/unit/core/test_custom.py +22 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_options.py +81 -2
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_page.py +3 -5
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_query.py +3 -3
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/test_sort.py +3 -3
- pyrsql-0.0.4/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +97 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +70 -119
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_resolver.py +115 -8
- pyrsql-0.0.4/tests/unit/orms/sqlalchemy/test_statement.py +61 -0
- pyrsql-0.0.4/tests/unit/parsing/test_ast.py +66 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/test_parser.py +27 -26
- pyrsql-0.0.4/tests/unit/parsing/test_source.py +22 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/selector/test_parser.py +28 -7
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/semantic/test_binder.py +39 -18
- pyrsql-0.0.4/tests/unit/sorting/test_ast.py +26 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/sorting/test_binder.py +32 -9
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/sorting/test_parser.py +18 -6
- pyrsql-0.0.2/docs/explanation/architecture.md +0 -352
- pyrsql-0.0.2/docs/explanation/extensibility.md +0 -72
- pyrsql-0.0.2/docs/index.md +0 -36
- pyrsql-0.0.2/docs/quickstart.md +0 -71
- pyrsql-0.0.2/docs/reference/options.md +0 -66
- pyrsql-0.0.2/docs/testing.md +0 -152
- pyrsql-0.0.2/docs/usage/fastapi-sqlalchemy.md +0 -85
- pyrsql-0.0.2/src/pyrsql/adapters/fastapi/config.py +0 -130
- pyrsql-0.0.2/src/pyrsql/adapters/fastapi/errors.py +0 -218
- pyrsql-0.0.2/src/pyrsql/core/compiler.py +0 -127
- pyrsql-0.0.2/src/pyrsql/core/procedure_policy.py +0 -89
- pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +0 -11
- pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +0 -114
- pyrsql-0.0.2/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +0 -83
- pyrsql-0.0.2/src/pyrsql/ir/__init__.py +0 -28
- pyrsql-0.0.2/src/pyrsql/ir/page.py +0 -40
- pyrsql-0.0.2/src/pyrsql/ir/query.py +0 -181
- pyrsql-0.0.2/src/pyrsql/ir/sort.py +0 -50
- pyrsql-0.0.2/src/pyrsql/orms/__init__.py +0 -15
- pyrsql-0.0.2/src/pyrsql/orms/base.py +0 -108
- pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/type_inference.py +0 -35
- pyrsql-0.0.2/src/pyrsql/parsing/ast.py +0 -123
- pyrsql-0.0.2/src/pyrsql/parsing/diagnostics.py +0 -29
- pyrsql-0.0.2/src/pyrsql/parsing/errors.py +0 -47
- pyrsql-0.0.2/src/pyrsql/parsing/limits.py +0 -36
- pyrsql-0.0.2/src/pyrsql/parsing/source.py +0 -83
- pyrsql-0.0.2/src/pyrsql/selector/ast.py +0 -86
- pyrsql-0.0.2/src/pyrsql/semantic/binder.py +0 -237
- pyrsql-0.0.2/src/pyrsql/semantic/diagnostics.py +0 -29
- pyrsql-0.0.2/src/pyrsql/semantic/errors.py +0 -69
- pyrsql-0.0.2/src/pyrsql/sorting/binder.py +0 -184
- pyrsql-0.0.2/src/pyrsql/sorting/limits.py +0 -27
- pyrsql-0.0.2/tests/unit/adapters/fastapi_adapter/test_config.py +0 -109
- pyrsql-0.0.2/tests/unit/core/json/test_values.py +0 -31
- pyrsql-0.0.2/tests/unit/ir/__init__.py +0 -1
- pyrsql-0.0.2/tests/unit/ir/test_page.py +0 -32
- pyrsql-0.0.2/tests/unit/ir/test_query.py +0 -124
- pyrsql-0.0.2/tests/unit/ir/test_sort.py +0 -72
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/reference/operators.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/custom-predicates.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/field-policies.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/join-hints.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/json.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/page.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/query.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/sort.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/docs/usage/value-conversion.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/adapters/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/core/joins.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/parsing/tokens.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/py.typed +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/semantic/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/src/pyrsql/sorting/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_conversion_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_json_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_json_path_builder_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_parser_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_selector_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_semantic_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/core/json/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/parsing/test_lexer.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/selector/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.4}/tests/unit/semantic/__init__.py +0 -0
- {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.
|
|
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
|
-
[](https://pypi.org/project/pyrsql/)
|
|
41
|
+
[](https://pypi.org/project/pyrsql/)
|
|
42
42
|
[](LICENSE)
|
|
43
43
|
|
|
44
44
|
pyrsql compiles RSQL query strings into ORM-specific statement objects
|
|
45
|
-
through a language frontend, semantic
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
one interface (`ORM`), not rewriting the parser or query
|
|
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
|
-
|
|
83
|
-
stmt
|
|
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
|
|
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
|
-
[](https://pypi.org/project/pyrsql/)
|
|
6
|
+
[](https://pypi.org/project/pyrsql/)
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
9
|
pyrsql compiles RSQL query strings into ORM-specific statement objects
|
|
10
|
-
through a language frontend, semantic
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
one interface (`ORM`), not rewriting the parser or query
|
|
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
|
-
|
|
48
|
-
stmt
|
|
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
|
|
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.
|
|
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`
|