Фильтрация¶
Для получения списка объектов с фильтрацией fastapi_sqlalchemy_toolkit
предоставляет два метода:
list
, который осуществляет предобработку значений, и filter
, который не производит дополнительных обработок.
Аналогично ведут себя методы paginated_list
и paginated_filter
, за исключением того, что они пагинирует результат
с помощью fastapi_pagination
.
Пусть имеются следующие модели:
class Base(DeclarativeBase):
id: Mapped[UUID] = mapped_column(
primary_key=True,
default=uuid4,
server_default=func.gen_random_uuid(),
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
class Parent(Base):
title: Mapped[str]
slug: Mapped[str] = mapped_column(unique=True)
children: Mapped[list["Child"]] = relationship(back_populates="parent")
class Child(Base):
title: Mapped[str]
slug: Mapped[str] = mapped_column(unique=True)
parent_id: Mapped[UUID] = mapped_column(ForeignKey("parent.id"))
parent: Mapped[Parent] = relationship(back_populates="children")
И менеджер:
from fastapi_sqlalchemy_toolkit import ModelManager
child_manager = ModelManager[Child, CreateChildSchema, PatchChildSchema](
Child, default_ordering=Child.title
)
Простая фильтрация по точному соответствию¶
@router.get("/children")
async def get_list(
session: Session,
slug: str | None = None,
) -> list[ChildListSchema]:
return await child_manager.list(
session,
slug=slug,
)
Запрос GET /children
сгенерирует следующий SQL:
Запрос GET /children?slug=child-1
сгенерирует следующий SQL:
SELECT child.title, child.slug, child.parent_id, child.id, child.created_at
FROM child
WHERE child.slug = :slug_1
По конвенции FastAPI
, необязательные параметры запроса типизируются как slug: str | None = None
.
При этом клиенты API обычно ожидают, что при запросе GET /children
будут возвращены все объекты Child
,
а не только те, у которых slug is null
. Поэтому метод list
(paginated_list
) отбрасывает фильтрацию
по этому параметру, если его значение не передано.
Более сложная фильтрация¶
Чтобы использовать фильтрацию не только по точному соответствию атрибуту модели,
в методах list
и paginated_list
можно передать параметр filter_expressions
.
Параметр filter_expressions
принимает словарь, в котором ключи могут быть:
-
Атрибутами основной модели (
Child.title
) -
Операторами атрибутов модели (
Child.title.ilike
) -
Функциями
sqlalchemy
над атрибутами модели (func.date(Child.created_at)
) -
Атрибутами связанной модели (
Parent.title
). Работает в том случае, если это модель, напрямую связанная с основной, а также если модели связывает только один внешний ключ.
Значение по ключу в словаре filter_expressions
-- это значение,
по которому должна осуществляться фильтрация.
Пример фильтрации по оператору атрибута модели:
@router.get("/children")
async def get_list(
session: Session,
title: str | None = None,
) -> list[ChildListSchema]:
return await child_manager.list(
session,
filter_expressions={
Child.title.ilike: title
},
)
Запрос GET /children
сгенерирует следующий SQL:
Запрос GET /children?title=ch
сгенерирует следующий SQL:
SELECT child.title, child.slug, child.parent_id, child.id, child.created_at
FROM child
WHERE lower(child.title) LIKE lower(:title_1)
Пример фильтрации по функции sqlalchemy
над атрибутом модели:
@router.get("/children")
async def get_list(
session: Session,
created_at_date: date | None = None,
) -> list[ChildListSchema]:
return await child_manager.list(
session,
filter_expressions={
func.date(Child.created_at): created_at_date
},
)
Запрос GET /children?created_at_date=2023-11-19
сгенерирует следующий SQL:
SELECT child.title, child.slug, child.parent_id, child.id, child.created_at
FROM child
WHERE date(child.created_at) = :date_1
Пример фильтрации по атрибуту связанной модели:
@router.get("/children")
async def get_list(
session: Session,
parent_title: str | None = None,
) -> list[ChildListSchema]:
return await child_manager.list(
session,
filter_expressions={
Parent.title.ilike: title
},
)
Запрос GET /children?parent_title=ch
сгенерирует следующий SQL:
SELECT parent.title, parent.slug, parent.id, parent.created_at,
child.title AS title_1, child.slug AS slug_1, child.parent_id, child.id AS id_1,
child.created_at AS created_at_1
FROM child LEFT OUTER JOIN parent ON parent.id = child.parent_id
WHERE lower(parent.title) LIKE lower(:title_1)
При фильтрации по полям связанных моделей через параметр filter_expression
,
необходимые для фильтрации join
будут сделаны автоматически.
Важно: работает только для моделей, напрямую связанных с основной, и только тогда, когда
эти модели связывает единственный внешний ключ.
Фильтрация без дополнительной обработки¶
Для фильтрации без дополнительной обработки в методах list
и paginated_list
можно
использовать параметр where
. Значение этого параметра будет напрямую
передано в метод .where()
экземпляра Select
в выражении запроса SQLAlchemy
.
Использовать параметр where
методов list
и paginated_list
имеет смысл тогда,
когда эти методы используются в списочном API эндпоинте и предобработка части параметров
запроса полезна, однако нужно также добавить фильтр без предобработок от fastapi_sqlalchemy_toolkit
.
В том случае, когда предобработки fastapi_sqlalchemy_toolkit
не нужны вообще, стоит использовать методы
filter
и paginated_filter
:
В отличие от метода list
, метод filter
:
-
Не игнорирует простые фильтры (
kwargs
) со значениемNone
-
Не имеет параметра
filter_expressions
, т. е. не будет выполнятьjoin
, необходимые для фильтрации по полям связанных моделей.
Фильтрация по null
через API¶
Если в списочном эндпоинте API требуется, чтобы можно было как отфильтровать значение поля
по переданному значению, так и отфильтровать его по null
, предлагается использовать параметр
nullable_filter_expressions
методов list
(paginated_list
):
from datetime import datetime
from fastapi_sqlalchemy_toolkit import NullableQuery
from app.managers import my_object_manager
from app.models import MyObject
@router.get("/my-objects")
async def get_my_objects(
session: Session,
deleted_at: datetime | NullableQuery | None = None
) -> list[MyObjectListSchema]:
return await my_object_manager.list(
session,
nullable_filter_expressions={
MyObject.deleted_at: deleted_at
}
)
Параметру с поддержкой фильтрации по null
нужно указать возможный тип
fastapi_sqlalchemy_toolkit.NullableQuery
.
Теперь при запросе GET /my-objects?deleted_at=
или GET /my-objects?deleted_at=null
вернутся объекты MyObject
, у которых deleted_at IS NULL
.
Фильтрация по обратным связям¶
Также в методах получения списков есть поддержка фильтрации
по обратным связям (relationship()
в направлении один ко многим) с использованием метода .any()
.