Skip to main content

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 SessionAsyncSession, BaseRepositoryAsyncBaseRepository 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 / delete helpers on repositories call session.flush() but do not commit unless documented otherwise.
  • delete_all() executes a bulk delete and does not commit — the caller owns session.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 BY expressions
  • 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sqlphilosophy-0.1.2.tar.gz (37.3 kB view details)

Uploaded Source

Built Distribution

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

sqlphilosophy-0.1.2-py3-none-any.whl (27.8 kB view details)

Uploaded Python 3

File details

Details for the file sqlphilosophy-0.1.2.tar.gz.

File metadata

  • Download URL: sqlphilosophy-0.1.2.tar.gz
  • Upload date:
  • Size: 37.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for sqlphilosophy-0.1.2.tar.gz
Algorithm Hash digest
SHA256 e0a5beb9e6ef24d8d6774eb95869dfe56824ff6de65d510de74507fdf3041870
MD5 f6eb1863ac0386656b0737a782539f72
BLAKE2b-256 44a0de5bba07f8d0c385aaf95bca75d1f4454c8ae9b08c267e5e7d2f5f37e50a

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlphilosophy-0.1.2.tar.gz:

Publisher: ci.yml on SignalSafeSoftware/sqlphilosophy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sqlphilosophy-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: sqlphilosophy-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 27.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for sqlphilosophy-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b9b5c78518d0ce439cf5e76f35403ffb2a6a2a07e4ffa4d93bbee8d880bd8b6d
MD5 6f2a15385568fb06fe9c1a5d6bb3d1f6
BLAKE2b-256 282c98d6ca03f55fbfd2d6003ad67e863c07a3c5540e4b311a4035f6e06162ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for sqlphilosophy-0.1.2-py3-none-any.whl:

Publisher: ci.yml on SignalSafeSoftware/sqlphilosophy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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