forked from cardosofelipe/fast-next-template
- Introduced `pyproject.toml` to centralize backend tool configurations (e.g., Ruff, mypy, coverage, pytest). - Replaced Black, isort, and Flake8 with Ruff for linting, formatting, and import sorting. - Updated `requirements.txt` to include Ruff and remove replaced tools. - Added `Makefile` to streamline development workflows with commands for linting, formatting, type-checking, testing, and cleanup.
185 lines
4.9 KiB
Python
185 lines
4.9 KiB
Python
"""
|
|
Common schemas used across the API for pagination, responses, filtering, and sorting.
|
|
"""
|
|
|
|
from enum import Enum
|
|
from math import ceil
|
|
from typing import TypeVar
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class SortOrder(str, Enum):
|
|
"""Sort order options."""
|
|
|
|
ASC = "asc"
|
|
DESC = "desc"
|
|
|
|
|
|
class PaginationParams(BaseModel):
|
|
"""Parameters for pagination."""
|
|
|
|
page: int = Field(default=1, ge=1, description="Page number (1-indexed)")
|
|
limit: int = Field(
|
|
default=20, ge=1, le=100, description="Number of items per page (max 100)"
|
|
)
|
|
|
|
@property
|
|
def offset(self) -> int:
|
|
"""Calculate the offset for database queries."""
|
|
return (self.page - 1) * self.limit
|
|
|
|
@property
|
|
def skip(self) -> int:
|
|
"""Alias for offset (compatibility with existing code)."""
|
|
return self.offset
|
|
|
|
model_config = {"json_schema_extra": {"example": {"page": 1, "limit": 20}}}
|
|
|
|
|
|
class SortParams(BaseModel):
|
|
"""Parameters for sorting."""
|
|
|
|
sort_by: str | None = Field(default=None, description="Field name to sort by")
|
|
sort_order: SortOrder = Field(
|
|
default=SortOrder.ASC, description="Sort order (asc or desc)"
|
|
)
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {"sort_by": "created_at", "sort_order": "desc"}
|
|
}
|
|
}
|
|
|
|
|
|
class PaginationMeta(BaseModel):
|
|
"""Metadata for paginated responses."""
|
|
|
|
total: int = Field(..., description="Total number of items")
|
|
page: int = Field(..., description="Current page number")
|
|
page_size: int = Field(..., description="Number of items in current page")
|
|
total_pages: int = Field(..., description="Total number of pages")
|
|
has_next: bool = Field(..., description="Whether there is a next page")
|
|
has_prev: bool = Field(..., description="Whether there is a previous page")
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"total": 150,
|
|
"page": 1,
|
|
"page_size": 20,
|
|
"total_pages": 8,
|
|
"has_next": True,
|
|
"has_prev": False,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class PaginatedResponse[T](BaseModel):
|
|
"""Generic paginated response wrapper."""
|
|
|
|
data: list[T] = Field(..., description="List of items")
|
|
pagination: PaginationMeta = Field(..., description="Pagination metadata")
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"data": [{"id": "123", "name": "Example Item"}],
|
|
"pagination": {
|
|
"total": 150,
|
|
"page": 1,
|
|
"page_size": 20,
|
|
"total_pages": 8,
|
|
"has_next": True,
|
|
"has_prev": False,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class MessageResponse(BaseModel):
|
|
"""Simple message response."""
|
|
|
|
success: bool = Field(default=True, description="Operation success status")
|
|
message: str = Field(..., description="Human-readable message")
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {"success": True, "message": "Operation completed successfully"}
|
|
}
|
|
}
|
|
|
|
|
|
class BulkActionRequest(BaseModel):
|
|
"""Request schema for bulk operations on multiple items."""
|
|
|
|
ids: list[UUID] = Field(
|
|
...,
|
|
min_length=1,
|
|
max_length=100,
|
|
description="List of item IDs to perform action on (max 100)",
|
|
)
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"ids": [
|
|
"550e8400-e29b-41d4-a716-446655440000",
|
|
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class BulkActionResponse(BaseModel):
|
|
"""Response schema for bulk operations."""
|
|
|
|
success: bool = Field(default=True, description="Operation success status")
|
|
message: str = Field(..., description="Human-readable message")
|
|
affected_count: int = Field(
|
|
..., description="Number of items affected by the operation"
|
|
)
|
|
|
|
model_config = {
|
|
"json_schema_extra": {
|
|
"example": {
|
|
"success": True,
|
|
"message": "Successfully deactivated 5 users",
|
|
"affected_count": 5,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
def create_pagination_meta(
|
|
total: int, page: int, limit: int, items_count: int
|
|
) -> PaginationMeta:
|
|
"""
|
|
Helper function to create pagination metadata.
|
|
|
|
Args:
|
|
total: Total number of items
|
|
page: Current page number
|
|
limit: Items per page
|
|
items_count: Number of items in current page
|
|
|
|
Returns:
|
|
PaginationMeta object with calculated values
|
|
"""
|
|
total_pages = ceil(total / limit) if limit > 0 else 0
|
|
|
|
return PaginationMeta(
|
|
total=total,
|
|
page=page,
|
|
page_size=items_count,
|
|
total_pages=total_pages,
|
|
has_next=page < total_pages,
|
|
has_prev=page > 1,
|
|
)
|