Skip to main content

Generic БД/ORM-инфраструктура на SQLAlchemy 2.0: диалект-нейтральный движок из DB-URL, Repository + UnitOfWork + DIP-протоколы. 0 завязок на конкретное приложение.

Project description

s-ormkit

Generic БД/ORM-инфраструктура для экосистемы S-kits на SQLAlchemy 2.0: диалект-нейтральный движок из DB-URL, Repository + UnitOfWork + DIP-протоколы.

Версия: 0.0.1 (первый релиз)
Лицензия: MIT
Зависимости: SQLAlchemy>=2.0 (и ничего больше)

Кит НЕ знает ни о каком приложении. Прикладная логика получает БД-слой через ORM так, что не зависит от sqlite/диалекта: движок конфигурируется через DB-URL и заменяется на postgresql://.../mysql://... без изменения кода.

Что входит

Declarative-база и миксины (base.py)

  • Base — общий declarative base для моделей-наследников
  • IntPkMixin — целочисленный автоинкрементный первичный ключ
  • TimestampMixincreated_at / updated_at через func.now() (диалект-нейтрально)

Движок из DB-URL (engine.py)

  • make_engine — резолв URL (аргумент → env → default), для sqlite включает PRAGMA foreign_keys=ON; create_engine ленив (postgres-URL создаётся без коннекта)
  • make_session_factorysessionmaker(expire_on_commit=False)
  • init_schema / drop_schema — создать/удалить таблицы
  • dispose_engine — освободить пул соединений

Repository-паттерн (repository.py)

  • BaseRepository[TModel, TDomain] — generic CRUD: add / get / get_or_none / list / update / remove / count
  • Маппинг ORM <-> domain через переопределяемые хуки to_domain / to_orm
  • Не коммитит сам (это делает UnitOfWork) — только flush для получения id

Транзакционная граница (unit_of_work.py)

  • UnitOfWork — контекст-менеджер: rollback при исключении, close всегда, commit явный

DIP-контракты (protocols.py)

  • RepositoryProtocol / UnitOfWorkProtocol — чтобы сервисы зависели от абстракций, а не от BaseRepository / SQLAlchemy

Быстрый старт

Кит generic — вы объявляете свои модели поверх Base и миксинов:

from dataclasses import dataclass

from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

from ormkit import (
    Base, IntPkMixin, TimestampMixin,
    BaseRepository, UnitOfWork,
    make_engine, make_session_factory, init_schema,
)


# 1. Свои ORM-модели
class User(Base, IntPkMixin, TimestampMixin):
    __tablename__ = "users"
    name: Mapped[str] = mapped_column(String(100))


# 2. Свой доменный объект (dataclass / pydantic / dict — кит не знает тип)
@dataclass
class UserDTO:
    name: str
    id: int | None = None


# 3. Свой репозиторий — переопределяем ТОЛЬКО хуки маппинга
class UserRepository(BaseRepository[User, UserDTO]):
    def to_domain(self, obj: User) -> UserDTO:
        return UserDTO(id=obj.id, name=obj.name)

    def to_orm(self, domain: UserDTO) -> User:
        return User(name=domain.name)

Конфигурация движка через DB-URL

# sqlite по умолчанию (для локали / тестов), FK enforcement включён автоматически
engine = make_engine("sqlite:///app.db")

# та же строка кода на проде — просто другой URL, коду всё равно:
engine = make_engine(default="postgresql://user:pass@host/db")
# или через переменную окружения ORMKIT_DB_URL:
engine = make_engine()

init_schema(engine)
session_factory = make_session_factory(engine)

Работа через UnitOfWork

with UnitOfWork(session_factory) as uow:
    repo = UserRepository(uow.session, User)

    saved = repo.add(UserDTO(name="Алиса"))        # flush -> id проставлен
    found = repo.get(saved.id)                      # NotFoundError, если нет
    everyone = repo.list()                          # фильтр: repo.list(name="Алиса")
    repo.update(saved.id, name="Алиса Смит")
    total = repo.count()

    uow.commit()   # без commit транзакция не персистится
# исключение в блоке -> автоматический rollback; сессия всегда закрывается

DIP: сервис зависит от абстракции

from ormkit import RepositoryProtocol

def register_user(repo: RepositoryProtocol[UserDTO], name: str) -> UserDTO:
    return repo.add(UserDTO(name=name))
# в проде — UserRepository, в тестах — in-memory фейк, реализующий тот же протокол

Архитектура

ormkit/
├── __init__.py          # Public API + __version__
├── exceptions.py        # OrmKitError, NotFoundError
├── base.py              # Base, IntPkMixin, TimestampMixin
├── engine.py            # make_engine, make_session_factory, init/drop_schema, dispose
├── repository.py        # BaseRepository[TModel, TDomain]
├── unit_of_work.py      # UnitOfWork
└── protocols.py         # RepositoryProtocol, UnitOfWorkProtocol

Тестирование

uv sync --extra dev
uv run pytest -q          # зелёный, coverage >= 80%
uv run ruff check .       # чисто

Тесты объявляют собственные демо-модели (_User, _Post) прямо в conftest.py — это доказывает, что кит generic и не тянет никакого приложения.

Диалект-нейтральность

  • Один код — любая СУБД. Меняется только DB-URL: sqlite://postgresql://mysql://. Прикладной код не трогается.
  • sqlite FK enforcement. Для sqlite make_engine навешивает PRAGMA foreign_keys=ON (иначе sqlite молча игнорирует внешние ключи). Отключается флагом foreign_keys=False.
  • Временные метки. TimestampMixin использует func.now(), работающий и в sqlite, и в postgres/mysql.
  • Ленивое создание. make_engine("postgresql://...") не коннектится — движок создаётся без живого сервера.

Лицензия

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

s_ormkit-0.0.1.tar.gz (45.0 kB view details)

Uploaded Source

Built Distribution

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

s_ormkit-0.0.1-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file s_ormkit-0.0.1.tar.gz.

File metadata

  • Download URL: s_ormkit-0.0.1.tar.gz
  • Upload date:
  • Size: 45.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_ormkit-0.0.1.tar.gz
Algorithm Hash digest
SHA256 3f0d56b8afc4a985718c2797736b6b023e068a6931f7868264145ef62842a148
MD5 ed4c9846fecbe591a2c5144e1ba87064
BLAKE2b-256 6c252dec2d169119e46689dc536f0db740abc32cc1fa39c3eda55ace288489d6

See more details on using hashes here.

File details

Details for the file s_ormkit-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: s_ormkit-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 12.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for s_ormkit-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a350e84328bf5e7b930785e296b343de99815f94d8583d296bb8346234345ecb
MD5 decf207fae1874a8d60595bf97f41d69
BLAKE2b-256 5fa3de17d5647a04dc7d36b6d6818da95e7a8e75d8518b03acc8ca400a294d70

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