mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-03-01 17:00:48 +01:00
Version 1.0.0 (#80)
* docs: fix typos * chore: build docs only when release * Version 1.0.0
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# CRUD
|
||||
|
||||
Generic async CRUD operations for SQLAlchemy models with search, pagination, and many-to-many support.
|
||||
Generic async CRUD operations for SQLAlchemy models with search, pagination, and many-to-many support. This module has features that are only compatible with Postgres.
|
||||
|
||||
!!! info
|
||||
This module has been coded and tested to be compatible with PostgreSQL only.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -12,10 +15,7 @@ The `crud` module provides [`AsyncCrud`](../reference/crud.md#fastapi_toolsets.c
|
||||
from fastapi_toolsets.crud import CrudFactory
|
||||
from myapp.models import User
|
||||
|
||||
UserCrud = CrudFactory(
|
||||
User,
|
||||
searchable_fields=[User.username, User.email],
|
||||
)
|
||||
UserCrud = CrudFactory(model=User)
|
||||
```
|
||||
|
||||
[`CrudFactory`](../reference/crud.md#fastapi_toolsets.crud.factory.CrudFactory) dynamically creates a class named `AsyncUserCrud` with `User` as its model.
|
||||
@@ -24,51 +24,56 @@ UserCrud = CrudFactory(
|
||||
|
||||
```python
|
||||
# Create
|
||||
user = await UserCrud.create(session, obj=UserCreateSchema(username="alice"))
|
||||
user = await UserCrud.create(session=session, obj=UserCreateSchema(username="alice"))
|
||||
|
||||
# Get one (raises NotFoundError if not found)
|
||||
user = await UserCrud.get(session, filters=[User.id == user_id])
|
||||
user = await UserCrud.get(session=session, filters=[User.id == user_id])
|
||||
|
||||
# Get first or None
|
||||
user = await UserCrud.first(session, filters=[User.email == email])
|
||||
user = await UserCrud.first(session=session, filters=[User.email == email])
|
||||
|
||||
# Get multiple
|
||||
users = await UserCrud.get_multi(session, filters=[User.is_active == True])
|
||||
users = await UserCrud.get_multi(session=session, filters=[User.is_active == True])
|
||||
|
||||
# Update
|
||||
user = await UserCrud.update(session, obj=UserUpdateSchema(username="bob"), filters=[User.id == user_id])
|
||||
user = await UserCrud.update(session=session, obj=UserUpdateSchema(username="bob"), filters=[User.id == user_id])
|
||||
|
||||
# Delete
|
||||
await UserCrud.delete(session, filters=[User.id == user_id])
|
||||
await UserCrud.delete(session=session, filters=[User.id == user_id])
|
||||
|
||||
# Count / exists
|
||||
count = await UserCrud.count(session, filters=[User.is_active == True])
|
||||
exists = await UserCrud.exists(session, filters=[User.email == email])
|
||||
count = await UserCrud.count(session=session, filters=[User.is_active == True])
|
||||
exists = await UserCrud.exists(session=session, filters=[User.email == email])
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
```python
|
||||
result = await UserCrud.paginate(
|
||||
session,
|
||||
filters=[User.is_active == True],
|
||||
order_by=[User.created_at.desc()],
|
||||
page=1,
|
||||
items_per_page=20,
|
||||
search="alice",
|
||||
search_fields=[User.username, User.email],
|
||||
@router.get(
|
||||
"",
|
||||
response_model=PaginatedResponse[User],
|
||||
)
|
||||
# result.data: list of users
|
||||
# result.pagination: Pagination(total_count, items_per_page, page, has_more)
|
||||
async def get_users(
|
||||
session: SessionDep,
|
||||
items_per_page: int = 50,
|
||||
page: int = 1,
|
||||
):
|
||||
return await crud.UserCrud.paginate(
|
||||
session=session,
|
||||
items_per_page=items_per_page,
|
||||
page=page,
|
||||
)
|
||||
```
|
||||
|
||||
The [`paginate`](../reference/crud.md#fastapi_toolsets.crud.factory.AsyncCrud.paginate) function will return a [`PaginatedResponse`](../reference/schemas.md#fastapi_toolsets.schemas.PaginatedResponse).
|
||||
|
||||
## Search
|
||||
|
||||
Declare searchable fields on the CRUD class. Relationship traversal is supported via tuples:
|
||||
|
||||
```python
|
||||
PostCrud = CrudFactory(
|
||||
Post,
|
||||
model=Post,
|
||||
searchable_fields=[
|
||||
Post.title,
|
||||
Post.content,
|
||||
@@ -77,18 +82,38 @@ PostCrud = CrudFactory(
|
||||
)
|
||||
```
|
||||
|
||||
This allow to do a search with the [`paginate`](../reference/crud.md#fastapi_toolsets.crud.factory.AsyncCrud.paginate) function:
|
||||
|
||||
```python
|
||||
@router.get(
|
||||
"",
|
||||
response_model=PaginatedResponse[User],
|
||||
)
|
||||
async def get_users(
|
||||
session: SessionDep,
|
||||
items_per_page: int = 50,
|
||||
page: int = 1,
|
||||
search: str | None = None,
|
||||
):
|
||||
return await crud.UserCrud.paginate(
|
||||
session=session,
|
||||
items_per_page=items_per_page,
|
||||
page=page,
|
||||
search=search,
|
||||
)
|
||||
```
|
||||
|
||||
## Many-to-many relationships
|
||||
|
||||
Use `m2m_fields` to map schema fields containing lists of IDs to SQLAlchemy relationships. The CRUD class resolves and validates all IDs before persisting:
|
||||
|
||||
```python
|
||||
PostCrud = CrudFactory(
|
||||
Post,
|
||||
model=Post,
|
||||
m2m_fields={"tag_ids": Post.tags},
|
||||
)
|
||||
|
||||
# schema: PostCreateSchema(title="Hello", tag_ids=[1, 2, 3])
|
||||
post = await PostCrud.create(session, obj=PostCreateSchema(...))
|
||||
post = await PostCrud.create(session=session, obj=PostCreateSchema(title="Hello", tag_ids=[1, 2, 3]))
|
||||
```
|
||||
|
||||
## Upsert
|
||||
@@ -97,7 +122,7 @@ Atomic `INSERT ... ON CONFLICT DO UPDATE` using PostgreSQL:
|
||||
|
||||
```python
|
||||
await UserCrud.upsert(
|
||||
session,
|
||||
session=session,
|
||||
obj=UserCreateSchema(email="alice@example.com", username="alice"),
|
||||
index_elements=[User.email],
|
||||
set_={"username"},
|
||||
@@ -106,11 +131,22 @@ await UserCrud.upsert(
|
||||
|
||||
## `as_response`
|
||||
|
||||
Pass `as_response=True` to any write operation to get a [`Response[ModelType]`](../reference/schemas.md#fastapi_toolsets.schemas.Response) back directly:
|
||||
Pass `as_response=True` to any write operation to get a [`Response[ModelType]`](../reference/schemas.md#fastapi_toolsets.schemas.Response) back directly for API usage:
|
||||
|
||||
```python
|
||||
response = await UserCrud.create(session, obj=schema, as_response=True)
|
||||
# response: Response[User]
|
||||
@router.get(
|
||||
"/{uuid}",
|
||||
response_model=Response[User],
|
||||
responses=generate_error_responses(NotFoundError),
|
||||
)
|
||||
async def get_user(session: SessionDep, uuid: UUID):
|
||||
return await crud.UserCrud.get(
|
||||
session=session,
|
||||
filters=[User.id == uuid],
|
||||
as_response=True,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[:material-api: API Reference](../reference/crud.md)
|
||||
|
||||
Reference in New Issue
Block a user