Safe typed query compilation for FastAPI.
Project description
Safe typed filtering for FastAPI, MongoDB, SQL, SQLAlchemy, SQLModel, and ODMs.
f="https://pypi.org/project/paramora/">
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, andEnum - strict vs loose validation
- sorting and pagination
- structured FastAPI
422errors - 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:
- Quickstart
- Usage guide
- How-to guides
- MongoDB backend
- Raw SQL backend
- SQLAlchemy and SQLModel
- Mongo ODM adapters
- Testing strategy
- Continuous Integration
- Benchmarking
Development
Install the full development environment:
uv sync --group dev --group docs
Run the same local quality gate that CI expects:
scripts/check.sh
Individual helper scripts are also available:
scripts/format.sh # Format and apply safe lint fixes
scripts/test.sh # Run pytest, forwarding any extra arguments
scripts/docs.sh serve # Serve the documentation site locally
scripts/docs.sh build # Build docs with MkDocs strict mode
scripts/benchmark.sh # Run benchmark scenarios
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 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
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 paramora-0.4.1.tar.gz.
File metadata
- Download URL: paramora-0.4.1.tar.gz
- Upload date:
- Size: 317.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- 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":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d90a47c8b0ded0e6b0d7c39b2177f28c835678b0b7cdd54fd42053a06aee7b8e
|
|
| MD5 |
3a3b09b3239fb4c85c57054a1fd31e21
|
|
| BLAKE2b-256 |
e690a1f5c92937a6c9181318559e265e0a2878b48582cfcef84230be53af2462
|
File details
Details for the file paramora-0.4.1-py3-none-any.whl.
File metadata
- Download URL: paramora-0.4.1-py3-none-any.whl
- Upload date:
- Size: 29.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- 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":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13a85dca4b44de1fdb207bda1f78080c9a13c69634a4439a3f8aacb576532d9c
|
|
| MD5 |
3e76946520db4fdc05a647c701203f6a
|
|
| BLAKE2b-256 |
67a43bc8a1ea19d38ca2f500cb114dbdf2da4296b73cd08b06c3cbc888190042
|