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는 요청 하나를 다음과 같은 단계로 처리합니다:
- ASGI 서버(uvicorn) 가 클라이언트 요청을 수신
- 요청은 Starlette 기반의 FastAPI 라우터로 전달됨
- 라우트 함수(보통 async def) 가 매칭되고 실행
- 내부에서 await가 발생하면, 이벤트 루프가 다른 요청을 처리
- 응답이 완성되면 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의 강력한 특징
- 자동 실행
FastAPI는 요청 처리 시점에 의존성을 자동 실행해서 라우터 함수에 전달합니다. - 상태 보존 또는 공유 가능
객체를 통해 설정, DB 등 재사용 가능 - 클래스형 서비스 구조
__call__로 라우터 바깥에서 의존성 인스턴스를 만들어 놓고 주입 가능 - 테스트 유리함
테스트할 때 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는 규모가 매우 크지 않으면 생략해도 됩니다.
요약 흐름 정리
- APIRouter로 라우팅을 파일 단위로 모듈화
- schemas, services, repositories 폴더로 책임을 명확히 분리
- 라우터는 서비스 레이어를 통해 도메인 로직에 접근
- Depends를 통해 의존성 안전하게 전달
- 필요한 경우 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 이후 객체 참조 유지
마무리 요약
- SQLAlchemy 2.0 async은 FastAPI와 완벽하게 호환되며, ORM 기능 + 비동기 성능을 함께 누릴 수 있음
- 세션은 Depends(get_db) 방식으로 라우터에 안전하게 주입
- 리포지토리 패턴을 적용하면 DB와 도메인 로직이 분리되어 테스트성과 유지보수가 뛰어남
- Alembic을 사용하면 모델 구조가 변경되더라도 마이그레이션을 안정적으로 관리 가능
- 규모가 작거나 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 문서화, 모니터링에 이점이 많습니다.
종합 예외 핸들링 흐름
- 커스텀 예외 정의 (각 도메인별)
- 전역 예외 핸들러 등록
- ApiResponse 형태로 응답 일관성 유지
- 로깅 도구로 내부 에러 추적 및 기록
- 필요시 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 연동 등에 활용
성능 최적화 정리 요약
- 캐싱: Redis + fastapi-cache2를 통해 I/O 반복을 줄임
- 쿼리 최적화: ORM 사용 시 join 최적화 또는 Raw SQL로 전환
- 미들웨어: 응답 시간, 요청 기록 등을 전역으로 추적
- 스트리밍: 대용량 응답이나 장시간 작업을 실시간 분리 처리
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 대시보드로 전송
- 릴리즈 버전, 환경 설정, 사용자 태깅 등도 지원
전체 구성 흐름
- FastAPI → Gunicorn(UvicornWorker) → Nginx로 연결
- Dockerfile + docker-compose로 환경 컨테이너화
- GitHub Actions로 CI/CD 구성 → 테스트 + 배포 자동화
- Prometheus + Grafana로 실시간 모니터링
- 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 처리, 간단한 데이터 분석 |