Skip to main content

Safe typed query compilation for FastAPI.

Project description

Paramora

Safe typed filtering for FastAPI, MongoDB, SQL, SQLAlchemy, SQLModel, and ODMs.

CI Docs Coverage PyPI Python versions License Pyright strict


Paramora turns user-controlled HTTP query parameters like:

/items?price__gte=10&status__in=free,busy&sort=-created_at&limit=20

into safe, typed backend query outputs for MongoDB, raw SQL, SQLAlchemy, SQLModel, and Mongo ODM adapters.

It helps FastAPI teams avoid hand-written filtering code, unsafe raw backend query exposure, inconsistent validation, and repeated pagination/sorting logic.

Status: Paramora is alpha software. Public APIs, backend emitter contracts, AST details, and error shapes may change before 1.0.

Why Paramora?

FastAPI makes endpoint code ergonomic, but filtering APIs often grow into messy manual parsing:

price = request.query_params.get("price__gte")
status = request.query_params.get("status__in")

query = {}
if price:
    query["price"] = {"$gte": float(price)}
if status:
    query["status"] = {"$in": status.split(",")}

Paramora lets you declare the query surface once:

from datetime import datetime
from typing import Annotated

from paramora import Query, QueryContract, query_field


class ItemQuery(QueryContract):
    status: Annotated[str, query_field("eq", "in")]
    active: bool
    created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
    price: Annotated[float, query_field("eq", "gte", "lte")]


item_query = Query(ItemQuery)

Then Paramora handles:

  • allowed fields and operators
  • type coercion for str, int, float, bool, datetime, and Enum
  • strict vs loose validation
  • sorting and pagination
  • structured FastAPI 422 errors
  • MongoDB, raw SQL, SQLAlchemy, SQLModel, and ODM emitter outputs

Install

uv add paramora

or:

pip install paramora

Optional backend extras:

uv add "paramora[sqlalchemy]"
uv add "paramora[sqlmodel]"
uv add "paramora[postgres]"
uv add "paramora[odm]"
uv add "paramora[all]"

Requirements

Paramora supports Python 3.10+ and FastAPI 0.115+.

Python 3.10 is the compatibility baseline. Runtime code intentionally avoids Python 3.11+/3.12+ only syntax so a wide range of FastAPI applications can adopt Paramora.

MongoDB quickstart

from datetime import datetime
from typing import Annotated

from fastapi import Depends, FastAPI
from paramora import CompiledQuery, MongoQuery, Query, QueryContract, query_field

app = FastAPI()


class ItemQuery(QueryContract):
    status: Annotated[str, query_field("eq", "in")]
    active: bool
    created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
    price: Annotated[float, query_field("eq", "gt", "gte", "lt", "lte")]


item_query: Query[MongoQuery] = Query(ItemQuery, default_limit=20, max_limit=100)


@app.get("/items")
def list_items(query: CompiledQuery[MongoQuery] = Depends(item_query)):
    mongo = query.output

    docs = (
        collection
        .find(mongo.filter)
        .sort(mongo.sort)
        .skip(mongo.offset)
        .limit(mongo.limit)
    )

    return list(docs)

Raw SQL quickstart

from datetime import datetime
from typing import Annotated

from fastapi import Depends, FastAPI
from paramora import CompiledQuery, Query, QueryContract, SqlQuery, SqliteEmitter, query_field

app = FastAPI()


class ItemQuery(QueryContract):
    status: Annotated[str, query_field("eq", "in")]
    created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
    price: Annotated[float, query_field("eq", "gte", "lte")]


item_query: Query[SqlQuery] = Query(ItemQuery, emitter=SqliteEmitter())


@app.get("/items")
def list_items(query: CompiledQuery[SqlQuery] = Depends(item_query)):
    sql = query.output
    statement = sql.select_statement("items", columns=("id", "status", "price"))
    rows = connection.execute(statement.text, statement.params).fetchall()
    return [dict(row) for row in rows]

Values are always returned as bound parameters. Paramora does not interpolate user values into SQL strings.

SQLAlchemy / SQLModel quickstart

from datetime import datetime
from typing import Annotated

import sqlalchemy as sa
from fastapi import Depends, FastAPI
from paramora import CompiledQuery, Query, QueryContract, query_field
from paramora.emitters.sqlalchemy import SqlAlchemyEmitter, SqlAlchemyQuery

metadata = sa.MetaData()
items = sa.Table(
    "items",
    metadata,
    sa.Column("id", sa.Integer, primary_key=True),
    sa.Column("status", sa.String),
    sa.Column("price", sa.Float),
    sa.Column("created_at", sa.DateTime),
)


class ItemQuery(QueryContract):
    status: Annotated[str, query_field("eq", "in")]
    price: Annotated[float, query_field("gte", "lte")]
    created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]


item_query: Query[SqlAlchemyQuery] = Query(
    ItemQuery,
    emitter=SqlAlchemyEmitter.from_table(items),
)

app = FastAPI()


@app.get("/items")
def list_items(query: CompiledQuery[SqlAlchemyQuery] = Depends(item_query)):
    statement = query.output.apply(sa.select(items))
    return {"sql": str(statement)}

Strict mode and loose mode

  • Query(MyContract) enables strict mode. It rejects unknown fields, unsupported operators, invalid values, unsafe sorting, and oversized limits.
  • Query() enables loose mode. It accepts unknown fields for trusted tools and prototypes, while still rejecting raw backend operator injection and unsafe SQL identifiers.

Use strict mode for public APIs.

Documentation

The full documentation site is available at:

https://ehsanahmadzadeh.github.io/Paramora/

Useful pages:

Development

uv sync --group dev --group docs
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest -vv
uv run mkdocs build --strict

The CI pipeline runs quality gates and the full test suite across every supported Python version: 3.10, 3.11, 3.12, 3.13, and 3.14. Coverage is generated on each Python version and uploaded to Codecov so regressions are visible from the README badge and pull requests.

The development environment installs the optional backend test dependencies so SQLAlchemy, SQLModel, MongoDB-compatible mongomock, and ODM-related tests can run when possible.

Contributing

Paramora is early and contributor-friendly. Good first contributions include:

  • improving examples
  • adding backend edge-case tests
  • improving documentation recipes
  • expanding SQLAlchemy / SQLModel coverage
  • improving benchmarks and profiling scripts

See CONTRIBUTING.md.

License

Paramora is released under the MIT License.

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

paramora-0.3.0.tar.gz (319.0 kB view details)

Uploaded Source

Built Distribution

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

paramora-0.3.0-py3-none-any.whl (29.4 kB view details)

Uploaded Python 3

File details

Details for the file paramora-0.3.0.tar.gz.

File metadata

  • Download URL: paramora-0.3.0.tar.gz
  • Upload date:
  • Size: 319.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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 paramora-0.3.0.tar.gz
Algorithm Hash digest
SHA256 938c186b08de0ce3cebd479f208703f219be915980f48bb9a51a2942c2ac5ebe
MD5 4f009140a74c2977b65acf78d016a671
BLAKE2b-256 f29be40bad5c36cf229a7bab294f3de70ad99725ca196f9329b41bf7541cb67b

See more details on using hashes here.

File details

Details for the file paramora-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: paramora-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 29.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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 paramora-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2cba8fc842228e3b04ce46b4878899620b09ed2be6fdbb52cd13435e70bc1ecd
MD5 6294e70561304125d9da13d1defa8713
BLAKE2b-256 ac9d776a889569386fe12c6d5c970710ce81db80099b231a9f04a8e650b97386

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