FastAPI 튜토리얼의 경로 매개변수와 숫자 검증 문서를 보다가 눈에 띄는 섹션이 있었습니다. 바로 "필요한 대로 매개변수 정렬하기"입니다. 파이썬 함수 정의에서 매개변수 순서 때문에 골치 아팠던 경험이 있다면, FastAPI가 이 문제를 어떻게 풀어내는지 흥미롭게 볼 만한 내용입니다.
파이썬 함수는 기본값이 없는 매개변수를 기본값이 있는 매개변수보다 앞에 둬야 합니다. 그렇지 않으면 문법 오류가 납니다.
그런데 FastAPI에서 경로 매개변수를 검증하려면 보통 이렇게 씁니다.
item_id: int = Path(title="The ID of the item to get")
여기서 Path(...)는 함수 매개변수의 "기본값" 자리에 들어갑니다. 그래서 만약 뒤에 기본값 없는 쿼리 매개변수 q: str가 온다면, 파이썬 입장에서는 "기본값 있는 매개변수 뒤에 기본값 없는 매개변수가 왔다"는 문법 오류가 발생할 수 있는 상황이 됩니다.
문서에서 가장 핵심적인 문장은 이거였습니다.
FastAPI에서는 중요하지 않습니다. 이름, 타입 그리고 기본값 선언(Query, Path 등)으로 매개변수를 감지하며 순서는 신경 쓰지 않습니다.
즉 FastAPI는 함수 시그니처를 위치 순서가 아니라 "이름 + 타입 + Query/Path 같은 선언"으로 분석합니다. 그래서 파이썬 문법만 통과하면 다음과 같이 기본값 없는 쿼리 매개변수 q를 앞에, 기본값 있어 보이는 item_id: int = Path(...)를 뒤에 두는 식으로 재정렬해서 문제를 피할 수 있습니다.
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(q: str, item_id: int = Path(title="The ID of the item to get")):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
파이썬 문법 규칙은 지키면서, FastAPI 입장에서 어떤 게 경로 매개변수이고 어떤 게 쿼리 매개변수인지는 순서가 아니라 선언 자체로 판단하는 겁니다.
사실 이 순서 문제는 Query()나 Path()를 함수 매개변수의 기본값 자리에 넣는 스타일 때문에 생깁니다. Annotated를 쓰면 애초에 기본값 자리를 쓰지 않기 때문에 이런 제약이 사라집니다.
from typing import Annotated
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
q: str, item_id: Annotated[int, Path(title="The ID of the item to get")]
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
Path(title=...)가 타입 힌트 안(Annotated[int, Path(...)])으로 들어가면서 더 이상 "기본값"으로 취급되지 않기 때문에, 원래 순서(q 먼저, item_id 나중)로 두든 바꿔서 두든 아무 문제가 없습니다. 지금 FastAPI 최신 버전 기준으로 권장되는 방식이기도 합니다.
*문서에는 Annotated를 쓰지 않는 경우를 위한 작은 트릭도 소개되어 있습니다. 함수의 첫 매개변수로 *를 두는 겁니다.
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
파이썬에서 *는 그 자체로는 아무 동작도 하지 않지만, 그 뒤에 오는 모든 매개변수를 반드시 키워드 인자로만 호출하도록 강제합니다(이른바 kwargs). 이렇게 하면 기본값이 있는 매개변수가 먼저 오든 나중에 오든 파이썬 문법 오류에서 자유로워집니다. 자주 쓸 일은 없지만, Annotated를 쓰지 않는 레거시 코드를 다룰 때는 알아두면 쓸모 있는 트릭입니다.
사실 이 섹션 자체는 아주 작은 이야기지만, FastAPI의 설계 철학을 잘 보여줍니다. 함수 시그니처의 "위치"라는 파이썬의 제약과, API 스펙을 표현해야 하는 "선언"이라는 요구사항이 부딪힐 수 있는데, FastAPI는 타입 힌트와 Query/Path 같은 명시적 선언을 분석해서 이 둘을 분리해줍니다. 개발자는 파이썬 문법만 신경 쓰면 되고, "이게 경로 매개변수인지 쿼리 매개변수인지"는 이름과 선언으로 알아서 판단해주는 거죠.
문서의 요약 부분에서 짚고 넘어갈 만한 포인트가 하나 더 있습니다.
Query, Path(아직 보지 못한 다른 것들도)를 사용하면 쿼리 매개변수와 문자열 검증에서와 마찬가지로 메타데이터와 문자열 검증을 선언할 수 있습니다. 그리고 숫자 검증 또한 선언할 수 있습니다.
즉 Query와 Path는 단순히 "이건 쿼리 매개변수, 이건 경로 매개변수"라는 위치 정보만 알려주는 게 아니라, title/description 같은 메타데이터, min_length/max_length/정규식 같은 문자열 검증, 그리고 숫자 검증까지 한 번에 선언할 수 있는 창구입니다. 숫자 검증에 쓰이는 키워드는 다음 4가지입니다.
gt: greater than (초과, ~보다 커야 함)ge: greater than or equal (이상, ~보다 크거나 같아야 함)lt: less than (미만, ~보다 작아야 함)le: less than or equal (이하, ~보다 작거나 같아야 함)예를 들어 item_id가 0 이상 1000 이하여야 하고, 쿼리 매개변수 size는 0보다 크고 10.5보다 작은 실수여야 한다면 다음과 같이 선언할 수 있습니다.
from typing import Annotated
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
*,
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str,
size: Annotated[float, Query(gt=0, lt=10.5)],
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
if size:
results.update({"size": size})
return results
여기서 중요한 건 이 네 키워드가 int뿐 아니라 float 같은 실수 타입에도 그대로 적용된다는 점입니다. gt=0을 걸면 0.5는 유효하지만 0.0이나 0은 거부됩니다. 즉 "1보다 작아도 되지만 반드시 0보다는 커야 한다"처럼, 정수 검증만으로는 표현하기 애매한 조건도 자연스럽게 표현할 수 있습니다.
그리고 문서는 Query, Path가 사실 공통 Param 클래스를 상속하는 서브클래스이기 때문에 이런 검증 옵션들을 동일하게 공유한다는 기술적인 사실도 짚어줍니다. 심지어 Query, Path 같은 이름은 실제로는 클래스가 아니라 함수이고, 호출하면 해당 이름의 클래스 인스턴스를 반환하는 구조라고 합니다. 클래스를 직접 쓰는 대신 함수 형태로 감싸둔 이유는 편집기가 타입 오류를 잘못 표시하지 않게 하기 위해서라는데, 사용자 경험(정확히는 개발자 경험)을 이런 세세한 부분까지 신경 쓴다는 인상을 받았습니다.
결국 Query/Path 하나로 "이 값이 어디서 오는지 + 어떤 메타데이터를 가지는지 + 문자열/숫자로서 어떤 조건을 만족해야 하는지"까지 한 번에 선언할 수 있다는 게 이 요약 섹션의 핵심입니다.
Query/Path 같은 선언으로 인식한다.Annotated를 쓰면 애초에 기본값 자리를 쓰지 않으므로 순서 문제 자체가 사라진다. 특별한 이유가 없다면 이 방식이 가장 깔끔하다.Annotated를 쓰지 않아야 하는 상황이라면, 함수 첫 매개변수로 *를 둬서 강제로 키워드 인자화하는 트릭을 쓸 수 있다.Query/Path는 메타데이터, 문자열 검증뿐 아니라 gt/ge/lt/le로 숫자 검증까지 함께 선언할 수 있고, int와 float 모두에 적용된다.작은 디테일이지만, 이런 부분에서도 "개발자가 파이썬 문법에 억지로 맞추게 하지 않는다"는 FastAPI의 태도가 느껴집니다.