본문 바로가기
Python/FastAPI

FastAPI 기초

by curious week 2025. 4. 11.

 

1. FastAPI 기본 구조와 핵심 개념

1-1. FastAPI는 어떤 구조인가요?

FastAPI 애플리케이션은 기본적으로 다음과 같은 형태로 구성됩니다:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI"}

여기서 FastAPI()는 애플리케이션 인스턴스를 생성하고, @app.get("/")는 HTTP GET 요청을 처리할 라우트를 정의하는 부분입니다.

1-2. FastAPI의 핵심 기반 기술

  • ASGI: Asynchronous Server Gateway Interface의 약자로, 비동기 처리를 지원하는 Python 서버 표준입니다. FastAPI는 이 구조를 기반으로 작성되어 빠른 응답성과 비동기 요청 처리를 지원합니다.
  • Starlette: FastAPI의 핵심 웹 기능은 Starlette 프레임워크 위에서 작동합니다. 라우팅, 미들웨어, 웹소켓 등은 Starlette에서 제공합니다.
  • Pydantic: 데이터 모델링과 유효성 검사를 담당합니다. 입력 및 출력 데이터를 자동으로 검증하고, 타입에 따라 오류 메시지를 제공합니다.

2. Path, Query, Body, Header, Cookie 파라미터 처리 방법

FastAPI에서는 함수의 매개변수의 타입 힌트에 따라 요청 데이터를 자동으로 추출하고 검사합니다.

2-1. Path Parameter

경로에 포함된 값을 추출합니다.

@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id}
  • item_id는 URL의 경로에서 추출되며 int로 타입이 지정되어 있어, 문자열이 들어오면 자동으로 오류가 발생합니다.

2-2. Query Parameter

URL의 쿼리 문자열에서 데이터를 받습니다.

@app.get("/search")
def search(q: str = "", page: int = 1):
    return {"query": q, "page": page}
  • /search?q=hello&page=2 처럼 요청할 수 있습니다.

2-3. Request Body (Pydantic 모델 활용)

요청 본문(JSON 등)을 처리할 때는 BaseModel을 상속한 클래스를 사용합니다.

from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    tags: list[str] = []

@app.post("/items/")
def create_item(item: Item):
    return item
  • JSON 형식으로 전송된 요청 본문은 Item 클래스로 파싱되어 자동 검증됩니다.
  • 타입이 맞지 않거나 누락된 필드는 자동으로 오류가 반환됩니다.

2-4. Header

HTTP 요청 헤더 값을 추출합니다.

from fastapi import Header

@app.get("/headers")
def read_header(user_agent: str = Header(default=None)):
    return {"User-Agent": user_agent}

2-5. Cookie

쿠키 값을 추출합니다.

from fastapi import Cookie

@app.get("/cookies")
def read_cookie(session_id: str = Cookie(default=None)):
    return {"session_id": session_id}

3. Pydantic 모델과 데이터 검증

3-1. Pydantic 모델의 기본 사용

from pydantic import BaseModel

class User(BaseModel):
    username: str
    email: str
    age: int
  • 사용자가 보내는 JSON 데이터를 이 모델로 자동 검증합니다.
  • username, email, age의 타입이 맞지 않으면 요청 자체가 422 응답으로 거절됩니다.

3-2. 유효성 검증 예시

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., max_length=30)
    price: float = Field(..., ge=0)
  • max_length=30 → 최대 30자 제한
  • ge=0 → 가격은 0 이상이어야 함

FastAPI는 이를 바탕으로 자동 문서화(Swagger UI) 도 생성합니다.


4. 응답 모델과 상태 코드 제어

4-1. 응답 모델 지정

응답 데이터 형식을 제한하고 검증할 수 있습니다.

class ItemOut(BaseModel):
    name: str
    price: float

@app.get("/items/{item_id}", response_model=ItemOut)
def get_item(item_id: int):
    return {"name": "Pen", "price": 1.5, "extra": "ignored"}
  • extra와 같이 모델에 정의되지 않은 필드는 응답에서 제거됩니다.

4-2. 상태 코드 지정

from fastapi import status

@app.post("/users/", status_code=status.HTTP_201_CREATED)
def create_user():
    return {"message": "User created"}
  • 명시적으로 HTTP 상태 코드를 지정할 수 있습니다.

4-3. 커스텀 응답

from fastapi.responses import JSONResponse

@app.get("/custom")
def custom_response():
    return JSONResponse(content={"error": "Forbidden"}, status_code=403)

5. 문서화 자동 제공

  • /docs: Swagger UI (대화형 API 테스트)
  • /redoc: ReDoc UI (정적 문서 스타일)

타입 힌트와 Pydantic 모델을 사용하면 자동으로 문서화가 됩니다. 별도로 OpenAPI 명세를 만들 필요가 없습니다.


 

2. 비동기 처리와 ASGI 구조 깊게 이해하기


1. async def vs def 차이와 비동기 I/O

def: 일반적인 동기 함수

  • 요청이 들어오면 함수 전체가 실행될 때까지 다른 처리를 하지 못하고 대기합니다.
  • 내부에 시간이 오래 걸리는 작업(예: 파일 읽기, DB 요청) 이 있으면 해당 요청이 끝날 때까지 서버 전체가 블로킹될 수 있습니다.
@app.get("/sync")
def sync_read():
    time.sleep(1)  # 전체 블로킹
    return {"message": "동기 응답"}

async def: 비동기 함수

  • 요청이 들어왔을 때 I/O 대기 중인 동안 제어권을 이벤트 루프에 반납합니다.
  • 이 덕분에 한 요청이 처리되는 동안 다른 요청도 병렬적으로 처리할 수 있습니다.
  • await 키워드를 통해 비동기 함수 호출을 처리합니다.
import asyncio

@app.get("/async")
async def async_read():
    await asyncio.sleep(1)  # I/O 대기 중 다른 요청 처리 가능
    return {"message": "비동기 응답"}

요약

  • CPU 바운드 작업: def + 별도 병렬 처리(threading, multiprocessing)
  • I/O 바운드 작업: async def + await 구조가 훨씬 유리

2. ASGI 서버: uvicorn과 hypercorn

FastAPI는 ASGI(Application Server Gateway Interface) 기반이기 때문에
ASGI를 지원하는 서버가 필요합니다. 대표적으로 두 가지가 있습니다.

2-1. uvicorn

  • 가장 널리 쓰이는 ASGI 서버
  • FastAPI 공식 문서에서도 기본으로 사용
  • Starlette 및 asyncio에 최적화되어 있음
uvicorn app:app --reload --host 0.0.0.0 --port 8000

2-2. hypercorn

  • ASGI 및 HTTP/2, QUIC, WebSocket 등 고급 기능 지원
  • quart, FastAPI, sanic 등과 함께 사용 가능
hypercorn app:app

둘 다 Python의 asyncio 기반으로 작동하며, FastAPI의 비동기 처리를 가능하게 하는 핵심입니다.


3. FastAPI의 비동기 Request Lifecycle

FastAPI는 요청 하나를 다음과 같은 단계로 처리합니다:

  1. ASGI 서버(uvicorn) 가 클라이언트 요청을 수신
  2. 요청은 Starlette 기반의 FastAPI 라우터로 전달됨
  3. 라우트 함수(보통 async def) 가 매칭되고 실행
  4. 내부에서 await가 발생하면, 이벤트 루프가 다른 요청을 처리
  5. 응답이 완성되면 JSON 직렬화되어 클라이언트에 반환

이때 중요한 점은, FastAPI는 내부적으로 Starlette의 BaseHTTPMiddleware와 같은 미들웨어 체인을 통해 요청을 처리한다는 점입니다. 즉,
요청이 도착한 순간부터 응답이 반환되기까지의 전 과정이 async 처리를 기반으로 흐릅니다.


4. 주요 비동기 라이브러리 연동 방법

FastAPI의 async def 라우터에서는 반드시 비동기 I/O 라이브러리를 사용해야 진짜 비동기 처리의 효과를 볼 수 있습니다.

4-1. httpx: 비동기 HTTP 클라이언트

pip install httpx
import httpx

@app.get("/external")
async def call_api():
    async with httpx.AsyncClient() as client:
        response = await client.get("https://httpbin.org/get")
        return response.json()
  • requests와 유사하지만, 비동기 대응이 가능함 (async with, await)

4-2. aiofiles: 비동기 파일 처리

pip install aiofiles
import aiofiles

@app.get("/read-file")
async def read_file():
    async with aiofiles.open("example.txt", mode="r") as f:
        contents = await f.read()
    return {"content": contents}
  • 일반적인 open()은 블로킹이기 때문에 aiofiles를 사용하는 것이 좋습니다.

4-3. asyncpg: PostgreSQL 비동기 클라이언트

pip install asyncpg
import asyncpg

@app.on_event("startup")
async def connect_to_db():
    app.state.db = await asyncpg.connect(user="user", password="pw", database="db", host="localhost")

@app.get("/users")
async def get_users():
    rows = await app.state.db.fetch("SELECT * FROM users")
    return [dict(row) for row in rows]
  • SQLAlchemy의 async 버전도 사용 가능하지만, asyncpg는 속도 면에서 가장 빠른 PostgreSQL 클라이언트 중 하나입니다.

마무리 요약

  • async def는 비동기 요청 처리에 필수이며, 내부에서 await할 수 있는 I/O 비동기 라이브러리를 함께 사용해야 합니다.
  • FastAPI는 ASGI 기반이며, uvicorn 또는 hypercorn이 서버 역할을 맡습니다.
  • httpx, aiofiles, asyncpg 같은 라이브러리를 적절히 사용하면 비동기 서버의 성능을 100% 활용할 수 있습니다.

 

3. 의존성 주입 시스템 (Depends)


1. Depends 기본 사용법

FastAPI에서 의존성은 Depends 함수를 통해 주입되며, 라우트 함수의 인자로 선언됩니다.

예시: 단순 의존성

from fastapi import Depends, FastAPI

app = FastAPI()

def get_token():
    return "SECRET_TOKEN"

@app.get("/secure-data")
def read_data(token: str = Depends(get_token)):
    return {"token": token}
  • 위의 예시는 get_token() 함수를 호출한 결과를 token 인자로 전달합니다.
  • FastAPI는 요청마다 get_token()을 실행해서 반환 값을 자동으로 넣어줍니다.

2. 상태 객체, 서비스 객체 주입하기

일반적으로 외부 자원(DB 연결, 설정 객체, 서비스 인스턴스 등)은 Depends를 통해 재사용 가능한 형태로 전달합니다.

예시: 상태 객체

class Settings:
    def __init__(self):
        self.api_key = "my-secret"

settings = Settings()

def get_settings():
    return settings

@app.get("/config")
def read_config(cfg: Settings = Depends(get_settings)):
    return {"api_key": cfg.api_key}

이런 식으로 의존성 주입을 하면 테스트 시에는 get_settings()를 mock 처리하여 테스트하기도 쉽습니다.


3. 클래스 기반 의존성 관리 (__call__ 사용)

Depends로 사용할 객체는 함수 뿐만 아니라 클래스도 가능합니다.
특히 클래스에 __call__() 메서드를 정의하면, 해당 클래스 인스턴스 자체를 Depends처럼 쓸 수 있습니다.

예시: 인증 처리 클래스

from fastapi import Request

class AuthService:
    def __init__(self, required_token: str):
        self.required_token = required_token

    async def __call__(self, request: Request):
        token = request.headers.get("Authorization")
        if token != self.required_token:
            raise Exception("Invalid token")
        return True

auth_check = AuthService(required_token="SECRET123")

@app.get("/secure", dependencies=[Depends(auth_check)])
async def secure_data():
    return {"msg": "Access Granted"}

이 방식의 장점은 다음과 같습니다:

  • 상태 (__init__)와 처리 로직 (__call__)을 분리 가능
  • Depends() 안에 직접 클래스를 넣을 수 있음
  • 테스트/재사용 편리

4. 공통 헤더, DB 세션, 인증 처리 공통화

실무에서는 다음 항목들을 공통 의존성으로 분리하여 사용합니다.

4-1. 공통 헤더

from fastapi import Header

async def get_user_agent(user_agent: str = Header(default="Unknown")):
    return user_agent

@app.get("/device")
async def device_info(agent: str = Depends(get_user_agent)):
    return {"User-Agent": agent}

4-2. DB 세션 (SQLAlchemy 예시)

from sqlalchemy.orm import Session

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users")
def read_users(db: Session = Depends(get_db)):
    return db.query(User).all()
  • yield를 통해 의존성 컨텍스트를 제공하면서 리소스 정리를 처리합니다.
  • FastAPI는 내부적으로 contextlib.contextmanager처럼 관리해줍니다.

4-3. 인증 처리 (JWT 토큰 검사 등)

from fastapi import Request, HTTPException

async def verify_token(request: Request):
    token = request.headers.get("Authorization")
    if not token or token != "VALID":
        raise HTTPException(status_code=401, detail="Unauthorized")
    return token

@app.get("/private")
async def private_data(token: str = Depends(verify_token)):
    return {"auth_token": token}

정리: Depends의 강력한 특징

  1. 자동 실행
    FastAPI는 요청 처리 시점에 의존성을 자동 실행해서 라우터 함수에 전달합니다.
  2. 상태 보존 또는 공유 가능
    객체를 통해 설정, DB 등 재사용 가능
  3. 클래스형 서비스 구조
    __call__로 라우터 바깥에서 의존성 인스턴스를 만들어 놓고 주입 가능
  4. 테스트 유리함
    테스트할 때 dependency_overrides를 통해 주입 함수/객체를 바꿀 수 있습니다.

 

4. 라우팅 구조와 모듈화, 구조 설계


1. APIRouter로 라우팅 분리하기

FastAPI는 APIRouter를 통해 라우터를 모듈 단위로 분리할 수 있습니다.

예시: routers/user.py

from fastapi import APIRouter

router = APIRouter(prefix="/users", tags=["users"])

@router.get("/")
def get_users():
    return [{"id": 1, "name": "Alice"}]

@router.post("/")
def create_user():
    return {"id": 2, "name": "Bob"}

메인 앱에서 등록

from fastapi import FastAPI
from routers import user

app = FastAPI()
app.include_router(user.router)

이렇게 하면 모든 /users/* 라우트는 routers/user.py에 정의된 내용을 기반으로 작동합니다.


2. 폴더 구조 예시

app/
├── main.py                 ← FastAPI 진입점
├── routers/                ← 각 라우팅 모듈
│   └── user.py
├── schemas/                ← Pydantic 모델 (입출력)
│   └── user.py
├── services/               ← 비즈니스 로직 계층
│   └── user_service.py
├── repositories/           ← DB 접근 (ORM, SQL 처리)
│   └── user_repository.py
├── core/                   ← 설정, 예외 처리, DI 설정 등
│   ├── config.py
│   └── security.py
  • routers/: APIRouter를 정의
  • schemas/: 요청/응답용 데이터 모델
  • services/: 비즈니스 로직 (도메인 로직)
  • repositories/: DB 접근 계층
  • core/: 공통 기능(설정, 보안, 미들웨어, 예외 등)

3. 비즈니스 로직과 라우팅 분리 (Service Layer)

라우팅은 최대한 얇게 유지하고, 핵심 로직은 services에 분리합니다.

services/user_service.py

from schemas.user import UserCreate
from repositories.user_repository import UserRepository

class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    def create_user(self, user: UserCreate):
        return self.repo.insert(user)

    def list_users(self):
        return self.repo.find_all()

routers/user.py

from fastapi import APIRouter, Depends
from schemas.user import UserCreate
from services.user_service import UserService
from repositories.user_repository import get_user_repository

router = APIRouter()

@router.get("/")
def get_users(service: UserService = Depends()):
    return service.list_users()

@router.post("/")
def create_user(user: UserCreate, service: UserService = Depends()):
    return service.create_user(user)

repositories/user_repository.py

class UserRepository:
    def insert(self, user):
        # DB 저장 로직
        ...

    def find_all(self):
        # DB 조회 로직
        ...

def get_user_repository():
    return UserRepository()

4. DI 기반 구조 설계 (Container, Provider 사용 optional)

FastAPI는 기본적으로 Depends()로 DI를 제공하지만, 더 복잡한 구조가 필요하면 다음을 고려할 수 있습니다.

1) get_service() 방식

서비스와 저장소를 의존성으로 구성:

def get_user_service():
    repo = get_user_repository()
    return UserService(repo)

라우터에서는 다음처럼 사용:

@router.get("/", response_model=list[UserOut])
def get_all(service: UserService = Depends(get_user_service)):
    return service.list_users()

2) 외부 DI 라이브러리 예시 (선택사항)

punq, python-dependency-injector 같은 라이브러리를 사용하면 DI 컨테이너를 명시적으로 만들 수 있습니다.

예시 (python-dependency-injector):

from dependency_injector import containers, providers

class Container(containers.DeclarativeContainer):
    user_repo = providers.Factory(UserRepository)
    user_service = providers.Factory(UserService, repo=user_repo)

FastAPI에 의존성 주입:

container = Container()

@app.get("/users")
def read_users(service: UserService = Depends(container.user_service.provider)):
    return service.list_users()

주의: FastAPI 자체 Depends()가 강력하기 때문에, 외부 DI는 규모가 매우 크지 않으면 생략해도 됩니다.


요약 흐름 정리

  1. APIRouter로 라우팅을 파일 단위로 모듈화
  2. schemas, services, repositories 폴더로 책임을 명확히 분리
  3. 라우터는 서비스 레이어를 통해 도메인 로직에 접근
  4. Depends를 통해 의존성 안전하게 전달
  5. 필요한 경우 DI 컨테이너로 의존성 명시적으로 구성 가능

 

5. Pydantic 고급 활용


1. 모델 상속 (Model Inheritance)

여러 Pydantic 모델에서 공통 필드를 상속받아 재사용할 수 있습니다.

from pydantic import BaseModel

class UserBase(BaseModel):
    username: str
    email: str

class UserCreate(UserBase):
    password: str

class UserOut(UserBase):
    id: int
  • UserCreate: 입력용 → password 포함
  • UserOut: 출력용 → id 포함, password 제외
  • 공통 필드는 UserBase로 추출하여 중복 제거

2. 중첩 구조 (Nested Models)

모델 안에 또 다른 모델을 필드로 가질 수 있습니다.

class Address(BaseModel):
    city: str
    zipcode: str

class User(BaseModel):
    username: str
    address: Address

JSON 입력 예:

{
  "username": "alice",
  "address": {
    "city": "Seoul",
    "zipcode": "12345"
  }
}

FastAPI는 이 중첩 모델을 자동으로 파싱해줍니다.


3. Field를 통한 제약 조건과 메타 정보 지정

from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(..., max_length=50, description="상품명")
    price: float = Field(..., ge=0, le=10000)
  • ... : 필수 값 (값 생략 시 오류 발생)
  • max_length, ge, le : 유효성 제약
  • description : 문서화용 설명

이러한 제약은 자동으로 Swagger UI 문서화에도 반영됩니다.


4. @validator: 필드 단위 유효성 검사

단일 필드 또는 서로 관련된 필드에 대한 검증을 정의할 수 있습니다.

from pydantic import BaseModel, validator

class User(BaseModel):
    username: str
    password: str

    @validator("username")
    def username_no_space(cls, v):
        if " " in v:
            raise ValueError("공백이 들어갈 수 없습니다.")
        return v

    @validator("password")
    def password_min_length(cls, v):
        if len(v) < 8:
            raise ValueError("비밀번호는 최소 8자 이상이어야 합니다.")
        return v
  • 여러 validator는 순서대로 실행됩니다.
  • 예외 발생 시 FastAPI는 422 Unprocessable Entity로 에러 응답을 반환합니다.

5. @root_validator: 모델 전체에 대한 유효성 검사

여러 필드 간의 관계를 검증할 때 사용합니다.

from pydantic import BaseModel, root_validator

class Register(BaseModel):
    password: str
    password_confirm: str

    @root_validator
    def check_password_match(cls, values):
        pw = values.get("password")
        confirm = values.get("password_confirm")
        if pw != confirm:
            raise ValueError("비밀번호가 일치하지 않습니다.")
        return values
  • values는 모든 필드의 값을 포함하는 딕셔너리입니다.
  • 반환 시 반드시 전체 values를 반환해야 합니다.

6. orm_mode: ORM ↔︎ Pydantic 변환

SQLAlchemy 등 ORM 객체를 Pydantic 모델로 변환하려면 orm_mode = True 설정이 필요합니다.

예시: SQLAlchemy 모델 → Pydantic 모델

class UserOut(BaseModel):
    id: int
    username: str

    class Config:
        orm_mode = True

SQLAlchemy 모델에서 다음처럼 사용할 수 있습니다:

db_user = db.query(User).first()
return UserOut.from_orm(db_user)
  • .from_orm() 메서드는 orm_mode = True 설정이 없으면 작동하지 않습니다.

7. 커스텀 타입과 유효성 검사 확장

UUID, EmailStr, HttpUrl 등 내장 타입 사용

from pydantic import BaseModel, EmailStr

class User(BaseModel):
    email: EmailStr

정규식 검증

from pydantic import constr

class User(BaseModel):
    nickname: constr(regex="^[a-zA-Z0-9_]{3,20}$")
  • 3자 이상 20자 이하, 영문/숫자/밑줄만 허용

마무리 요약

  • Pydantic은 단순 JSON → Python 객체를 넘어서 타입 기반 검증, 문서화, ORM 연동까지 제공합니다.
  • 모델을 세분화하고 유효성 검사를 설계하면, API의 신뢰성, 보안성, 디버깅 효율성이 크게 향상됩니다.
  • 중첩 모델, 상속 구조, Field, @validator, @root_validator, orm_mode는 대규모 프로젝트에서 필수적인 기능입니다.

 

6. DB 연동과 ORM 사용


1. ORM 선택지

ORM 비동기 지원 특징

SQLAlchemy O (2.0부터 native async 지원) 가장 널리 쓰이며 자유도가 높고 풍부한 기능 제공
Tortoise ORM O Django 스타일, 완전한 async-first
GINO O Sanic 기반, SQLAlchemy Core 스타일
Prisma 제한적 Rust 기반 클라이언트, Python 대응은 미완성 수준 (2025 기준)

대부분의 FastAPI 프로젝트는 SQLAlchemy 2.0 (async) 또는 Tortoise ORM을 사용합니다.
이번 설명은 SQLAlchemy 2.0 async 기반으로 진행하고, 필요 시 Tortoise 예제도 비교 설명해드릴 수 있습니다.


2. SQLAlchemy 2.0 (Async ORM) 구성 흐름

설치

pip install sqlalchemy aiosqlite asyncpg psycopg2

(PostgreSQL: asyncpg, SQLite: aiosqlite)


기본 구조 (예: PostgreSQL)

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost:5432/dbname"

engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

class Base(DeclarativeBase):
    pass

모델 정의

from sqlalchemy import Integer, String
from sqlalchemy.orm import Mapped, mapped_column

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    username: Mapped[str] = mapped_column(String, unique=True, nullable=False)

세션 의존성 (FastAPI Depends 연동)

from fastapi import Depends

async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

라우터에서 사용:

from sqlalchemy import select

@app.get("/users")
async def get_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(User))
    return result.scalars().all()

트랜잭션 처리

async with AsyncSessionLocal.begin() as session:
    session.add(user1)
    session.add(user2)

명시적으로 commit, rollback 할 수도 있습니다.


3. 리포지토리 패턴 적용

예시: repositories/user_repository.py

from sqlalchemy import select
from models import User

class UserRepository:
    def __init__(self, session):
        self.session = session

    async def find_all(self):
        result = await self.session.execute(select(User))
        return result.scalars().all()

    async def insert(self, user: User):
        self.session.add(user)
        await self.session.commit()
        await self.session.refresh(user)
        return user

→ 라우터에서는 Depends(get_user_repository) 로 주입하여 사용합니다.
→ 서비스 레이어에서 리포지토리 사용 시 비즈니스 로직과 DB 로직을 분리할 수 있어 유지보수에 매우 유리합니다.


4. Alembic을 이용한 마이그레이션 관리

Alembic은 SQLAlchemy 기반 마이그레이션 도구입니다.

설치

pip install alembic

초기화

alembic init alembic

alembic/env.py에서 다음 설정 수정:

from yourapp.db import Base
target_metadata = Base.metadata

# DB URL 동적으로 가져오기
import os
config.set_main_option("sqlalchemy.url", os.getenv("DATABASE_URL"))

마이그레이션 생성 및 적용

alembic revision --autogenerate -m "create users table"
alembic upgrade head

→ 모델 변경 시 위 과정을 반복하면 DB 스키마가 자동 관리됩니다.


5. Tortoise ORM 간단 비교 예시

설치 및 설정

pip install tortoise-orm
TORTOISE_ORM = {
    "connections": {"default": "sqlite://db.sqlite3"},
    "apps": {
        "models": {
            "models": ["models.user", "aerich.models"],
            "default_connection": "default"
        }
    },
}

모델 정의:

from tortoise.models import Model
from tortoise import fields

class User(Model):
    id = fields.IntField(pk=True)
    username = fields.CharField(max_length=30, unique=True)

라우터에서:

@app.get("/users")
async def list_users():
    return await User.all()

6. 커넥션 풀, 성능 팁

  • create_async_engine(pool_size=10, max_overflow=20) 등으로 조절 가능
  • 비동기 ORM에서는 await 누락 시 작동 안 하므로 항상 await 사용 확인
  • expire_on_commit=False 설정으로 commit 이후 객체 참조 유지

마무리 요약

  1. SQLAlchemy 2.0 async은 FastAPI와 완벽하게 호환되며, ORM 기능 + 비동기 성능을 함께 누릴 수 있음
  2. 세션은 Depends(get_db) 방식으로 라우터에 안전하게 주입
  3. 리포지토리 패턴을 적용하면 DB와 도메인 로직이 분리되어 테스트성과 유지보수가 뛰어남
  4. Alembic을 사용하면 모델 구조가 변경되더라도 마이그레이션을 안정적으로 관리 가능
  5. 규모가 작거나 Django 스타일이 익숙하면 Tortoise ORM도 고려 가능

 

7. 보안 및 인증/인가 시스템 구성


1. OAuth2 + JWT 인증 흐름 구현

FastAPI는 OAuth2 password flow를 OAuth2PasswordBearer로 쉽게 구성할 수 있습니다.
우선 JWT 기반 로그인 인증을 직접 구현하는 구조부터 설명합니다.

1-1. JWT 토큰 발급 로직

pip install python-jose[cryptography] passlib[bcrypt]
from jose import jwt
from datetime import datetime, timedelta

SECRET_KEY = "secret"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

1-2. 로그인 API

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordRequestForm

@app.post("/login")
async def login(form: OAuth2PasswordRequestForm = Depends()):
    user = authenticate_user(form.username, form.password)
    if not user:
        raise HTTPException(status_code=401, detail="Invalid credentials")
    token = create_access_token({"sub": user.username})
    return {"access_token": token, "token_type": "bearer"}

2. Depends + Security 조합으로 인증 필터링

2-1. 인증 필터

from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from pydantic import BaseModel

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")

class TokenData(BaseModel):
    sub: str

async def get_current_user(token: str = Depends(oauth2_scheme)):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        if not username:
            raise Exception()
        return {"username": username}
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

2-2. 보호된 라우터

@app.get("/me")
async def read_me(current_user: dict = Depends(get_current_user)):
    return {"user": current_user}

3. 사용자 권한 부여 (scopes)

JWT에 포함된 권한을 기반으로 엔드포인트 접근을 제어할 수 있습니다.

def create_access_token(data: dict, scopes: list[str]):
    to_encode = data.copy()
    to_encode.update({"scopes": scopes})
    ...
def get_current_user_with_scope(token: str = Depends(oauth2_scheme)):
    payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
    if "admin" not in payload.get("scopes", []):
        raise HTTPException(status_code=403, detail="권한이 없습니다")
    return {"username": payload["sub"]}

4. 패스워드 해싱 (bcrypt, argon2)

passlib을 사용한 bcrypt

from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain, hashed):
    return pwd_context.verify(plain, hashed)

def hash_password(password):
    return pwd_context.hash(password)
  • 회원가입 시 hash_password()로 저장
  • 로그인 시 verify_password()로 검증

argon2도 사용 가능하며, 더 보안성이 높습니다: passlib[argon2] 설치 후 schemes=["argon2"]


5. CORS 설정

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://yourdomain.com"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
  • allow_origins=["*"]은 개발 중에는 가능하지만 배포 시 반드시 제한해야 합니다.

6. Rate Limiting (속도 제한)

내장 기능은 없지만, 다음 중 하나를 사용할 수 있습니다.

  • slowapi: Starlette 기반 rate limiter
  • redis + custom middleware: 요청 수 제한 구현
pip install slowapi
from slowapi import Limiter
from slowapi.util import get_remote_address
from fastapi import Request

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.get("/limited")
@limiter.limit("5/minute")
def limited_route(request: Request):
    return {"message": "OK"}

7. 보안 헤더 설정 (HTTPS 권장)

FastAPI는 기본적으로 Starlette 위에서 작동하므로, SecureMiddleware 또는 nginx 설정을 활용해 보안 헤더를 설정합니다.

예: 미들웨어로 헤더 추가

from starlette.middleware.base import BaseHTTPMiddleware

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-Content-Type-Options"] = "nosniff"
        return response

app.add_middleware(SecurityHeadersMiddleware)

보안 기능 정리

  • JWT를 사용하여 인증 상태 유지 (OAuth2PasswordBearer + Depends)
  • Depends 구조로 필터링, 권한 체크 (scopes)
  • passlib을 통한 안전한 비밀번호 해싱
  • CORS 제한으로 외부 요청 보호
  • Rate Limiting을 통해 공격 방지
  • 보안 헤더 적용 및 HTTPS 기반 배포 필요

 

8. 예외 처리 & 로깅 시스템


1. 커스텀 예외 클래스 작성

FastAPI에서는 기본적으로 HTTPException을 사용하지만, 도메인별 예외 상황을 명확히 구분하고 싶을 때는 직접 예외 클래스를 정의할 수 있습니다.

예시: 사용자 인증 실패 예외

from fastapi import HTTPException, status

class UnauthorizedException(HTTPException):
    def __init__(self, detail="인증 정보가 유효하지 않습니다"):
        super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)

사용 예시

@app.get("/secure")
def secure_route():
    raise UnauthorizedException()
  • 명확한 도메인별 예외명을 통해 코드 가독성과 유지보수성이 높아집니다.

2. 전역 예외 핸들러 등록 (app.exception_handler)

FastAPI는 예외 타입별로 전역 예외 처리기를 등록할 수 있습니다.

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(UnauthorizedException)
async def handle_unauthorized(request: Request, exc: UnauthorizedException):
    return JSONResponse(
        status_code=exc.status_code,
        content={"success": False, "error": {"message": exc.detail}},
    )
  • Request 객체를 통해 로그 기록, 요청자 정보 분석 등도 가능
  • 커스텀 예외 외에도 RequestValidationError, HTTPException, Exception 등을 처리할 수 있습니다

3. 로깅 설정

운영 환경에서는 Python 기본 로깅보다 구조화된 로깅 도구가 훨씬 유용합니다. 대표적으로 다음이 많이 사용됩니다:

3-1. Loguru

pip install loguru
from loguru import logger

logger.add("logs/app.log", rotation="1 MB", retention="10 days", level="INFO")

logger.info("정상 처리 완료")
logger.error("예외 발생", exc_info=True)
  • 기본 print() 또는 logging보다 직관적이고 유연함
  • 로그 회전, 파일 분리, 로그 수준 제어 용이

3-2. Structlog (선택)

pip install structlog
  • JSON 기반 로깅에 유리 (ELK, CloudWatch 연동)
  • 체계적인 context, 구조화된 이벤트 로깅에 적합

4. 응답 표준화 구조 설계

실서비스에서는 API 응답을 표준화하면 프론트엔드 처리와 디버깅에 매우 유리합니다.

예시: 응답 스키마 정의

from pydantic import BaseModel

class ApiResponse(BaseModel):
    success: bool
    data: dict | list | None = None
    error: dict | None = None

사용 예시

@app.get("/users", response_model=ApiResponse)
def get_users():
    return ApiResponse(success=True, data={"users": [...]})

에러 응답도 동일 구조 유지

@app.exception_handler(UnauthorizedException)
async def handle_auth_error(request: Request, exc: UnauthorizedException):
    return JSONResponse(
        status_code=exc.status_code,
        content=ApiResponse(success=False, error={"message": exc.detail}).dict()
    )

실전 예외 분류 기준 (실제 많이 쓰는 유형)

  • BadRequestException: 입력값 오류
  • UnauthorizedException: 로그인/토큰 문제
  • ForbiddenException: 권한 부족
  • NotFoundException: 리소스 없음
  • ConflictException: 중복/충돌 상황
  • InternalServerErrorException: 시스템 오류

이런 식으로 분리하면 에러 핸들링, API 문서화, 모니터링에 이점이 많습니다.


종합 예외 핸들링 흐름

  1. 커스텀 예외 정의 (각 도메인별)
  2. 전역 예외 핸들러 등록
  3. ApiResponse 형태로 응답 일관성 유지
  4. 로깅 도구로 내부 에러 추적 및 기록
  5. 필요시 Slack, Sentry, Cloud 로그 연동 가능

예외 처리 확장 방향

  • RequestValidationError (Pydantic 자동 유효성 검사) 커스터마이징
  • Unhandled Exception 처리로 500 에러 일괄 로깅
  • 테스트 코드에서 커스텀 예외 발생 여부 확인
  • 클라이언트 전용 메시지와 서버 전용 디버그 메시지 분리 (detail, log)

 

9. 테스트 코드 작성 (pytest 기반)


1. 기본 구조와 TestClient 사용

FastAPI는 starlette.testclient.TestClient를 기반으로 동기 환경에서 전체 라우트를 테스트할 수 있습니다.

예시: test_main.py

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello, FastAPI"}
  • TestClient는 실제 서버 없이 요청을 가상으로 보내 응답을 검증합니다.
  • 실제 요청과 거의 동일한 방식으로 테스트 가능

2. 의존성 오버라이드로 DB mock 처리

실제 DB와 연결하지 않고, 테스트용 DB나 mock을 사용하려면 app.dependency_overrides를 사용합니다.

예시: 가짜 DB 주입

from fastapi import Depends
from app.dependencies import get_db
from app.main import app

class FakeDB:
    def query(self):
        return ["mocked data"]

def override_get_db():
    return FakeDB()

app.dependency_overrides[get_db] = override_get_db

def test_with_mock_db():
    client = TestClient(app)
    res = client.get("/data")
    assert res.status_code == 200
    assert "mocked data" in res.text
  • 실제 DB 접근 없이, 테스트용 mock 객체를 주입 가능
  • 테스트 단위별로 오버라이드 관리 가능

3. 인증 토큰 포함 테스트

JWT 인증이 포함된 API를 테스트할 경우, 테스트 전용 토큰을 생성하거나, 인증 의존성을 오버라이드합니다.

예시 1: 실제 JWT 생성 후 헤더에 포함

from app.auth import create_access_token

def test_authenticated_request():
    token = create_access_token({"sub": "testuser"})
    headers = {"Authorization": f"Bearer {token}"}
    res = client.get("/me", headers=headers)
    assert res.status_code == 200

예시 2: 인증 의존성을 테스트 전용 사용자로 오버라이드

from app.dependencies import get_current_user

def fake_user():
    return {"username": "testuser", "role": "admin"}

app.dependency_overrides[get_current_user] = fake_user
  • 인증이 복잡할 경우 의존성 오버라이드를 사용하는 것이 관리가 쉬움

4. 비동기 테스트 구성 (pytest-asyncio)

FastAPI는 비동기 함수(async def)를 테스트할 수 있도록 pytest-asyncio를 사용할 수 있습니다.

설치:

pip install pytest pytest-asyncio

예시:

import pytest
from httpx import AsyncClient
from app.main import app

@pytest.mark.asyncio
async def test_async_route():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        response = await ac.get("/async-endpoint")
    assert response.status_code == 200
  • AsyncClient는 실제 비동기 요청처럼 동작
  • pytest.mark.asyncio 데코레이터로 비동기 함수 테스트 가능

5. 테스트 커버리지 확대 방향

  • 입력 유효성 테스트 (Pydantic 검증 포함)
  • DB CRUD 테스트 (트랜잭션 롤백 처리 포함)
  • 실패 케이스 테스트 (401, 403, 422, 500)
  • 응답 모델 필드 검증 (예상 schema 형태 확인)
  • 커스텀 예외 처리 확인 (raise → JSON 응답 검증)

6. 테스트 전용 설정 (DB, 환경변수 분리)

  • .env.test 파일로 DB URL, API 키 등을 테스트용으로 분리
  • pytest 설정 파일(pytest.ini)에서 경로 및 옵션 제어
  • 테스트 전용 SQLite 사용 시 in-memory(sqlite:///:memory:) 설정 활용

마무리

  • FastAPI는 테스트 용이성이 매우 높은 구조를 가지고 있으며, TestClient를 통해 실제와 유사한 방식으로 테스트 가능
  • 의존성 주입 구조를 그대로 활용하여 테스트용 객체를 주입할 수 있음
  • 인증, DB, 비동기 요청까지 통합적으로 테스트할 수 있으며, pytest 기반 생태계와 잘 통합됨

 

10. 성능 최적화와 캐싱 전략


1. Redis 캐싱 (fastapi-cache2, aioredis)

목적

  • 동일 요청에 대해 중복 연산 없이 응답을 반환
  • 빠른 결과 응답을 통해 부하 감소

설치

pip install fastapi-cache2 aioredis

기본 사용 예시

from fastapi import FastAPI
from fastapi_cache2 import FastAPICache
from fastapi_cache2.backends.redis import RedisBackend
from fastapi_cache2.decorator import cache
import aioredis

app = FastAPI()

@app.on_event("startup")
async def startup():
    redis = aioredis.from_url("redis://localhost", encoding="utf-8", decode_responses=True)
    FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")

@app.get("/expensive-data")
@cache(expire=60)  # 60초 동안 캐싱
async def get_data():
    # 시간 오래 걸리는 작업
    return {"result": "expensive computation"}
  • 요청 결과가 60초 동안 Redis에 저장되며, 같은 요청이 오면 캐시된 결과 반환
  • 키는 URL + 쿼리 파라미터 기준으로 자동 생성됨

2. 쿼리 최적화 (ORM vs Raw SQL)

ORM은 생산성은 높지만 성능 이슈가 생길 수 있으므로 적절한 쿼리 전략이 필요합니다.

주의할 점

  • N+1 문제: ORM이 연관 객체를 반복해서 쿼리
  • .join(), .options(selectinload(...)) 등으로 해결
  • 복잡한 조건일 경우 Raw SQL 사용 고려

예시: SQLAlchemy ORM vs Raw SQL

# ORM 방식
result = await db.execute(select(User).where(User.email == email))
user = result.scalar_one_or_none()

# Raw SQL 방식
result = await db.execute(text("SELECT * FROM users WHERE email = :email"), {"email": email})
user = result.fetchone()
  • Read-heavy 상황에서는 ORM을 제한하고 Raw SQL이나 View 활용을 고려
  • 쿼리 캐싱(Redis), 인덱싱, LIMIT, 정규화 구조 개선도 포함됨

3. FastAPI 미들웨어로 로깅/트래픽 추적

미들웨어는 요청과 응답 사이에서 성능 측정, 트래픽 분석 등을 수행할 수 있습니다.

예시: 요청/응답 시간 측정 미들웨어

from starlette.middleware.base import BaseHTTPMiddleware
import time
import logging

logger = logging.getLogger(__name__)

class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        start = time.time()
        response = await call_next(request)
        duration = round(time.time() - start, 4)
        logger.info(f"{request.method} {request.url.path} completed in {duration}s")
        return response

app.add_middleware(LoggingMiddleware)
  • 요청 URL, 처리 시간 등을 로깅하여 트래픽 모니터링 가능
  • IP, User-Agent, Header 분석도 가능

4. Response Streaming (yield, background tasks)

FastAPI는 큰 데이터를 조각(chunk) 으로 보내거나, 처리 중에 백그라운드 작업을 분리할 수 있는 기능도 제공합니다.

4-1. yield를 활용한 스트리밍

from fastapi.responses import StreamingResponse

@app.get("/stream")
def stream_text():
    def generate():
        for i in range(10):
            yield f"line {i}\n"
            time.sleep(1)
    return StreamingResponse(generate(), media_type="text/plain")
  • 클라이언트는 한 줄씩 실시간으로 받아볼 수 있음
  • 영상, 파일, 로그 출력 등에 사용 가능

4-2. 백그라운드 작업

from fastapi import BackgroundTasks

def write_log(data: str):
    with open("log.txt", "a") as f:
        f.write(data + "\n")

@app.post("/submit")
def submit_task(background_tasks: BackgroundTasks):
    background_tasks.add_task(write_log, "사용자 제출 완료")
    return {"msg": "작업 처리 중"}
  • 사용자는 응답을 빠르게 받고, 실제 작업은 비동기 처리됨
  • 이메일 전송, 로그 저장, 외부 API 연동 등에 활용

성능 최적화 정리 요약

  1. 캐싱: Redis + fastapi-cache2를 통해 I/O 반복을 줄임
  2. 쿼리 최적화: ORM 사용 시 join 최적화 또는 Raw SQL로 전환
  3. 미들웨어: 응답 시간, 요청 기록 등을 전역으로 추적
  4. 스트리밍: 대용량 응답이나 장시간 작업을 실시간 분리 처리

 

11. 배포와 운영 자동화


1. Gunicorn + Uvicorn + Nginx 조합

목적

  • Gunicorn: WSGI/ASGI 앱을 여러 워커로 실행 (프로세스 관리)
  • Uvicorn: FastAPI 실행용 ASGI 서버 (싱글 프로세스)
  • Nginx: 리버스 프록시 + SSL 처리

실행 명령 예시

gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000
  • -k: uvicorn worker를 사용
  • -w: 워커 수 (CPU 코어 수 기반으로 설정)
  • Nginx에서는 외부 포트를 열고 내부에서 Gunicorn에 프록시로 연결합니다.

Nginx 설정 예시

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass         http://localhost:8000;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
    }
}

2. Dockerfile 작성, Docker Compose

Dockerfile 예시

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["gunicorn", "app.main:app", "-k", "uvicorn.workers.UvicornWorker", "-w", "4", "-b", "0.0.0.0:8000"]

docker-compose.yml 예시

version: "3.8"
services:
  web:
    build: .
    ports:
      - "80:8000"
    depends_on:
      - redis
    environment:
      - ENV=production
  redis:
    image: redis:7
  • depends_on으로 Redis, DB 등 의존 서비스 명시
  • volume, env, network 등을 통해 확장 가능

3. CI/CD (GitHub Actions, GitLab CI)

GitHub Actions 예시 .github/workflows/deploy.yml

name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: 3.11

      - name: Install dependencies
        run: |
          pip install -r requirements.txt

      - name: Run Tests
        run: |
          pytest

      - name: Deploy to server
        uses: appleboy/ssh-action@v0.1.6
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /home/app && git pull
            docker compose down
            docker compose up -d --build
  • 코드 푸시 → 테스트 → SSH 원격 배포 자동화 가능

4. Prometheus + Grafana 모니터링

Prometheus

  • FastAPI + Starlette 기반 메트릭 수집용 middleware 존재 (prometheus_fastapi_instrumentator)
pip install prometheus_fastapi_instrumentator
from prometheus_fastapi_instrumentator import Instrumentator

instrumentator = Instrumentator()
instrumentator.instrument(app).expose(app)
  • /metrics 엔드포인트에서 Prometheus가 메트릭 수집

Grafana

  • Prometheus 데이터 소스로 대시보드 시각화
  • 응답 시간, 요청 수, 에러율 등 확인 가능

5. 로깅 및 에러 수집 (Sentry)

Sentry 설치 및 설정

pip install sentry-sdk
import sentry_sdk
sentry_sdk.init(
    dsn="https://<your_key>@sentry.io/<project_id>",
    traces_sample_rate=1.0
)
  • 자동으로 예외 정보, 트레이스백, 사용자 요청정보를 Sentry 대시보드로 전송
  • 릴리즈 버전, 환경 설정, 사용자 태깅 등도 지원

전체 구성 흐름

  1. FastAPI → Gunicorn(UvicornWorker) → Nginx로 연결
  2. Dockerfile + docker-compose로 환경 컨테이너화
  3. GitHub Actions로 CI/CD 구성 → 테스트 + 배포 자동화
  4. Prometheus + Grafana로 실시간 모니터링
  5. Sentry로 에러 추적 및 알림

 

1. FastAPI 관련 핵심 라이브러리

fastapi 비동기 웹 프레임워크, Pydantic 기반 검증
uvicorn FastAPI 실행용 ASGI 서버
starlette FastAPI의 기반, 미들웨어/응답/요청 처리
pydantic 타입 기반 데이터 검증 및 직렬화

2. 데이터베이스 / ORM

sqlalchemy 대표적인 ORM, 2.0부터 비동기 지원
alembic SQLAlchemy용 DB 마이그레이션 도구
tortoise-orm Django 스타일 async ORM
databases async DB 드라이버, SQLAlchemy와 분리 사용 가능
asyncpg PostgreSQL용 고성능 비동기 드라이버

3. 보안 / 인증 / 암호화

python-jose JWT 인코딩/디코딩 (서명 포함)
passlib 비밀번호 해싱 (bcrypt, argon2 등 지원)
bcrypt 해시 알고리즘, passlib 내부적으로 사용 가능
python-dotenv .env 파일에서 환경변수 불러오기

4. 테스트 / 개발 편의

pytest 단위 테스트 프레임워크
pytest-asyncio 비동기 함수 테스트용 pytest 플러그인
httpx requests 대체 비동기 HTTP 클라이언트
faker 더미 테스트 데이터 생성
factory_boy 테스트용 객체 생성 자동화

5. 캐시 / Redis

aioredis 비동기 Redis 클라이언트
fastapi-cache2 FastAPI용 Redis 기반 캐시 데코레이터
redis-py 동기 Redis 클라이언트 (비추천)

6. 로깅 / 모니터링 / 에러 추적

loguru Python 로그 출력 간소화, 컬러 지원
structlog 구조화된 JSON 로그 작성
sentry-sdk Sentry와 연동해 에러 수집 및 알림
prometheus_fastapi_instrumentator Prometheus 메트릭 수집용 미들웨어

7. LLM / NLP

transformers HuggingFace 모델 로딩 및 텍스트 생성
datasets HuggingFace 데이터셋 사용
tokenizers BPE, WordPiece 등 토크나이저 직접 처리
accelerate GPU 분산 추론/학습 보조 도구
bitsandbytes 양자화 모델 로딩 및 파인튜닝 도구
sentencepiece Google BPE 토크나이저 도구

8. 기타 도구

tqdm 진행률 표시
orjson, ujson 고속 JSON 파서
jinja2 템플릿 엔진 (HTML 또는 프롬프트 렌더링용)
openpyxl, pandas 엑셀/CSV 처리, 간단한 데이터 분석