Enterprise-grade data layer framework for Python с продвинутой системой фильтрации, сортировки и пагинации.
dplex — это современный фреймворк для построения слоя работы с данными в Python приложениях. Он предоставляет унифицированный подход к фильтрации, сортировке и пагинации данных, работая поверх SQLAlchemy ORM.
- 🔍 Продвинутая фильтрация — 11 типов фильтров с множественными операциями
- 📊 Гибкая сортировка — множественная сортировка с контролем NULL значений
- 📄 Пагинация из коробки — встроенная поддержка limit/offset
- 🎯 Типобезопасность — полная поддержка type hints Python 3.9+
- 🏗️ Архитектурные паттерны — Repository и Service patterns
- ⚡ Производительность — оптимизированные SQL запросы без N+1 проблем
pip install dplex- Python 3.9+
- SQLAlchemy 2.0+
- Pydantic 2.0+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
email: Mapped[str]
age: Mapped[int]
is_active: Mapped[bool]from enum import StrEnum
from dplex import DPFilters, StringFilter, IntFilter, BooleanFilter
class UserSortField(StrEnum):
NAME = "name"
EMAIL = "email"
AGE = "age"
CREATED_AT = "created_at"
class UserFilters(DPFilters[UserSortField]):
name: StringFilter | None = None
email: StringFilter | None = None
age: IntFilter | None = None
is_active: BooleanFilter | None = Nonefrom sqlalchemy.ext.asyncio import AsyncSession
from dplex import DPRepo, Sort, Order
class UserRepository(DPRepo[User, int]):
pass
# В вашем коде
async def get_users(session: AsyncSession):
repo = UserRepository(session, User)
# Создайте фильтры
filters = UserFilters(
name=StringFilter(icontains="john"),
age=IntFilter(gte=18, lte=65),
is_active=BooleanFilter(eq=True),
sort=Sort(by=UserSortField.NAME, order=Order.ASC),
limit=10,
offset=0
)
# Получите данные
users = await repo.get_all(filters=filters)
return usersdplex предоставляет 11 специализированных типов фильтров:
Фильтрация строковых полей с поддержкой паттернов и регистронезависимого поиска.
from dplex import StringFilter
# Точное совпадение
StringFilter(eq="[email protected]")
# Содержит (регистронезависимо)
StringFilter(icontains="john")
# Начинается с
StringFilter(starts_with="Dr.")
# Заканчивается на
StringFilter(ends_with=".com")
# Список значений
StringFilter(in_=["admin", "moderator"])
# Комбинация условий
StringFilter(
icontains="john",
ends_with="@example.com",
ne="[email protected]"
)Доступные операции:
eq,ne— равно/не равноin_,not_in— в списке/не в спискеgt,gte,lt,lte— больше/меньше (лексикографически)contains,icontains— содержит (с учетом регистра/без)startswith,istartswith— начинается сendswith,iendswith— заканчивается наis_null— NULL проверка
Фильтрация целочисленных полей.
from dplex import IntFilter
# Диапазон
IntFilter(gte=18, lte=65)
# Список значений
IntFilter(in_=[1, 2, 3, 5, 8])
# Неравенство
IntFilter(ne=0)Доступные операции:
eq,ne— равно/не равноin_,not_in— в списке/не в спискеgt,gte,lt,lte— больше/меньшеis_null— NULL проверка
Фильтрация чисел с плавающей точкой.
from dplex import FloatFilter
# Диапазон с точностью
FloatFilter(gte=0.0, lt=100.0)
# Точное значение
FloatFilter(eq=3.14159)Операции: аналогичны IntFilter
Фильтрация точных десятичных чисел (Decimal).
from decimal import Decimal
from dplex import DecimalFilter
# Для финансовых расчетов
DecimalFilter(gte=Decimal("0.01"), lte=Decimal("999999.99"))Операции: аналогичны IntFilter
Фильтрация даты и времени.
from datetime import datetime
from dplex import DateTimeFilter
# Диапазон дат
DateTimeFilter(
gte=datetime(2024, 1, 1),
lt=datetime(2024, 12, 31)
)
# После определенной даты
DateTimeFilter(gt=datetime(2024, 6, 1))Операции: eq, ne, in_, not_in, gt, gte, lt, lte, is_null
Фильтрация только даты (без времени).
from datetime import date
from dplex import DateFilter
# Конкретная дата
DateFilter(eq=date(2024, 1, 1))
# Диапазон
DateFilter(gte=date(2024, 1, 1), lte=date(2024, 12, 31))Операции: аналогичны DateTimeFilter
Фильтрация только времени.
from datetime import time
from dplex import TimeFilter
# Рабочие часы
TimeFilter(gte=time(9, 0), lt=time(18, 0))Операции: аналогичны DateTimeFilter
Фильтрация Unix timestamp (целые числа).
from dplex import TimestampFilter
# После определенного момента
TimestampFilter(gte=1704067200) # 2024-01-01 00:00:00Операции: аналогичны IntFilter
Фильтрация булевых значений.
from dplex import BooleanFilter
# Только активные
BooleanFilter(eq=True)
# NULL проверка
BooleanFilter(is_null=False)Операции: eq, ne, is_null
Фильтрация enum полей.
from enum import Enum
from dplex import EnumFilter
class UserRole(Enum):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
# Конкретная роль
EnumFilter(eq=UserRole.ADMIN)
# Несколько ролей
EnumFilter(in_=[UserRole.ADMIN, UserRole.USER])Операции: eq, ne, in_, not_in, is_null
Фильтрация UUID полей.
import uuid
from dplex import UUIDFilter
# Конкретный UUID
UUIDFilter(eq=uuid.UUID("123e4567-e89b-12d3-a456-426614174000"))
# Список UUID
UUIDFilter(in_=[
uuid.UUID("123e4567-e89b-12d3-a456-426614174000"),
uuid.UUID("223e4567-e89b-12d3-a456-426614174000")
])Операции: eq, ne, in_, not_in, is_null
from dplex import Sort, Order
# По возрастанию
filters = UserFilters(
sort=Sort(by=UserSortField.NAME, order=Order.ASC)
)
# По убыванию
filters = UserFilters(
sort=Sort(by=UserSortField.AGE, order=Order.DESC)
)# Сначала по возрасту (DESC), затем по имени (ASC)
filters = UserFilters(
sort=[
Sort(by=UserSortField.AGE, order=Order.DESC),
Sort(by=UserSortField.NAME, order=Order.ASC)
]
)from dplex import NullsPlacement
# NULL значения в начале
filters = UserFilters(
sort=Sort(
by=UserSortField.NAME,
order=Order.ASC,
nulls=NullsPlacement.FIRST
)
)
# NULL значения в конце
filters = UserFilters(
sort=Sort(
by=UserSortField.NAME,
order=Order.ASC,
nulls=NullsPlacement.LAST
)
)# Первая страница (10 записей)
filters = UserFilters(limit=10, offset=0)
# Вторая страница
filters = UserFilters(limit=10, offset=10)
# Третья страница
filters = UserFilters(limit=10, offset=20)DPRepo предоставляет базовый функционал для работы с моделями через Repository Pattern.
from dplex import DPRepo
from sqlalchemy.ext.asyncio import AsyncSession
class UserRepository(DPRepo[User, int]):
"""Репозиторий для работы с пользователями"""
pass
# Использование
async def example(session: AsyncSession):
repo = UserRepository(session, User)# Все записи
users = await repo.get_all()
# С фильтрами
users = await repo.get_all(filters=UserFilters(
is_active=BooleanFilter(eq=True),
limit=10
))user = await repo.get_by_id(user_id=1)
if user is None:
# Запись не найдена
passnew_user = await repo.create(
name="John Doe",
email="[email protected]",
age=30
)updated_user = await repo.update(
item_id=1,
name="Jane Doe",
age=31
)deleted_user = await repo.delete(item_id=1)if await repo.exists(item_id=1):
print("Пользователь существует")# Всего записей
total = await repo.count()
# С фильтрами
active_count = await repo.count(filters=UserFilters(
is_active=BooleanFilter(eq=True)
))DPService расширяет функционал репозитория, добавляя бизнес-логику и работу с Pydantic схемами.
from dplex import DPService
from pydantic import BaseModel
# Pydantic схемы
class UserResponse(BaseModel):
id: int
name: str
email: str
age: int
class Config:
from_attributes = True
class UserCreate(BaseModel):
name: str
email: str
age: int
class UserUpdate(BaseModel):
name: str | None = None
email: str | None = None
age: int | None = None
# Сервис
class UserService(DPService[User, int, UserResponse, UserCreate, UserUpdate, UserFilters]):
"""Сервис для работы с пользователями"""
def __init__(self, session: AsyncSession):
super().__init__(
session=session,
model=User,
response_schema=UserResponse
)
# Использование
async def example(session: AsyncSession):
service = UserService(session)users: list[UserResponse] = await service.get_all(
filters=UserFilters(
is_active=BooleanFilter(eq=True),
limit=10
)
)user: UserResponse | None = await service.get_by_id(user_id=1)create_data = UserCreate(
name="John Doe",
email="[email protected]",
age=30
)
new_user: UserResponse = await service.create(create_data)update_data = UserUpdate(age=31)
updated_user: UserResponse = await service.update(
item_id=1,
update_schema=update_data
)deleted_user: UserResponse = await service.delete(item_id=1)filters = UserFilters(
# Имя содержит "john" (регистронезависимо)
name=StringFilter(icontains="john"),
# Email в домене example.com
email=StringFilter(endswith="@example.com"),
# Возраст от 18 до 65
age=IntFilter(gte=18, lte=65),
# Только активные
is_active=BooleanFilter(eq=True),
# Сортировка: сначала по возрасту (DESC), затем по имени (ASC)
sort=[
Sort(by=UserSortField.AGE, order=Order.DESC),
Sort(by=UserSortField.NAME, order=Order.ASC)
],
# Пагинация
limit=20,
offset=0
)
users = await repo.get_all(filters=filters)# Создать фильтры
filters = UserFilters(
name=StringFilter(icontains="john"),
age=IntFilter(gte=18)
)
# Проверить наличие фильтров
if filters.has_filters():
print(f"Активных фильтров: {filters.get_filter_count()}")
# Получить активные фильтры
active = filters.get_active_filters()
print(active) # {'name': StringFilter(...), 'age': IntFilter(...)}
# Получить имена полей с фильтрами
fields = filters.get_filter_fields()
print(fields) # ['name', 'age']
# Сводка по фильтрам
summary = filters.get_filter_summary()
print(summary) # {'name': 1, 'age': 1}
# Очистить фильтры
filters.clear_filters()
print(filters.has_filters()) # Falsefilters = UserFilters(limit=10, offset=20)
# Проверить наличие пагинации
if filters.has_pagination():
info = filters.get_pagination_info()
print(info) # {'limit': 10, 'offset': 20}filters = UserFilters(
sort=Sort(by=UserSortField.NAME, order=Order.ASC)
)
if filters.has_sort():
print("Сортировка установлена")Репозиторий инкапсулирует логику доступа к данным, предоставляя коллекцию-подобный интерфейс.
class UserRepository(DPRepo[User, int]):
async def get_active_users(self) -> list[User]:
"""Кастомный метод репозитория"""
return await self.get_all(
filters=UserFilters(
is_active=BooleanFilter(eq=True)
)
)
async def get_by_email(self, email: str) -> User | None:
"""Найти пользователя по email"""
users = await self.get_all(
filters=UserFilters(
email=StringFilter(eq=email),
limit=1
)
)
return users[0] if users else NoneСервис содержит бизнес-логику и работает с Pydantic схемами для валидации.
class UserService(DPService[User, int, UserResponse, UserCreate, UserUpdate, UserFilters]):
async def register_user(self, data: UserCreate) -> UserResponse:
"""Регистрация нового пользователя с дополнительной логикой"""
# Проверка уникальности email
existing = await self.repo.get_all(
filters=UserFilters(
email=StringFilter(eq=data.email),
limit=1
)
)
if existing:
raise ValueError("Email уже используется")
# Создание пользователя
return await self.create(data)
async def deactivate_user(self, user_id: int) -> UserResponse:
"""Деактивация пользователя"""
return await self.update(
item_id=user_id,
update_schema=UserUpdate(is_active=False)
)dplex следует современным практикам Python:
# ✅ Правильно — встроенные типы Python 3.9+
users: list[User] | None = None
data: dict[str, int] = {}
# ❌ Неправильно — старый синтаксис typing
from typing import List, Dict, Optional
users: Optional[List[User]] = None
data: Dict[str, int] = {}MIT License
- Документация: [в разработке]
- Issues: [GitHub Issues]
- Обсуждения: [GitHub Discussions]
- Начальный релиз
- Поддержка 11 типов фильтров
- Repository и Service patterns
- Множественная сортировка с контролем NULL
- Встроенная пагинация
- Полная типобезопасность