diff --git a/alembic/versions/20250414_024110_fd7a3654_update_fruit.py b/alembic/versions/20250414_024110_fd7a3654_update_fruit.py new file mode 100644 index 0000000..40ddbbe --- /dev/null +++ b/alembic/versions/20250414_024110_fd7a3654_update_fruit.py @@ -0,0 +1,27 @@ +"""create fruits table +Revision ID: 0002 +Revises: 0001 +Create Date: 2024-01-20 10:00:00.000000 +""" +from alembic import op +import sqlalchemy as sa + +revision = '0002' +down_revision = '0001' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'fruits', + sa.Column('id', sa.String(36), primary_key=True), + sa.Column('name', sa.String(), nullable=False), + sa.Column('color', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.func.now()), + sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now()) + ) + op.create_index(op.f('ix_fruits_name'), 'fruits', ['name'], unique=False) + +def downgrade(): + op.drop_index(op.f('ix_fruits_name'), table_name='fruits') + op.drop_table('fruits') \ No newline at end of file diff --git a/endpoints/create-fruit.post.py b/endpoints/create-fruit.post.py index e69de29..a7bf0a4 100644 --- a/endpoints/create-fruit.post.py +++ b/endpoints/create-fruit.post.py @@ -0,0 +1,26 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from core.database import get_db +from schemas.fruit import FruitCreate, FruitSchema +from helpers.fruit_helpers import create_fruit, sanitize_fruit_name, normalize_color_name, validate_fruit_data + +router = APIRouter() + +@router.post("/create-fruit", response_model=FruitSchema, status_code=status.HTTP_201_CREATED) +async def create_new_fruit( + fruit: FruitCreate, + db: Session = Depends(get_db) +): + # Sanitize and normalize input data + sanitized_name = sanitize_fruit_name(fruit.name) + normalized_color = normalize_color_name(fruit.color) + + # Validate the sanitized data + fruit_data = {"name": sanitized_name, "color": normalized_color} + if not validate_fruit_data(fruit_data): + raise HTTPException(status_code=400, detail="Invalid fruit data") + + # Create fruit using helper function + fruit.name = sanitized_name + fruit.color = normalized_color + return create_fruit(db=db, fruit_data=fruit) \ No newline at end of file diff --git a/helpers/fruit_helpers.py b/helpers/fruit_helpers.py new file mode 100644 index 0000000..9df05e0 --- /dev/null +++ b/helpers/fruit_helpers.py @@ -0,0 +1,110 @@ +from typing import List, Optional, Dict, Any +from sqlalchemy.orm import Session +from models.fruit import Fruit +from schemas.fruit import FruitCreate +from sqlalchemy.exc import IntegrityError +from fastapi import HTTPException + +def create_fruit(db: Session, fruit_data: FruitCreate) -> Fruit: + """ + Creates a new fruit in the database. + + Args: + db (Session): The database session. + fruit_data (FruitCreate): The data for the fruit to create. + + Returns: + Fruit: The newly created fruit object. + + Raises: + HTTPException: If there's an error creating the fruit. + """ + try: + db_fruit = Fruit(**fruit_data.dict()) + db.add(db_fruit) + db.commit() + db.refresh(db_fruit) + return db_fruit + except IntegrityError: + db.rollback() + raise HTTPException(status_code=400, detail="Fruit with this name already exists") + except Exception as e: + db.rollback() + raise HTTPException(status_code=500, detail=str(e)) + +def get_fruit_by_name(db: Session, name: str) -> Optional[Fruit]: + """ + Retrieves a fruit by its name. + + Args: + db (Session): The database session. + name (str): The name of the fruit to retrieve. + + Returns: + Optional[Fruit]: The fruit object if found, otherwise None. + """ + return db.query(Fruit).filter(Fruit.name == name).first() + +def get_fruits_by_color(db: Session, color: str) -> List[Fruit]: + """ + Retrieves all fruits of a specific color. + + Args: + db (Session): The database session. + color (str): The color to filter by. + + Returns: + List[Fruit]: A list of fruits matching the specified color. + """ + return db.query(Fruit).filter(Fruit.color == color).all() + +def validate_fruit_data(fruit_data: Dict[str, Any]) -> bool: + """ + Validates fruit data before creation. + + Args: + fruit_data (Dict[str, Any]): The fruit data to validate. + + Returns: + bool: True if the data is valid, False otherwise. + """ + if not fruit_data: + return False + if not isinstance(fruit_data.get("name"), str) or not isinstance(fruit_data.get("color"), str): + return False + if len(fruit_data.get("name", "")) < 1 or len(fruit_data.get("color", "")) < 1: + return False + return True + +def sanitize_fruit_name(name: str) -> str: + """ + Sanitizes the fruit name by removing leading/trailing whitespace and converting to lowercase. + + Args: + name (str): The fruit name to sanitize. + + Returns: + str: The sanitized fruit name. + """ + return name.strip().lower() + +def normalize_color_name(color: str) -> str: + """ + Normalizes color names to a standard format. + + Args: + color (str): The color name to normalize. + + Returns: + str: The normalized color name. + """ + color_mapping = { + "red": "Red", + "green": "Green", + "yellow": "Yellow", + "orange": "Orange", + "purple": "Purple", + "brown": "Brown", + } + normalized = color.strip().lower() + return color_mapping.get(normalized, normalized.capitalize()) \ No newline at end of file diff --git a/models/fruit.py b/models/fruit.py new file mode 100644 index 0000000..67332f9 --- /dev/null +++ b/models/fruit.py @@ -0,0 +1,14 @@ +from sqlalchemy import Column, String, DateTime +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.sql import func +from core.database import Base +import uuid + +class Fruit(Base): + __tablename__ = "fruits" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name = Column(String, nullable=False, index=True) + color = Column(String, nullable=False) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file diff --git a/schemas/fruit.py b/schemas/fruit.py new file mode 100644 index 0000000..2c18a4a --- /dev/null +++ b/schemas/fruit.py @@ -0,0 +1,32 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from uuid import UUID + +class FruitBase(BaseModel): + name: str = Field(..., min_length=1, description="Name of the fruit") + color: str = Field(..., min_length=1, description="Color of the fruit") + +class FruitCreate(FruitBase): + pass + +class FruitUpdate(BaseModel): + name: Optional[str] = Field(None, min_length=1, description="Name of the fruit") + color: Optional[str] = Field(None, min_length=1, description="Color of the fruit") + +class FruitSchema(FruitBase): + id: UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True + schema_extra = { + "example": { + "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", + "name": "Apple", + "color": "Red", + "created_at": "2023-01-01T12:00:00", + "updated_at": "2023-01-01T12:00:00" + } + } \ No newline at end of file