티스토리 뷰
Annotated는 단순히 FastAPI의 기능을 넘어, 현대 파이썬(3.9+)에서 타입 시스템을 확장하는 표준 방식입니다. 왜 이 방식이 "대세"가 되었는지, 그리고 실무에서 어떤 파워를 발휘하는지 심층적으로 파헤쳐 보겠습니다.
1. Annotated의 본질: "타입에 포스트잇 붙이기"
Annotated는 PEP 593에서 도입되었습니다. 기본 구조는 Annotated[T, x]인데, 여기서 T는 실제 데이터 타입이고, x는 그 타입에 추가하는 **메타데이터(설명서)**입니다.
• 파이썬 입장: "음, 이건 T 타입이군. 뒤에 붙은 x는 무시할게."
• FastAPI 입장: "오, T 타입이구나? 그런데 뒤에 x라는 설정이 있네? 이걸로 검증이나 의존성 주입을 처리해야지!"
이처럼 런타임 로직과 타입 선언을 분리할 수 있다는 점이 핵심입니다.
2. 왜 기존 방식보다 Annotated가 좋을까?
기존 방식은 함수 매개변수에 직접 Query(), Depends() 등을 할당했습니다. 하지만 Annotated를 쓰면 다음과 같은 이점이 생깁니다.
🛠️ 이점 1: 코드 재사용성 (DRY)
똑같은 검증 로직이나 의존성을 여러 곳에서 써야 할 때, 아예 새로운 타입처럼 정의할 수 있습니다
from typing import Annotated
from fastapi import Query
# 반복되는 검색 쿼리 설정을 하나의 '타입'으로 정의
SearchQuery = Annotated[
str,
Query(min_length=2, max_length=20, description="검색어는 2~20자 사이")
]
@app.get("/items/")
async def read_items(q: SearchQuery = None): # 훨씬 간결해짐!
return {"q": q}
@app.get("/users/")
async def read_users(q: SearchQuery = None): # 다른 곳에서도 그대로 사용
return {"q": q}
🧪 이점 2: 테스트 및 정적 분석 (Linting)
기존 방식(q: str = Query(...))은 기본값 자리에 Query 객체가 들어가 있어서, 단위 테스트를 할 때 실제 문자열을 넘기기 까다로울 수 있습니다. Annotated를 쓰면 타입 힌트가 명확해져서 mypy 같은 도구가 에러를 더 잘 잡아냅니다.
가장 빈번하게 사용되고, 생산성을 극대화해주는 Top 4 패턴
1. 페이징 처리 (Pagination) 패턴
가장 흔하게 쓰이는 패턴입니다. 목록 조회 API에서 항상 들어가는 skip과 limit을 매번 적지 않고 하나로 묶이
from typing import Annotated
from fastapi import Query, Depends
# 공통 로직 정의
async def pagination_params(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100)
):
return {"skip": skip, "limit": limit}
# Annotated를 활용한 타입 별칭(Alias) 생성
PaginationDep = Annotated[dict, Depends(pagination_params)]
@app.get("/items")
async def read_items(pagination: PaginationDep):
return {"message": "목록 조회", "params": pagination}
2. 데이터베이스 세션 주입 패턴
SQLAlchemy나 Tortoise ORM을 쓸 때 필수적인 패턴입니다. DB 연결을 매번 함수 안에 적지 않고 '주입'받습니다.
from sqlalchemy.orm import Session
from database import SessionLocal # 사용자 설정 DB 세션
# DB 세션 생성 함수
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# 프로젝트 전체에서 쓸 'DB' 타입 정의
DB = Annotated[Session, Depends(get_db)]
@app.post("/users")
async def create_user(user_data: UserCreate, db: DB):
# db.add(user_data)... 등의 로직 수행
return {"status": "success"}
3. 현재 로그인 유저 (Auth) 패턴
보안이 필요한 API에서 "이 요청을 보낸 사람이 누구인지"를 바로 가져오는 패턴입니다.
# 인증 로직 (예: JWT 검증)
async def get_current_user(token: Annotated[str, Header()]):
user = decode_token(token) # 토큰 해독 로직
if not user:
raise HTTPException(status_code=401)
return user
# '관리자' 전용 의존성도 만들 수 있습니다.
CurrentUser = Annotated[User, Depends(get_current_user)]
@app.get("/me")
async def read_user_me(current_user: CurrentUser):
return current_user
4. 특정 형식 검증 (Validation) 패턴
이메일, 전화번호, 혹은 특정 정규표현식이 필요한 ID 값 등을 미리 정의해두는 방식입니다.
from fastapi import Path
# ID 형식이 'USER-숫자' 여야 하는 경우
UserID = Annotated[
str,
Path(pattern=r"^USER-\d+$", description="유저 ID는 USER-숫자 형식이어야 합니다.")
]
@app.get("/users/{user_id}")
async def get_user(user_id: UserID):
return {"user_id": user_id}