FastAPI 라우터 선언 자동화

Dev Smile·2024년 8월 9일
1

FastAPI

목록 보기
1/10
post-thumbnail

FastAPI 애플리케이션을 개발하면서 다양한 모듈에서 라우트를 정의해주고 이를 수동으로 선언해주는 것이 귀찮아서, route 파일을 자동으로 읽어서 선언할 수 있도록 구현해보았습니다.

문제점

기존의 방법은 다음과 같습니다.

# /app/app.py
from fastapi import FastAPI
from app.api.user import Router as user_router
from app.api.product import Router as product_router

app = FastAPI()

app.include_router(user_router, prefix="/user", tags=["User"])
app.include_router(product_router, prefix="/product", tags=["Product"])
...
  1. /app/api 폴더에 router 파일을 만들어서 각 API 엔드포인트를 관리합니다.
  2. /app/app.py에서 위 코드 처럼 각 router 를 import하고 include_router를 사용하여 포함합니다.
  3. 애플리케이션이 확장됨에 따라, 수동으로 import 하고 포함해야 하는 라우트와 라우터의 수가 증가합니다.
  4. 이를 해결하기 위해 우리는 라우터 포함 과정을 자동화할 수 있습니다.

해결책: 라우터 선언 자동화

다음 Python 코드는 FastAPI 애플리케이션에서 모든 라우터와 웹소켓 엔드포인트를 포함하는 과정을 자동화합니다. 스크립트가 어떻게 동작하는지 살펴보겠습니다.

# /app/api/__init__.py
import os
import importlib
from fastapi import APIRouter

def include_routers(app):
    api_router = APIRouter()

    api_dir = os.path.dirname(__file__)
    for root, dirs, files in os.walk(api_dir):
        for file in files:
            if file.endswith(".py") and file != "__init__.py":
                module_path = os.path.relpath(os.path.join(str(root), str(file)), str(api_dir))
                module_path = module_path.replace(os.sep, ".")[:-3]
                module_name = f"app.api.{module_path}"
                module = importlib.import_module(module_name)

                if hasattr(module, "Router"):
                    path = module_path.split(".")
                    if len(path) == 2:
                        tag = module_path.split(".")[1].title()
                    else:
                        # 폴더 안에 있다면, 2번째부터 " " 으로 연결
                        tag = " ".join(module_path.split(".")[1:]).title()

                    api_router.include_router(
                        module.Router,
                        tags=[tag],
                        prefix=f"/{module_path.replace('.', '/')}"
                    )

                if hasattr(module, "websocket_endpoint") and callable(module.websocket_endpoint):
                    websocket_path = f"/{module_path.replace('.', '/')}"
                    app.add_websocket_route(f"{websocket_path}", module.websocket_endpoint)

    app.include_router(api_router, prefix="/api")
# /app/app.py
from fastapi import FastAPI
from include_routers import include_routers

app = FastAPI()

include_routers(app)

# 추가적인 앱 설정

동작 방식

  1. APIRouter 초기화: 먼저 모든 라우트를 포함할 APIRouter 인스턴스를 생성합니다.

  2. 디렉토리 탐색: os.walk를 사용하여 API 모듈이 위치한 디렉토리를 탐색합니다. 이때 __init__.py(본인)를 제외한 모든 Python 파일을 찾습니다.

  3. 모듈 동적 임포트: 찾은 각 Python 파일에 대해 모듈 경로를 구성하고, importlib.import_module을 사용하여 모듈을 동적으로 임포트합니다.

  4. 라우터 포함:

    • 모듈에 Router라는 속성이 있는지 확인합니다(APIRouter의 인스턴스여야 함).
    • Router가 존재하면, 파일 경로를 기준으로 적절한 태그를 결정한 후 해당 라우터를 api_router에 URL 프리픽스와 함께 포함시킵니다.
    • 폴더의 depth에 따라 포함할 수 있도록 합니다.
  5. 웹소켓 엔드포인트 포함: 모듈에 websocket_endpoint 함수가 있고 호출 가능하면, 이를 FastAPI 애플리케이션에 웹소켓 라우트로 추가합니다.

  6. 최종 라우터 포함: 마지막으로, 모든 동적으로 발견된 라우트를 포함한 api_router를 /api 프리픽스와 함께 FastAPI 애플리케이션에 포함시킵니다.

  7. 위 코드를 메인 FastAPI 애플리케이션 파일(app.py)에서 include_routers(app) 함수를 호출

장점

  • 확장성: 프로젝트가 커져도 새로운 라우트를 수동으로 추가할 필요가 없습니다. 새로운 모듈을 생성하고 Router 또는 웹소켓 엔드포인트를 정의하면, 스크립트가 이를 자동으로 포함시킵니다.

  • 유지보수성: 오류 발생 가능성을 줄이고, 모든 것이 동적으로 처리되므로 라우트를 유지보수하기가 더 쉽습니다.

  • 유연성: 이 접근 방식은 프로젝트의 크기나 구조에 관계없이 잘 동작합니다. 모듈을 폴더에 정리해도 스크립트가 이에 맞게 동작합니다.

결론

FastAPI에서 라우터 선언 과정을 자동화하여 app.py의 코드를 깔끔하게 관리하고, 자동으로 호출할 수 있도록 하여, 시간 절약과 유지 보수에 도움이 되었습니다.
이 글이 FastAPI 프로젝트 구조화에 도움이 되었기를 바랍니다. 질문이나 의견이 있다면 언제든 댓글로 남겨주세요!

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN