From cf810bf4f40f4896263a491b4c866a006040337b Mon Sep 17 00:00:00 2001 From: Backend IM Bot Date: Tue, 29 Apr 2025 20:19:49 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20add=20fruit=20endpoints=20to=20get=20al?= =?UTF-8?q?l=20fruits=20and=20single=20fruit,=20with=20sorting=20capabilit?= =?UTF-8?q?ies=20=E2=9C=85=20(auto-linted)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20250429_201921_c2003b42_update_fruit.py | 33 +++++++ endpoints/fruits.get.py | 25 +++++ helpers/fruit_helpers.py | 96 +++++++++++++++++++ models/fruit.py | 15 +++ requirements.txt | 3 + schemas/fruit.py | 25 +++++ 6 files changed, 197 insertions(+) create mode 100644 alembic/versions/20250429_201921_c2003b42_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/20250429_201921_c2003b42_update_fruit.py b/alembic/versions/20250429_201921_c2003b42_update_fruit.py new file mode 100644 index 0000000..9a15b9d --- /dev/null +++ b/alembic/versions/20250429_201921_c2003b42_update_fruit.py @@ -0,0 +1,33 @@ +"""create table for fruits + +Revision ID: 2c9a83e4f6b3 +Revises: 0001 +Create Date: 2023-05-22 12:00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import func +import uuid + +# revision identifiers, used by Alembic. +revision = '2c9a83e4f6b3' +down_revision = '0001' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'fruits', + 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('description', sa.String(), nullable=True), + sa.Column('price', sa.Integer(), 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()) + ) + op.create_index(op.f('ix_fruits_name'), 'fruits', ['name'], unique=True) + +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/fruits.get.py b/endpoints/fruits.get.py index e69de29..7542883 100644 --- a/endpoints/fruits.get.py +++ b/endpoints/fruits.get.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from typing import List, Optional +from sqlalchemy.orm import Session +from core.database import get_db +from schemas.fruit import FruitSchema +from helpers.fruit_helpers import get_all_fruits, get_fruit_by_id + +router = APIRouter() + +@router.get("/fruits", status_code=status.HTTP_200_OK, response_model=List[FruitSchema]) +def get_fruits( + order_by: Optional[str] = None, + db: Session = Depends(get_db) +): + return get_all_fruits(db, order_by) + +@router.get("/fruits/{fruit_id}", status_code=status.HTTP_200_OK, response_model=FruitSchema) +def get_fruit( + fruit_id: str, + db: Session = Depends(get_db) +): + fruit = get_fruit_by_id(db, fruit_id) + if not fruit: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Fruit not found") + return 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..cc80565 --- /dev/null +++ b/helpers/fruit_helpers.py @@ -0,0 +1,96 @@ +from typing import List, Optional +from uuid import UUID +from sqlalchemy.orm import Session +from sqlalchemy import asc, desc +from models.fruit import Fruit +from schemas.fruit import FruitCreate, FruitUpdate + +def get_all_fruits(db: Session, order_by: Optional[str] = None) -> List[Fruit]: + """ + Retrieves all fruits from the database. + + Args: + db (Session): The database session. + order_by (Optional[str]): The field to order the results by ('asc' or 'desc'). + + Returns: + List[Fruit]: A list of all fruit objects. + """ + query = db.query(Fruit) + if order_by == 'asc': + query = query.order_by(asc(Fruit.name)) + elif order_by == 'desc': + query = query.order_by(desc(Fruit.name)) + return query.all() + +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 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. + """ + db_fruit = Fruit(**fruit_data.dict()) + db.add(db_fruit) + db.commit() + db.refresh(db_fruit) + return db_fruit + +def update_fruit(db: Session, fruit_id: UUID, fruit_data: FruitUpdate) -> Optional[Fruit]: + """ + Updates an existing fruit in the database. + + Args: + db (Session): The database session. + fruit_id (UUID): The ID of the fruit to update. + fruit_data (FruitUpdate): The updated data for the fruit. + + Returns: + Optional[Fruit]: The updated fruit object if found, otherwise None. + """ + db_fruit = db.query(Fruit).filter(Fruit.id == fruit_id).first() + if not db_fruit: + return None + + for field, value in fruit_data.dict(exclude_unset=True).items(): + setattr(db_fruit, field, value) + + db.commit() + db.refresh(db_fruit) + return db_fruit + +def delete_fruit(db: Session, fruit_id: UUID) -> bool: + """ + Deletes a fruit from the database. + + Args: + db (Session): The database session. + fruit_id (UUID): The ID of the fruit to delete. + + Returns: + bool: True if the fruit was successfully deleted, False otherwise. + """ + db_fruit = db.query(Fruit).filter(Fruit.id == fruit_id).first() + if not db_fruit: + return False + + db.delete(db_fruit) + db.commit() + return True \ No newline at end of file diff --git a/models/fruit.py b/models/fruit.py new file mode 100644 index 0000000..1123a55 --- /dev/null +++ b/models/fruit.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, String, Integer, 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) + description = Column(String, nullable=True) + price = Column(Integer, 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/requirements.txt b/requirements.txt index 596e6f3..db12c92 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,6 @@ sqlalchemy>=1.4.0 python-dotenv>=0.19.0 bcrypt>=3.2.0 alembic>=1.13.1 +jose +passlib +pydantic diff --git a/schemas/fruit.py b/schemas/fruit.py new file mode 100644 index 0000000..19c8db8 --- /dev/null +++ b/schemas/fruit.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime +import uuid + +class FruitBase(BaseModel): + name: str = Field(..., description="Name of the fruit") + description: Optional[str] = Field(None, description="Description of the fruit") + price: int = Field(..., description="Price of the fruit") + +class FruitCreate(FruitBase): + pass + +class FruitUpdate(FruitBase): + name: Optional[str] = Field(None, description="Name of the fruit") + description: Optional[str] = Field(None, description="Description of the fruit") + price: Optional[int] = Field(None, description="Price of the fruit") + +class FruitSchema(FruitBase): + id: uuid.UUID + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True \ No newline at end of file