Skip to main content

adc-aiopg

Async-библиотека для PostgreSQL на базе asyncpg. Предоставляет пул соединений с бинарными кодеками, query builder поверх SQLAlchemy, generic DAO с фильтрами, систему версионирования таблиц и интеграцию с Alembic.

Установка

pip install git+https://github.com/ascet-dev/adc-aiopg.git@main

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

import asyncio
from adc_aiopg import create_db_pool, PGPoolManager

async def main():
    pool = await create_db_pool("postgresql://user:pass@localhost/mydb")
    pm = PGPoolManager(pool)

    async with pm.transaction() as conn:
        rows = await pm.fetch(
            select(users).where(users.c.active == True)
        )

    await pool.close()

asyncio.run(main())

API

create_db_pool

Создает asyncpg.Pool с предустановленными бинарными кодеками для jsonb, timestamp, timestamptz (через ujson).

from adc_aiopg import create_db_pool

pool = await create_db_pool(
    dsn="postgresql://user:pass@localhost/mydb",
    min_size=5,
    max_size=20,
)

compile_query

Компилирует SQLAlchemy-выражение в строку с позиционными параметрами $1, $2, ... для asyncpg.

from adc_aiopg import compile_query
from sqlalchemy import select

sql, params = compile_query(select(users).where(users.c.id == 42))
# sql: "SELECT ... WHERE users.id = $1"
# params: [42]

PGPoolManager

Обертка над пулом. Принимает SQLAlchemy-выражения, компилирует и выполняет. Результаты возвращаются как dict.

from adc_aiopg import PGPoolManager

pm = PGPoolManager(pool)

# Получить все строки
rows = await pm.fetch(select(users))

# Одна строка
row = await pm.fetchrow(select(users).where(users.c.id == 1))

# Скалярное значение
count = await pm.fetchval(select(func.count()).select_from(users))

# Контекстные менеджеры
async with pm.connection() as conn:
    ...

async with pm.transaction() as conn:
    ...

PGDataAccessObject[T]

Generic DAO поверх SQLModel. Полный CRUD с типизированными результатами. Хранит конфигурацию таблицы: model, metadata, table_name, table_model.

from sqlmodel import Field
from adc_aiopg import PGDataAccessObject
from adc_aiopg.types import Base

class User(Base):
    id: int = Field(primary_key=True)
    name: str
    email: str
    active: bool = True

# Быстрое создание DAO из модели
UserDAO = PGDataAccessObject.from_model(User, "users", metadata=meta)
user_dao = UserDAO(db_pool=pool)

# CRUD
user = await user_dao.create(name="Alice", email="alice@example.com")
user = await user_dao.get_by_id(1)
users = await user_dao.search(active=True)
user = await user_dao.update_by_id(1, name="Alice Smith")
await user_dao.delete_by_id(1)

# Soft delete (ставит archived=True)
await user_dao.archive_by_id(1)

Кастомный DAO

Наследуйтесь от PGDataAccessObject и добавляйте свои методы:

class UsersDAO(PGDataAccessObject[User], table_name="users"):
    model = User

    async def find_by_email(self, email: str) -> User | None:
        results = await self.search(email=email)
        return results[0] if results else None

Если модель уже является таблицей (table=True), bind() использует её как есть:

class User(Base, table=True):
    __tablename__ = "users"
    id: int = Field(primary_key=True)
    name: str

class UsersDAO(PGDataAccessObject[User]):
    model = User  # уже selectable — bind() не создаёт обёртку

Фильтры

DAO поддерживает суффиксные фильтры в search() и других методах:

# Сравнение
users = await dao.search(age_gt=18)          # age > 18
users = await dao.search(age_ge=18)          # age >= 18
users = await dao.search(age_lt=65)          # age < 65
users = await dao.search(age_le=65)          # age <= 65
users = await dao.search(status_ne="banned") # status != 'banned'

# Списки
users = await dao.search(role_in=["admin", "moderator"])
users = await dao.search(role_notin=["banned"])

# NULL
users = await dao.search(deleted_at_is=None)
users = await dao.search(deleted_at_isnot=None)

# LIKE / ILIKE
users = await dao.search(name_ilike="%alice%")

Пагинация

from adc_aiopg.types import Paginated

result: Paginated[User] = await dao.paginated_search(
    limit=20, offset=0, active=True
)
# result.items: list[User]
# result.pagination: Pagination(total=150, limit=20, offset=0)

PostgresAccessLayer + TableDescriptor

Декларативный слой доступа к БД. Группирует несколько DAO в одном объекте. Передаёт metadata всем DAO и вызывает bind() при определении класса (для миграций).

from adc_aiopg import PostgresAccessLayer, PGDataAccessObject, TableDescriptor
from sqlalchemy import MetaData

meta = MetaData()

class User(Base):
    id: int = Field(primary_key=True)
    name: str

class Post(Base):
    id: int = Field(primary_key=True)
    title: str
    author_id: int

# Простые DAO из моделей
class DB(PostgresAccessLayer, metadata=meta):
    users = TableDescriptor(PGDataAccessObject.from_model(User, "users"))
    posts = TableDescriptor(PGDataAccessObject.from_model(Post, "posts"))

# Кастомный DAO с дополнительными методами
class UsersDAO(PGDataAccessObject[User], table_name="users"):
    model = User

    async def find_by_email(self, email: str) -> User | None:
        results = await self.search(email=email)
        return results[0] if results else None

class DB(PostgresAccessLayer, metadata=meta):
    users: UsersDAO = TableDescriptor(UsersDAO)
    posts = TableDescriptor(PGDataAccessObject.from_model(Post, "posts"))

# Использование
db = DB(pool)
user = await db.users.find_by_email("alice@example.com")
posts = await db.posts.search(author_id=1)

# Произвольные запросы через PGPoolManager
count = await db.pm.fetchval(select(func.count()).select_from(users))

TableDescriptor — минимальный дескриптор, принимает DAO-класс. Если table_name не задан на DAO, используется имя атрибута как fallback.

Транзакции

PostgresAccessLayer.transaction() объединяет вызовы всех DAO этого слоя в одну транзакцию на одном соединении — включая кастомные методы DAO, использующие fetch/fetchrow/fetchval. Commit при выходе из блока, rollback при исключении:

async with db.transaction():
    user = await db.users.create(name="Alice")
    await db.posts.create(title="Hello", author_id=user.id)

Вне блока поведение прежнее: каждый вызов берёт своё соединение из пула (autocommit).

  • Вложенные transaction() на том же пуле переиспользуют соединение и открывают savepoint: откат внутреннего блока не отменяет внешнюю транзакцию. ⚠️ До 1.1.0 вложенный вызов брал отдельное соединение с независимой транзакцией.
  • Несколько пулов в одном процессе изолированы: транзакция одного слоя не затрагивает запросы слоёв с другими пулами.
  • asyncio.gather внутри блока не поддерживается: соединение asyncpg нельзя использовать конкурентно, будет InterfaceError: another operation is in progress. Выполняйте вызовы последовательно.
  • asyncio.create_task внутри блока: таск наследует транзакционный контекст и обязан завершиться до выхода из блока, иначе он может обратиться к уже возвращённому в пул соединению.

Версионирование таблиц

Создает shadow-таблицу {table}_log и PostgreSQL-триггер для автоматического логирования изменений.

from adc_aiopg import declare_version_table

VersionedUser = declare_version_table(User)
# Создается таблица users_log с теми же колонками
# Триггер автоматически пишет в _log при INSERT/UPDATE/DELETE

Alembic-интеграция

В env.py вашего Alembic-проекта:

from adc_aiopg.alembic_env import run_alembic
from myapp.models import Base

run_alembic(
    sqlalchemy_url="postgresql://user:pass@localhost/mydb",
    target_metadata=Base.metadata,
)

Автоматически:

  • Нумерует миграции инкрементально (0001, 0002, ...)
  • Добавляет CREATE/DROP TRIGGER для версионированных таблиц

Типы

Base

Базовый класс для моделей (наследует SQLModel):

from adc_aiopg.types import Base

class User(Base, table=True):
    __tablename__ = "users"
    id: int = Field(primary_key=True)
    name: str

# Проекции
UserPartial = User.partial()       # все поля Optional
UserNames = User.only("id", "name")  # только указанные поля
UserNoEmail = User.exclude("email")  # все кроме указанных

sqla_enum

Хелпер для enum-полей с поддержкой SQLAlchemy:

from adc_aiopg.enum import sqla_enum

class Role(str, Enum):
    admin = "admin"
    user = "user"

class User(Base, table=True):
    role: Role = sqla_enum(Role)

Требования

  • Python >= 3.8
  • asyncpg >= 0.27.0
  • sqlalchemy >= 2.0.0
  • sqlmodel >= 0.0.8
  • pydantic >= 2.0.0
  • alembic >= 1.11.0
  • ujson >= 5.10.0

Лицензия

MIT

Download files

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

Source Distribution

adc_aiopg-1.1.1.tar.gz (21.9 kB view details)

Uploaded Source

Built Distribution

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

adc_aiopg-1.1.1-py3-none-any.whl (17.2 kB view details)

Uploaded Python 3

File details

Details for the file adc_aiopg-1.1.1.tar.gz.

File metadata

  • Download URL: adc_aiopg-1.1.1.tar.gz
  • Upload date:
  • Size: 21.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for adc_aiopg-1.1.1.tar.gz
Algorithm Hash digest
SHA256 ebfbb0c961809026dea2cf352c1ffcab8e51b84a53b8b135b70adc369b1ac692
MD5 8a307281d82ef7deb43d74721290e3ea
BLAKE2b-256 b5ed1f059364785e6152677f72eb36077bc5476eb78753319917fb7acd83de77

See more details on using hashes here.

File details

Details for the file adc_aiopg-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: adc_aiopg-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 17.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for adc_aiopg-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e83b7e777a0f10d2681e0bc5e54218482781bfb8831b1d18bc1558c9c7383660
MD5 5c4002652efbeb563fdba1658868f815
BLAKE2b-256 435b8d08cc1e7e43b6f29c1810f2e37e6299bc00a560518dae5e629dbaf36cab

See more details on using hashes here.

Release history Release notifications | RSS feed

1.2.0

2 files

This release

1.1.1 This release

2 files

1.1.0

2 files

1.0.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page