Skip to content

Commit e49bd84

Browse files
authored
refactor FastApi API코드 리펙토링 (#28)
* -refactor 1. BaseModel으로 Request,Response 공통화 2. API 재설계 3. API 3계층 구조로 구조변경 * -refactor 1.에러 핸들로 공통화
1 parent 757f862 commit e49bd84

File tree

9 files changed

+227
-44
lines changed

9 files changed

+227
-44
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# app/api/endpoints/keywords.py
2+
from fastapi import APIRouter
3+
from app.decorators.logging import log_api_call
4+
from ...errors.CustomException import *
5+
from fastapi import APIRouter
6+
7+
from ...model.schemas import *
8+
# 이 파일만의 독립적인 라우터를 생성합니다.
9+
router = APIRouter()
10+
11+
@router.get("/")
12+
async def root():
13+
return {"message": "blog API"}
14+
15+
@router.post("/rag/create", response_model=ResponsetBlogCreate)
16+
async def rag_create(request: RequestBlogCreate):
17+
return {"message": "blog API"}
18+
19+
@router.post("/publish", response_model=RequestBlogPublish)
20+
async def publish(request: ResponsetBlogPublish):
21+
return {"message": "blog API"}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# app/api/endpoints/keywords.py
2+
from ...service.keyword_service import keyword_search
3+
4+
from fastapi import APIRouter
5+
from app.decorators.logging import log_api_call
6+
from ...errors.CustomException import *
7+
from fastapi import APIRouter
8+
from ...errors.CustomException import *
9+
from ...model.schemas import RequestNaverSearch, ResponseNaverSearch, RequestSadaguValidate, ResponsetSadaguValidate
10+
11+
# 이 파일만의 독립적인 라우터를 생성합니다.
12+
router = APIRouter()
13+
14+
@router.get("/")
15+
async def root():
16+
return {"message": "Items API"}
17+
18+
@router.post("/search")
19+
async def search(request: RequestNaverSearch):
20+
"""
21+
이 엔드포인트는 아래와 같은 JSON 요청을 받습니다.
22+
RequestBase와 RequestNaverSearch의 모든 필드를 포함해야 합니다.
23+
{
24+
"job_id": "job-123",
25+
"schedule_id": "schedule-456",
26+
"schedule_his_id": 789,
27+
"tag": "fastapi",
28+
"category": "tech",
29+
"start_date": "2025-09-01T12:00:00",
30+
"end_date": "2025-09-02T15:00:00"
31+
}
32+
"""
33+
job_id = request.job_id
34+
schedule_id = request.schedule_id
35+
category = request.category
36+
keywords = "밥밥밥"
37+
return ResponseNaverSearch(
38+
job_id=job_id,
39+
schedule_id=schedule_id,
40+
category=category,
41+
keyword=keywords,
42+
total_keyword = {1: "바밥밥", 2: "밥밥밥", 3: "바밤바"}
43+
)
44+
45+
@router.post("/search/test",response_model=ResponsetSadaguValidate)
46+
async def search(request: RequestSadaguValidate):
47+
"""
48+
이 엔드포인트는 아래와 같은 JSON 요청을 받습니다.
49+
RequestBase와 RequestNaverSearch의 모든 필드를 포함해야 합니다.
50+
{
51+
"job_id": "job-123",
52+
"schedule_id": "schedule-456",
53+
"schedule_his_id": 789,
54+
"tag": "fastapi",
55+
"category": "tech",
56+
"start_date": "2025-09-01T12:00:00",
57+
"end_date": "2025-09-02T15:00:00"
58+
}
59+
"""
60+
response_data= keyword_search(request)
61+
return response_data
62+
63+
@router.post("/ssadagu/validate",response_model=ResponseNaverSearch)
64+
async def ssadagu_validate(request: RequestNaverSearch):
65+
return ResponseNaverSearch()

apps/pre-processing-service/app/api/endpoints/processing.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

apps/pre-processing-service/app/api/endpoints/embedding.py renamed to apps/pre-processing-service/app/api/endpoints/product.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
# app/api/endpoints/embedding.py
21
from fastapi import APIRouter
32
from app.decorators.logging import log_api_call
43
from ...errors.CustomException import *
54
from fastapi import APIRouter
65

6+
from ...model.schemas import *;
7+
78
# 이 파일만의 독립적인 라우터를 생성합니다.
89
router = APIRouter()
910

10-
@router.get("/")
11-
async def root():
12-
return {"message": "Items API"}
11+
@router.post("/crawl",response_model=ResponsetSadaguCrawl)
12+
async def crawl(request: RequestSadaguCrawl):
13+
return ResponsetSadaguCrawl()

apps/pre-processing-service/app/api/router.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
# app/api/router.py
22
from fastapi import APIRouter
3-
from .endpoints import embedding, processing,test
3+
from .endpoints import keywords, blog,test,product
44
from ..core.config import settings
55

66
api_router = APIRouter()
77

88
# embedding API URL
9-
api_router.include_router(embedding.router, prefix="/emb", tags=["Embedding"])
9+
api_router.include_router(keywords.router, prefix="/keywords", tags=["keyword"])
1010

1111
# processing API URL
12-
api_router.include_router(processing.router, prefix="/prc", tags=["Processing"])
12+
api_router.include_router(blog.router, prefix="/blog", tags=["blog"])
13+
14+
#상품 API URL
15+
api_router.include_router(product.router, prefix="/product", tags=["product"])
1316

1417
#모듈 테스터를 위한 endpoint
1518
api_router.include_router(test.router, prefix="/test", tags=["Test"])
Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,90 @@
1-
# app/errors/handlers.py
21
from fastapi import Request, status
32
from fastapi.responses import JSONResponse
3+
from pydantic import BaseModel
44
from starlette.exceptions import HTTPException as StarletteHTTPException
55
from fastapi.exceptions import RequestValidationError
66
from .messages import ERROR_MESSAGES, get_error_message
77
from ..errors.CustomException import CustomException
88

9+
class ErrorBaseModel(BaseModel):
10+
"""
11+
모든 에러 응답의 기반이 되는 Pydantic 모델.
12+
API의 에러 응답 형식을 통일하는 역할을 합니다.
13+
"""
14+
status_code: int
15+
detail: str
16+
code: str
17+
918
# CustomException 핸들러
1019
async def custom_exception_handler(request: Request, exc: CustomException):
1120
"""
1221
CustomException을 상속받는 모든 예외를 처리합니다.
1322
"""
23+
# 변경점: ErrorBaseModel을 사용하여 응답 본문 생성
24+
error_content = ErrorBaseModel(
25+
status_code=exc.status_code,
26+
detail=exc.detail,
27+
code=exc.code
28+
)
1429
return JSONResponse(
1530
status_code=exc.status_code,
16-
content={
17-
"error_code": exc.code,
18-
"message": exc.detail,
19-
},
31+
content=error_content.model_dump(),
2032
)
2133

34+
2235
# FastAPI의 HTTPException 핸들러 (예: 404 Not Found)
2336
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
2437
"""
2538
FastAPI에서 기본적으로 발생하는 HTTP 관련 예외를 처리합니다.
2639
"""
27-
if exc.status_code == status.HTTP_404_NOT_FOUND:
28-
# 404 에러의 경우, FastAPI의 기본 "Not Found" 메시지 대신 우리가 정의한 메시지를 사용합니다.
29-
message = ERROR_MESSAGES.get(exc.status_code, "요청하신 리소스를 찾을 수 없습니다.")
30-
else:
31-
# 다른 HTTP 예외들은 FastAPI가 제공하는 detail 메시지를 우선적으로 사용합니다.
32-
message = get_error_message(exc.status_code, exc.detail)
40+
message = get_error_message(exc.status_code, exc.detail)
3341

42+
# 변경점: ErrorBaseModel을 사용하여 응답 본문 생성
43+
error_content = ErrorBaseModel(
44+
status_code=exc.status_code,
45+
detail=message,
46+
code=f"HTTP_{exc.status_code}"
47+
)
3448
return JSONResponse(
3549
status_code=exc.status_code,
36-
content={
37-
"error_code": f"HTTP_{exc.status_code}",
38-
"message": message
39-
},
50+
content=error_content.model_dump(),
4051
)
4152

53+
4254
# Pydantic Validation Error 핸들러 (422)
4355
async def validation_exception_handler(request: Request, exc: RequestValidationError):
4456
"""
4557
Pydantic 모델 유효성 검사 실패 시 발생하는 예외를 처리합니다.
4658
"""
59+
# 변경점: ErrorBaseModel을 기본 구조로 사용하고, 추가 정보를 더함
60+
base_error = ErrorBaseModel(
61+
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
62+
detail=ERROR_MESSAGES[status.HTTP_422_UNPROCESSABLE_ENTITY],
63+
code="VALIDATION_ERROR"
64+
)
65+
66+
# 모델의 내용과 추가적인 'details' 필드를 결합
67+
response_content = base_error.model_dump()
68+
response_content["details"] = exc.errors()
69+
4770
return JSONResponse(
4871
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
49-
content={
50-
"error_code": "VALIDATION_ERROR",
51-
"message": ERROR_MESSAGES[status.HTTP_422_UNPROCESSABLE_ENTITY],
52-
"details": exc.errors(),
53-
},
72+
content=response_content,
5473
)
5574

75+
5676
# 처리되지 않은 모든 예외 핸들러 (500)
5777
async def unhandled_exception_handler(request: Request, exc: Exception):
58-
# ...
78+
"""
79+
처리되지 않은 모든 예외를 처리합니다.
80+
"""
81+
# 변경점: ErrorBaseModel을 사용하여 응답 본문 생성
82+
error_content = ErrorBaseModel(
83+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
84+
detail=ERROR_MESSAGES[status.HTTP_500_INTERNAL_SERVER_ERROR],
85+
code="INTERNAL_SERVER_ERROR"
86+
)
5987
return JSONResponse(
6088
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
61-
content={
62-
"error_code": "INTERNAL_SERVER_ERROR",
63-
"message": ERROR_MESSAGES[status.HTTP_500_INTERNAL_SERVER_ERROR],
64-
},
89+
content=error_content.model_dump(),
6590
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
from pydantic import BaseModel
4+
5+
6+
7+
#기본 요청
8+
class RequestBase(BaseModel):
9+
job_id: str
10+
schedule_id: str
11+
schedule_his_id: Optional[int] = None
12+
13+
#기본 응답
14+
class ResponseBase(BaseModel):
15+
job_id: str
16+
schedule_id: str
17+
status: str
18+
19+
20+
#네이버 키워드 추출
21+
class RequestNaverSearch(RequestBase):
22+
tag: str
23+
category: str
24+
startDate :datetime
25+
endDate :datetime
26+
27+
class ResponseNaverSearch(ResponseBase):
28+
category: str
29+
keyword: str
30+
total_keyword: dict[int, str]
31+
32+
33+
#키워드 사다구몰 검증
34+
class RequestSadaguValidate(RequestBase):
35+
tag: str
36+
category: str
37+
38+
class ResponsetSadaguValidate(ResponseBase):
39+
keyword: str
40+
41+
42+
#사다구몰 상품 크롤링
43+
class RequestSadaguCrawl(RequestBase):
44+
tag: str
45+
category: str
46+
47+
class ResponsetSadaguCrawl(ResponseBase):
48+
pass
49+
50+
#블로그 생성
51+
class RequestBlogCreate(RequestBase):
52+
tag: str
53+
category: str
54+
55+
class ResponsetBlogCreate(ResponseBase):
56+
pass
57+
58+
#블로그 배포
59+
class RequestBlogPublish(RequestBase):
60+
tag: str
61+
category: str
62+
63+
class ResponsetBlogPublish(ResponseBase):
64+
pass

apps/pre-processing-service/app/service/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Pydantic 모델을 가져오기 위해 schemas 파일 import
2+
from ..model.schemas import RequestNaverSearch
3+
4+
def keyword_search(request: RequestNaverSearch) -> dict:
5+
"""
6+
네이버 검색 요청을 처리하는 비즈니스 로직입니다.
7+
입력받은 데이터를 기반으로 응답 데이터를 생성하여 딕셔너리로 반환합니다.
8+
"""
9+
10+
response_data = request.model_dump()
11+
12+
response_data["keyword"] = "밥밥밥"
13+
total_keyword = {1: "바밥밥", 2: "밥밥밥", 3: "바밤바"}
14+
response_data["total_keyword"] = total_keyword
15+
16+
return response_data

0 commit comments

Comments
 (0)