From 43dd74c223cfd4586f485ebedc8a445fe3be85f4 Mon Sep 17 00:00:00 2001 From: Donkie Date: Sat, 1 Apr 2023 23:39:42 +0200 Subject: [PATCH] Added full CRUD operations for filaments --- spoolson/api/v1/filament.py | 82 +++++++++++++++++------------------ spoolson/api/v1/models.py | 4 ++ spoolson/api/v1/router.py | 3 +- spoolson/database/filament.py | 37 +++++++++++++++- 4 files changed, 83 insertions(+), 43 deletions(-) diff --git a/spoolson/api/v1/filament.py b/spoolson/api/v1/filament.py index 5a82234..a5ca6fc 100644 --- a/spoolson/api/v1/filament.py +++ b/spoolson/api/v1/filament.py @@ -3,12 +3,14 @@ from typing import Annotated, Optional, Union from fastapi import APIRouter, Depends +from fastapi.exceptions import RequestValidationError from pydantic import BaseModel, Field +from pydantic.error_wrappers import ErrorWrapper from sqlalchemy.ext.asyncio import AsyncSession -from spoolson.api.v1.models import Filament, Vendor +from spoolson.api.v1.models import Filament, Message +from spoolson.database import filament from spoolson.database.database import get_db_session -from spoolson.database.filament import create_filament router = APIRouter( prefix="/filament", @@ -37,30 +39,23 @@ class FilamentParameters(BaseModel): comment: Optional[str] = Field(max_length=1024, description="Free text comment about this filament type.") +class FilamentUpdateParameters(FilamentParameters): + density: Optional[float] = Field(gt=0, description="The density of this filament in g/cm3.") + diameter: Optional[float] = Field(gt=0, description="The diameter of this filament in mm.") + + @router.get("/") async def find(_vendor: Union[int, None] = None) -> list[Filament]: return [] @router.get("/{filament_id}") -async def get(_filament_id: int) -> Filament: - return Filament( - id=0, - name=None, - vendor=Vendor( - id=0, - name="asdf", - comment=None, - ), - material=None, - price=None, - density=1, - diameter=1, - weight=None, - spool_weight=None, - article_number=None, - comment=None, - ) +async def get( + db: Annotated[AsyncSession, Depends(get_db_session)], + filament_id: int, +) -> Filament: + db_item = await filament.get_by_id(db, filament_id) + return Filament.from_db(db_item) @router.post("/") @@ -68,7 +63,7 @@ async def create( db: Annotated[AsyncSession, Depends(get_db_session)], body: FilamentParameters, ) -> Filament: - db_item = await create_filament( + db_item = await filament.create( db=db, density=body.density, diameter=body.diameter, @@ -85,27 +80,32 @@ async def create( return Filament.from_db(db_item) -@router.put("/{filament_id}") -async def update() -> Filament: - return Filament( - id=0, - name=None, - vendor=Vendor( - id=0, - name="asdf", - comment=None, - ), - material=None, - price=None, - density=1, - diameter=1, - weight=None, - spool_weight=None, - article_number=None, - comment=None, +@router.patch("/{filament_id}") +async def update( + db: Annotated[AsyncSession, Depends(get_db_session)], + filament_id: int, + body: FilamentUpdateParameters, +) -> Filament: + patch_data = body.dict(exclude_unset=True) + + if "density" in patch_data and body.density is None: + raise RequestValidationError([ErrorWrapper(ValueError("density cannot be unset"), ("query", "density"))]) + if "diameter" in patch_data and body.diameter is None: + raise RequestValidationError([ErrorWrapper(ValueError("diameter cannot be unset"), ("query", "diameter"))]) + + db_item = await filament.update( + db=db, + filament_id=filament_id, + data=patch_data, ) + return Filament.from_db(db_item) + @router.delete("/{filament_id}") -async def delete() -> None: - pass +async def delete( + db: Annotated[AsyncSession, Depends(get_db_session)], + filament_id: int, +) -> Message: + await filament.delete(db, filament_id) + return Message(message="Success!") diff --git a/spoolson/api/v1/models.py b/spoolson/api/v1/models.py index e6979c6..88fa24d 100644 --- a/spoolson/api/v1/models.py +++ b/spoolson/api/v1/models.py @@ -8,6 +8,10 @@ from pydantic import BaseModel, Field from spoolson.database import models +class Message(BaseModel): + message: str = Field() + + class Vendor(BaseModel): id: int = Field(description="Unique internal ID of this vendor.") name: str = Field(max_length=64, description="Vendor name.") diff --git a/spoolson/api/v1/router.py b/spoolson/api/v1/router.py index 74699bb..ae1da69 100644 --- a/spoolson/api/v1/router.py +++ b/spoolson/api/v1/router.py @@ -7,7 +7,7 @@ from starlette.responses import Response from spoolson.exceptions import ItemNotFoundError -from . import filament, spool, vendor +from . import filament, models, spool, vendor # ruff: noqa: D103 @@ -15,6 +15,7 @@ app = FastAPI( title="Spoolson REST API v1", version="1.0.0", root_path_in_servers=False, + responses={404: {"model": models.Message}}, ) diff --git a/spoolson/database/filament.py b/spoolson/database/filament.py index fb3eaf4..0faf3eb 100644 --- a/spoolson/database/filament.py +++ b/spoolson/database/filament.py @@ -5,9 +5,10 @@ from typing import Optional from sqlalchemy.ext.asyncio import AsyncSession from spoolson.database import models, vendor +from spoolson.exceptions import ItemNotFoundError -async def create_filament( +async def create( *, db: AsyncSession, density: float, @@ -41,3 +42,37 @@ async def create_filament( db.add(db_item) await db.flush() return db_item + + +async def get_by_id(db: AsyncSession, filament_id: int) -> models.Filament: + """Get a filament object from the database by the unique ID.""" + filament = await db.get(models.Filament, filament_id) + if filament is None: + raise ItemNotFoundError(f"No filament with ID {filament_id} found.") + return filament + + +async def update( + *, + db: AsyncSession, + filament_id: int, + data: dict, +) -> models.Filament: + """Update the fields of a filament object.""" + filament = await get_by_id(db, filament_id) + for k, v in data.items(): + if k == "vendor_id": + if v is None: + filament.vendor = None + else: + filament.vendor = await vendor.get_by_id(db, v) + else: + setattr(filament, k, v) + await db.flush() + return filament + + +async def delete(db: AsyncSession, filament_id: int) -> None: + """Delete a filament object.""" + filament = await get_by_id(db, filament_id) + await db.delete(filament)