FastAPI 0.149.0이 2026년 10월 19일 공개됐습니다. 같은 날 발표된 0.147.0과 0.148.0까지 묶어서 보면 중요한 공통점이 있습니다. HTML 폼을 제출할 때 누락된 값·빈 문자열·체크되지 않은 체크박스를 FastAPI가 해석하는 방식이 일부 바뀌었다는 점입니다.
API 서버가 정상적으로 시작돼도 기존 폼 입력의 검증 결과나 model_dump(exclude_unset=True) 출력이 달라질 수 있습니다. 이 글은 공식 릴리스 노트를 기준으로 바뀐 동작, 영향 범위, 최소 재현 코드, 안전한 업그레이드 순서를 정리합니다. 공식 문서의 동작 설명과 각 프로젝트의 검증 결과는 구분해야 합니다.
1. FastAPI 0.149.0: 빈 문자열을 누락된 폼 값으로 처리
브라우저는 비어 있는 일반 텍스트 입력칸을 빈 문자열("")로 제출할 수 있습니다. 0.149.0에서 폼 모델의 스칼라 필드에 빈 문자열이 들어오면 이를 값이 없는 것으로 처리합니다. 필수 필드는 검증 오류를 내고, 기본값 또는 기본값 팩토리가 있는 필드는 기본값을 사용합니다.
예를 들어 nickname: str이 필수인 폼 모델에서 nickname=""을 제출하면 성공 응답이 아니라 검증 실패 응답을 예상해야 합니다. 반면 locale: str = "ko"처럼 기본값이 선언된 항목은 빈 값 입력 시 기본값을 적용할 수 있습니다. 이때 기본값이 사용된 필드는 exclude_unset=True 결과에서 명시적으로 입력한 필드처럼 표시되지 않습니다.
단, 리스트 안의 빈 문자열은 보존되고, JSON 요청과 Pydantic 모델을 직접 검증할 때의 규칙은 이 변경으로 달라지지 않습니다. 따라서 폼 요청과 JSON 요청의 테스트를 혼동하지 않아야 합니다.
2. 0.148.0: 기본값 검증과 exclude_unset 결과 변화
0.148.0에서는 누락된 Query·Header·Cookie·Form 입력에 적용되는 기본값을 사용자가 직접 전달한 데이터로 보지 않습니다. 따라서 기본값에 대한 검증을 의도했다면 Field(validate_default=True) 또는 해당 매개변수의 validate_default=True 설정을 검토해야 합니다.
기본값이 적용되었다고 해서 model_dump(exclude_unset=True)에 자동으로 포함되는 것도 아닙니다. 저장 API에서 이 결과를 부분 업데이트용 딕셔너리로 사용하는 경우 특히 확인할 필요가 있습니다. 기본값 팩토리 호출 횟수 등도 공식 노트에 변경 사항으로 명시돼 있습니다.
3. 0.147.0: 체크하지 않은 체크박스는 False로 해석
HTML의 체크박스는 사용자가 체크하지 않으면 일반적으로 요청 본문에 해당 필드가 들어가지 않습니다. FastAPI 0.147.0부터 폼의 bool 또는 bool | None 필드가 누락되면 False로 처리합니다. 심지어 모델 필드의 기본값이 True 또는 None이어도 폼에서는 체크되지 않은 상태가 False로 반영될 수 있습니다.
이 동작은 JSON API와 다른 부분입니다. 데이터베이스 업데이트 로직에서 True 기본값을 기대하거나 필수 불리언 누락을 오류로 판단하던 코드가 있다면, 폼의 실제 전송 결과를 다시 확인해야 합니다.
4. 직접 확인할 수 있는 최소 테스트 코드
아래 예제는 FastAPI 0.149.0 환경에서 점검하도록 작성한 재현 코드입니다. 먼저 테스트 전용 가상환경에서 pip install "fastapi==0.149.0" python-multipart httpx를 설치하세요. 프로젝트에 맞춰 이미 사용하는 Pydantic·Starlette 버전도 함께 검사하는 것이 좋습니다.
from typing import Annotated
from fastapi import FastAPI, Form
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class ProfileForm(BaseModel):
nickname: str
consent: bool = True
locale: str = "ko"
@app.post("/profile")
def save_profile(data: Annotated[ProfileForm, Form()]):
return {
"data": data.model_dump(),
"provided": data.model_dump(exclude_unset=True),
}
client = TestClient(app)
# 빈 필수 텍스트 입력: 검증 실패
assert client.post("/profile", data={"nickname": ""}).status_code == 422
# 체크박스 미전송: False로 해석
response = client.post("/profile", data={"nickname": "tester"})
assert response.status_code == 200
assert response.json()["data"]["consent"] is False
# 전달하지 않은 기본값 필드는 exclude_unset에서 제외
assert "locale" not in response.json()["provided"]
실제 서비스는 쿠키·헤더·쿼리 매개변수, 폼 모델의 리스트 필드, 기본값 팩토리, 부분 업데이트 방식에 따라 영향 범위가 달라집니다. 테스트 코드가 통과하더라도 서비스 전체가 호환된다는 뜻은 아닙니다.
5. 배포 전 확인할 회귀 테스트 체크리스트
첫째, 폼 필수 텍스트 필드에 빈 문자열을 전송해 응답 코드가 의도에 맞는지 확인합니다. 둘째, 기본값이 있는 필드를 아예 생략한 경우와 빈 문자열을 전송한 경우를 각각 시험합니다. 셋째, 체크박스를 체크한 경우와 체크하지 않은 경우를 별도 케이스로 분리합니다.
넷째, exclude_unset=True 결과를 데이터베이스 변경이나 권한·동의 저장에 사용하는지 확인합니다. 다섯째, 웹 프런트엔드가 실제로 어떤 application/x-www-form-urlencoded 또는 multipart/form-data 요청을 보내는지 네트워크 기록에서 확인합니다.
운영 반영은 기존 의존성을 고정한 상태에서 별도 테스트 환경을 만든 뒤, 새 버전에서 테스트를 재실행하고 결과를 비교하는 순서가 안전합니다. 예외가 발견되면 롤백할 수 있도록 requirements.txt 또는 잠금 파일과 배포 버전을 보존하세요.
6. 이전 버전 글과 공식 출처
직전 릴리스 계열의 직렬화·텔레메트리 변경은 기존 FastAPI 0.143.4 업데이트와 SSE·JSONL 처리 글에서 구분해 설명했습니다. 이번 글은 그 뒤인 0.147~0.149 폼 입력 호환성에 초점을 맞춘 후속편입니다.
1차 출처: FastAPI 공식 릴리스 노트(0.147.0·0.148.0·0.149.0, 2026년 10월 19일), FastAPI Form Models 공식 가이드, FastAPI Form Data 및 Boolean Form Fields 문서.
확인 기준: 2026년 10월 20일(KST). 예시 코드는 공식 동작을 이해하기 위한 테스트용이며, 실제 서버 적용 전 프로젝트 환경에서 별도 검증해야 합니다.