pyrsql 0.0.3__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.3 → pyrsql-0.0.4}/PKG-INFO +51 -10
- {pyrsql-0.0.3 → pyrsql-0.0.4}/README.md +50 -9
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/explanation/architecture.md +3 -4
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/explanation/extensibility.md +9 -17
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/index.md +27 -9
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/quickstart.md +39 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/reference/api.md +1 -2
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/testing.md +0 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/fastapi-sqlalchemy.md +20 -4
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/fastapi.md +23 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/pyproject.toml +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/__init__.py +0 -6
- {pyrsql-0.0.3 → 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.3 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/criteria.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/dependency.py +130 -53
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/adapters/fastapi/errors.py +9 -14
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/api.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/__init__.py +0 -8
- pyrsql-0.0.4/src/pyrsql/core/compiler.py +15 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/conversion.py +2 -3
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/options.py +41 -153
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/page.py +4 -9
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/query.py +4 -8
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/sort.py +4 -8
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/source_issues.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/helpers.py +4 -16
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/integration.py +21 -103
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/resource.py +10 -80
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/__init__.py +0 -6
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/base.py +5 -19
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_page.py +3 -4
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_query.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/compiled_sort.py +1 -1
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/introspection.py +4 -16
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/json_path.py +6 -9
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/orm.py +10 -33
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/resolver.py +1 -17
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/sorter.py +7 -15
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/translator.py +3 -16
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/type_inference.py +5 -8
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/types.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/__init__.py +1 -4
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/ast.py +4 -20
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/errors.py +0 -2
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/lexer.py +7 -29
- pyrsql-0.0.4/src/pyrsql/parsing/limits.py +19 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/operators.py +1 -92
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/parser.py +8 -15
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/source.py +0 -23
- pyrsql-0.0.4/src/pyrsql/selector/ast.py +31 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/selector/parser.py +34 -72
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/semantic/binder.py +0 -12
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/binder.py +0 -6
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/errors.py +1 -1
- pyrsql-0.0.4/src/pyrsql/sorting/limits.py +16 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/parser.py +3 -36
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/test_dependency.py +27 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/test_public_api.py +3 -7
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_hpp.py +21 -1
- pyrsql-0.0.4/tests/unit/adapters/fastapi_adapter/test_config.py +39 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/test_criteria.py +4 -13
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/conftest.py +0 -5
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_options.py +2 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_page.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_query.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_sort.py +3 -3
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/test_integration.py +0 -182
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/parsing/test_lexer.py +0 -32
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/parsing/test_parser.py +0 -72
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/parsing/test_source.py +1 -6
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/selector/test_parser.py +10 -88
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/sorting/test_parser.py +0 -62
- 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.4}/docs/contributing.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/reference/operators.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/reference/options.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/async.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/custom-predicates.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/errors.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/field-policies.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/join-hints.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/json.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/page.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/query.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/sort.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/docs/usage/value-conversion.md +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/adapters/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/binding_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/field_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/joins.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/json/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/json/options.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/json/path.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/json/query.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/json/values.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/procedure_policy.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/core/validation.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/examples.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/integrations/fastapi/sqlalchemy/payloads.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/coercion.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/orms/sqlalchemy/statement.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/parsing/tokens.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/py.typed +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/selector/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/semantic/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/semantic/diagnostics.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/semantic/errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/src/pyrsql/sorting/diagnostics.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_adapter/test_async_dependency.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/test_async_integration.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/functional/fastapi_sqlalchemy/test_integration.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_async_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_page_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_query_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/integration/sqlalchemy/test_sort_pipeline.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_conversion_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_json_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_json_path_builder_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_parser_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_path_resolver_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_selector_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_semantic_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/performance/test_sqlalchemy_translation_bench.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_configuration_hardening.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_error_sanitization.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_injection.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_limits.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_policies.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_fastapi_sqlalchemy_resource_security.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_sqlalchemy_injection.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/security/test_sqlalchemy_jsonpath_security.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/adapters/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/adapters/fastapi_adapter/test_errors.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/json/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/json/test_options.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/json/test_path.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/json/test_query.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/json/test_values.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_conversion.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/core/test_custom.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/fastapi/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/integrations/fastapi_sqlalchemy/test_examples.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/conftest.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_coercion.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_json_path_builder.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_orm.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_resolver.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/orms/sqlalchemy/test_statement.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/parsing/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/parsing/test_ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/selector/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/semantic/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/semantic/test_binder.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/sorting/__init__.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/tests/unit/sorting/test_ast.py +0 -0
- {pyrsql-0.0.3 → pyrsql-0.0.4}/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.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
|
|
@@ -46,6 +46,11 @@ through a language frontend, semantic binding, and pluggable backend
|
|
|
46
46
|
lowering - making it easy to expose complex query capabilities in your API
|
|
47
47
|
without coupling to a specific ORM or framework.
|
|
48
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.
|
|
53
|
+
|
|
49
54
|
**Current backends:** SQLAlchemy 2.0
|
|
50
55
|
**Current framework adapters:** FastAPI
|
|
51
56
|
**Planned:** Django ORM, SQLModel, Flask
|
|
@@ -73,9 +78,41 @@ language.
|
|
|
73
78
|
## Quickstart
|
|
74
79
|
|
|
75
80
|
```bash
|
|
76
|
-
pip install pyrsql[sqlalchemy]
|
|
81
|
+
pip install pyrsql[fastapi,sqlalchemy]
|
|
77
82
|
```
|
|
78
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
|
+
|
|
79
116
|
```python
|
|
80
117
|
import pyrsql
|
|
81
118
|
from sqlalchemy import select
|
|
@@ -84,13 +121,13 @@ from pyrsql.orms.sqlalchemy import SQLAlchemyORM
|
|
|
84
121
|
orm = SQLAlchemyORM()
|
|
85
122
|
stmt = select(User)
|
|
86
123
|
|
|
87
|
-
|
|
88
|
-
stmt
|
|
124
|
+
stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
|
|
125
|
+
stmt,
|
|
126
|
+
User,
|
|
127
|
+
orm=orm,
|
|
128
|
+
)
|
|
89
129
|
|
|
90
|
-
# Sort: by name ascending, then company name descending
|
|
91
130
|
stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
|
|
92
|
-
|
|
93
|
-
# Paginate: page 0, 25 items per page
|
|
94
131
|
stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
|
|
95
132
|
```
|
|
96
133
|
|
|
@@ -168,6 +205,9 @@ options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
|
|
|
168
205
|
|
|
169
206
|
### FastAPI Integration
|
|
170
207
|
|
|
208
|
+
Use this adapter directly when you need only parsed `RequestCriteria`; for
|
|
209
|
+
SQLAlchemy routes, prefer the integration below.
|
|
210
|
+
|
|
171
211
|
```python
|
|
172
212
|
from fastapi import Depends, FastAPI
|
|
173
213
|
from pyrsql.adapters.fastapi import criteria_dependency
|
|
@@ -181,10 +221,11 @@ def list_items(criteria = Depends(dependency)):
|
|
|
181
221
|
```
|
|
182
222
|
|
|
183
223
|
- Auto-extracts `filter`, `sort`, `page`, `size` from query params
|
|
184
|
-
- `HTTP
|
|
224
|
+
- Structured `HTTP 400` parse errors and `HTTP 422` semantic errors
|
|
185
225
|
- OpenAPI examples from configuration
|
|
186
226
|
- One-based paging support
|
|
187
227
|
- Custom query parameter names
|
|
228
|
+
- Optional repeated sort parameters through `SortParameterFormat.REPEATED`
|
|
188
229
|
|
|
189
230
|
### FastAPI + SQLAlchemy Integration
|
|
190
231
|
|
|
@@ -199,7 +240,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
199
240
|
```
|
|
200
241
|
|
|
201
242
|
- `select_dependency`, `count_select_dependency`, `paginated_select_dependency`
|
|
202
|
-
- Declarative `resource()` with auto-generated OpenAPI examples
|
|
243
|
+
- Declarative `resource()` with auto-generated OpenAPI examples and field policies
|
|
203
244
|
- `applier_dependency` for custom base statements
|
|
204
245
|
- Compatible with both sync `Session` and async `AsyncSession` execution
|
|
205
246
|
- FastAPI parse and page-validation failures become structured `HTTP 400` payloads
|
|
@@ -207,7 +248,7 @@ def list_users(stmt = Depends(integration.select_dependency(User))):
|
|
|
207
248
|
|
|
208
249
|
### Concurrency and Validation
|
|
209
250
|
|
|
210
|
-
- Shared
|
|
251
|
+
- Shared base-select and ORM metadata caches are protected for free-threaded execution
|
|
211
252
|
- Async support is validated for adapter, ORM, and integration flows
|
|
212
253
|
- Dedicated async, free-threaded, and security test suites validate these flows
|
|
213
254
|
|
|
@@ -11,6 +11,11 @@ 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
|
+
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
|
+
|
|
14
19
|
**Current backends:** SQLAlchemy 2.0
|
|
15
20
|
**Current framework adapters:** FastAPI
|
|
16
21
|
**Planned:** Django ORM, SQLModel, Flask
|
|
@@ -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
|
|
|
@@ -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,20 +10,38 @@ 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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -4,7 +4,6 @@ from pyrsql import orms
|
|
|
4
4
|
from pyrsql.api import apply, compile, parse # noqa: A004
|
|
5
5
|
from pyrsql.core import (
|
|
6
6
|
DEFAULT_JSON_OPTIONS,
|
|
7
|
-
CompilationResult,
|
|
8
7
|
CustomPredicateDefinition,
|
|
9
8
|
FieldPolicySet,
|
|
10
9
|
FieldValueConverterSet,
|
|
@@ -14,13 +13,11 @@ from pyrsql.core import (
|
|
|
14
13
|
JSONScalarNormalizer,
|
|
15
14
|
JSONScalarValue,
|
|
16
15
|
JSONSortScalarType,
|
|
17
|
-
PageCompilationResult,
|
|
18
16
|
PageRequest,
|
|
19
17
|
ProcedureAccessPolicy,
|
|
20
18
|
Query,
|
|
21
19
|
QueryOptions,
|
|
22
20
|
Sort,
|
|
23
|
-
SortCompilationResult,
|
|
24
21
|
SortOptions,
|
|
25
22
|
ValueConverter,
|
|
26
23
|
ValueConverterRegistry,
|
|
@@ -30,7 +27,6 @@ from pyrsql.orms.base import ORM
|
|
|
30
27
|
__all__ = (
|
|
31
28
|
"DEFAULT_JSON_OPTIONS",
|
|
32
29
|
"ORM",
|
|
33
|
-
"CompilationResult",
|
|
34
30
|
"CustomPredicateDefinition",
|
|
35
31
|
"FieldPolicySet",
|
|
36
32
|
"FieldValueConverterSet",
|
|
@@ -40,13 +36,11 @@ __all__ = (
|
|
|
40
36
|
"JSONScalarNormalizer",
|
|
41
37
|
"JSONScalarValue",
|
|
42
38
|
"JSONSortScalarType",
|
|
43
|
-
"PageCompilationResult",
|
|
44
39
|
"PageRequest",
|
|
45
40
|
"ProcedureAccessPolicy",
|
|
46
41
|
"Query",
|
|
47
42
|
"QueryOptions",
|
|
48
43
|
"Sort",
|
|
49
|
-
"SortCompilationResult",
|
|
50
44
|
"SortOptions",
|
|
51
45
|
"ValueConverter",
|
|
52
46
|
"ValueConverterRegistry",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""FastAPI adapter for pyrsql request extraction and error translation."""
|
|
2
2
|
|
|
3
|
-
from pyrsql.adapters.fastapi.config import
|
|
3
|
+
from pyrsql.adapters.fastapi.config import (
|
|
4
|
+
FastAPICriteriaConfig,
|
|
5
|
+
SortParameterFormat,
|
|
6
|
+
)
|
|
4
7
|
from pyrsql.adapters.fastapi.criteria import RequestCriteria
|
|
5
8
|
from pyrsql.adapters.fastapi.dependency import (
|
|
6
9
|
CriteriaDependency,
|
|
@@ -11,5 +14,6 @@ __all__ = (
|
|
|
11
14
|
"CriteriaDependency",
|
|
12
15
|
"FastAPICriteriaConfig",
|
|
13
16
|
"RequestCriteria",
|
|
17
|
+
"SortParameterFormat",
|
|
14
18
|
"criteria_dependency",
|
|
15
19
|
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Configuration objects for the FastAPI adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
import msgspec
|
|
10
|
+
|
|
11
|
+
from pyrsql.core.options import QueryOptions, SortOptions
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
|
|
16
|
+
_DEFAULT_FILTER_PARAMETER = "filter"
|
|
17
|
+
_DEFAULT_SORT_PARAMETER = "sort"
|
|
18
|
+
_DEFAULT_PAGE_PARAMETER = "page"
|
|
19
|
+
_DEFAULT_SIZE_PARAMETER = "size"
|
|
20
|
+
_DEFAULT_QUERY_OPTIONS = QueryOptions()
|
|
21
|
+
_DEFAULT_SORT_OPTIONS = SortOptions()
|
|
22
|
+
_EMPTY_OPENAPI_EXAMPLES: Mapping[str, Any] = MappingProxyType({})
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SortParameterFormat(Enum):
|
|
26
|
+
"""Supported HTTP representations for sort criteria."""
|
|
27
|
+
|
|
28
|
+
SEMICOLON = "semicolon"
|
|
29
|
+
REPEATED = "repeated"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FastAPICriteriaConfig(
|
|
33
|
+
msgspec.Struct,
|
|
34
|
+
frozen=True,
|
|
35
|
+
gc=False,
|
|
36
|
+
kw_only=True,
|
|
37
|
+
):
|
|
38
|
+
"""Configures FastAPI query parameter extraction for pyrsql.
|
|
39
|
+
|
|
40
|
+
Attributes:
|
|
41
|
+
filter_parameter: Query parameter used for filtering expressions.
|
|
42
|
+
sort_parameter: Query parameter used for sort expressions.
|
|
43
|
+
sort_parameter_format: HTTP representation accepted for sort criteria.
|
|
44
|
+
page_parameter: Query parameter used for page numbers.
|
|
45
|
+
size_parameter: Query parameter used for page size.
|
|
46
|
+
default_page_size: Default size used when only a page number is given.
|
|
47
|
+
max_page_size: Maximum accepted page size.
|
|
48
|
+
one_based_paging: Whether page numbers start at 1 instead of 0.
|
|
49
|
+
query_options: Query parsing and semantic configuration.
|
|
50
|
+
sort_options: Sort parsing and semantic configuration.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
filter_parameter: str = _DEFAULT_FILTER_PARAMETER
|
|
54
|
+
sort_parameter: str = _DEFAULT_SORT_PARAMETER
|
|
55
|
+
sort_parameter_format: SortParameterFormat = SortParameterFormat.SEMICOLON
|
|
56
|
+
page_parameter: str = _DEFAULT_PAGE_PARAMETER
|
|
57
|
+
size_parameter: str = _DEFAULT_SIZE_PARAMETER
|
|
58
|
+
default_page_size: int | None = None
|
|
59
|
+
max_page_size: int | None = None
|
|
60
|
+
one_based_paging: bool = False
|
|
61
|
+
query_options: QueryOptions = _DEFAULT_QUERY_OPTIONS
|
|
62
|
+
sort_options: SortOptions = _DEFAULT_SORT_OPTIONS
|
|
63
|
+
filter_openapi_examples: Mapping[str, Any] = _EMPTY_OPENAPI_EXAMPLES
|
|
64
|
+
sort_openapi_examples: Mapping[str, Any] = _EMPTY_OPENAPI_EXAMPLES
|
|
65
|
+
page_openapi_examples: Mapping[str, Any] = _EMPTY_OPENAPI_EXAMPLES
|
|
66
|
+
size_openapi_examples: Mapping[str, Any] = _EMPTY_OPENAPI_EXAMPLES
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def minimum_page_number(self) -> int:
|
|
70
|
+
"""The minimum accepted page number for the adapter.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The lowest valid page number accepted by the adapter.
|
|
74
|
+
"""
|
|
75
|
+
return 1 if self.one_based_paging else 0
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def default_page_number(self) -> int:
|
|
79
|
+
"""The page number used when only size is provided.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The implicit page number used when only page size is provided.
|
|
83
|
+
"""
|
|
84
|
+
return 1 if self.one_based_paging else 0
|