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 binder, logical IR, 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 IR 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

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

Documentation

Full documentation at wskr00.github.io/pyrsql.

Section Description
Quickstart One-minute primer
Usage Filter, sort, page, JSON, FastAPI, 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
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.2.tar.gz (101.6 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.2-py3-none-any.whl (92.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyrsql-0.0.2.tar.gz
  • Upload date:
  • Size: 101.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","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.2.tar.gz
Algorithm Hash digest
SHA256 558a13ac15fcdac28ef32217d280ae042dda8247da1d368793772b2cd231600a
MD5 c87c7c2aa9f2e3133884570271a7e8a1
BLAKE2b-256 f006d9aaa56fec215dd11e89d9954479387779e21c883e48d26cd713e6e66a87

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pyrsql-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 92.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2b72a79e15db1b04bd7e4ca278cd468a62526c18a3fe84e69a77ad0a6450eb73
MD5 3a0196434b26b2681c5973e2bc1b3e2c
BLAKE2b-256 85e0b56cc630c61ec7c0494fd6e387b6e4793b358e25b2aefcb4880c944a4fe7

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