Skip to main content

Validation-first SQLAlchemy queries with Pydantic row validation.

Project description

RowGuard

CI PyPI Documentation Python Versions

RowGuard makes every SQLAlchemy query return validated Pydantic models—or explicit rejected rows. It does not silently drop bad data.

Use it when you already have SQLAlchemy Core tables/selects and need typed reads with deterministic rejection handling. It is not an ORM and does not replace SQLAlchemy or Pydantic.

Status

Current release: 0.4.0 (sync + async Core, streaming). See Supported vs planned for what is shipped versus deferred (ORM 0.5, callback/quarantine 0.6).

Install

pip install rowguard

Requires Python 3.10+, Pydantic v2, SQLAlchemy 2.x, and SQLRules ≥0.4. See the installation guide.

Quickstart

Full walkthrough: Quickstart.

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:
    # Disable SQLRules pushdown so invalid rows reach Pydantic and appear in rejected.
    result = rowguard.select(
        session=session,
        table=users,
        model=UserRead,
        on_reject="collect",
        use_sqlrules=False,
    )
    print(result.models)
    print(result.rejected)

    with rowguard.stream(
        session=session,
        table=users,
        model=UserRead,
        on_reject="skip",
        use_sqlrules=False,
    ) as stream:
        for model in stream:
            print(model)

With use_sqlrules=True (the default), supported constraints such as age >= 18 are pushed into SQL, so invalid candidate rows may never be returned. See SQLRules pushdown and the FAQ.

Public API (0.4.0)

Full reference: API guide · Python autodoc · Error catalog.

Function Purpose
select(...) Build and execute a table query with validation
execute(...) Validate rows from an existing Select
validate_rows(...) Validate mappings without SQL
compile_plan(...) Compile an ExecutionPlan without executing
stream(...) Stream validated models without buffering accepted rows
aselect(...) Async select for AsyncSession / AsyncConnection
aexecute(...) Async execute for AsyncSession / AsyncConnection
astream(...) Async stream (AsyncStreamResult) without buffering accepted rows

Rejection policies: raise (default), collect, skip — see rejection policies.

Optional planning knobs: compiled_rules= (precompiled SQLRules), strict= (Pydantic), field_map= / column_map= (validated at plan time).

Streaming knobs: yield_per=, observers= (StreamObserver / BaseStreamObserver). Observers remain sync callables in 0.4. See the streaming guide.

Async note: only DB I/O is awaited. Pydantic validation runs on the event loop; heavy models can block. Prefer async with rowguard.astream(...) for cleanup. Install async extras with pip install rowguard[async]. Details: async guide.

Architecture

Pydantic Model
      │
      ▼
SQLRules
      │
      ▼
SQLAlchemy Query
      │
      ▼
Database
      │
      ▼
Row Adapter
      │
      ▼
Pydantic Validation
      │
      ├── Accepted Model
      └── Rejected Row

More detail: architecture overview · design philosophy · specification.

Documentation

Build docs locally:

pip install -e ".[docs]"
make docs
# open docs/_build/html/index.html

Development

See Contributing, Security, and Releasing.

pip install -e ".[dev,async]"
make all          # ruff + mypy + pytest --cov
python examples/basic.py
python examples/streaming.py
python examples/async_basic.py

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

rowguard-0.4.0.tar.gz (219.2 kB view details)

Uploaded Source

Built Distribution

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

rowguard-0.4.0-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file rowguard-0.4.0.tar.gz.

File metadata

  • Download URL: rowguard-0.4.0.tar.gz
  • Upload date:
  • Size: 219.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rowguard-0.4.0.tar.gz
Algorithm Hash digest
SHA256 5965b20a0053a7e83c67b79e25304fc9ae8fa5d8c5b6000085cf0a814e2fc425
MD5 96d55b7a663e19b28832c80f3effde78
BLAKE2b-256 f1a38ea2f8e534f473b5f674e091a06e09ad4f0bed9dac08f2582904aef46f6f

See more details on using hashes here.

File details

Details for the file rowguard-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: rowguard-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 36.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rowguard-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a48283e572a6c6d6c97afa5a5a3c44cc4bf09f9365d0c4895a6faa492140a67c
MD5 0d03db3092d8611b655e513741c13f8b
BLAKE2b-256 e08cd21fd04b5f059a235941208691891fc613b93aa3ad5a7854c6362234d5ca

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