티스토리 뷰

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}

댓글
D-DAY
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함