41 lines
1.8 KiB
Markdown
41 lines
1.8 KiB
Markdown
---
|
|
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
|