Metadata-Version: 2.4
Name: rowguard
Version: 0.3.1
Summary: Validation-first SQLAlchemy queries with Pydantic row validation.
Project-URL: Homepage, https://github.com/eddiethedean/rowguard
Project-URL: Documentation, https://github.com/eddiethedean/rowguard#readme
Project-URL: Repository, https://github.com/eddiethedean/rowguard
Project-URL: Changelog, https://github.com/eddiethedean/rowguard/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/eddiethedean/rowguard/issues
Author: RowGuard Contributors
License: MIT
License-File: LICENSE
Keywords: pydantic,query,sqlalchemy,sqlrules,validation
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: pydantic<3,>=2.7
Requires-Dist: sqlalchemy<3,>=2.0
Requires-Dist: sqlrules>=0.4.0
Provides-Extra: async
Requires-Dist: aiosqlite>=0.20; extra == 'async'
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-benchmark>=4; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: postgresql
Requires-Dist: psycopg[binary]>=3.1; extra == 'postgresql'
Description-Content-Type: text/markdown

# RowGuard

Validation-first database queries for SQLAlchemy and Pydantic.

RowGuard executes SQLAlchemy queries, validates every returned row against a
Pydantic model, and explicitly handles rows that fail validation.

## Status

**0.3.1** — streaming lifecycle fixes, raise-policy stream stats, and planning
correctness for `field_map` / plan cache / `compile_plan`. Async streaming remains
deferred to 0.4.0; ORM remains deferred to 0.5.0.

## Install

```bash
pip install rowguard
```

Requires Python 3.10+, Pydantic v2, SQLAlchemy 2.x, and SQLRules.

## Quickstart

```python
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.

## Public API (0.3.1)

| 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 |

Rejection policies: `raise` (default), `collect`, `skip`.

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

Streaming knobs: `yield_per=`, `observers=` (`StreamObserver` / `BaseStreamObserver`).

## Architecture

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

## Documentation

- [SPEC.md](SPEC.md) — product specification
- [API.md](API.md) — public API
- [ARCHITECTURE.md](ARCHITECTURE.md) — layered design
- [ROADMAP.md](ROADMAP.md) — release plan
- [docs/](docs/) — detailed design notes

## Development

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

## License

MIT
