Skip to content

crud

Here's the reference for the CRUD classes, factory, and search utilities.

You can import the main symbols from fastapi_toolsets.crud:

from fastapi_toolsets.crud import CrudFactory, AsyncCrud
from fastapi_toolsets.crud.search import SearchConfig, get_searchable_fields, build_search_filters

fastapi_toolsets.crud.factory.AsyncCrud

Bases: Generic[ModelType]

Generic async CRUD operations for SQLAlchemy models.

Subclass this and set the model class variable, or use CrudFactory.

count(session, filters=None, *, joins=None, outer_join=False) async classmethod

Count records matching the filters.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any] | None

List of SQLAlchemy filter conditions

None
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False

Returns:

Type Description
int

Number of matching records

create(session, obj, *, schema=None) async classmethod

create(
    session: AsyncSession,
    obj: BaseModel,
    *,
    schema: type[SchemaType],
) -> Response[SchemaType]
create(
    session: AsyncSession,
    obj: BaseModel,
    *,
    schema: None = ...,
) -> ModelType

Create a new record in the database.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
obj BaseModel

Pydantic model with data to create

required
schema type[BaseModel] | None

Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a Response[schema].

None

Returns:

Type Description
ModelType | Response[Any]

Created model instance, or Response[schema] when schema is given.

cursor_paginate(session, *, cursor=None, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, order_joins=None, items_per_page=20, search=None, search_fields=None, search_column=None, order_fields=None, facet_fields=None, filter_by=None, schema) async classmethod

Get paginated results using cursor-based pagination.

Parameters:

Name Type Description Default
session AsyncSession

DB async session.

required
cursor str | None

Cursor string from a previous CursorPagination. Omit (or pass None) to start from the beginning.

None
filters list[Any] | None

List of SQLAlchemy filter conditions.

None
joins JoinType | None

List of (model, condition) tuples for joining related tables.

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN.

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options. Falls back to default_load_options when not provided.

None
order_by OrderByClause | None

Additional ordering applied after the cursor column.

None
items_per_page int

Number of items per page (default 20).

20
search str | SearchConfig | None

Search query string or SearchConfig object.

None
search_fields Sequence[SearchFieldType] | None

Fields to search in (overrides class default).

None
search_column str | None

Restrict search to a single column key.

None
order_fields Sequence[OrderFieldType] | None

Fields allowed for sorting (overrides class default).

None
facet_fields Sequence[FacetFieldType] | None

Columns to compute distinct values for (overrides class default).

None
filter_by dict[str, Any] | BaseModel | None

Dict of {column_key: value} to filter by declared facet fields. Keys must match the column.key of a facet field. Scalar → equality, list → IN clause. Raises InvalidFacetFilterError for unknown keys.

None
schema type[BaseModel]

Optional Pydantic schema to serialize each item into.

required

Returns:

Type Description
CursorPaginatedResponse[Any]

PaginatedResponse with CursorPagination metadata

cursor_paginate_params(*, default_page_size=20, max_page_size=100, search=True, filter=True, order=True, search_fields=None, facet_fields=None, order_fields=None, default_order_field=None, default_order='asc') classmethod

Return a FastAPI dependency that collects all params for :meth:cursor_paginate.

Parameters:

Name Type Description Default
default_page_size int

Default items_per_page value.

20
max_page_size int

Maximum items_per_page value.

100
search bool

Enable search query parameters.

True
filter bool

Enable facet filter query parameters.

True
order bool

Enable order query parameters.

True
search_fields Sequence[SearchFieldType] | None

Override searchable fields.

None
facet_fields Sequence[FacetFieldType] | None

Override facet fields.

None
order_fields Sequence[OrderFieldType] | None

Override order fields.

None
default_order_field QueryableAttribute[Any] | None

Default field to order by when order_by is absent.

None
default_order Literal['asc', 'desc']

Default sort direction.

'asc'

Returns:

Name Type Description
Callable[..., Awaitable[dict[str, Any]]]

An async dependency that resolves to a dict ready to be unpacked

into Callable[..., Awaitable[dict[str, Any]]]

meth:cursor_paginate.

delete(session, filters, *, return_response=False) async classmethod

delete(
    session: AsyncSession,
    filters: list[Any],
    *,
    return_response: Literal[True],
) -> Response[None]
delete(
    session: AsyncSession,
    filters: list[Any],
    *,
    return_response: Literal[False] = ...,
) -> None

Delete records from the database.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any]

List of SQLAlchemy filter conditions

required
return_response bool

When True, returns Response[None] instead of None. Useful for API endpoints that expect a consistent response envelope.

False

Returns:

Type Description
None | Response[None]

None, or Response[None] when return_response=True.

exists(session, filters, *, joins=None, outer_join=False) async classmethod

Check if a record exists.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any]

List of SQLAlchemy filter conditions

required
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False

Returns:

Type Description
bool

True if at least one record matches

first(session, filters=None, *, joins=None, outer_join=False, with_for_update=False, load_options=None, schema=None) async classmethod

first(
    session: AsyncSession,
    filters: list[Any] | None = None,
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: type[SchemaType],
) -> Response[SchemaType] | None
first(
    session: AsyncSession,
    filters: list[Any] | None = None,
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: None = ...,
) -> ModelType | None

Get the first matching record, or None.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any] | None

List of SQLAlchemy filter conditions

None
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False
with_for_update bool

Lock the row for update

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options (e.g., selectinload)

None
schema type[BaseModel] | None

Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a Response[schema].

None

Returns:

Type Description
ModelType | Response[Any] | None

Model instance, Response[schema] when schema is given,

ModelType | Response[Any] | None

or None when no record matches.

get(session, filters, *, joins=None, outer_join=False, with_for_update=False, load_options=None, schema=None) async classmethod

get(
    session: AsyncSession,
    filters: list[Any],
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: type[SchemaType],
) -> Response[SchemaType]
get(
    session: AsyncSession,
    filters: list[Any],
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: None = ...,
) -> ModelType

Get exactly one record. Raises NotFoundError if not found.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any]

List of SQLAlchemy filter conditions

required
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False
with_for_update bool

Lock the row for update

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options (e.g., selectinload)

None
schema type[BaseModel] | None

Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a Response[schema].

None

Returns:

Type Description
ModelType | Response[Any]

Model instance, or Response[schema] when schema is given.

Raises:

Type Description
NotFoundError

If no record found

MultipleResultsFound

If more than one record found

get_multi(session, *, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, limit=None, offset=None) async classmethod

Get multiple records from the database.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any] | None

List of SQLAlchemy filter conditions

None
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options

None
order_by OrderByClause | None

Column or list of columns to order by

None
limit int | None

Max number of rows to return

None
offset int | None

Rows to skip

None

Returns:

Type Description
Sequence[ModelType]

List of model instances

get_or_none(session, filters, *, joins=None, outer_join=False, with_for_update=False, load_options=None, schema=None) async classmethod

get_or_none(
    session: AsyncSession,
    filters: list[Any],
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: type[SchemaType],
) -> Response[SchemaType] | None
get_or_none(
    session: AsyncSession,
    filters: list[Any],
    *,
    joins: JoinType | None = None,
    outer_join: bool = False,
    with_for_update: bool = False,
    load_options: Sequence[ExecutableOption] | None = None,
    schema: None = ...,
) -> ModelType | None

Get exactly one record, or None if not found.

Like :meth:get but returns None instead of raising :class:~fastapi_toolsets.exceptions.NotFoundError when no record matches the filters.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any]

List of SQLAlchemy filter conditions

required
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False
with_for_update bool

Lock the row for update

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options (e.g., selectinload)

None
schema type[BaseModel] | None

Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a Response[schema].

None

Returns:

Type Description
ModelType | Response[Any] | None

Model instance, Response[schema] when schema is given,

ModelType | Response[Any] | None

or None when no record matches.

Raises:

Type Description
MultipleResultsFound

If more than one record found

offset_paginate(session, *, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, order_joins=None, page=1, items_per_page=20, include_total=True, search=None, search_fields=None, search_column=None, order_fields=None, facet_fields=None, filter_by=None, schema) async classmethod

Get paginated results using offset-based pagination.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
filters list[Any] | None

List of SQLAlchemy filter conditions

None
joins JoinType | None

List of (model, condition) tuples for joining related tables

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options

None
order_by OrderByClause | None

Column or list of columns to order by

None
page int

Page number (1-indexed)

1
items_per_page int

Number of items per page

20
include_total bool

When False, skip the COUNT query; pagination.total_count will be None.

True
search str | SearchConfig | None

Search query string or SearchConfig object

None
search_fields Sequence[SearchFieldType] | None

Fields to search in (overrides class default)

None
search_column str | None

Restrict search to a single column key.

None
order_fields Sequence[OrderFieldType] | None

Fields allowed for sorting (overrides class default).

None
facet_fields Sequence[FacetFieldType] | None

Columns to compute distinct values for (overrides class default)

None
filter_by dict[str, Any] | BaseModel | None

Dict of {column_key: value} to filter by declared facet fields. Keys must match the column.key of a facet field. Scalar → equality, list → IN clause. Raises InvalidFacetFilterError for unknown keys.

None
schema type[BaseModel]

Pydantic schema to serialize each item into.

required

Returns:

Type Description
OffsetPaginatedResponse[Any]

PaginatedResponse with OffsetPagination metadata

offset_paginate_params(*, default_page_size=20, max_page_size=100, include_total=True, search=True, filter=True, order=True, search_fields=None, facet_fields=None, order_fields=None, default_order_field=None, default_order='asc') classmethod

Return a FastAPI dependency that collects all params for :meth:offset_paginate.

Parameters:

Name Type Description Default
default_page_size int

Default items_per_page value.

20
max_page_size int

Maximum items_per_page value.

100
include_total bool

Whether to include total count (not a query param).

True
search bool

Enable search query parameters.

True
filter bool

Enable facet filter query parameters.

True
order bool

Enable order query parameters.

True
search_fields Sequence[SearchFieldType] | None

Override searchable fields.

None
facet_fields Sequence[FacetFieldType] | None

Override facet fields.

None
order_fields Sequence[OrderFieldType] | None

Override order fields.

None
default_order_field QueryableAttribute[Any] | None

Default field to order by when order_by is absent.

None
default_order Literal['asc', 'desc']

Default sort direction.

'asc'

Returns:

Name Type Description
Callable[..., Awaitable[dict[str, Any]]]

An async dependency that resolves to a dict ready to be unpacked

into Callable[..., Awaitable[dict[str, Any]]]

meth:offset_paginate.

paginate(session, *, pagination_type=PaginationType.OFFSET, filters=None, joins=None, outer_join=False, load_options=None, order_by=None, order_joins=None, page=1, cursor=None, items_per_page=20, include_total=True, search=None, search_fields=None, search_column=None, order_fields=None, facet_fields=None, filter_by=None, schema) async classmethod

paginate(
    session: AsyncSession,
    *,
    pagination_type: Literal[PaginationType.OFFSET],
    filters: list[Any] | None = ...,
    joins: JoinType | None = ...,
    outer_join: bool = ...,
    load_options: Sequence[ExecutableOption] | None = ...,
    order_by: OrderByClause | None = ...,
    order_joins: list[Any] | None = ...,
    page: int = ...,
    cursor: str | None = ...,
    items_per_page: int = ...,
    include_total: bool = ...,
    search: str | SearchConfig | None = ...,
    search_fields: Sequence[SearchFieldType] | None = ...,
    search_column: str | None = ...,
    order_fields: Sequence[OrderFieldType] | None = ...,
    facet_fields: Sequence[FacetFieldType] | None = ...,
    filter_by: dict[str, Any] | BaseModel | None = ...,
    schema: type[BaseModel],
) -> OffsetPaginatedResponse[Any]
paginate(
    session: AsyncSession,
    *,
    pagination_type: Literal[PaginationType.CURSOR],
    filters: list[Any] | None = ...,
    joins: JoinType | None = ...,
    outer_join: bool = ...,
    load_options: Sequence[ExecutableOption] | None = ...,
    order_by: OrderByClause | None = ...,
    order_joins: list[Any] | None = ...,
    page: int = ...,
    cursor: str | None = ...,
    items_per_page: int = ...,
    include_total: bool = ...,
    search: str | SearchConfig | None = ...,
    search_fields: Sequence[SearchFieldType] | None = ...,
    search_column: str | None = ...,
    order_fields: Sequence[OrderFieldType] | None = ...,
    facet_fields: Sequence[FacetFieldType] | None = ...,
    filter_by: dict[str, Any] | BaseModel | None = ...,
    schema: type[BaseModel],
) -> CursorPaginatedResponse[Any]

Get paginated results using either offset or cursor pagination.

Parameters:

Name Type Description Default
session AsyncSession

DB async session.

required
pagination_type PaginationType

Pagination strategy. Defaults to PaginationType.OFFSET.

OFFSET
filters list[Any] | None

List of SQLAlchemy filter conditions.

None
joins JoinType | None

List of (model, condition) tuples for joining related tables.

None
outer_join bool

Use LEFT OUTER JOIN instead of INNER JOIN.

False
load_options Sequence[ExecutableOption] | None

SQLAlchemy loader options. Falls back to default_load_options when not provided.

None
order_by OrderByClause | None

Column or expression to order results by.

None
page int

Page number (1-indexed). Only used when pagination_type is OFFSET.

1
cursor str | None

Cursor token from a previous :class:.CursorPaginatedResponse. Only used when pagination_type is CURSOR.

None
items_per_page int

Number of items per page (default 20).

20
include_total bool

When False, skip the COUNT query; only applies when pagination_type is OFFSET.

True
search str | SearchConfig | None

Search query string or :class:.SearchConfig object.

None
search_fields Sequence[SearchFieldType] | None

Fields to search in (overrides class default).

None
search_column str | None

Restrict search to a single column key.

None
order_fields Sequence[OrderFieldType] | None

Fields allowed for sorting (overrides class default).

None
facet_fields Sequence[FacetFieldType] | None

Columns to compute distinct values for (overrides class default).

None
filter_by dict[str, Any] | BaseModel | None

Dict of {column_key: value} to filter by declared facet fields. Keys must match the column.key of a facet field. Scalar → equality, list → IN clause. Raises :exc:.InvalidFacetFilterError for unknown keys.

None
schema type[BaseModel]

Pydantic schema to serialize each item into.

required

Returns:

Type Description
OffsetPaginatedResponse[Any] | CursorPaginatedResponse[Any]

class:.OffsetPaginatedResponse when pagination_type is

OffsetPaginatedResponse[Any] | CursorPaginatedResponse[Any]

OFFSET, :class:.CursorPaginatedResponse when it is

OffsetPaginatedResponse[Any] | CursorPaginatedResponse[Any]

CURSOR.

paginate_params(*, default_page_size=20, max_page_size=100, default_pagination_type=PaginationType.OFFSET, include_total=True, search=True, filter=True, order=True, search_fields=None, facet_fields=None, order_fields=None, default_order_field=None, default_order='asc') classmethod

Return a FastAPI dependency that collects all params for :meth:paginate.

Parameters:

Name Type Description Default
default_page_size int

Default items_per_page value.

20
max_page_size int

Maximum items_per_page value.

100
default_pagination_type PaginationType

Default pagination strategy.

OFFSET
include_total bool

Whether to include total count (not a query param).

True
search bool

Enable search query parameters.

True
filter bool

Enable facet filter query parameters.

True
order bool

Enable order query parameters.

True
search_fields Sequence[SearchFieldType] | None

Override searchable fields.

None
facet_fields Sequence[FacetFieldType] | None

Override facet fields.

None
order_fields Sequence[OrderFieldType] | None

Override order fields.

None
default_order_field QueryableAttribute[Any] | None

Default field to order by when order_by is absent.

None
default_order Literal['asc', 'desc']

Default sort direction.

'asc'

Returns:

Name Type Description
Callable[..., Awaitable[dict[str, Any]]]

An async dependency that resolves to a dict ready to be unpacked

into Callable[..., Awaitable[dict[str, Any]]]

meth:paginate.

update(session, obj, filters, *, exclude_unset=True, exclude_none=False, schema=None) async classmethod

update(
    session: AsyncSession,
    obj: BaseModel,
    filters: list[Any],
    *,
    exclude_unset: bool = True,
    exclude_none: bool = False,
    schema: type[SchemaType],
) -> Response[SchemaType]
update(
    session: AsyncSession,
    obj: BaseModel,
    filters: list[Any],
    *,
    exclude_unset: bool = True,
    exclude_none: bool = False,
    schema: None = ...,
) -> ModelType

Update a record in the database.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
obj BaseModel

Pydantic model with update data

required
filters list[Any]

List of SQLAlchemy filter conditions

required
exclude_unset bool

Exclude fields not explicitly set in the schema

True
exclude_none bool

Exclude fields with None value

False
schema type[BaseModel] | None

Pydantic schema to serialize the result into. When provided, the result is automatically wrapped in a Response[schema].

None

Returns:

Type Description
ModelType | Response[Any]

Updated model instance, or Response[schema] when schema is given.

Raises:

Type Description
NotFoundError

If no record found

upsert(session, obj, index_elements, *, set_=None, where=None) async classmethod

Create or update a record (PostgreSQL only).

Uses INSERT ... ON CONFLICT for atomic upsert.

Parameters:

Name Type Description Default
session AsyncSession

DB async session

required
obj BaseModel

Pydantic model with data

required
index_elements list[str]

Columns for ON CONFLICT (unique constraint)

required
set_ BaseModel | None

Pydantic model for ON CONFLICT DO UPDATE SET

None
where WhereHavingRole | None

WHERE clause for ON CONFLICT DO UPDATE

None

Returns:

Type Description
ModelType | None

Model instance

fastapi_toolsets.crud.factory.CrudFactory(model, *, base_class=AsyncCrud, searchable_fields=None, facet_fields=None, order_fields=None, m2m_fields=None, default_load_options=None, cursor_column=None)

Create a CRUD class for a specific model.

Parameters:

Name Type Description Default
model type[ModelType]

SQLAlchemy model class

required
base_class type[AsyncCrud[Any]]

Optional base class to inherit from instead of AsyncCrud. Use this to share custom methods across multiple CRUD classes while still using the factory shorthand.

AsyncCrud
searchable_fields Sequence[SearchFieldType] | None

Optional list of searchable fields

None
facet_fields Sequence[FacetFieldType] | None

Optional list of columns to compute distinct values for in paginated responses. Supports direct columns (User.status) and relationship tuples ((User.role, Role.name)). Can be overridden per call.

None
order_fields Sequence[OrderFieldType] | None

Optional list of model attributes that callers are allowed to order by via offset_paginate_params(). Can be overridden per call.

None
m2m_fields M2MFieldType | None

Optional mapping for many-to-many relationships. Maps schema field names (containing lists of IDs) to SQLAlchemy relationship attributes.

None
default_load_options Sequence[ExecutableOption] | None

Default SQLAlchemy loader options applied to all read queries when no explicit load_options are passed. Use this instead of lazy="selectin" on the model so that loading strategy is explicit and per-CRUD. Overridden entirely (not merged) when load_options is provided at call-site.

None
cursor_column Any | None

Required to call cursor_paginate. Must be monotonically ordered (e.g. integer PK, UUID v7, timestamp). See the cursor pagination docs for supported column types.

None

Returns:

Type Description
type[AsyncCrud[ModelType]]

AsyncCrud subclass bound to the model

Example
from fastapi_toolsets.crud import CrudFactory
from myapp.models import User, Post

UserCrud = CrudFactory(User)
PostCrud = CrudFactory(Post)

# With searchable fields:
UserCrud = CrudFactory(
    User,
    searchable_fields=[User.username, User.email, (User.role, Role.name)]
)

# With many-to-many fields:
# Schema has `tag_ids: list[UUID]`, model has `tags` relationship to Tag
PostCrud = CrudFactory(
    Post,
    m2m_fields={"tag_ids": Post.tags},
)

# With facet fields for filter dropdowns / faceted search:
UserCrud = CrudFactory(
    User,
    facet_fields=[User.status, User.country, (User.role, Role.name)],
)

# With a fixed cursor column for cursor_paginate:
PostCrud = CrudFactory(
    Post,
    cursor_column=Post.created_at,
)

# With default load strategy (replaces lazy="selectin" on the model):
ArticleCrud = CrudFactory(
    Article,
    default_load_options=[selectinload(Article.category), selectinload(Article.tags)],
)

# Override default_load_options for a specific call:
article = await ArticleCrud.get(
    session,
    [Article.id == 1],
    load_options=[selectinload(Article.category)],  # tags won't load
)

# Usage
user = await UserCrud.get(session, [User.id == 1])
posts = await PostCrud.get_multi(session, filters=[Post.user_id == user.id])

# Create with M2M - tag_ids are automatically resolved
post = await PostCrud.create(session, PostCreate(title="Hello", tag_ids=[id1, id2]))

# With search
result = await UserCrud.offset_paginate(session, search="john")

# With joins (inner join by default):
users = await UserCrud.get_multi(
    session,
    joins=[(Post, Post.user_id == User.id)],
    filters=[Post.published == True],
)

# With outer join:
users = await UserCrud.get_multi(
    session,
    joins=[(Post, Post.user_id == User.id)],
    outer_join=True,
)

# With a shared custom base class:
from typing import Generic, TypeVar
from sqlalchemy.orm import DeclarativeBase

T = TypeVar("T", bound=DeclarativeBase)

class AuditedCrud(AsyncCrud[T], Generic[T]):
    @classmethod
    async def get_active(cls, session):
        return await cls.get_multi(session, filters=[cls.model.is_active == True])

UserCrud = CrudFactory(User, base_class=AuditedCrud)

fastapi_toolsets.crud.search.SearchConfig dataclass

Advanced search configuration.

Attributes:

Name Type Description
query str

The search string

fields Sequence[SearchFieldType] | None

Fields to search (columns or tuples for relationships)

case_sensitive bool

Case-sensitive search (default: False)

match_mode Literal['any', 'all']

"any" (OR) or "all" (AND) to combine fields

fastapi_toolsets.crud.search.get_searchable_fields(model, *, include_relationships=True, max_depth=1) cached

Auto-detect String fields on a model and its relationships.

Parameters:

Name Type Description Default
model type[DeclarativeBase]

SQLAlchemy model class

required
include_relationships bool

Include fields from many-to-one/one-to-one relationships

True
max_depth int

Max depth for relationship traversal (default: 1)

1

Returns:

Type Description
list[SearchFieldType]

List of columns and tuples (relationship, column)

fastapi_toolsets.crud.search.build_search_filters(model, search, search_fields=None, default_fields=None, search_column=None)

Build SQLAlchemy filter conditions for search.

Parameters:

Name Type Description Default
model type[DeclarativeBase]

SQLAlchemy model class

required
search str | SearchConfig

Search string or SearchConfig

required
search_fields Sequence[SearchFieldType] | None

Fields specified per-call (takes priority)

None
default_fields Sequence[SearchFieldType] | None

Default fields (from ClassVar)

None
search_column str | None

Optional key to narrow search to a single field. Must match one of the resolved search field keys.

None

Returns:

Type Description
tuple[list[ColumnElement[bool]], list[InstrumentedAttribute[Any]]]

Tuple of (filter_conditions, joins_needed)

Raises:

Type Description
NoSearchableFieldsError

If no searchable field has been configured