From 7421c50569f41e4985c8dffebe005d22b58fe01f Mon Sep 17 00:00:00 2001 From: Backend IM Bot Date: Wed, 30 Apr 2025 09:45:31 +0000 Subject: [PATCH] feat: add POST /fruits endpoint to create new fruits --- .../20250430_094514_6958f2e3_update_fruit.py | 34 ++++++++++++ endpoints/fruits.post.py | 15 +++++ helpers/fruit_helpers.py | 55 +++++++++++++++++++ models/fruit.py | 14 +++++ schemas/fruit.py | 23 ++++++++ 5 files changed, 141 insertions(+) create mode 100644 alembic/versions/20250430_094514_6958f2e3_update_fruit.py create mode 100644 helpers/fruit_helpers.py create mode 100644 models/fruit.py create mode 100644 schemas/fruit.py diff --git a/alembic/versions/20250430_094514_6958f2e3_update_fruit.py b/alembic/versions/20250430_094514_6958f2e3_update_fruit.py new file mode 100644 index 0000000..e2eec57 --- /dev/null +++ b/alembic/versions/20250430_094514_6958f2e3_update_fruit.py @@ -0,0 +1,34 @@ +"""create table for fruits + +Revision ID: 9f2b6a4e5c28 +Revises: 2d7c4f9e8a1c +Create Date: 2023-05-24 12:00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import func +import uuid + +# revision identifiers, used by Alembic. +revision = '9f2b6a4e5c28' +down_revision = '2d7c4f9e8a1c' +branch_labels = None +depends_on = None + +def upgrade(): + table_name = "fruits" + + op.create_table( + table_name, + sa.Column('id', sa.String(36), primary_key=True, default=lambda: str(uuid.uuid4())), + sa.Column('name', sa.String(), nullable=False, unique=True), + sa.Column('color', sa.String(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=func.now()), + sa.Column('updated_at', sa.DateTime(), server_default=func.now(), onupdate=func.now()), + sa.Index('ix_fruits_name', 'name') + ) + +def downgrade(): + table_name = "fruits" + op.drop_table(table_name) \ No newline at end of file diff --git a/endpoints/fruits.post.py b/endpoints/fruits.post.py index e69de29..1ef2aa1 100644 --- a/endpoints/fruits.post.py +++ b/endpoints/fruits.post.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter, Depends, status +from sqlalchemy.orm import Session +from core.database import get_db +from schemas.fruit import FruitSchema, FruitCreate +from helpers.fruit_helpers import create_fruit + +router = APIRouter() + +@router.post("/fruits", status_code=status.HTTP_201_CREATED, response_model=FruitSchema) +async def create_new_fruit( + fruit_data: FruitCreate, + db: Session = Depends(get_db) +): + new_fruit = create_fruit(db=db, fruit_data=fruit_data) + return new_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..6e4c968 --- /dev/null +++ b/helpers/fruit_helpers.py @@ -0,0 +1,55 @@ +from typing import Optional +from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError +from models.fruit import Fruit +from schemas.fruit import FruitCreate +from uuid import UUID + +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: + IntegrityError: If a fruit with the same name already exists. + """ + 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 ValueError(f"A fruit with the name '{fruit_data.name}' already exists.") + +def get_fruit_by_id(db: Session, fruit_id: UUID) -> Optional[Fruit]: + """ + Retrieves a single fruit by its ID. + + Args: + db (Session): The database session. + fruit_id (UUID): The ID of the fruit to retrieve. + + Returns: + Optional[Fruit]: The fruit object if found, otherwise None. + """ + return db.query(Fruit).filter(Fruit.id == fruit_id).first() + +def get_all_fruits(db: Session) -> list[Fruit]: + """ + Retrieves all fruits from the database. + + Args: + db (Session): The database session. + + Returns: + list[Fruit]: A list of all fruit objects. + """ + return db.query(Fruit).all() \ No newline at end of file diff --git a/models/fruit.py b/models/fruit.py new file mode 100644 index 0000000..94bdc69 --- /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, unique=True, 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..a8b93ad --- /dev/null +++ b/schemas/fruit.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +from uuid import UUID + +class FruitBase(BaseModel): + name: str = Field(..., description="The name of the fruit") + color: str = Field(..., description="The color of the fruit") + +class FruitCreate(FruitBase): + pass + +class FruitUpdate(FruitBase): + name: Optional[str] = Field(None, description="The updated name of the fruit") + color: Optional[str] = Field(None, description="The updated color of the fruit") + +class FruitSchema(FruitBase): + id: UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True \ No newline at end of file