Skip to main content

Compiler-oriented RSQL query engine for safe, typed, and extensible Python ORM-backed APIs.

Project description

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.

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, 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[sqlalchemy]
import pyrsql
from sqlalchemy import select
from pyrsql.orms.sqlalchemy import SQLAlchemyORM

orm = SQLAlchemyORM()
stmt = select(User)

# Filter: name equals "demo", company name contains "acme"
stmt = pyrsql.parse("name==demo;company.name==acme*").apply(stmt, User, orm=orm)

# Sort: by name ascending, then company name descending
stmt = pyrsql.Sort.parse("name,asc;company.name,desc").apply(stmt, User, orm=orm)

# Paginate: page 0, 25 items per page
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)
  • 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

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
  • HTTP 422 with structured diagnostics on parse/semantic errors
  • OpenAPI examples from configuration
  • One-based paging support
  • Custom query parameter names

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
  • 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 integration 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyrsql-0.0.3.tar.gz (120.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyrsql-0.0.3-py3-none-any.whl (95.6 kB view details)

Uploaded Python 3

File details

Details for the file pyrsql-0.0.3.tar.gz.

File metadata

  • Download URL: pyrsql-0.0.3.tar.gz
  • Upload date:
  • Size: 120.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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

Hashes for pyrsql-0.0.3.tar.gz
Algorithm Hash digest
SHA256 82443a5ac3842ad0fad331cfbee6608c3c9bafc0fcc058194943e7b885d8f53b
MD5 0399a5590b8b7fe91b0c27a5b5660f17
BLAKE2b-256 e946e66d68bcd38611e3369fd8b85cfbd72788256b5bc35a50fdd11b146d0744

See more details on using hashes here.

File details

Details for the file pyrsql-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: pyrsql-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 95.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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

Hashes for pyrsql-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b742ba2689f98875f25a502f6c15a45e80d7d86aea3d252263f4fd0d4c31ca78
MD5 c31886b3743cb19c697a3d754db3156c
BLAKE2b-256 9faa681dc5ee484cda9c3d800aea0ebd3674c09ed12b03cd487857ea7ee85884

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page