Skip to main content

pyrsql

A compiler-oriented RSQL query engine for safe, typed, and extensible filtering, sorting, and pagination in Python APIs.

Python License

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.1+
  • 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, PageRequest have zero ORM dependencies
  • Pluggable backends - implement compile_query / compile_sort / compile_page_request for 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 msgspec models 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 LIKE escape character
  • SELECT DISTINCT support
  • Configurable parser limits (query length, depth, argument count)

Sort

  • Multi-field: name,asc;company.name,desc,ic
  • Ignore-case modifier (ic or ignorecase)
  • 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_exists via PostgreSQL jsonpath
  • Structured values: arrays and objects passed as jsonpath vars
  • Temporal: JSONOptions(use_datetime=True) for datetime-aware jsonpath
  • 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, size from query params
  • Structured HTTP 400 parse errors and HTTP 422 semantic 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_dependency for custom base statements
  • Compatible with both sync Session and async AsyncSession execution
  • FastAPI parse and page-validation failures become structured HTTP 400 payloads
  • FastAPI semantic and backend integration failures become structured HTTP 422 payloads

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

Acknowledgements

pyrsql was strongly inspired by Jirutka's rsql-parser and PerplexHub's rsql-jpa-specification, especially their RSQL syntax and filtering behavior. I am grateful to their maintainers and contributors for their work.

License

MIT.

Metadata

Release files for pyrsql 0.0.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyrsql 0.0.6
File Size Uploaded Provenance
pyrsql-0.0.6.tar.gz 127.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pyrsql 0.0.6
File Interpreter ABI Platform
pyrsql-0.0.6-py3-none-any.whl Python 3 none any Details

Total release size: 216.9 kB

Release files / pyrsql-0.0.6.tar.gz

Download URL pyrsql-0.0.6.tar.gz
Size 127.0 kB
Tags Source
SHA-256 checksum
How to use checksums
556f5a0abc1b6d8c8c5b9b9178212a3c68997ec78a8cf5e16a57b2f92f00e3ea
BLAKE2b-256 checksum
How to use checksums
ce61430a59fd9f42c1f78675a8ed56ece96716188394c7b69e437b56717a5725
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}

Release files / pyrsql-0.0.6-py3-none-any.whl

Download URL pyrsql-0.0.6-py3-none-any.whl
Size 89.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3169f66eea6b7702496be160139e92dbc3769936409e4d2b8850dfa57ae89923
BLAKE2b-256 checksum
How to use checksums
b39a29f8eda55ebd77d6936f19c7c0fcead05915752706d1c6c4b92dd73a14c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","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}

Security for pyrsql 0.0.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page