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.0.tar.gz (21.6 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.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: adc_aiopg-1.1.0.tar.gz
  • Upload date:
  • Size: 21.6 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.0.tar.gz
Algorithm Hash digest
SHA256 b54c1fb5893deae6238e98b579401ff9b1cf456884ef865b1411cb8ebde6bb54
MD5 448a78ac5fa059abc663925fc021163c
BLAKE2b-256 fa48560e9b49bb1eac2c27179e53751c2f2820e2b52358b3a1abe24133f198c7

See more details on using hashes here.

File details

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

File metadata

  • Download URL: adc_aiopg-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.1 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7d3420aa2c9ab76f9a23dfbeb491db97f4893f3dbfb446e4b9af9c0c7536a346
MD5 130c7ac3004f5a5040c02435c7626441
BLAKE2b-256 e720e778ad7662151cf46fa7f2e3290401135f3bd06cd54a7de408327afa7365

See more details on using hashes here.

Release history Release notifications | RSS feed

1.2.0

2 files

1.1.1

2 files

This release

1.1.0 This release

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