pyrsql 0.0.2__tar.gz → 0.0.3__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.3}/PKG-INFO +25 -10
- {pyrsql-0.0.2 → pyrsql-0.0.3}/README.md +23 -8
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/contributing.md +1 -1
- pyrsql-0.0.3/docs/explanation/architecture.md +245 -0
- pyrsql-0.0.3/docs/explanation/extensibility.md +90 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/index.md +22 -0
- pyrsql-0.0.3/docs/quickstart.md +143 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/reference/api.md +0 -1
- pyrsql-0.0.3/docs/reference/options.md +107 -0
- pyrsql-0.0.3/docs/testing.md +185 -0
- pyrsql-0.0.3/docs/usage/async.md +138 -0
- pyrsql-0.0.3/docs/usage/errors.md +213 -0
- pyrsql-0.0.3/docs/usage/fastapi-sqlalchemy.md +150 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/fastapi.md +26 -4
- {pyrsql-0.0.2 → pyrsql-0.0.3}/pyproject.toml +8 -13
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/__init__.py +2 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/adapters/fastapi/config.py +82 -43
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/adapters/fastapi/criteria.py +5 -22
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/adapters/fastapi/dependency.py +26 -28
- pyrsql-0.0.3/src/pyrsql/adapters/fastapi/errors.py +347 -0
- pyrsql-0.0.3/src/pyrsql/core/binding_policy.py +78 -0
- pyrsql-0.0.3/src/pyrsql/core/compiler.py +45 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/conversion.py +185 -92
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/custom.py +10 -2
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/field_policy.py +67 -21
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/json/__init__.py +1 -6
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/json/options.py +62 -25
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/json/path.py +28 -21
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/json/query.py +7 -16
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/json/values.py +27 -6
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/options.py +196 -158
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/page.py +27 -29
- pyrsql-0.0.3/src/pyrsql/core/procedure_policy.py +173 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/query.py +17 -53
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/sort.py +16 -54
- pyrsql-0.0.3/src/pyrsql/core/source_issues.py +64 -0
- pyrsql-0.0.3/src/pyrsql/core/validation.py +16 -0
- pyrsql-0.0.3/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +17 -0
- pyrsql-0.0.3/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +215 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +79 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +132 -84
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -7
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +134 -101
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/__init__.py +2 -0
- pyrsql-0.0.3/src/pyrsql/orms/base.py +72 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/__init__.py +1 -1
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/coercion.py +53 -13
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/compiled_page.py +6 -4
- pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/compiled.py → pyrsql-0.0.3/src/pyrsql/orms/sqlalchemy/compiled_query.py +3 -3
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +5 -5
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/errors.py +13 -1
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/introspection.py +87 -54
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/json_path.py +57 -59
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/orm.py +34 -13
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/resolver.py +117 -46
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/sorter.py +47 -37
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/statement.py +35 -13
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/translator.py +69 -51
- pyrsql-0.0.3/src/pyrsql/orms/sqlalchemy/type_inference.py +59 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/types.py +5 -6
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/__init__.py +1 -4
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/ast.py +1 -35
- pyrsql-0.0.3/src/pyrsql/parsing/diagnostics.py +9 -0
- pyrsql-0.0.3/src/pyrsql/parsing/errors.py +21 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/lexer.py +54 -37
- pyrsql-0.0.3/src/pyrsql/parsing/limits.py +48 -0
- pyrsql-0.0.3/src/pyrsql/parsing/normalization.py +51 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/operators.py +78 -36
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/parser.py +29 -14
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/source.py +0 -31
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/selector/__init__.py +1 -6
- pyrsql-0.0.3/src/pyrsql/selector/ast.py +45 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/selector/parser.py +120 -22
- pyrsql-0.0.3/src/pyrsql/semantic/binder.py +155 -0
- pyrsql-0.0.3/src/pyrsql/semantic/diagnostics.py +9 -0
- pyrsql-0.0.3/src/pyrsql/semantic/errors.py +39 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/sorting/ast.py +4 -14
- pyrsql-0.0.3/src/pyrsql/sorting/binder.py +118 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/sorting/diagnostics.py +2 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/sorting/errors.py +3 -1
- pyrsql-0.0.3/src/pyrsql/sorting/limits.py +33 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/sorting/parser.py +66 -12
- pyrsql-0.0.3/tests/__init__.py +1 -0
- pyrsql-0.0.3/tests/functional/fastapi_adapter/test_async_dependency.py +114 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/fastapi_adapter/test_dependency.py +6 -5
- pyrsql-0.0.3/tests/functional/fastapi_sqlalchemy/test_async_integration.py +234 -0
- pyrsql-0.0.3/tests/integration/sqlalchemy/test_async_pipeline.py +104 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/sqlalchemy/test_query_pipeline.py +49 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/sqlalchemy/test_sort_pipeline.py +27 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_path_resolver_bench.py +0 -1
- pyrsql-0.0.3/tests/security/__init__.py +1 -0
- pyrsql-0.0.3/tests/security/conftest.py +109 -0
- pyrsql-0.0.3/tests/security/test_configuration_hardening.py +30 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +101 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_hpp.py +86 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_injection.py +49 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_limits.py +215 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_policies.py +183 -0
- pyrsql-0.0.3/tests/security/test_fastapi_sqlalchemy_resource_security.py +54 -0
- pyrsql-0.0.3/tests/security/test_sqlalchemy_injection.py +77 -0
- pyrsql-0.0.3/tests/security/test_sqlalchemy_jsonpath_security.py +70 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/adapters/fastapi_adapter/conftest.py +11 -16
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/adapters/fastapi_adapter/test_config.py +11 -9
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/adapters/fastapi_adapter/test_criteria.py +1 -22
- pyrsql-0.0.3/tests/unit/adapters/fastapi_adapter/test_errors.py +30 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/json/test_options.py +14 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/json/test_path.py +7 -18
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/json/test_query.py +13 -22
- pyrsql-0.0.3/tests/unit/core/json/test_values.py +72 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/test_conversion.py +82 -0
- pyrsql-0.0.3/tests/unit/core/test_custom.py +22 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/test_options.py +79 -2
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/test_page.py +0 -2
- pyrsql-0.0.3/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +97 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +184 -51
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/test_resolver.py +115 -8
- pyrsql-0.0.3/tests/unit/orms/sqlalchemy/test_statement.py +61 -0
- pyrsql-0.0.3/tests/unit/parsing/test_ast.py +66 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/parsing/test_lexer.py +32 -0
- pyrsql-0.0.3/tests/unit/parsing/test_operators.py +105 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/parsing/test_parser.py +83 -10
- pyrsql-0.0.3/tests/unit/parsing/test_source.py +27 -0
- pyrsql-0.0.3/tests/unit/selector/test_ast.py +30 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/selector/test_parser.py +100 -1
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/semantic/test_binder.py +39 -18
- pyrsql-0.0.3/tests/unit/sorting/test_ast.py +26 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/sorting/test_binder.py +32 -9
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/sorting/test_parser.py +74 -0
- 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/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/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/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/base.py +0 -108
- pyrsql-0.0.2/src/pyrsql/orms/sqlalchemy/type_inference.py +0 -35
- 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/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/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.3}/docs/reference/operators.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/custom-predicates.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/field-policies.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/join-hints.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/json.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/page.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/query.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/sort.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/docs/usage/value-conversion.md +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/adapters/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/adapters/fastapi/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/api.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/core/joins.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/parsing/tokens.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/py.typed +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/semantic/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/src/pyrsql/sorting/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/functional/test_public_api.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_conversion_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_json_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_json_path_builder_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_parser_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_selector_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_semantic_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/adapters/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/json/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/test_query.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/core/test_sort.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/integrations/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/parsing/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/selector/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/tests/unit/semantic/__init__.py +0 -0
- {pyrsql-0.0.2 → pyrsql-0.0.3}/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.3
|
|
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,13 @@ 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
48
|
|
|
49
49
|
**Current backends:** SQLAlchemy 2.0
|
|
50
50
|
**Current framework adapters:** FastAPI
|
|
@@ -53,9 +53,10 @@ your API without coupling to a specific ORM or framework.
|
|
|
53
53
|
## Why pyrsql?
|
|
54
54
|
|
|
55
55
|
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
|
|
56
|
+
pyrsql is built as a **compiler pipeline**. Parsing, semantic analysis, and
|
|
57
|
+
backend lowering are separate stages. Adding a new ORM backend means
|
|
58
|
+
implementing one interface (`ORM`), not rewriting the parser or query
|
|
59
|
+
language.
|
|
59
60
|
|
|
60
61
|
- **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
|
|
61
62
|
- **Pluggable backends** - implement `compile_query` / `compile_sort` /
|
|
@@ -64,6 +65,10 @@ one interface (`ORM`), not rewriting the parser or query language.
|
|
|
64
65
|
- **Custom operators** - define your own RSQL operators with per-ORM lowering
|
|
65
66
|
- **Field policies** - whitelist, blacklist, aliases at global and per-model level
|
|
66
67
|
- **Type-safe** - strict mypy, Google-style docstrings, immutable value objects
|
|
68
|
+
- **Performance-oriented internals** - immutable `msgspec` models across the
|
|
69
|
+
core pipeline and configuration objects
|
|
70
|
+
- **Security-oriented request handling** - parser/sort limits, structural
|
|
71
|
+
allowlists/blocklists, sanitized FastAPI error payloads
|
|
67
72
|
|
|
68
73
|
## Quickstart
|
|
69
74
|
|
|
@@ -196,6 +201,15 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
196
201
|
- `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
|
|
197
202
|
- Declarative `resource()` with auto-generated OpenAPI examples
|
|
198
203
|
- `applier_dependency` for custom base statements
|
|
204
|
+
- Compatible with both sync `Session` and async `AsyncSession` execution
|
|
205
|
+
- FastAPI parse and page-validation failures become structured `HTTP 400` payloads
|
|
206
|
+
- FastAPI semantic and backend integration failures become structured `HTTP 422` payloads
|
|
207
|
+
|
|
208
|
+
### Concurrency and Validation
|
|
209
|
+
|
|
210
|
+
- Shared integration and ORM metadata caches are protected for free-threaded execution
|
|
211
|
+
- Async support is validated for adapter, ORM, and integration flows
|
|
212
|
+
- Dedicated async, free-threaded, and security test suites validate these flows
|
|
199
213
|
|
|
200
214
|
## Documentation
|
|
201
215
|
|
|
@@ -204,12 +218,13 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
|
|
|
204
218
|
| Section | Description |
|
|
205
219
|
|---------|-------------|
|
|
206
220
|
| [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 |
|
|
221
|
+
| [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, async flows, custom predicates |
|
|
208
222
|
| [API Reference](https://wskr00.github.io/pyrsql/reference/api/) | Auto-generated from docstrings |
|
|
209
223
|
| [Operators](https://wskr00.github.io/pyrsql/reference/operators/) | Complete operator table |
|
|
210
224
|
| [Options](https://wskr00.github.io/pyrsql/reference/options/) | QueryOptions, SortOptions, JSONOptions |
|
|
211
225
|
| [Architecture](https://wskr00.github.io/pyrsql/explanation/architecture/) | Pipeline, modules, design |
|
|
212
226
|
| [Extensibility](https://wskr00.github.io/pyrsql/explanation/extensibility/) | Adding backends and adapters |
|
|
227
|
+
| [Testing](https://wskr00.github.io/pyrsql/testing/) | Test layers, async, security, free-threaded validation |
|
|
213
228
|
| [Contributing](https://wskr00.github.io/pyrsql/contributing/) | Setup, workflow, standards |
|
|
214
229
|
|
|
215
230
|
## Development Principles
|
|
@@ -3,13 +3,13 @@
|
|
|
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
13
|
|
|
14
14
|
**Current backends:** SQLAlchemy 2.0
|
|
15
15
|
**Current framework adapters:** FastAPI
|
|
@@ -18,9 +18,10 @@ your API without coupling to a specific ORM or framework.
|
|
|
18
18
|
## Why pyrsql?
|
|
19
19
|
|
|
20
20
|
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
|
|
21
|
+
pyrsql is built as a **compiler pipeline**. Parsing, semantic analysis, and
|
|
22
|
+
backend lowering are separate stages. Adding a new ORM backend means
|
|
23
|
+
implementing one interface (`ORM`), not rewriting the parser or query
|
|
24
|
+
language.
|
|
24
25
|
|
|
25
26
|
- **ORM-neutral core** - `Query`, `Sort`, `PageRequest` have zero ORM dependencies
|
|
26
27
|
- **Pluggable backends** - implement `compile_query` / `compile_sort` /
|
|
@@ -29,6 +30,10 @@ one interface (`ORM`), not rewriting the parser or query language.
|
|
|
29
30
|
- **Custom operators** - define your own RSQL operators with per-ORM lowering
|
|
30
31
|
- **Field policies** - whitelist, blacklist, aliases at global and per-model level
|
|
31
32
|
- **Type-safe** - strict mypy, Google-style docstrings, immutable value objects
|
|
33
|
+
- **Performance-oriented internals** - immutable `msgspec` models across the
|
|
34
|
+
core pipeline and configuration objects
|
|
35
|
+
- **Security-oriented request handling** - parser/sort limits, structural
|
|
36
|
+
allowlists/blocklists, sanitized FastAPI error payloads
|
|
32
37
|
|
|
33
38
|
## Quickstart
|
|
34
39
|
|
|
@@ -161,6 +166,15 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
161
166
|
- `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
|
|
162
167
|
- Declarative `resource()` with auto-generated OpenAPI examples
|
|
163
168
|
- `applier_dependency` for custom base statements
|
|
169
|
+
- Compatible with both sync `Session` and async `AsyncSession` execution
|
|
170
|
+
- FastAPI parse and page-validation failures become structured `HTTP 400` payloads
|
|
171
|
+
- FastAPI semantic and backend integration failures become structured `HTTP 422` payloads
|
|
172
|
+
|
|
173
|
+
### Concurrency and Validation
|
|
174
|
+
|
|
175
|
+
- Shared integration and ORM metadata caches are protected for free-threaded execution
|
|
176
|
+
- Async support is validated for adapter, ORM, and integration flows
|
|
177
|
+
- Dedicated async, free-threaded, and security test suites validate these flows
|
|
164
178
|
|
|
165
179
|
## Documentation
|
|
166
180
|
|
|
@@ -169,12 +183,13 @@ Full documentation at **[wskr00.github.io/pyrsql](https://wskr00.github.io/pyrsq
|
|
|
169
183
|
| Section | Description |
|
|
170
184
|
|---------|-------------|
|
|
171
185
|
| [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 |
|
|
186
|
+
| [Usage](https://wskr00.github.io/pyrsql/usage/query/) | Filter, sort, page, JSON, FastAPI, async flows, custom predicates |
|
|
173
187
|
| [API Reference](https://wskr00.github.io/pyrsql/reference/api/) | Auto-generated from docstrings |
|
|
174
188
|
| [Operators](https://wskr00.github.io/pyrsql/reference/operators/) | Complete operator table |
|
|
175
189
|
| [Options](https://wskr00.github.io/pyrsql/reference/options/) | QueryOptions, SortOptions, JSONOptions |
|
|
176
190
|
| [Architecture](https://wskr00.github.io/pyrsql/explanation/architecture/) | Pipeline, modules, design |
|
|
177
191
|
| [Extensibility](https://wskr00.github.io/pyrsql/explanation/extensibility/) | Adding backends and adapters |
|
|
192
|
+
| [Testing](https://wskr00.github.io/pyrsql/testing/) | Test layers, async, security, free-threaded validation |
|
|
178
193
|
| [Contributing](https://wskr00.github.io/pyrsql/contributing/) | Setup, workflow, standards |
|
|
179
194
|
|
|
180
195
|
## 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,245 @@
|
|
|
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
|
+
- `CompilationResult`, `SortCompilationResult`, `PageCompilationResult`
|
|
112
|
+
|
|
113
|
+
The `core/json/` package owns JSON-aware comparison models and value
|
|
114
|
+
normalization, keeping JSON semantics ORM-neutral.
|
|
115
|
+
|
|
116
|
+
### `orms`
|
|
117
|
+
|
|
118
|
+
Owns backend lowering.
|
|
119
|
+
|
|
120
|
+
Current production backend:
|
|
121
|
+
|
|
122
|
+
- `orms.sqlalchemy`
|
|
123
|
+
|
|
124
|
+
Its job is:
|
|
125
|
+
|
|
126
|
+
- consume normalized query, sort, and page objects
|
|
127
|
+
- resolve model metadata
|
|
128
|
+
- coerce values
|
|
129
|
+
- lower to SQLAlchemy statements
|
|
130
|
+
|
|
131
|
+
It should not redo semantic interpretation already handled upstream.
|
|
132
|
+
|
|
133
|
+
For PostgreSQL JSON/JSONB lowering, the current design separates:
|
|
134
|
+
|
|
135
|
+
- whole-document JSON predicates
|
|
136
|
+
- nested JSON path predicates
|
|
137
|
+
- JSON sort extraction rules
|
|
138
|
+
|
|
139
|
+
This keeps the backend close to SQLAlchemy and PostgreSQL primitives instead of
|
|
140
|
+
forcing every JSON case through one generic lowering path.
|
|
141
|
+
|
|
142
|
+
### `adapters`
|
|
143
|
+
|
|
144
|
+
Own framework-level request and error adaptation.
|
|
145
|
+
|
|
146
|
+
Current adapter:
|
|
147
|
+
|
|
148
|
+
- `adapters.fastapi`
|
|
149
|
+
|
|
150
|
+
It is responsible for:
|
|
151
|
+
|
|
152
|
+
- extracting query params
|
|
153
|
+
- building `RequestCriteria`
|
|
154
|
+
- translating pyrsql failures into framework-native HTTP errors
|
|
155
|
+
- adding OpenAPI metadata
|
|
156
|
+
|
|
157
|
+
### `integrations`
|
|
158
|
+
|
|
159
|
+
Own stack-specific orchestration across adapter + backend.
|
|
160
|
+
|
|
161
|
+
Current integration:
|
|
162
|
+
|
|
163
|
+
- `integrations.fastapi.sqlalchemy`
|
|
164
|
+
|
|
165
|
+
It is responsible for:
|
|
166
|
+
|
|
167
|
+
- request criteria application helpers
|
|
168
|
+
- route-ready dependencies
|
|
169
|
+
- declarative resource configuration
|
|
170
|
+
- stack-specific DX
|
|
171
|
+
|
|
172
|
+
### `api`
|
|
173
|
+
|
|
174
|
+
Owns the smallest public helper surface:
|
|
175
|
+
|
|
176
|
+
- `parse(...)`
|
|
177
|
+
- `compile(...)`
|
|
178
|
+
- `apply(...)`
|
|
179
|
+
|
|
180
|
+
## Extensibility
|
|
181
|
+
|
|
182
|
+
### Adding a new ORM backend
|
|
183
|
+
|
|
184
|
+
Implement `pyrsql.orms.base.ORM`:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
class ORM(ABC):
|
|
188
|
+
def compile_query(self, query: Query) -> CompiledQuery: ...
|
|
189
|
+
def compile_sort(self, sort: Sort) -> CompiledSort: ...
|
|
190
|
+
def compile_page_request(self, page_request: PageRequest) -> CompiledPageRequest: ...
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Each method receives one ORM-neutral core object and returns a compiled object
|
|
194
|
+
with an `apply(target, model)` method.
|
|
195
|
+
|
|
196
|
+
### Adding a new framework adapter
|
|
197
|
+
|
|
198
|
+
Implement `pyrsql.adapters.*` to extract query parameters from requests and
|
|
199
|
+
translate pyrsql failures into framework-native HTTP responses.
|
|
200
|
+
|
|
201
|
+
The FastAPI adapter is the reference implementation.
|
|
202
|
+
|
|
203
|
+
## Design Principles
|
|
204
|
+
|
|
205
|
+
The project follows:
|
|
206
|
+
|
|
207
|
+
- object-oriented design
|
|
208
|
+
- SOLID
|
|
209
|
+
- strong typing
|
|
210
|
+
- backend-independent core semantics
|
|
211
|
+
- performance-oriented data modeling
|
|
212
|
+
- explicit concurrency boundaries
|
|
213
|
+
|
|
214
|
+
### Practical interpretation
|
|
215
|
+
|
|
216
|
+
- semantic work should happen before the ORM backend
|
|
217
|
+
- ORMs should do lowering, not semantic analysis
|
|
218
|
+
- adapters should do transport adaptation, not query semantics
|
|
219
|
+
- integrations should provide DX, not execution frameworks
|
|
220
|
+
|
|
221
|
+
## Performance Direction
|
|
222
|
+
|
|
223
|
+
The project uses `msgspec` where it improves compactness and immutable data
|
|
224
|
+
modeling:
|
|
225
|
+
|
|
226
|
+
- diagnostics
|
|
227
|
+
- parser/source structures
|
|
228
|
+
- adapter and integration payloads
|
|
229
|
+
- core query/sort/options/value-policy models
|
|
230
|
+
|
|
231
|
+
Other performance principles:
|
|
232
|
+
|
|
233
|
+
- cache repeated base statements when safe
|
|
234
|
+
- avoid repeating semantic work in the ORM backend
|
|
235
|
+
- keep shared cache lock scope small in concurrency-sensitive paths
|
|
236
|
+
|
|
237
|
+
## Async and Free-Threaded Support
|
|
238
|
+
|
|
239
|
+
These are related but separate concerns:
|
|
240
|
+
|
|
241
|
+
- async support means generated SQLAlchemy statements work with both `Session`
|
|
242
|
+
and `AsyncSession`
|
|
243
|
+
- free-threaded support means shared mutable caches are synchronized explicitly
|
|
244
|
+
|
|
245
|
+
The core query pipeline stays synchronous and ORM-neutral in both cases.
|
|
@@ -0,0 +1,90 @@
|
|
|
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.orms.base import ORM, CompiledPageRequest, CompiledQuery, CompiledSort
|
|
13
|
+
|
|
14
|
+
class MyORM(ORM):
|
|
15
|
+
@property
|
|
16
|
+
def name(self) -> str:
|
|
17
|
+
return "my-orm"
|
|
18
|
+
|
|
19
|
+
def compile_query(self, query: Query) -> CompiledQuery:
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
def compile_sort(self, sort: Sort) -> CompiledSort:
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def compile_page_request(self, page_request: PageRequest) -> CompiledPageRequest:
|
|
26
|
+
...
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Each `compile_*` method receives one ORM-neutral core object (`Query`, `Sort`,
|
|
30
|
+
or `PageRequest`) and returns a compiled object with an `apply(target, model)`
|
|
31
|
+
method.
|
|
32
|
+
|
|
33
|
+
The current reference implementation is `SQLAlchemyORM` in
|
|
34
|
+
`pyrsql.orms.sqlalchemy`.
|
|
35
|
+
|
|
36
|
+
## Adding a new framework adapter
|
|
37
|
+
|
|
38
|
+
Create a module under `pyrsql.adapters` that:
|
|
39
|
+
|
|
40
|
+
1. extracts query parameters from requests
|
|
41
|
+
2. builds `RequestCriteria`
|
|
42
|
+
3. translates parse, semantic, and page-validation failures into
|
|
43
|
+
framework-appropriate error responses
|
|
44
|
+
|
|
45
|
+
The FastAPI adapter (`pyrsql.adapters.fastapi`) is the reference:
|
|
46
|
+
|
|
47
|
+
- `FastAPICriteriaConfig` holds public parameter names and defaults
|
|
48
|
+
- `criteria_dependency()` returns a FastAPI dependency callable
|
|
49
|
+
|
|
50
|
+
## Backend contract in detail
|
|
51
|
+
|
|
52
|
+
### `CompiledQuery`
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
class CompiledQuery(Protocol):
|
|
56
|
+
def apply(self, target: _TargetT, model: type[_ModelT]) -> _TargetT: ...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Receives an ORM-specific target and model class, then returns the modified
|
|
60
|
+
target with query conditions applied.
|
|
61
|
+
|
|
62
|
+
### `CompiledSort`
|
|
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`.
|
|
69
|
+
|
|
70
|
+
## Integration layer
|
|
71
|
+
|
|
72
|
+
Adapters and ORMs can stay independent. The `integrations/` package is where
|
|
73
|
+
stack-specific DX belongs.
|
|
74
|
+
|
|
75
|
+
For example, `integrations.fastapi.sqlalchemy` is allowed to know both:
|
|
76
|
+
|
|
77
|
+
- the FastAPI adapter contract
|
|
78
|
+
- the SQLAlchemy backend contract
|
|
79
|
+
|
|
80
|
+
That is the right place for:
|
|
81
|
+
|
|
82
|
+
- route-ready dependencies
|
|
83
|
+
- paginated statement bundles
|
|
84
|
+
- OpenAPI example generation tied to SQLAlchemy model metadata
|
|
85
|
+
|
|
86
|
+
## Planned backends and adapters
|
|
87
|
+
|
|
88
|
+
- **Django ORM** - compile to Django `QuerySet`
|
|
89
|
+
- **SQLModel** - likely reuse most SQLAlchemy lowering
|
|
90
|
+
- **Flask** - request adapter for `request.args`
|
|
@@ -34,3 +34,25 @@ stmt = pyrsql.parse("name==demo;age=gt=18").apply(select(User), User, orm=orm)
|
|
|
34
34
|
- **Field policies** - whitelist, blacklist, aliases (global + per-model)
|
|
35
35
|
- **JSON / JSONB** - PostgreSQL `jsonpath` with structured values and datetime support
|
|
36
36
|
- **Type-safe** - strict mypy, Google-style docstrings, immutable value objects
|
|
37
|
+
- **Async-compatible execution** - generated SQLAlchemy statements work with
|
|
38
|
+
both `Session` and `AsyncSession`
|
|
39
|
+
- **Free-threaded-safe shared caches** - integration and ORM helper caches are
|
|
40
|
+
guarded for concurrent access
|
|
41
|
+
- **Security-oriented request handling** - parser/sort limits, structural
|
|
42
|
+
allowlists/blocklists, and sanitized FastAPI error payloads
|
|
43
|
+
|
|
44
|
+
## Integration options
|
|
45
|
+
|
|
46
|
+
- **FastAPI adapter** - parse `filter`, `sort`, `page`, and `size` into
|
|
47
|
+
`RequestCriteria`
|
|
48
|
+
- **FastAPI + SQLAlchemy integration** - route-ready dependencies,
|
|
49
|
+
declarative `resource()` helpers, count/paginated bundles, and normalized
|
|
50
|
+
backend error translation
|
|
51
|
+
|
|
52
|
+
## Next steps
|
|
53
|
+
|
|
54
|
+
- [Quickstart](quickstart.md)
|
|
55
|
+
- [FastAPI Adapter](usage/fastapi.md)
|
|
56
|
+
- [FastAPI + SQLAlchemy](usage/fastapi-sqlalchemy.md)
|
|
57
|
+
- [Async Flows](usage/async.md)
|
|
58
|
+
- [Architecture](explanation/architecture.md)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install pyrsql # core only
|
|
7
|
+
pip install pyrsql[sqlalchemy] # with SQLAlchemy
|
|
8
|
+
pip install pyrsql[fastapi] # with FastAPI
|
|
9
|
+
pip install pyrsql[fastapi,sqlalchemy] # both
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Filter
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from pyrsql import Query
|
|
16
|
+
|
|
17
|
+
Query.parse("name==demo") # equal
|
|
18
|
+
Query.parse("age=gt=18") # greater than
|
|
19
|
+
Query.parse("id=in=(1,2,3)") # IN
|
|
20
|
+
Query.parse("name=like=demo*") # LIKE
|
|
21
|
+
Query.parse("score=bt=(10,20)") # BETWEEN
|
|
22
|
+
Query.parse("name==demo;age=gt=18") # AND
|
|
23
|
+
Query.parse("name==demo,age=gt=18") # OR
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Sort
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from pyrsql import Sort
|
|
30
|
+
|
|
31
|
+
Sort.parse("name") # ascending
|
|
32
|
+
Sort.parse("name,desc") # descending
|
|
33
|
+
Sort.parse("name,asc;company.name,desc") # multi-field
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Paginate
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pyrsql import PageRequest
|
|
40
|
+
|
|
41
|
+
PageRequest.of(0, 25) # first page, 25 items
|
|
42
|
+
PageRequest.of(2, 25) # third page, 25 items
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## SQLAlchemy
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pyrsql import PageRequest, Query, Sort
|
|
49
|
+
from pyrsql.orms.sqlalchemy import SQLAlchemyORM
|
|
50
|
+
from sqlalchemy import select
|
|
51
|
+
|
|
52
|
+
orm = SQLAlchemyORM()
|
|
53
|
+
|
|
54
|
+
stmt = select(User)
|
|
55
|
+
stmt = Query.parse("name==demo;company.name==acme*").apply(
|
|
56
|
+
stmt,
|
|
57
|
+
User,
|
|
58
|
+
orm=orm,
|
|
59
|
+
)
|
|
60
|
+
stmt = Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
|
|
61
|
+
stmt = PageRequest.of(0, 25).apply(stmt, User, orm=orm)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## FastAPI
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from typing import Annotated
|
|
68
|
+
|
|
69
|
+
from fastapi import Depends, FastAPI
|
|
70
|
+
from pyrsql.adapters.fastapi import RequestCriteria, criteria_dependency
|
|
71
|
+
|
|
72
|
+
app = FastAPI()
|
|
73
|
+
|
|
74
|
+
@app.get("/items")
|
|
75
|
+
def list_items(
|
|
76
|
+
criteria: Annotated[RequestCriteria, Depends(criteria_dependency())],
|
|
77
|
+
):
|
|
78
|
+
return {"is_empty": criteria.is_empty}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Query params `filter`, `sort`, `page`, `size` are extracted automatically.
|
|
82
|
+
Parse and page-validation errors become structured `HTTP 400` responses.
|
|
83
|
+
Semantic and backend integration errors become structured `HTTP 422`
|
|
84
|
+
responses.
|
|
85
|
+
|
|
86
|
+
## FastAPI + SQLAlchemy
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from typing import Annotated, Any
|
|
90
|
+
|
|
91
|
+
from fastapi import Depends, FastAPI
|
|
92
|
+
|
|
93
|
+
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
94
|
+
|
|
95
|
+
app = FastAPI()
|
|
96
|
+
integration = FastAPISQLAlchemyIntegration()
|
|
97
|
+
|
|
98
|
+
@app.get("/users")
|
|
99
|
+
def list_users(
|
|
100
|
+
stmt: Annotated[Any, Depends(integration.select_dependency(User))],
|
|
101
|
+
):
|
|
102
|
+
return {"sql": str(stmt)}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Also available:
|
|
106
|
+
|
|
107
|
+
- `count_select_dependency(...)`
|
|
108
|
+
- `paginated_select_dependency(...)`
|
|
109
|
+
- declarative `resource(...)`
|
|
110
|
+
|
|
111
|
+
## Async execution
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from typing import Annotated, Any
|
|
115
|
+
|
|
116
|
+
from fastapi import Depends, FastAPI
|
|
117
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
118
|
+
|
|
119
|
+
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
|
|
120
|
+
|
|
121
|
+
app = FastAPI()
|
|
122
|
+
integration = FastAPISQLAlchemyIntegration()
|
|
123
|
+
|
|
124
|
+
@app.get("/users")
|
|
125
|
+
async def list_users(
|
|
126
|
+
stmt: Annotated[Any, Depends(integration.select_dependency(User))],
|
|
127
|
+
session: Annotated[AsyncSession, Depends(get_async_session)],
|
|
128
|
+
):
|
|
129
|
+
result = await session.scalars(stmt)
|
|
130
|
+
return result.all()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`pyrsql` builds statements only; your application decides whether to execute
|
|
134
|
+
them through sync or async SQLAlchemy sessions.
|
|
135
|
+
|
|
136
|
+
## Notes
|
|
137
|
+
|
|
138
|
+
- `pyrsql` builds statements and criteria objects only. Your application
|
|
139
|
+
decides whether execution happens through sync or async SQLAlchemy sessions.
|
|
140
|
+
- Shared integration and ORM metadata caches are validated for free-threaded
|
|
141
|
+
execution.
|
|
142
|
+
- Dedicated async, free-threaded, and security suites are documented in
|
|
143
|
+
[Testing](testing.md).
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
- `pyrsql.integrations.fastapi` - FastAPI + SQLAlchemy (FastAPISQLAlchemyIntegration, FastAPISQLAlchemyResource)
|
|
37
37
|
- `pyrsql.orms.sqlalchemy` - SQLAlchemy backend (SQLAlchemyORM)
|
|
38
38
|
- `pyrsql.core` - ORM-neutral core types and options
|
|
39
|
-
- `pyrsql.ir` - Logical intermediate representation
|
|
40
39
|
- `pyrsql.parsing` - Lexer, parser, operators, limits
|
|
41
40
|
- `pyrsql.selector` - Selector syntax
|
|
42
41
|
- `pyrsql.semantic` - Semantic binding
|