A SQL-first Python library for querying and mapping data across multiple database backends.
Project description
RowQuery
A SQL-first Python library for querying and mapping data across multiple database backends.
Features
- Multi-Database Support: SQLite, PostgreSQL, MySQL, Oracle with unified interface
- SQL-First Design: Load queries from
.sqlfiles organized in namespaces, or pass inline SQL directly - Inline SQL: Execute raw SQL strings alongside registry keys — no registry required for ad-hoc queries
- SQL Sanitization: Configurable sanitizer strips comments, blocks statement stacking, and restricts SQL verbs
- Flexible Mapping: Map results to dataclasses, Pydantic models, or plain classes
- Aggregate Mapping: Reconstruct complex object graphs from joined queries (single-pass O(n))
- Transaction Management: Context manager support with automatic rollback
- Migration Management: Version-controlled database migrations
- Repository Pattern: Optional DDD-style repository base classes
- Async Support: Full async/await support for all operations
- Type Safe: Fully typed with mypy strict mode
Installation
Core (SQLite support included)
pip install rowquery
With Database Drivers
pip install rowquery[postgres] # PostgreSQL
pip install rowquery[mysql] # MySQL
pip install rowquery[oracle] # Oracle
pip install rowquery[all] # All drivers
Quick Start
1. Organize Your SQL Files
sql/
user/
get_by_id.sql
list_active.sql
order/
create.sql
2. Execute Queries
from row_query import Engine, ConnectionConfig, SQLRegistry
config = ConnectionConfig(driver="sqlite", database="app.db")
registry = SQLRegistry("sql/")
engine = Engine.from_config(config, registry)
# Registry key lookup (dot-separated namespace)
user = engine.fetch_one("user.get_by_id", {"id": 1})
users = engine.fetch_all("user.list_active")
count = engine.fetch_scalar("user.count")
# Inline SQL — pass a raw SQL string directly
user = engine.fetch_one("SELECT * FROM users WHERE id = ?", 1)
users = engine.fetch_all("SELECT * FROM users WHERE active = ?", True)
count = engine.fetch_scalar("SELECT COUNT(*) FROM users")
rows = engine.fetch_all("SELECT * FROM users WHERE id IN (?, ?)", [1, 2])
3. Map to Models
from dataclasses import dataclass
from row_query.mapping import ModelMapper
@dataclass
class User:
id: int
name: str
email: str
mapper = ModelMapper(User)
user = engine.fetch_one("user.get_by_id", {"id": 1}, mapper=mapper)
# Returns: User(id=1, name="Alice", email="alice@example.com")
4. Aggregate Mapping (Reconstruct Object Graphs)
from row_query.mapping import aggregate, AggregateMapper
from dataclasses import dataclass
@dataclass
class Order:
id: int
total: float
@dataclass
class UserWithOrders:
id: int
name: str
email: str
orders: list[Order]
# Build mapping plan for complex object graph
plan = (
aggregate(UserWithOrders, prefix="user__")
.key("id")
.auto_fields()
.collection("orders", Order, prefix="order__", key="id")
.build()
)
# Execute joined query and map in single pass
users = engine.fetch_all("user.with_orders", mapper=AggregateMapper(plan))
5. Transactions
# Use context manager for automatic rollback on error
with engine.transaction() as tx:
tx.execute("user.create", {"name": "Alice", "email": "alice@example.com"})
tx.execute("audit.log", {"action": "user_created"})
# Commits on exit, rolls back on exception
6. SQL Sanitization
from row_query import Engine, SQLSanitizer
# Configure what inline SQL is permitted
sanitizer = SQLSanitizer(
strip_comments=True, # Remove -- and /* */ comments (default: True)
block_multiple_statements=True, # Reject "SELECT 1; DROP TABLE t" (default: True)
allowed_verbs=frozenset({"SELECT"}), # Only allow SELECT statements (default: None = any)
)
engine = Engine.from_config(config, registry, sanitizer=sanitizer)
# Inline SQL is sanitized before execution
users = engine.fetch_all("SELECT * FROM users -- get all") # comment stripped
engine.execute("DROP TABLE users") # raises SQLSanitizationError (verb not allowed)
engine.execute("SELECT 1; DROP TABLE t") # raises SQLSanitizationError (multiple statements)
# Registry queries are always trusted and never sanitized
users = engine.fetch_all("user.list_active") # no sanitization applied
7. Async Support
from row_query import AsyncEngine, ConnectionConfig
config = ConnectionConfig(driver="sqlite", database="app.db")
engine = AsyncEngine.from_config(config, registry)
async def fetch_users():
# Registry key or inline SQL — both work
users = await engine.fetch_all("user.list_active")
users = await engine.fetch_all("SELECT * FROM users WHERE active = ?", True)
return users
# Async transactions
async with engine.transaction() as tx:
await tx.execute("user.create", {"name": "Bob"})
await tx.execute("INSERT INTO audit (action) VALUES (?)", "user_created")
Documentation
- Examples - Runnable code examples
- CONTRIBUTING.md - Development guide
- CHANGELOG.md - Version history
Development
This project uses uv for package management.
# Install dependencies
uv sync --extra all --extra dev
# Run tests
uv run pytest
# Run tests with coverage
uv run pytest --cov=row_query --cov-report=html
# Lint and format
uv run ruff check row_query/ tests/
uv run ruff format row_query/ tests/
# Type check
uv run mypy row_query/
License
MIT License - see LICENSE for details.
Contributing
Contributions welcome! Please read CONTRIBUTING.md first.
Project details
Release history Release notifications | RSS feed
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 rowquery-0.1.0.tar.gz.
File metadata
- Download URL: rowquery-0.1.0.tar.gz
- Upload date:
- Size: 48.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7132b3e4b64ab03b1c63430fa14ae6bdae2d3000b53814e3bfb3b7c54ab5abe3
|
|
| MD5 |
df5b6b8c4043fd4c21f7edb3075fc074
|
|
| BLAKE2b-256 |
a4ccc5673f461520022337449dc64f7c333f8b65032be0c42013caa58e2befe4
|
Provenance
The following attestation bundles were made for rowquery-0.1.0.tar.gz:
Publisher:
release.yml on MaksimShevtsov/RowQuery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rowquery-0.1.0.tar.gz -
Subject digest:
7132b3e4b64ab03b1c63430fa14ae6bdae2d3000b53814e3bfb3b7c54ab5abe3 - Sigstore transparency entry: 963790704
- Sigstore integration time:
-
Permalink:
MaksimShevtsov/RowQuery@2c5758d3c7cfa0bc7e2c373e896433001eece8e8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MaksimShevtsov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c5758d3c7cfa0bc7e2c373e896433001eece8e8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rowquery-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rowquery-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
89214c94a846b7973760181b95d2b444a1a22edda1288096128f4b37aa7190c9
|
|
| MD5 |
faacc447ed4e60e2b476f7e13a2a6464
|
|
| BLAKE2b-256 |
c9dc5ac0ec394c9a95dde5557c84ff27dfff318bb8da15ff4a66eca831ff00ea
|
Provenance
The following attestation bundles were made for rowquery-0.1.0-py3-none-any.whl:
Publisher:
release.yml on MaksimShevtsov/RowQuery
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rowquery-0.1.0-py3-none-any.whl -
Subject digest:
89214c94a846b7973760181b95d2b444a1a22edda1288096128f4b37aa7190c9 - Sigstore transparency entry: 963790780
- Sigstore integration time:
-
Permalink:
MaksimShevtsov/RowQuery@2c5758d3c7cfa0bc7e2c373e896433001eece8e8 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/MaksimShevtsov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c5758d3c7cfa0bc7e2c373e896433001eece8e8 -
Trigger Event:
push
-
Statement type: