Skip to content

Commit 97bf8a7

Browse files
authored
Merge pull request #29 from Zerohertz/issue#24/refactor/apiresponse
[Refactor] APIResponse(JSONResponse)
2 parents d725d8e + 7a6d65d commit 97bf8a7

File tree

13 files changed

+116
-60
lines changed

13 files changed

+116
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,4 @@ secrets.yaml
178178

179179
# Etc
180180
junit.xml
181+
test.db

app/api/v1/endpoints/admin/users.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from typing import Sequence
2-
31
from dependency_injector.wiring import Provide, inject
42
from fastapi import Depends, status
3+
from fastapi.responses import JSONResponse
54

65
from app.core.auth import AdminDeps
76
from app.core.container import Container
@@ -14,7 +13,8 @@
1413

1514
@router.get(
1615
"/",
17-
response_model=Sequence[UserResponse],
16+
response_model=list[UserResponse],
17+
response_class=JSONResponse,
1818
status_code=status.HTTP_200_OK,
1919
summary="",
2020
description="",
@@ -29,6 +29,7 @@ async def get_users(
2929
@router.get(
3030
"/{id}",
3131
response_model=UserResponse,
32+
response_class=JSONResponse,
3233
status_code=status.HTTP_200_OK,
3334
summary="",
3435
description="",
@@ -44,6 +45,7 @@ async def get_user(
4445
@router.put(
4546
"/{id}",
4647
response_model=UserResponse,
48+
response_class=JSONResponse,
4749
status_code=status.HTTP_200_OK,
4850
summary="",
4951
description="",
@@ -60,6 +62,7 @@ async def put_user(
6062
@router.patch(
6163
"/{id}",
6264
response_model=UserResponse,
65+
response_class=JSONResponse,
6366
status_code=status.HTTP_200_OK,
6467
summary="",
6568
description="",
@@ -76,6 +79,7 @@ async def patch_user(
7679
@router.delete(
7780
"/{id}",
7881
response_model=UserResponse,
82+
response_class=JSONResponse,
7983
status_code=status.HTTP_200_OK,
8084
summary="",
8185
description="",

app/api/v1/endpoints/auth.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from dependency_injector.wiring import Provide, inject
2-
from fastapi import Depends, Response, status
3-
from fastapi.responses import RedirectResponse
2+
from fastapi import Depends, status
3+
from fastapi.responses import JSONResponse, RedirectResponse
44

55
from app.core.auth import AuthDeps
66
from app.core.configs import configs
@@ -21,6 +21,7 @@
2121
@router.post(
2222
"/refresh",
2323
response_model=JwtAccessToken,
24+
response_class=JSONResponse,
2425
status_code=status.HTTP_200_OK,
2526
summary="",
2627
description="",
@@ -36,6 +37,7 @@ async def post_refresh_token(
3637
@router.post(
3738
"/register",
3839
response_model=UserResponse,
40+
response_class=JSONResponse,
3941
status_code=status.HTTP_201_CREATED,
4042
summary="Register with password",
4143
description="",
@@ -51,6 +53,7 @@ async def register_password(
5153
@router.post(
5254
"/login",
5355
response_model=JwtToken,
56+
response_class=JSONResponse,
5457
status_code=status.HTTP_200_OK,
5558
summary="Log in with password",
5659
description="",
@@ -65,14 +68,14 @@ async def log_in_password(
6568

6669
@router.get(
6770
"/oauth/login/github",
68-
response_model=Response,
71+
response_model=None,
72+
response_class=RedirectResponse,
6973
status_code=status.HTTP_302_FOUND,
7074
summary="Log in with GitHub OAuth",
7175
description="GitHub OAuth를 위해 redirection",
7276
)
7377
async def log_in_github():
7478
# NOTE: &scope=repo,user
75-
# TODO: APIResponse (related: #24)
7679
return RedirectResponse(
7780
f"https://github.com/login/oauth/authorize?client_id={configs.GITHUB_OAUTH_CLIENT_ID}"
7881
)
@@ -81,6 +84,7 @@ async def log_in_github():
8184
@router.get(
8285
"/oauth/callback/github",
8386
response_model=JwtToken,
87+
response_class=JSONResponse,
8488
status_code=status.HTTP_200_OK,
8589
summary="Callback for GitHub OAuth",
8690
description="GitHub OAuth에 의해 redirection될 endpoint",
@@ -106,6 +110,7 @@ async def callback_github(
106110
@router.get(
107111
"/me",
108112
response_model=UserOut,
113+
response_class=JSONResponse,
109114
status_code=status.HTTP_200_OK,
110115
summary="",
111116
description="",

app/api/v1/endpoints/shields.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime
22

33
from fastapi import APIRouter
4+
from fastapi.responses import JSONResponse
45

56
from app.schemas.shields import Shields
67
from app.utils.shields import dday
@@ -11,6 +12,7 @@
1112
@router.get(
1213
"/jmy",
1314
response_model=Shields,
15+
response_class=JSONResponse,
1416
status_code=200,
1517
summary="",
1618
description="",

app/api/v1/endpoints/users.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from dependency_injector.wiring import Provide, inject
22
from fastapi import Depends, status
3+
from fastapi.responses import JSONResponse
34

45
from app.core.auth import AuthDeps
56
from app.core.container import Container
@@ -13,6 +14,7 @@
1314
@router.put(
1415
"/",
1516
response_model=UserResponse,
17+
response_class=JSONResponse,
1618
status_code=status.HTTP_200_OK,
1719
summary="",
1820
description="",
@@ -29,6 +31,7 @@ async def put_user(
2931
@router.patch(
3032
"/",
3133
response_model=UserResponse,
34+
response_class=JSONResponse,
3235
status_code=status.HTTP_200_OK,
3336
summary="",
3437
description="",
@@ -45,6 +48,7 @@ async def patch_user(
4548
@router.delete(
4649
"/",
4750
response_model=UserResponse,
51+
response_class=JSONResponse,
4852
status_code=status.HTTP_200_OK,
4953
summary="",
5054
description="",

app/core/container.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Container(DeclarativeContainer):
1111
"app.core.auth",
1212
"app.api.v1.endpoints.users",
1313
"app.api.v1.endpoints.auth",
14+
"app.api.v1.endpoints.admin.users",
1415
]
1516
)
1617

app/core/router.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from functools import wraps
2-
from typing import Any, Callable, Coroutine, Sequence, Type, TypeVar, cast
2+
from typing import Any, Callable, Coroutine, Type, TypeVar
33

44
from fastapi import APIRouter, Response
55
from fastapi.types import DecoratedCallable
66
from loguru import logger
77
from pydantic import BaseModel
88

9+
from app.exceptions.router import RouterTypeError
910
from app.schemas.responses import APIResponse
1011

1112
T = TypeVar("T", bound=BaseModel)
@@ -15,8 +16,9 @@ class CoreAPIRouter(APIRouter):
1516
def api_route( # type: ignore
1617
self,
1718
path: str,
18-
*args,
19-
response_model: Type[T] | Type[Response],
19+
*,
20+
response_model: Type[T],
21+
response_class: Type[Response],
2022
status_code: int,
2123
**kwargs,
2224
) -> Callable[
@@ -27,30 +29,58 @@ def decorator(
2729
func: DecoratedCallable,
2830
) -> Callable[..., Coroutine[Any, Any, APIResponse[T] | Response]]:
2931
@wraps(func)
30-
async def success(
32+
async def endpoint(
3133
*_args: tuple, **_kwargs: dict
3234
) -> APIResponse[T] | Response:
3335
response: Any = await func(*_args, **_kwargs)
34-
# FIXME: 우선 response_model이 List와 같은 Sequence로 구성된 경우는 차후에 해결 (#24)
35-
if isinstance(response, Sequence):
36-
pass
37-
elif not isinstance(response, response_model):
38-
logger.warning(f"{type(response)}: {response}")
39-
raise TypeError
40-
if isinstance(response, (BaseModel, Sequence)):
41-
return APIResponse[T].success(
42-
status=status_code, data=cast(T, response)
36+
if isinstance(response, response_class):
37+
return response
38+
if isinstance(response, tuple):
39+
# NOTE: 아래 두 변수는 차후 사용 시 추가
40+
# media_type: str | None = None,
41+
# background: BackgroundTask | None = None,
42+
content, headers = response
43+
return response_class(
44+
content=APIResponse[response_model] # type: ignore[valid-type]
45+
.success(
46+
status=status_code,
47+
data=content,
48+
)
49+
.model_dump(mode="json"),
50+
status_code=status_code,
51+
headers=headers,
4352
)
44-
return response
53+
if isinstance(response, (BaseModel, list)):
54+
return response_class(
55+
content=APIResponse[response_model] # type: ignore[valid-type]
56+
.success(
57+
status=status_code,
58+
data=response,
59+
)
60+
.model_dump(mode="json"),
61+
status_code=status_code,
62+
)
63+
logger.error(f"{type(response)}: {response}")
64+
raise RouterTypeError
4565

46-
self.add_api_route(
47-
path,
48-
success,
49-
*args,
50-
response_model=APIResponse[T],
51-
status_code=status_code,
52-
**kwargs,
53-
)
54-
return success
66+
if response_model is None:
67+
self.add_api_route(
68+
path=path,
69+
endpoint=endpoint,
70+
response_model=response_model,
71+
response_class=response_class,
72+
status_code=status_code,
73+
**kwargs,
74+
)
75+
else:
76+
self.add_api_route(
77+
path=path,
78+
endpoint=endpoint,
79+
response_model=APIResponse[response_model], # type: ignore[valid-type]
80+
response_class=response_class,
81+
status_code=status_code,
82+
**kwargs,
83+
)
84+
return endpoint
5585

5686
return decorator

app/exceptions/router.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from fastapi import status
2+
3+
from app.exceptions.base import CoreException
4+
5+
6+
class RouterException(CoreException):
7+
status: int
8+
message: str
9+
10+
11+
class RouterTypeError(RouterException):
12+
status: int = status.HTTP_500_INTERNAL_SERVER_ERROR
13+
message: str = "Invalid router return type."

app/schemas/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
from app.core.configs import configs
99

1010

11-
class BaseSchemaRequest(BaseModel, abc.ABC):
11+
class BaseRequest(BaseModel, abc.ABC):
1212
model_config = ConfigDict(from_attributes=True)
1313

1414

15-
class BaseSchemaResponse(BaseModel, abc.ABC):
15+
class BaseResponse(BaseModel, abc.ABC):
1616
model_config = ConfigDict(from_attributes=True)
1717
id: int
1818
created_at: datetime

app/schemas/responses.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Generic, Optional, Sequence, TypeVar
2+
from typing import Generic, Optional, TypeVar
33

44
import pytz
55
from pydantic import BaseModel
@@ -31,11 +31,11 @@ class User(BaseModel):
3131

3232
status: int
3333
message: str
34-
data: Optional[T | Sequence[T]] = None
34+
data: Optional[T] = None
3535
timestamp: datetime
3636

3737
@classmethod
38-
def success(cls, *, status: int, data: T | Sequence[T]) -> "APIResponse[T]":
38+
def success(cls, *, status: int, data: T) -> "APIResponse[T]":
3939
return cls(
4040
status=status,
4141
message="The request has been successfully processed.",

0 commit comments

Comments
 (0)