add pydantic on backend
This commit is contained in:
parent
abce2bb6ef
commit
580d04d1e7
4 changed files with 94 additions and 15 deletions
|
|
@ -1,10 +1,13 @@
|
||||||
import time
|
import time
|
||||||
|
from decimal import Decimal
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token, create_refresh_token
|
from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token, create_refresh_token
|
||||||
from app import db
|
from app import db
|
||||||
from app.models import User, Product, OrderItem, Order
|
from app.models import User, Product, OrderItem, Order
|
||||||
from app.celery import celery
|
from app.celery import celery
|
||||||
|
from app.schemas import ProductCreateRequest, ProductResponse
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
|
|
@ -104,21 +107,28 @@ def create_product():
|
||||||
if not user or not user.is_admin:
|
if not user or not user.is_admin:
|
||||||
return jsonify({"error": "Admin access required"}), 403
|
return jsonify({"error": "Admin access required"}), 403
|
||||||
|
|
||||||
data = request.get_json()
|
try:
|
||||||
|
# Validate request data using Pydantic schema
|
||||||
|
product_data = ProductCreateRequest(**request.get_json())
|
||||||
|
|
||||||
product = Product(
|
product = Product(
|
||||||
name=data["name"],
|
name=product_data.name,
|
||||||
description=data.get("description"),
|
description=product_data.description,
|
||||||
price=data["price"],
|
price=product_data.price,
|
||||||
stock=data.get("stock", 0),
|
stock=product_data.stock,
|
||||||
image_url=data.get("image_url"),
|
image_url=product_data.image_url,
|
||||||
category=data.get("category")
|
category=product_data.category
|
||||||
)
|
)
|
||||||
|
|
||||||
db.session.add(product)
|
db.session.add(product)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return jsonify(product.to_dict()), 201
|
# Use Pydantic schema for response
|
||||||
|
response = ProductResponse.model_validate(product)
|
||||||
|
return jsonify(response.model_dump()), 201
|
||||||
|
|
||||||
|
except ValidationError as e:
|
||||||
|
return jsonify({"error": "Validation error", "details": e.errors()}), 400
|
||||||
|
|
||||||
|
|
||||||
@api_bp.route("/products/<int:product_id>", methods=["PUT"])
|
@api_bp.route("/products/<int:product_id>", methods=["PUT"])
|
||||||
|
|
|
||||||
4
backend/app/schemas/__init__.py
Normal file
4
backend/app/schemas/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
"""Pydantic schemas for request/response validation"""
|
||||||
|
from app.schemas.product import ProductCreateRequest, ProductResponse
|
||||||
|
|
||||||
|
__all__ = ["ProductCreateRequest", "ProductResponse"]
|
||||||
64
backend/app/schemas/product.py
Normal file
64
backend/app/schemas/product.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
"""Pydantic schemas for Product model"""
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
from decimal import Decimal
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCreateRequest(BaseModel):
|
||||||
|
"""Schema for creating a new product"""
|
||||||
|
name: str = Field(..., min_length=1, max_length=200, description="Product name")
|
||||||
|
description: Optional[str] = Field(None, description="Product description")
|
||||||
|
price: Decimal = Field(..., gt=0, description="Product price (must be greater than 0)")
|
||||||
|
stock: int = Field(default=0, ge=0, description="Product stock quantity")
|
||||||
|
image_url: Optional[str] = Field(None, max_length=500, description="Product image URL")
|
||||||
|
category: Optional[str] = Field(None, description="Product category")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"name": "Handcrafted Wooden Bowl",
|
||||||
|
"description": "A beautiful handcrafted bowl made from oak",
|
||||||
|
"price": 45.99,
|
||||||
|
"stock": 10,
|
||||||
|
"image_url": "https://example.com/bowl.jpg",
|
||||||
|
"category": "Woodwork"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@field_validator("price")
|
||||||
|
@classmethod
|
||||||
|
def validate_price(cls, v: Decimal) -> Decimal:
|
||||||
|
"""Validate that price has at most 2 decimal places"""
|
||||||
|
if v.as_tuple().exponent < -2:
|
||||||
|
raise ValueError("Price must have at most 2 decimal places")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class ProductResponse(BaseModel):
|
||||||
|
"""Schema for product response"""
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
price: float
|
||||||
|
stock: int
|
||||||
|
image_url: Optional[str] = None
|
||||||
|
is_active: bool
|
||||||
|
created_at: Optional[datetime] = None
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
json_schema_extra = {
|
||||||
|
"example": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Handcrafted Wooden Bowl",
|
||||||
|
"description": "A beautiful handcrafted bowl made from oak",
|
||||||
|
"price": 45.99,
|
||||||
|
"stock": 10,
|
||||||
|
"image_url": "https://example.com/bowl.jpg",
|
||||||
|
"is_active": True,
|
||||||
|
"created_at": "2024-01-15T10:30:00",
|
||||||
|
"updated_at": "2024-01-15T10:30:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,3 +8,4 @@ python-dotenv==1.0.0
|
||||||
Werkzeug==3.0.1
|
Werkzeug==3.0.1
|
||||||
SQLAlchemy==2.0.23
|
SQLAlchemy==2.0.23
|
||||||
celery[redis]==5.3.6
|
celery[redis]==5.3.6
|
||||||
|
pydantic==2.5.3
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue