claude code

This commit is contained in:
2026-01-28 19:22:30 +01:00
parent f9b1d43c81
commit bdbfa4e25a
104 changed files with 9591 additions and 261 deletions

View File

@@ -0,0 +1,68 @@
"""Package des schémas Pydantic."""
from app.schemas.category import (
CategoryCreate,
CategoryResponse,
CategoryUpdate,
CategoryWithItemCount,
)
from app.schemas.common import (
ErrorResponse,
PaginatedResponse,
PaginationParams,
SuccessResponse,
)
from app.schemas.document import (
DocumentCreate,
DocumentResponse,
DocumentUpdate,
DocumentUploadResponse,
)
from app.schemas.item import (
ItemCreate,
ItemFilter,
ItemResponse,
ItemSummary,
ItemUpdate,
ItemWithRelations,
)
from app.schemas.location import (
LocationCreate,
LocationResponse,
LocationTree,
LocationUpdate,
LocationWithChildren,
LocationWithItemCount,
)
__all__ = [
# Category
"CategoryCreate",
"CategoryUpdate",
"CategoryResponse",
"CategoryWithItemCount",
# Location
"LocationCreate",
"LocationUpdate",
"LocationResponse",
"LocationWithChildren",
"LocationWithItemCount",
"LocationTree",
# Item
"ItemCreate",
"ItemUpdate",
"ItemResponse",
"ItemWithRelations",
"ItemSummary",
"ItemFilter",
# Document
"DocumentCreate",
"DocumentUpdate",
"DocumentResponse",
"DocumentUploadResponse",
# Common
"PaginationParams",
"PaginatedResponse",
"ErrorResponse",
"SuccessResponse",
]

View File

@@ -0,0 +1,48 @@
"""Schémas Pydantic pour les catégories.
Définit les schémas de validation pour les requêtes et réponses API.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
class CategoryBase(BaseModel):
"""Schéma de base pour les catégories."""
name: str = Field(..., min_length=1, max_length=100, description="Nom de la catégorie")
description: str | None = Field(None, max_length=1000, description="Description optionnelle")
color: str | None = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$", description="Couleur hex (#RRGGBB)")
icon: str | None = Field(None, max_length=50, description="Nom de l'icône")
class CategoryCreate(CategoryBase):
"""Schéma pour la création d'une catégorie."""
pass
class CategoryUpdate(BaseModel):
"""Schéma pour la mise à jour d'une catégorie (tous les champs optionnels)."""
name: str | None = Field(None, min_length=1, max_length=100)
description: str | None = Field(None, max_length=1000)
color: str | None = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
icon: str | None = Field(None, max_length=50)
class CategoryResponse(CategoryBase):
"""Schéma de réponse pour une catégorie."""
model_config = ConfigDict(from_attributes=True)
id: int
created_at: datetime
updated_at: datetime
class CategoryWithItemCount(CategoryResponse):
"""Schéma de réponse avec le nombre d'objets."""
item_count: int = Field(default=0, description="Nombre d'objets dans cette catégorie")

View File

@@ -0,0 +1,60 @@
"""Schémas Pydantic communs.
Définit les schémas réutilisables (pagination, erreurs, etc.).
"""
from typing import Generic, TypeVar
from pydantic import BaseModel, Field
T = TypeVar("T")
class PaginationParams(BaseModel):
"""Paramètres de pagination."""
page: int = Field(default=1, ge=1, description="Numéro de page (commence à 1)")
page_size: int = Field(default=20, ge=1, le=100, description="Nombre d'éléments par page")
@property
def offset(self) -> int:
"""Calcule l'offset pour la requête SQL."""
return (self.page - 1) * self.page_size
class PaginatedResponse(BaseModel, Generic[T]):
"""Réponse paginée générique."""
items: list[T]
total: int = Field(..., description="Nombre total d'éléments")
page: int = Field(..., description="Page actuelle")
page_size: int = Field(..., description="Taille de la page")
pages: int = Field(..., description="Nombre total de pages")
@classmethod
def create(
cls, items: list[T], total: int, page: int, page_size: int
) -> "PaginatedResponse[T]":
"""Crée une réponse paginée."""
pages = (total + page_size - 1) // page_size if page_size > 0 else 0
return cls(
items=items,
total=total,
page=page,
page_size=page_size,
pages=pages,
)
class ErrorResponse(BaseModel):
"""Schéma de réponse d'erreur."""
detail: str = Field(..., description="Message d'erreur")
type: str = Field(..., description="Type d'erreur")
class SuccessResponse(BaseModel):
"""Schéma de réponse de succès."""
message: str = Field(..., description="Message de succès")
id: int | None = Field(None, description="ID de l'élément concerné")

View File

@@ -0,0 +1,63 @@
"""Schémas Pydantic pour les documents attachés.
Définit les schémas de validation pour les requêtes et réponses API.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from app.models.document import DocumentType
class DocumentBase(BaseModel):
"""Schéma de base pour les documents."""
type: DocumentType = Field(..., description="Type de document")
description: str | None = Field(None, max_length=500, description="Description optionnelle")
class DocumentCreate(DocumentBase):
"""Schéma pour la création d'un document (métadonnées seulement).
Le fichier est uploadé séparément via multipart/form-data.
"""
item_id: int = Field(..., description="ID de l'objet associé")
class DocumentUpdate(BaseModel):
"""Schéma pour la mise à jour d'un document."""
type: DocumentType | None = None
description: str | None = Field(None, max_length=500)
class DocumentResponse(BaseModel):
"""Schéma de réponse pour un document."""
model_config = ConfigDict(from_attributes=True)
id: int
filename: str
original_name: str
type: DocumentType
mime_type: str
size_bytes: int
file_path: str
description: str | None
item_id: int
created_at: datetime
updated_at: datetime
class DocumentUploadResponse(BaseModel):
"""Schéma de réponse après upload d'un document."""
id: int
filename: str
original_name: str
type: DocumentType
mime_type: str
size_bytes: int
message: str = "Document uploadé avec succès"

View File

@@ -0,0 +1,98 @@
"""Schémas Pydantic pour les objets d'inventaire.
Définit les schémas de validation pour les requêtes et réponses API.
"""
from datetime import date, datetime
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field
from app.models.item import ItemStatus
from app.schemas.category import CategoryResponse
from app.schemas.location import LocationResponse
class ItemBase(BaseModel):
"""Schéma de base pour les objets."""
name: str = Field(..., min_length=1, max_length=200, description="Nom de l'objet")
description: str | None = Field(None, description="Description détaillée")
quantity: int = Field(default=1, ge=0, description="Quantité en stock")
status: ItemStatus = Field(default=ItemStatus.IN_STOCK, description="Statut de l'objet")
brand: str | None = Field(None, max_length=100, description="Marque")
model: str | None = Field(None, max_length=100, description="Modèle")
serial_number: str | None = Field(None, max_length=100, description="Numéro de série")
url: str | None = Field(None, max_length=500, description="Lien vers page produit")
price: Decimal | None = Field(None, ge=0, decimal_places=2, description="Prix d'achat")
purchase_date: date | None = Field(None, description="Date d'achat")
notes: str | None = Field(None, description="Notes libres")
class ItemCreate(ItemBase):
"""Schéma pour la création d'un objet."""
category_id: int = Field(..., description="ID de la catégorie")
location_id: int = Field(..., description="ID de l'emplacement")
class ItemUpdate(BaseModel):
"""Schéma pour la mise à jour d'un objet (tous les champs optionnels)."""
name: str | None = Field(None, min_length=1, max_length=200)
description: str | None = None
quantity: int | None = Field(None, ge=0)
status: ItemStatus | None = None
brand: str | None = Field(None, max_length=100)
model: str | None = Field(None, max_length=100)
serial_number: str | None = Field(None, max_length=100)
url: str | None = Field(None, max_length=500)
price: Decimal | None = Field(None, ge=0)
purchase_date: date | None = None
notes: str | None = None
category_id: int | None = None
location_id: int | None = None
class ItemResponse(ItemBase):
"""Schéma de réponse pour un objet."""
model_config = ConfigDict(from_attributes=True)
id: int
category_id: int
location_id: int
created_at: datetime
updated_at: datetime
class ItemWithRelations(ItemResponse):
"""Schéma de réponse avec les relations (catégorie et emplacement)."""
category: CategoryResponse
location: LocationResponse
class ItemSummary(BaseModel):
"""Schéma résumé pour les listes d'objets."""
model_config = ConfigDict(from_attributes=True)
id: int
name: str
quantity: int
status: ItemStatus
brand: str | None
category_id: int
location_id: int
class ItemFilter(BaseModel):
"""Schéma pour filtrer les objets."""
category_id: int | None = None
location_id: int | None = None
status: ItemStatus | None = None
search: str | None = Field(None, min_length=2, description="Recherche textuelle")
min_price: Decimal | None = None
max_price: Decimal | None = None

View File

@@ -0,0 +1,70 @@
"""Schémas Pydantic pour les emplacements.
Définit les schémas de validation pour les requêtes et réponses API.
"""
from datetime import datetime
from pydantic import BaseModel, ConfigDict, Field
from app.models.location import LocationType
class LocationBase(BaseModel):
"""Schéma de base pour les emplacements."""
name: str = Field(..., min_length=1, max_length=100, description="Nom de l'emplacement")
type: LocationType = Field(..., description="Type d'emplacement")
description: str | None = Field(None, max_length=500, description="Description optionnelle")
class LocationCreate(LocationBase):
"""Schéma pour la création d'un emplacement."""
parent_id: int | None = Field(None, description="ID du parent (None si racine)")
class LocationUpdate(BaseModel):
"""Schéma pour la mise à jour d'un emplacement (tous les champs optionnels)."""
name: str | None = Field(None, min_length=1, max_length=100)
type: LocationType | None = None
description: str | None = Field(None, max_length=500)
parent_id: int | None = None
class LocationResponse(LocationBase):
"""Schéma de réponse pour un emplacement."""
model_config = ConfigDict(from_attributes=True)
id: int
parent_id: int | None
path: str
created_at: datetime
updated_at: datetime
class LocationWithChildren(LocationResponse):
"""Schéma de réponse avec les enfants."""
children: list["LocationWithChildren"] = Field(default_factory=list)
class LocationWithItemCount(LocationResponse):
"""Schéma de réponse avec le nombre d'objets."""
item_count: int = Field(default=0, description="Nombre d'objets à cet emplacement")
class LocationTree(BaseModel):
"""Schéma pour l'arborescence complète des emplacements."""
id: int
name: str
type: LocationType
path: str
children: list["LocationTree"] = Field(default_factory=list)
item_count: int = 0
model_config = ConfigDict(from_attributes=True)