RowGuard
RowGuard turns SQLAlchemy query results into validated Pydantic models.
By default, SQLRules (a required dependency) pushes supported model constraints into SQL so invalid candidates are filtered before fetch. Every row that is fetched is either an accepted model or an explicit rejection—never ignored after fetch.
Need to inspect invalid rows in Python? Pass use_sqlrules=False and
on_reject="collect".
Use it when you already have SQLAlchemy Core tables/selects or ORM / SQLModel mapped classes and need typed reads with deterministic rejection handling. It is not an ORM and does not replace SQLAlchemy, Pydantic, or SQLModel—it validates reads over those stacks.
Status
Current release: 0.6.0 (Core + async + ORM/SQLModel + rejection platform). See Supported vs planned for what is shipped versus deferred.
Install
pip install rowguard # Core (includes SQLRules)
pip install "rowguard[async]" # aiosqlite + greenlet for async
pip install "rowguard[sqlmodel]" # SQLModel table-source support
pip install "rowguard[postgresql]" # psycopg driver helper
| Extra | When you need it |
|---|---|
| (none) | Sync Core / ORM reads |
async |
aselect / astream examples and async tests |
sqlmodel |
SQLModel mapped classes as table= / source= |
postgresql |
Optional PostgreSQL driver |
dev |
Contributors: pytest, ruff, mypy, … |
docs |
Sphinx documentation build |
Requires Python 3.10+, Pydantic v2, SQLAlchemy 2.x, and SQLRules ≥1.0 (3.10–3.12 tested in CI; 3.13 untested). See the installation guide.
Quickstart
Full walkthrough: Quickstart.
1. Default path (library defaults)
Invalid candidates are filtered in SQL. rejected is empty.
from typing import Annotated
from pydantic import BaseModel, Field
from sqlalchemy import Column, Integer, MetaData, String, Table, create_engine
from sqlalchemy.orm import Session
import rowguard
class UserRead(BaseModel):
id: int
name: str
age: Annotated[int, Field(ge=18)]
metadata = MetaData()
users = Table(
"users",
metadata,
Column("id", Integer, primary_key=True),
Column("name", String),
Column("age", Integer),
)
engine = create_engine("sqlite+pysqlite:///:memory:")
metadata.create_all(engine)
with engine.begin() as connection:
connection.execute(
users.insert(),
[
{"id": 1, "name": "Ada", "age": 37},
{"id": 2, "name": "Legacy", "age": 12},
],
)
with Session(engine) as session:
result = rowguard.select(session=session, table=users, model=UserRead)
print(result.models) # (UserRead(id=1, name='Ada', age=37),)
print(result.rejected) # ()
2. Inspect rejections in Python
with Session(engine) as session:
result = rowguard.select(
session=session,
table=users,
model=UserRead,
on_reject="collect", # default is "raise"
use_sqlrules=False, # default is True
)
print(result.models) # Ada
print(result.rejected) # Legacy failed age >= 18
See SQLRules pushdown and the FAQ.
Public API (0.6.0)
| Function | Purpose |
|---|---|
select / execute |
Buffered validated reads |
stream |
Stream accepted models (no accepted-row buffer) |
aselect / aexecute / astream |
Async counterparts |
validate_rows |
Validate mappings without SQL |
compile_plan |
Inspect an ExecutionPlan without executing |
Rejection policies: raise (default), collect, skip, log, callback,
quarantine — plus optional max_rejections / max_rejection_rate. See
Rejection policies.
table= vs source=: use table= on select/stream (Core Table or
mapped class). On execute with a projected Select, pass the mapped class as
source=. Full parameter contracts:
API guide ·
Python autodoc ·
Errors.
ORM / SQLModel guide · Streaming · Async · Performance · Upgrading.
Documentation
Start here → Installation → Quickstart.
Development
See Contributing and Security.
make install # .[dev,async,sqlmodel] — matches CI
make all
python examples/sqlrules_default.py
python examples/basic.py
License
Metadata
Release files for rowguard 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| rowguard-0.6.0.tar.gz | 260.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rowguard-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 316.4 kB
Release files / rowguard-0.6.0.tar.gz
| Download URL | rowguard-0.6.0.tar.gz |
|---|---|
| Size | 260.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6ff46a4bd006483a80f452efcf1c2bc58d579e1e2e4dca7529720b7ecb4edd27
|
|
BLAKE2b-256 checksum How to use checksums |
88181c825b6b14bb7eb50d7a5ff6a292e0799f4cf22bff497684c91f12262119
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / rowguard-0.6.0-py3-none-any.whl
| Download URL | rowguard-0.6.0-py3-none-any.whl |
|---|---|
| Size | 55.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c3f30219fc13522d16ca160ee40ad1a62f62e64b5d83d94ff8f73b7352ddf54d
|
|
BLAKE2b-256 checksum How to use checksums |
23039d6703b24e334c4c4b1ed1b8b0957b348b8d23c1240962163f4692d64bf0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|