Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1125,13 +1125,13 @@ With the `get_multi` method we get a python `dict` with full suport for paginati
}
```

And in the endpoint, we can import from `fastcrud.paginated` the following functions and Pydantic Schema:
And in the endpoint, we can import from `fastcrud` the following functions and Pydantic Schema:

```python
from typing import Annotated
from fastapi import Depends, Request
from sqlalchemy.ext.asyncio import AsyncSession
from fastcrud.paginated import (
from fastcrud import (
PaginatedListResponse, # What you'll use as a response_model to validate
paginated_response, # Creates a paginated response based on the parameters
compute_offset, # Calculate the offset for pagination ((page - 1) * items_per_page)
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/api/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async def get_user(
### 2. Get Multiple Items (with Pagination)

```python
from fastcrud.paginated import PaginatedListResponse, paginated_response
from fastcrud import PaginatedListResponse, paginated_response

@router.get("/", response_model=PaginatedListResponse[UserRead])
async def get_users(
Expand Down
4 changes: 2 additions & 2 deletions docs/user-guide/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def get_users(db: Annotated[AsyncSession, Depends(async_get_db)]):
async def create_user(
user_data: UserCreate,
db: Annotated[AsyncSession, Depends(async_get_db)]
):
):
return await crud_users.create(db=db, object=user_data)
```

Expand All @@ -50,7 +50,7 @@ async def get_profile(current_user: Annotated[dict, Depends(get_current_user)]):
### 📊 **Easy Pagination**
Paginate any endpoint with one line:
```python
from fastcrud.paginated import PaginatedListResponse
from fastcrud import PaginatedListResponse

@router.get("/", response_model=PaginatedListResponse[UserRead])
async def get_users(page: int = 1, items_per_page: int = 10):
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/api/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This guide shows you how to add pagination to your API endpoints using the boile
Here's how to add basic pagination to any endpoint:

```python
from fastcrud.paginated import PaginatedListResponse
from fastcrud import PaginatedListResponse

@router.get("/", response_model=PaginatedListResponse[UserRead])
async def get_users(
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/api/versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async def create_user(user_data: UserCreate):
```python
# src/app/api/v2/users.py
from app.schemas.user import UserReadV2, UserCreateV2 # New schemas
from fastcrud.paginated import PaginatedListResponse
from fastcrud import PaginatedListResponse

# Breaking change: Always return paginated response
@router.get("/", response_model=PaginatedListResponse[UserReadV2])
Expand Down
3 changes: 2 additions & 1 deletion docs/user-guide/database/crud.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@ async def user_management_example(db: AsyncSession):
Using FastCRUD's pagination utilities:

```python
from fastcrud.paginated import compute_offset, paginated_response
from fastcrud import compute_offset, paginated_response


async def get_paginated_users(
db: AsyncSession,
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/database/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ users = await crud_users.get_multi(
For pagination with page numbers, use `PaginatedListResponse`:

```python
from fastcrud.paginated import PaginatedListResponse
from fastcrud import PaginatedListResponse

# In API endpoint - ONLY for paginated list responses
@router.get("/users/", response_model=PaginatedListResponse[UserRead])
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Create `src/app/api/v1/categories.py`:
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Request
from fastcrud.paginated import PaginatedListResponse, compute_offset
from fastcrud import PaginatedListResponse, compute_offset
from sqlalchemy.ext.asyncio import AsyncSession

from ...api.dependencies import get_current_superuser, get_current_user
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ Ready to dive in? Here are recommended learning paths:
3. Set up [Background Task Processing](background-tasks/index.md)
4. Review the [Production Guide](production.md) for deployment considerations

Choose your path based on your needs and experience level. Each section builds upon previous concepts while remaining self-contained for reference use.
Choose your path based on your needs and experience level. Each section builds upon previous concepts while remaining self-contained for reference use.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies = [
"arq>=0.25.0",
"bcrypt>=4.1.1",
"psycopg2-binary>=2.9.9",
"fastcrud>=0.15.5",
"fastcrud>=0.19.0",
"crudadmin>=0.4.2",
"gunicorn>=23.0.0",
"ruff>=0.11.13",
Expand Down
7 changes: 2 additions & 5 deletions src/app/api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

async def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[AsyncSession, Depends(async_get_db)]
) -> dict[str, Any] | None:
) -> dict[str, Any]:
token_data = await verify_token(token, TokenType.ACCESS, db)
if token_data is None:
raise UnauthorizedException("User not authenticated.")
Expand All @@ -34,10 +34,7 @@ async def get_current_user(
user = await crud_users.get(db=db, username=token_data.username_or_email, is_deleted=False)

if user:
if hasattr(user, 'model_dump'):
return user.model_dump()
else:
return user
return user

raise UnauthorizedException("User not authenticated.")

Expand Down
96 changes: 40 additions & 56 deletions src/app/api/v1/posts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Annotated, Any, cast

from fastapi import APIRouter, Depends, Request
from fastcrud.paginated import PaginatedListResponse, compute_offset, paginated_response
from fastcrud import PaginatedListResponse, compute_offset, paginated_response
from sqlalchemy.ext.asyncio import AsyncSession

from ...api.dependencies import get_current_superuser, get_current_user
Expand All @@ -22,28 +23,30 @@ async def write_post(
post: PostCreate,
current_user: Annotated[dict, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(async_get_db)],
) -> PostRead:
db_user = await crud_users.get(
db=db, username=username, is_deleted=False, schema_to_select=UserRead, return_as_model=True
)
) -> dict[str, Any]:
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if db_user is None:
raise NotFoundException("User not found")

db_user = cast(UserRead, db_user)
if current_user["id"] != db_user.id:
db_user = cast(dict[str, Any], db_user)

if current_user["id"] != db_user["id"]:
raise ForbiddenException()

post_internal_dict = post.model_dump()
post_internal_dict["created_by_user_id"] = db_user.id
post_internal_dict["created_by_user_id"] = db_user["id"]

post_internal = PostCreateInternal(**post_internal_dict)
created_post = await crud_posts.create(db=db, object=post_internal)

post_read = await crud_posts.get(db=db, id=created_post.id, schema_to_select=PostRead)
if created_post is None:
raise NotFoundException("Failed to create post")

post_read = await crud_posts.get(db=db, id=created_post["id"], schema_to_select=PostRead)
if post_read is None:
raise NotFoundException("Created post not found")

return cast(PostRead, post_read)
return cast(dict[str, Any], post_read)


@router.get("/{username}/posts", response_model=PaginatedListResponse[PostRead])
Expand All @@ -59,22 +62,16 @@ async def read_posts(
page: int = 1,
items_per_page: int = 10,
) -> dict:
db_user = await crud_users.get(
db=db,
username=username,
is_deleted=False,
schema_to_select=UserRead,
return_as_model=True
)
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if not db_user:
raise NotFoundException("User not found")

db_user = cast(UserRead, db_user)
db_user = cast(dict[str, Any], db_user)
posts_data = await crud_posts.get_multi(
db=db,
offset=compute_offset(page, items_per_page),
limit=items_per_page,
created_by_user_id=db_user.id,
created_by_user_id=db_user["id"],
is_deleted=False,
)

Expand All @@ -86,25 +83,20 @@ async def read_posts(
@cache(key_prefix="{username}_post_cache", resource_id_name="id")
async def read_post(
request: Request, username: str, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]
) -> PostRead:
db_user = await crud_users.get(
db=db,
username=username,
is_deleted=False,
schema_to_select=UserRead,
return_as_model=True
)
) -> dict[str, Any]:
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if db_user is None:
raise NotFoundException("User not found")

db_user = cast(UserRead, db_user)
db_user = cast(dict[str, Any], db_user)

db_post = await crud_posts.get(
db=db, id=id, created_by_user_id=db_user.id, is_deleted=False, schema_to_select=PostRead
db=db, id=id, created_by_user_id=db_user["id"], is_deleted=False, schema_to_select=PostRead
)
if db_post is None:
raise NotFoundException("Post not found")

return cast(PostRead, db_post)
return cast(dict[str, Any], db_post)


@router.patch("/{username}/post/{id}")
Expand All @@ -117,24 +109,21 @@ async def patch_post(
current_user: Annotated[dict, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(async_get_db)],
) -> dict[str, str]:
db_user = await crud_users.get(
db=db,
username=username,
is_deleted=False,
schema_to_select=UserRead,
return_as_model=True
)
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if db_user is None:
raise NotFoundException("User not found")

db_user = cast(UserRead, db_user)
if current_user["id"] != db_user.id:
db_user = cast(dict[str, Any], db_user)

if current_user["id"] != db_user["id"]:
raise ForbiddenException()

db_post = await crud_posts.get(db=db, id=id, is_deleted=False, schema_to_select=PostRead)
if db_post is None:
raise NotFoundException("Post not found")

db_post = cast(dict[str, Any], db_post)

await crud_posts.update(db=db, object=values, id=id)
return {"message": "Post updated"}

Expand All @@ -148,24 +137,21 @@ async def erase_post(
current_user: Annotated[dict, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(async_get_db)],
) -> dict[str, str]:
db_user = await crud_users.get(
db=db,
username=username,
is_deleted=False,
schema_to_select=UserRead,
return_as_model=True
)
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if db_user is None:
raise NotFoundException("User not found")

db_user = cast(UserRead, db_user)
if current_user["id"] != db_user.id:
db_user = cast(dict[str, Any], db_user)

if current_user["id"] != db_user["id"]:
raise ForbiddenException()

db_post = await crud_posts.get(db=db, id=id, is_deleted=False, schema_to_select=PostRead)
if db_post is None:
raise NotFoundException("Post not found")

db_post = cast(dict[str, Any], db_post)

await crud_posts.delete(db=db, id=id)

return {"message": "Post deleted"}
Expand All @@ -176,19 +162,17 @@ async def erase_post(
async def erase_db_post(
request: Request, username: str, id: int, db: Annotated[AsyncSession, Depends(async_get_db)]
) -> dict[str, str]:
db_user = await crud_users.get(
db=db,
username=username,
is_deleted=False,
schema_to_select=UserRead,
return_as_model=True
)
db_user = await crud_users.get(db=db, username=username, is_deleted=False, schema_to_select=UserRead)
if db_user is None:
raise NotFoundException("User not found")

db_user = cast(dict[str, Any], db_user)

db_post = await crud_posts.get(db=db, id=id, is_deleted=False, schema_to_select=PostRead)
if db_post is None:
raise NotFoundException("Post not found")

db_post = cast(dict[str, Any], db_post)

await crud_posts.db_delete(db=db, id=id)
return {"message": "Post deleted from the database"}
return {"message": "Post deleted from the database"}
Loading