Portable SQLAlchemy repository kit: sync and async CRUD, statement builders, sort/pagination, and SQL helpers.
Project description
sqlphilosophy
Portable SQLAlchemy repository kit: sync and async CRUD, fluent statement builders, sort/pagination, Core SQL helpers, and optional audit listeners.
| PyPI | sqlphilosophy |
| GitHub | SignalSafeSoftware/sqlphilosophy |
| Import | sqlphilosophy (explicit submodules — no root re-exports) |
| Python | 3.12+ |
| License | MIT — see LICENSE |
What this package does
- Repository pattern for a single mapped model (
BaseRepository,AsyncBaseRepository). - Fluent query builders with pagination/sort (
StatementQueryBuilder,ListQuery,SortConfig). - SQL helpers for row mapping, partial updates, filters, and developer-defined raw SQL fragments.
- Optional audit listeners and timestamp mixins.
What this package does not do
- Migrations, schema design, or connection pooling configuration.
- Authorization, multi-tenant isolation, or query sandboxing.
- Automatic commits for normal CRUD — see Transaction ownership below.
Install
pip install sqlphilosophy
Async ORM (AsyncSession) also needs greenlet:
pip install sqlphilosophy[async]
Requires Python 3.12+ and SQLAlchemy 2.x.
Full example (sync model + session)
from sqlalchemy import String, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
from sqlphilosophy.sorting import ListQuery
from sqlphilosophy.sync.repository import BaseRepository
class Base(DeclarativeBase):
pass
class Widget(Base):
__tablename__ = "widget"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(64))
engine = create_engine("sqlite:///:memory:", future=True)
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)
with SessionLocal() as session:
repo = BaseRepository(Widget, session)
widget = repo.create(name="alpha") # stages + flush; does not commit
session.commit()
page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
assert page.total >= 1
Async: swap Session → AsyncSession, BaseRepository → AsyncBaseRepository from sqlphilosophy.aio.repository, and await repository methods.
Package layout
| Module | Contents |
|---|---|
sqlphilosophy.types |
Portable typing aliases (RowMapping, PrimaryKey, SqlFilter, …) |
sqlphilosophy.sql |
Row mapping helpers, partial updates, Core table helpers, filter builders |
sqlphilosophy.sorting |
ListQuery, SortConfig, SortSpec, pagination/sort resolution |
sqlphilosophy.sync |
Sync BaseRepository, StatementQueryBuilder, RepositoryFactory protocol |
sqlphilosophy.aio |
Async AsyncBaseRepository, AsyncStatementQueryBuilder, AsyncRepositoryFactory |
sqlphilosophy.audit |
Optional SQLAlchemy audit listeners and timestamp mixins |
Sync usage
from sqlalchemy.orm import Session
from sqlphilosophy.sorting import ListQuery, SortConfig, SortSpec
from sqlphilosophy.sql import partial_update_model, row_int
from sqlphilosophy.sync.protocols import RepositoryFactory
from sqlphilosophy.sync.repository import BaseRepository
from sqlphilosophy.sync.query import SqlAlchemyStatementBuilder
repo = BaseRepository(User, session)
rows = repo.statement().where(User.active.is_(True)).mappings().all()
repo = BaseRepository(User, session, factory)
page = repo.statement().fetch_page(ListQuery.from_page(page=1, size=20))
other = repo.for_repo(OrderRepository)
Async usage
from sqlalchemy.ext.asyncio import AsyncSession
from sqlphilosophy.aio.repository import AsyncBaseRepository
repo = AsyncBaseRepository(User, session)
rows = await repo.statement().where(User.active.is_(True)).mappings().all()
Transaction ownership
create/update/deletehelpers on repositories callsession.flush()but do not commit unless documented otherwise.delete_all()executes a bulk delete and does not commit — the caller ownssession.commit()/rollback()for the work unit.batched_purge_ids(...)deletes matching rows in batches and commits after each batch — treat it as a destructive, application-level operation you must authorize first.- Your application owns
session.commit()/rollback()for normal request/work-unit boundaries.
Raw SQL trust boundaries
The following must be developer-defined and must never be built from end-user input:
- Raw SQL fragments passed to SQL helper functions
- Literal column names, table names, and
ORDER BYexpressions - Sort field allowlists wired into query builders
User-supplied values must use bind parameters (SQLAlchemy bound values), not string concatenation into SQL text or identifiers. See SECURITY.md.
Destructive helpers
delete_all()— removes all rows for the repository model (sync and async variants). Does not commit; caller must commit or roll back.batched_purge_ids(...)— deletes matching rows in batches and commits each batch.
Call only after your application has authorized the operation. These helpers assume the caller understands the data loss impact.
Audit mixins
from sqlphilosophy.audit.context import audit_context
from sqlphilosophy.audit.listener import configure_audit_listeners
from sqlphilosophy.audit.model import TimestampModel
configure_audit_listeners()
with audit_context(actor_id=42):
session.add(MyModel(name="example"))
session.flush()
session.commit()
Audit listeners record changes; they do not enforce access control.
Development
This repo uses uv:
uv sync --extra dev
uv run pytest
uv run flake8 .
uv run python -m build
Security
See SECURITY.md for vulnerability reporting and SQL trust boundaries.
Releasing
See RELEASING.md for GitHub + PyPI trusted publishing. See CHANGELOG.md.
License
MIT — see LICENSE.
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 sqlphilosophy-0.1.6.tar.gz.
File metadata
- Download URL: sqlphilosophy-0.1.6.tar.gz
- Upload date:
- Size: 25.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c36fd72d37521d7836158d1765a5861b4a18764a46a21ea60399a219c9f96741
|
|
| MD5 |
9f78b0a56ff732f613879381c0e6c388
|
|
| BLAKE2b-256 |
213530b5c4898eec57ecde7d4c278674a3e878a4ab95cb05422673601569a60b
|
Provenance
The following attestation bundles were made for sqlphilosophy-0.1.6.tar.gz:
Publisher:
ci.yml on SignalSafeSoftware/sqlphilosophy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlphilosophy-0.1.6.tar.gz -
Subject digest:
c36fd72d37521d7836158d1765a5861b4a18764a46a21ea60399a219c9f96741 - Sigstore transparency entry: 2006314154
- Sigstore integration time:
-
Permalink:
SignalSafeSoftware/sqlphilosophy@1d1f20a57dfdc44e0b8fed976036483b542f33f3 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/SignalSafeSoftware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@1d1f20a57dfdc44e0b8fed976036483b542f33f3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sqlphilosophy-0.1.6-py3-none-any.whl.
File metadata
- Download URL: sqlphilosophy-0.1.6-py3-none-any.whl
- Upload date:
- Size: 28.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
513d962c343624f35f656bb3b4c0fd7526733a02f676582b8042f8957bf2c550
|
|
| MD5 |
d5f886fc924e0777c9b3eb2d609e02bf
|
|
| BLAKE2b-256 |
a98d52f7fb7f39d09b5971a5073b4dbdf76383df33ba33a4342a2287e799784e
|
Provenance
The following attestation bundles were made for sqlphilosophy-0.1.6-py3-none-any.whl:
Publisher:
ci.yml on SignalSafeSoftware/sqlphilosophy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sqlphilosophy-0.1.6-py3-none-any.whl -
Subject digest:
513d962c343624f35f656bb3b4c0fd7526733a02f676582b8042f8957bf2c550 - Sigstore transparency entry: 2006314434
- Sigstore integration time:
-
Permalink:
SignalSafeSoftware/sqlphilosophy@1d1f20a57dfdc44e0b8fed976036483b542f33f3 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/SignalSafeSoftware
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@1d1f20a57dfdc44e0b8fed976036483b542f33f3 -
Trigger Event:
push
-
Statement type: