--- title: "Pydantic Empty String → None Coercion" description: "field_validator with mode='before' to treat empty string as absent optional field, preventing 422/400 on CC-only or AD-only payloads" tags: [pydantic, fastapi, python, validation] created: 2026-05-01 updated: 2026-05-01 projects: [video-accessibility] --- # Pydantic Empty String → None Coercion ## Problem Frontend forms often send `""` for optional fields that were not filled in. Pydantic `str | None` fields accept `""` as a valid non-None string, so downstream guards like `if request.audio_description_vtt:` silently skip validation — but then attempt VTT format parsing on an empty string, producing confusing 400 errors. **Concrete bug (video-accessibility):** CC-only jobs sent `audio_description_vtt: ""`. The PATCH `/vtt` handler tried to validate the empty string as a VTT file and rejected it with 400 instead of ignoring it. ## Solution ```python from typing import Any from pydantic import BaseModel, field_validator class VttUpdateRequest(BaseModel): captions_vtt: str | None = None audio_description_vtt: str | None = None @field_validator('captions_vtt', 'audio_description_vtt', mode='before') @classmethod def empty_str_to_none(cls, v: Any) -> str | None: return None if v == '' else v ``` `mode='before'` runs before Pydantic's own type coercion, so the empty string is converted to `None` before field assignment. After this, `if request.audio_description_vtt:` correctly evaluates to `False`. ## Notes - Works for any `str | None` field where `""` should mean "not provided" - Python 3.10+ syntax (`str | None`); use `Optional[str]` + `from __future__ import annotations` for older versions - Ruff UP007 flags `Optional[str]` — prefer `str | None` - Apply to all optional VTT / text content fields in upload/edit schemas