Skip to content

Commit 9b9e167

Browse files
committed
feat(#5): endpoints now return more semantic responses when errors occur
1 parent d10f39d commit 9b9e167

6 files changed

Lines changed: 70 additions & 40 deletions

File tree

api/database/models/model_base.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from datetime import datetime
4-
from typing import Any, List
4+
from typing import Any, List, Union
55

66
from fastapi_async_sqlalchemy import db
77
from sqlalchemy import DateTime, MetaData, select
@@ -30,6 +30,8 @@ class ModelBase(DeclarativeBase):
3030
async def new(cls, **kwargs) -> Self:
3131
obj: Self = cls(**kwargs)
3232
await obj.save()
33+
await db.session.flush()
34+
await db.session.refresh(obj)
3335
return obj
3436

3537
@classmethod
@@ -55,7 +57,7 @@ async def get(
5557
return result.scalars().first()
5658

5759
@classmethod
58-
async def get_by_id(cls, id: int) -> Self:
60+
async def get_by_id(cls, id: int) -> Union[Self, None]:
5961
return await cls.get(cls.id == id)
6062

6163
async def save(self) -> None:
@@ -66,15 +68,5 @@ async def update(self, **kwargs):
6668
setattr(self, attr, value)
6769
await self.save()
6870

69-
@classmethod
70-
async def update_by_id(cls, id: int, **kwargs):
71-
obj = await cls.get_by_id(id)
72-
await obj.update(**kwargs)
73-
7471
async def delete(self):
7572
await db.session.delete(self)
76-
77-
@classmethod
78-
async def delete_by_id(cls, id: int):
79-
obj = await cls.get_by_id(id)
80-
await obj.delete()

api/entrypoints/user.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
from typing import List
22

3-
from api.schemas.user import UserCreate
3+
from api.schemas.user import UserCreate, UserOut
44
from api.services.user_service import UserService
55
from fastapi import APIRouter
66

77
router = APIRouter()
88

99

10-
@router.get("/", response_model=List[UserCreate])
11-
async def get_users() -> List[UserCreate]:
10+
@router.get("/", response_model=List[UserOut])
11+
async def get_users() -> List[UserOut]:
1212
"""
1313
Retrieve a list of all users.
1414
1515
Returns:
1616
List[User]: A list of User objects.
1717
"""
18-
return await UserService.get_all()
18+
return await UserService().get_all()
1919

2020

21-
@router.get("/{user_id}", response_model=UserCreate)
22-
async def get_user(user_id: int):
21+
@router.get("/{user_id}", response_model=UserOut)
22+
async def get_user(user_id: int) -> UserOut:
2323
"""
2424
Retrieve a user by ID.
2525
@@ -29,10 +29,10 @@ async def get_user(user_id: int):
2929
Returns:
3030
User: The User object.
3131
"""
32-
return await UserService.get_user(user_id)
32+
return await UserService().get_user(user_id)
3333

3434

35-
@router.post("/", response_model=UserCreate)
35+
@router.post("/", response_model=UserOut)
3636
async def create_user(user: UserCreate):
3737
"""
3838
Create a new user.
@@ -43,11 +43,11 @@ async def create_user(user: UserCreate):
4343
Returns:
4444
User: The created User object.
4545
"""
46-
return await UserService.create_user(user)
46+
return await UserService().create_user(user)
4747

4848

4949
@router.put("/{user_id}", response_model=None)
50-
async def update_user(user_id: int, user: UserCreate):
50+
async def update_user(user_id: int, user: UserCreate) -> None:
5151
"""
5252
Update a user by ID.
5353
@@ -58,15 +58,15 @@ async def update_user(user_id: int, user: UserCreate):
5858
Returns:
5959
User: The updated User object.
6060
"""
61-
return await UserService.update_user(user_id, user)
61+
return await UserService().update_user(user_id, user)
6262

6363

64-
@router.delete("/{user_id}")
65-
async def delete_user(user_id: int):
64+
@router.delete("/{user_id}", response_model=None)
65+
async def delete_user(user_id: int) -> None:
6666
"""
6767
Delete a user by ID.
6868
6969
Args:
7070
user_id (int): The ID of the user.
7171
"""
72-
return await UserService.delete_user(user_id)
72+
return await UserService().delete_user(user_id)

api/exceptions/http_exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from api.database.models.model_base import ModelBase
2+
from fastapi import HTTPException, status
3+
4+
5+
class NotFoundException(HTTPException):
6+
def __init__(self, model: ModelBase):
7+
super().__init__(
8+
status_code=status.HTTP_404_NOT_FOUND,
9+
detail=f"{model.__name__} not found",
10+
)

api/schemas/base_db_schema.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
4+
from pydantic import BaseModel, field_serializer
5+
6+
7+
class BaseDBSchema(BaseModel):
8+
id: int
9+
created_at: datetime
10+
updated_at: datetime
11+
deleted_at: Optional[datetime]
12+
13+
class Config:
14+
orm_mode = True
15+
16+
@field_serializer("created_at", "updated_at", "deleted_at")
17+
def serialize_dt(self, dt: Optional[datetime]):
18+
if not dt:
19+
return dt
20+
return dt.strftime("%d-%m-%Y %H:%M:%S")

api/schemas/user.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
from api.schemas.base_db_schema import BaseDBSchema
12
from pydantic import BaseModel
23

34

45
class UserCreate(BaseModel):
56
name: str
67
email: str
78
password: str
9+
10+
11+
class UserOut(BaseDBSchema):
12+
name: str
13+
email: str

api/services/user_service.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from typing import List
1+
from typing import List, Union
22

33
from api.database.models.users import User
4+
from api.exceptions.http_exceptions import NotFoundException
45
from api.schemas.user import UserCreate
56
from loguru import logger
67

78

89
class UserService:
9-
@staticmethod
10-
async def get_all() -> List[UserCreate]:
10+
async def get_all(self) -> List[User]:
1111
"""
1212
Retrieve a list of all users.
1313
@@ -16,8 +16,7 @@ async def get_all() -> List[UserCreate]:
1616
"""
1717
return await User.get_all()
1818

19-
@staticmethod
20-
async def get_user(user_id: int):
19+
async def get_user(self, user_id: int) -> User:
2120
"""
2221
Retrieve a user by ID.
2322
@@ -27,10 +26,12 @@ async def get_user(user_id: int):
2726
Returns:
2827
User: The User object.
2928
"""
30-
return await User.get_by_id(user_id)
29+
user: Union[User, None] = await User.get_by_id(user_id)
30+
if not user:
31+
raise NotFoundException(User)
32+
return user
3133

32-
@staticmethod
33-
async def create_user(user: UserCreate):
34+
async def create_user(self, user: UserCreate) -> User:
3435
"""
3536
Create a new user.
3637
@@ -43,8 +44,7 @@ async def create_user(user: UserCreate):
4344
logger.info(user.model_dump())
4445
return await User.new(**user.model_dump())
4546

46-
@staticmethod
47-
async def update_user(user_id: int, user: UserCreate):
47+
async def update_user(self, user_id: int, updated_user: UserCreate):
4848
"""
4949
Update a user by ID.
5050
@@ -55,15 +55,17 @@ async def update_user(user_id: int, user: UserCreate):
5555
Returns:
5656
User: The updated User object.
5757
"""
58-
return await User.update_by_id(user_id, **user.model_dump())
58+
user: User = await self.get_user(user_id)
59+
return await user.update(**updated_user.model_dump())
5960

60-
@staticmethod
61-
async def delete_user(user_id: int):
61+
async def delete_user(self, user_id: int):
6262
"""
6363
Delete a user by ID.
6464
6565
Args:
6666
user_id (int): The ID of the user.
6767
"""
68-
await User.delete_by_id(user_id)
68+
69+
user: User = await self.get_user(user_id)
70+
await user.delete()
6971
return {"message": "User deleted successfully"}

0 commit comments

Comments
 (0)