-
Notifications
You must be signed in to change notification settings - Fork 1
common response 생성 및 테스트 router 생성 #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,10 @@ | ||
| from fastapi import APIRouter | ||
|
|
||
| from app.api import test_api | ||
| api_router = APIRouter() | ||
|
|
||
| # api_router.include_router(connect_driver.router, prefix="/connections", tags=["Driver"]) | ||
| # 테스트 라우터 | ||
| api_router.include_router(test_api.router, prefix="/test", tags=["Test"]) | ||
|
|
||
| # 라우터 | ||
| # api_router.include_router(connect_driver.router, prefix="/connections", tags=["Driver"]) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| from fastapi import APIRouter | ||
|
|
||
| from app.schemas.response import ResponseMessage | ||
| from app.core.exceptions import APIException | ||
| from app.core.status import CommonCode | ||
|
|
||
| router = APIRouter() | ||
|
|
||
| @router.get("", response_model=ResponseMessage, summary="타입 변환을 이용한 성공/실패/버그 테스트") | ||
| def simple_test(mode: str): | ||
| """ | ||
| curl 테스트 시 아래 명령어 사용 | ||
| curl -i -X GET "http://localhost:<port>/api/test?mode=1" | ||
| curl -i -X GET "http://localhost:8000/api/test?mode=1" | ||
|
|
||
| 쿼리 파라미터 'mode' 값에 따라 다른 응답을 반환합니다. | ||
|
|
||
| - **mode=1**: 성공 응답 (200 OK) | ||
| - **mode=2**: 커스텀 성공 응답 (200 OK) | ||
| - **mode=기타 숫자**: 예상된 실패 (404 Not Found) | ||
| - **mode=문자열**: 예상치 못한 서버 버그 (500 Internal Server Error) | ||
| """ | ||
| try: | ||
| # 1. 입력받은 mode를 정수(int)로 변환 시도 | ||
| mode_int = int(mode) | ||
|
|
||
| # 2. 정수로 변환 성공 시, 값에 따라 분기 | ||
| if mode_int == 1: | ||
| # 기본 성공 코드(SUCCESS)로 응답 | ||
| return ResponseMessage.success( | ||
| value={"detail": "기본 성공 테스트입니다."} | ||
| ) | ||
| elif mode_int == 2: | ||
| # 커스텀 성공 코드(CREATED)로 응답 | ||
| return ResponseMessage.success( | ||
| value={"detail": "커스텀 성공 코드(CREATED) 테스트입니다."}, | ||
| code=CommonCode.CREATED | ||
| ) | ||
| else: | ||
| # 그 외 숫자는 '데이터 없음' 오류로 처리 | ||
| raise APIException(CommonCode.NO_SEARCH_DATA) | ||
|
|
||
| except ValueError: | ||
| # 3. 정수로 변환 실패 시 (문자열이 들어온 경우) | ||
| # 예상치 못한 버그를 강제로 발생시킵니다. | ||
| # 이 에러는 generic_exception_handler가 처리하게 됩니다. | ||
| raise TypeError("의도적으로 발생시킨 타입 에러입니다.") | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import traceback | ||
| from fastapi import Request, status | ||
| from fastapi.responses import JSONResponse | ||
| from app.core.status import CommonCode | ||
|
|
||
|
|
||
| class APIException(Exception): | ||
| """ | ||
| API 로직 내에서 발생하는 모든 예상된 오류에 사용할 기본 예외 클래스입니다. | ||
| """ | ||
| def __init__(self, code: CommonCode, *args): | ||
| self.code_enum = code | ||
| self.message = code.get_message(*args) | ||
| super().__init__(self.message) | ||
|
|
||
| async def api_exception_handler(request: Request, exc: APIException): | ||
| """ | ||
| APIException이 발생했을 때, 이를 감지하여 표준화된 JSON 오류 응답을 반환합니다. | ||
| """ | ||
| return JSONResponse( | ||
| status_code=exc.code_enum.http_status, | ||
| content={ | ||
| "code": exc.code_enum.code, | ||
| "message": exc.message, | ||
| "data": None | ||
| } | ||
| ) | ||
|
|
||
| async def generic_exception_handler(request: Request, exc: Exception): | ||
| """ | ||
| 처리되지 않은 모든 예외를 잡아, 일관된 500 서버 오류를 반환합니다. | ||
| """ | ||
| # 운영 환경에서는 파일 로그나 모니터링 시스템으로 보내야 합니다. | ||
| print("="*20, "UNEXPECTED ERROR", "="*20) | ||
| traceback.print_exc() | ||
| print("="*50) | ||
|
|
||
| # 사용자에게는 간단한 500 에러 메시지만 보여줍니다. | ||
| error_response = { | ||
| "code": CommonCode.FAIL.code, | ||
| "message": CommonCode.FAIL.message, | ||
| "data": None | ||
| } | ||
|
|
||
| return JSONResponse( | ||
| status_code=CommonCode.FAIL.http_status, | ||
| content=error_response, | ||
| ) |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| from enum import Enum | ||
| from fastapi import status | ||
|
|
||
| class CommonCode(Enum): | ||
| """ | ||
| 애플리케이션의 모든 상태 코드를 중앙에서 관리합니다. | ||
| 각 멤버는 (HTTP 상태 코드, 고유 비즈니스 코드, 기본 메시지) 튜플을 값으로 가집니다. | ||
| 상태 코드 참고: https://developer.mozilla.org/ko/docs/Web/HTTP/Status | ||
| """ | ||
|
|
||
| # ================================== | ||
| # 성공 (Success) - 2xx | ||
| # ================================== | ||
| SUCCESS = (status.HTTP_200_OK, "2000", "성공적으로 처리되었습니다.") | ||
| CREATED = (status.HTTP_201_CREATED, "2001", "성공적으로 생성되었습니다.") | ||
| SUCCESS_DB_CONNECT = (status.HTTP_200_OK, "2002", "디비 연결을 성공하였습니다.") | ||
|
|
||
| # ================================== | ||
| # 클라이언트 오류 (Client Error) - 4xx | ||
| # ================================== | ||
| NO_VALUE = (status.HTTP_400_BAD_REQUEST, "4000", "필수 값이 존재하지 않습니다.") | ||
| DUPLICATION = (status.HTTP_409_CONFLICT, "4001", "이미 존재하는 데이터입니다.") | ||
| NO_SEARCH_DATA = (status.HTTP_404_NOT_FOUND, "4002", "요청한 데이터를 찾을 수 없습니다.") | ||
|
|
||
| # ================================== | ||
| # 서버 오류 (Server Error) - 5xx | ||
| # ================================== | ||
| FAIL = (status.HTTP_500_INTERNAL_SERVER_ERROR, "9999", "서버 처리 중 오류가 발생했습니다.") | ||
|
|
||
|
|
||
| def __init__(self, http_status: int, code: str, message: str): | ||
| """Enum 멤버가 생성될 때 각 값을 속성으로 할당합니다.""" | ||
| self.http_status = http_status | ||
| self.code = code | ||
| self.message = message | ||
|
|
||
| def get_message(self, *args) -> str: | ||
| """ | ||
| 메시지 포맷팅이 필요한 경우, 인자를 받아 완성된 메시지를 반환합니다. | ||
| """ | ||
| return self.message % args if args else self.message | ||
|
|
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이제 앞으로 #테스트 라우터 부분처럼 작성해야하는거죠?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분은 라우터 pr과 합쳐지면 바뀌어야합니다. |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자, 여기까지의 흐름이 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| from typing import Generic, TypeVar, Optional | ||
| from pydantic import BaseModel, Field | ||
| from app.core.status import CommonCode | ||
|
|
||
| T = TypeVar('T') | ||
|
|
||
| class ResponseMessage(BaseModel, Generic[T]): | ||
| """ | ||
| 모든 API 응답에 사용될 공용 스키마입니다. | ||
| """ | ||
| code: str = Field(..., description="응답을 나타내는 고유 상태 코드") | ||
| message: str = Field(..., description="응답 메시지") | ||
| data: Optional[T] = Field(None, description="반환될 실제 데이터") | ||
|
|
||
| @classmethod | ||
| def success( | ||
| cls, | ||
| value: Optional[T] = None, | ||
| code: CommonCode = CommonCode.SUCCESS, | ||
| *args | ||
| ) -> "ResponseMessage[T]": | ||
| """ | ||
| 성공 응답을 생성하는 팩토리 메서드입니다. | ||
| """ | ||
| return cls( | ||
| code=code.code, | ||
| message=code.get_message(*args), | ||
| data=value | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uvicorn app.main:app --reload앞부분에
cd app이라고 되어있긴 한데 앞부분(cd app)을 지우던지 여기서app.main:app을main:app으로 해야할 지 정해야할 것 같습니다.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그리고 동적포트라서
/app 인경우
python main.pyoruvicorn main:app --host 0.0.0.0 --port 39722 --reload이렇게 가야 되던데 그렇지 않나요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 원래 리드미에 적혀있는 대로 cd app 해서 진행하니 app 내부에 생기는 문제가 발생하더라고요.
그래서 현재 부분으로 수정했습니다.
cd app 부분이 clone한 프로젝트로 이해하고 넘어갔는데 아닌가요??
저 부분 포트를 지정해줄 필요가 있나요? main.py에서 지정해주기 때문에 main 위치만 지정해주면 되는 거 아닌가요??