pyrsql
A compiler-oriented RSQL query engine for safe, typed, and extensible filtering, sorting, and pagination in Python APIs.
pyrsql compiles RSQL query strings into ORM-specific statement objects through a language frontend, semantic binding, and pluggable backend lowering - making it easy to expose complex query capabilities in your API without coupling to a specific ORM or framework.
For FastAPI applications using SQLAlchemy, the recommended entry point is
FastAPISQLAlchemyIntegration. It provides route-ready dependencies and
declarative resources; the lower-level query, adapter, and ORM APIs remain
available when more control is needed.
Current backends: SQLAlchemy 2.0
Current framework adapters: FastAPI
Planned: Django ORM, SQLModel, Flask
Why pyrsql?
Most API filtering libraries are tightly coupled to one ORM or one framework.
pyrsql is built as a compiler pipeline. Parsing, semantic analysis, and
backend lowering are separate stages. Adding a new ORM backend means
implementing one interface (ORM), not rewriting the parser or query
language.
- ORM-neutral core -
Query,Sort,PageRequesthave zero ORM dependencies - Pluggable backends - implement
compile_query/compile_sort/compile_page_requestfor any ORM - Pluggable framework adapters - FastAPI today, Flask/Django tomorrow
- Custom operators - define your own RSQL operators with per-ORM lowering
- Field policies - whitelist, blacklist, aliases at global and per-model level
- Type-safe - strict mypy, Google-style docstrings, immutable value objects
- Performance-oriented internals - immutable
msgspecmodels across the core pipeline and configuration objects - Security-oriented request handling - parser/sort limits, structural allowlists/blocklists, sanitized FastAPI error payloads
Quickstart
pip install pyrsql[fastapi,sqlalchemy]
from typing import Annotated, Any
from fastapi import Depends, FastAPI
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
app = FastAPI()
integration = FastAPISQLAlchemyIntegration()
users = integration.resource(
User,
filterable_fields={"id", "name"},
sortable_fields={"name"},
default_sort="name,asc",
max_page_size=100,
)
@app.get("/users")
def list_users(
stmt: Annotated[Any, Depends(users.select_dependency())],
):
return {"sql": str(stmt)}
Clients can now use filter, sort, page, and size query parameters.
See FastAPI + SQLAlchemy
for count, pagination, custom base statements, error behavior, and async use.
Lower-level SQLAlchemy API
Use the direct API when you do not need a FastAPI route dependency:
import pyrsql
from sqlalchemy import select
from pyrsql.orms.sqlalchemy import SQLAlchemyORM
orm = SQLAlchemyORM()
stmt = select(User)
stmt = pyrsql.parse("name==demo;company.name==acme*").apply(
stmt,
User,
orm=orm,
)
stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)
stmt = pyrsql.PageRequest.of(0, 25).apply(stmt, User, orm=orm)
Features
Query (filter)
- 20+ built-in operators:
==,!=,=gt=,=ge=,=lt=,=le=,=in=,=out=,=like=,=ilike=,=bt=,=na=,=nn=, etc. - Logical composition:
;(AND),,(OR) - Grouping with parentheses
- Wildcard matching (
*demo*) and case-insensitive markers (^demo) - Strict equality mode (literal
*and^) - Configurable
LIKEescape character SELECT DISTINCTsupport- Configurable parser limits (query length, depth, argument count)
Sort
- Multi-field:
name,asc;company.name,desc,ic - Ignore-case modifier (
ic) - Function selectors:
@upper[name],asc
Pagination
- Page-number + page-size API:
PageRequest.of(0, 25) - Offset + limit API:
PageRequest.from_offset(offset=50, limit=25) - Configurable max page size
Field Mapping & Access Control
- Aliases:
field_mapping={"username": "user.name"} - Whitelist/blacklist:
field_whitelist,field_blacklist - Per-model policies:
model_field_mapping,model_field_whitelist,model_field_blacklist
Custom Predicates
Define custom operators with ORM-specific lowering:
from pyrsql import CustomPredicateDefinition, QueryOptions
from pyrsql.parsing.operators import ComparisonOperator
all_match = ComparisonOperator(name="all_match", spellings=("=all=",), ...)
options = QueryOptions(
custom_predicates={
"all_match": CustomPredicateDefinition(operator=all_match, argument_type=str),
},
)
Value Conversion
- Built-in:
bool,int,float,Decimal,UUID,date,time,datetime,enum - Custom converters:
ValueConverterRegistry+with_converter - Field-scoped:
field_value_converters={"created_at": my_converter} - Model-scoped:
model_field_value_converters={MyModel: {"field": my_converter}}
Join Hints
from pyrsql import QueryOptions
from pyrsql.core.joins import JoinHint
options = QueryOptions(join_hints={"User.company": JoinHint.LEFT})
PostgreSQL JSON / JSONB
- Whole-document: direct
==,!=,=in=,=out=,=na=,=nn=against JSONB columns - Nested path:
jsonb_path_existsvia PostgreSQLjsonpath - Structured values: arrays and objects passed as
jsonpathvars - Temporal:
JSONOptions(use_datetime=True)for datetime-awarejsonpath - JSON sort: text, integer, float, numeric, boolean, date, time, datetime
- Custom function names:
JSONOptions(path_exists_function=...)
FastAPI Integration
Use this adapter directly when you need only parsed RequestCriteria; for
SQLAlchemy routes, prefer the integration below.
from fastapi import Depends, FastAPI
from pyrsql.adapters.fastapi import criteria_dependency
app = FastAPI()
dependency = criteria_dependency()
@app.get("/items")
def list_items(criteria = Depends(dependency)):
...
- Auto-extracts
filter,sort,page,sizefrom query params - Structured
HTTP 400parse errors andHTTP 422semantic errors - OpenAPI examples from configuration
- One-based paging support
- Custom query parameter names
- Optional repeated sort parameters through
SortParameterFormat.REPEATED
FastAPI + SQLAlchemy Integration
from pyrsql.integrations.fastapi import FastAPISQLAlchemyIntegration
integration = FastAPISQLAlchemyIntegration()
@app.get("/users")
def list_users(stmt = Depends(integration.select_dependency(User))):
return {"sql": str(stmt)}
select_dependency,count_select_dependency,paginated_select_dependency- Declarative
resource()with auto-generated OpenAPI examples and field policies applier_dependencyfor custom base statements- Compatible with both sync
Sessionand asyncAsyncSessionexecution - FastAPI parse and page-validation failures become structured
HTTP 400payloads - FastAPI semantic and backend integration failures become structured
HTTP 422payloads
Concurrency and Validation
- Shared base-select and ORM metadata caches are protected for free-threaded execution
- Async support is validated for adapter, ORM, and integration flows
- Dedicated async, free-threaded, and security test suites validate these flows
Documentation
Full documentation at wskr00.github.io/pyrsql.
| Section | Description |
|---|---|
| Quickstart | One-minute primer |
| Usage | Filter, sort, page, JSON, FastAPI, async flows, custom predicates |
| API Reference | Auto-generated from docstrings |
| Operators | Complete operator table |
| Options | QueryOptions, SortOptions, JSONOptions |
| Architecture | Pipeline, modules, design |
| Extensibility | Adding backends and adapters |
| Testing | Test layers, async, security, free-threaded validation |
| Contributing | Setup, workflow, standards |
Development Principles
- Object-oriented design
- SOLID principles
- Google Python Style Guide
- Strong typing
- ORM-neutral public API
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrsql-0.0.4.tar.gz.
File metadata
- Download URL: pyrsql-0.0.4.tar.gz
- Upload date:
- Size: 113.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bcbffe456277690978a2610218905aa0884a59ddf920bc9e1a092a2fb1781e6
|
|
| MD5 |
a2283609dbdd391d6866c81a2e204dfb
|
|
| BLAKE2b-256 |
970886abe7f88c9da22e87eff6ed5c333e33179882a7cd64bf07768284571e65
|
File details
Details for the file pyrsql-0.0.4-py3-none-any.whl.
File metadata
- Download URL: pyrsql-0.0.4-py3-none-any.whl
- Upload date:
- Size: 89.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42f4305c0176fc70eeb730591580a968778cb3f2f4d2ac21e8337003b394940f
|
|
| MD5 |
0edbefb77a0ea149b0fa969787516260
|
|
| BLAKE2b-256 |
8f72417845f5ca594a46a99bb750af03978caab6a71f2ae338dafee23722608d
|