Fix: Pydantic ValidationError — Field Required, Value Not a Valid Type, or Extra Fields
Quick Answer
How to fix Pydantic v2 validation errors — required fields, type coercion, model_validator, custom validators, extra fields config, and migrating from Pydantic v1.
The Problem
Pydantic raises a ValidationError when creating a model:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
User(id='not-an-int', name='Alice', email='[email protected]')
# ValidationError: 1 validation error for User
# id
# Input should be a valid integer, unable to parse string as an integer
# [type=int_parsing, input_value='not-an-int', input_type=str]Or extra fields cause unexpected errors:
User(id=1, name='Alice', email='[email protected]', role='admin')
# ValidationError: 1 validation error for User
# role
# Extra inputs are not permitted [type=extra_forbidden, ...]Or Pydantic v1 validators stop working after upgrading to v2:
# v1 code
from pydantic import validator
class User(BaseModel):
name: str
@validator('name')
def name_must_be_capitalized(cls, v):
return v.capitalize()
# In v2 — @validator is deprecated, gives warnings or errorsOr a nested model doesn’t validate as expected:
class Address(BaseModel):
street: str
city: str
class User(BaseModel):
address: Address
User(address={'street': '123 Main St'})
# ValidationError: 1 validation error for User
# address.city
# Field required [type=missing, ...]Why This Happens
Pydantic validates all input against model field definitions. Errors arise from:
- Type mismatches — Pydantic v2 is stricter than v1. A
strwhereintis expected raises an error rather than silently coercing. - Missing required fields — fields without defaults are required. Omitting them raises
Field required. - Extra fields forbidden by default — Pydantic v2 forbids extra fields by default in strict mode. In regular mode, extra fields are ignored (not forbidden).
- Validator API changes in v2 —
@validator(v1) is replaced by@field_validatorand@model_validator(v2). The function signatures differ. - Nested model validation — nested models are validated recursively. An error in a nested model is reported with the full path (
address.city). model_configreplacingclass Config— Pydantic v2 usesmodel_config = ConfigDict(...)instead of the innerclass Config.
Fix 1: Read Pydantic Error Messages
Pydantic v2 error messages are detailed and actionable:
from pydantic import BaseModel, ValidationError
class User(BaseModel):
id: int
name: str
email: str
try:
user = User(id='bad', name=None, email='invalid')
except ValidationError as e:
print(e)
# 3 validation errors for User
# id
# Input should be a valid integer [type=int_parsing, ...]
# name
# Input should be a valid string [type=string_type, ...]
# email
# Value error, invalid email [type=value_error, ...]
# Access errors programmatically
for error in e.errors():
print(f"Field: {error['loc']}") # ('id',) or ('address', 'city')
print(f"Type: {error['type']}") # 'int_parsing', 'missing', etc.
print(f"Msg: {error['msg']}") # Human-readable message
print(f"Input: {error['input']}") # The value that failedError types reference:
| Error type | Meaning |
|---|---|
missing | Required field not provided |
int_parsing | Can’t parse value as integer |
string_type | Expected string, got something else |
extra_forbidden | Extra field not allowed |
value_error | Custom validator raised ValueError |
assertion_error | Custom validator raised AssertionError |
literal_error | Value not in Literal options |
enum | Value not a valid enum member |
Fix 2: Fix Type Validation Errors
Pydantic v2 has two validation modes — strict and lax:
from pydantic import BaseModel, field_validator
class Product(BaseModel):
id: int
price: float
name: str
in_stock: bool
# Lax mode (default) — type coercion where reasonable
product = Product(
id='123', # '123' → 123 (string to int: OK in lax mode)
price='9.99', # '9.99' → 9.99 (string to float: OK)
name=42, # 42 → '42' (int to str: OK in lax mode)
in_stock='true', # 'true' → True (string to bool: OK)
)
print(product) # id=123 price=9.99 name='42' in_stock=True
# Strict mode — no coercion
from pydantic import ConfigDict
class StrictProduct(BaseModel):
model_config = ConfigDict(strict=True)
id: int
price: float
StrictProduct(id='123', price='9.99')
# ValidationError: id must be int, not strPer-field strict mode:
from pydantic import BaseModel
from pydantic.functional_validators import BeforeValidator
from typing import Annotated
class Order(BaseModel):
# id must be exactly an int (no string coercion)
id: Annotated[int, Field(strict=True)]
# price allows string coercion
price: float # '9.99' → 9.99Fix 3: Handle Optional and Default Fields
from pydantic import BaseModel, Field
from typing import Optional
class User(BaseModel):
id: int
name: str
# Optional with default None
email: Optional[str] = None # email is optional, defaults to None
age: int | None = None # Same with union syntax (Python 3.10+)
# Required with no default — MUST be provided
role: str # No default — required
# Field with validation constraints
score: float = Field(default=0.0, ge=0, le=100) # 0 <= score <= 100
username: str = Field(min_length=3, max_length=50)
tags: list[str] = Field(default_factory=list) # Mutable default — use factory
# Required fields:
User(id=1, name='Alice', role='user') # OK — email/age use defaults
User(id=1, name='Alice') # Error — role requiredDefault values and mutability:
# WRONG — mutable default shared across instances (in plain Python too)
class User(BaseModel):
tags: list[str] = [] # Pydantic handles this correctly (creates new list each time)
# But for custom mutable types, use default_factory:
metadata: dict = Field(default_factory=dict)Fix 4: Write Validators in Pydantic v2
The validator API changed significantly from v1 to v2:
from pydantic import BaseModel, field_validator, model_validator
from typing import Self
class User(BaseModel):
name: str
email: str
age: int
password: str
confirm_password: str
# Field validator — validates a single field
@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
if '@' not in v:
raise ValueError('Invalid email format')
return v.lower() # Return the transformed value
# Field validator — runs before type validation
@field_validator('age', mode='before')
@classmethod
def parse_age(cls, v) -> int:
if isinstance(v, str) and v.isdigit():
return int(v)
return v # Let Pydantic handle type conversion
# Model validator — validates multiple fields together
@model_validator(mode='after')
def passwords_match(self) -> Self:
if self.password != self.confirm_password:
raise ValueError('Passwords do not match')
return self
# Model validator before field validation
@model_validator(mode='before')
@classmethod
def normalize_input(cls, data: dict) -> dict:
if isinstance(data, dict) and 'username' in data:
data['name'] = data.pop('username') # Rename field
return dataPydantic v1 vs v2 validator comparison:
# Pydantic v1
from pydantic import validator
class UserV1(BaseModel):
name: str
@validator('name')
def name_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
# Pydantic v2 — equivalent
from pydantic import field_validator
class UserV2(BaseModel):
name: str
@field_validator('name')
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
if not v.strip():
raise ValueError('Name cannot be empty')
return v.strip()
# Key differences:
# 1. @classmethod is now required
# 2. Type annotations on the value parameter (v: str)
# 3. @validator → @field_validator
# 4. @root_validator → @model_validatorFix 5: Configure Extra Field Handling
Control how Pydantic handles fields not defined in the model:
from pydantic import BaseModel, ConfigDict
# Ignore extra fields (Pydantic v2 default behavior)
class UserIgnore(BaseModel):
model_config = ConfigDict(extra='ignore') # Default in v2
name: str
email: str
UserIgnore(name='Alice', email='[email protected]', role='admin')
# role is silently ignored — no error
# Forbid extra fields
class UserStrict(BaseModel):
model_config = ConfigDict(extra='forbid')
name: str
email: str
UserStrict(name='Alice', email='[email protected]', role='admin')
# ValidationError: role — Extra inputs are not permitted
# Allow extra fields (store them in model)
class UserFlexible(BaseModel):
model_config = ConfigDict(extra='allow')
name: str
email: str
user = UserFlexible(name='Alice', email='[email protected]', role='admin')
user.role # 'admin' — accessible as attribute
user.model_extra # {'role': 'admin'} — all extra fieldsFix 6: Validate Data from APIs and External Sources
Parse and validate incoming data with proper error handling:
from pydantic import BaseModel, ValidationError
from typing import Any
import json
class UserResponse(BaseModel):
id: int
name: str
email: str
created_at: datetime
def parse_user_response(raw_data: Any) -> UserResponse | None:
try:
return UserResponse.model_validate(raw_data)
except ValidationError as e:
logger.error(f"Failed to parse user response: {e}")
# Log specific errors
for error in e.errors():
logger.error(f" Field {error['loc']}: {error['msg']}")
return None
# From JSON string
def parse_user_json(json_str: str) -> UserResponse:
return UserResponse.model_validate_json(json_str)
# Validate a dict
raw = {'id': 1, 'name': 'Alice', 'email': '[email protected]', 'created_at': '2024-01-15T10:30:00'}
user = UserResponse.model_validate(raw) # Parses created_at string → datetime
# Update a model with partial data
existing_user = UserResponse(id=1, name='Alice', email='[email protected]', created_at=datetime.now())
updated = existing_user.model_copy(update={'email': '[email protected]'})FastAPI automatic validation:
from fastapi import FastAPI
from pydantic import BaseModel, field_validator
app = FastAPI()
class CreateUserRequest(BaseModel):
name: str
email: str
age: int
@field_validator('age')
@classmethod
def age_must_be_adult(cls, v: int) -> int:
if v < 18:
raise ValueError('Must be at least 18 years old')
return v
@app.post('/users')
async def create_user(user: CreateUserRequest):
# FastAPI automatically validates the request body
# Returns 422 Unprocessable Entity with error details on validation failure
return {'created': user.model_dump()}Fix 7: Migrate from Pydantic v1 to v2
Key changes when upgrading:
# v1
from pydantic import BaseModel
from typing import Optional
class User(BaseModel):
name: str
email: Optional[str] # v1: Optional[str] without default = required but nullable
class Config:
orm_mode = True # v1
use_enum_values = True # v1
validate_assignment = True # v1
# v2 equivalent
from pydantic import BaseModel, ConfigDict
from typing import Optional
class User(BaseModel):
model_config = ConfigDict(
from_attributes=True, # v2: was orm_mode
use_enum_values=True,
validate_assignment=True,
)
name: str
email: Optional[str] = None # v2: Optional without default is still required
# Add = None to make it truly optional
# Common v1 → v2 API changes:
# .dict() → .model_dump()
# .json() → .model_dump_json()
# .parse_obj() → .model_validate()
# .parse_raw() → .model_validate_json()
# .schema() → .model_json_schema()
# orm_mode → from_attributes
# @validator → @field_validator (requires @classmethod)
# @root_validator → @model_validatorStill Not Working?
Circular references — Pydantic v2 handles circular references with model_rebuild(). If a model references itself or another model that references it, call Model.model_rebuild() after all models are defined.
__fields__ vs model_fields — Pydantic v1 used User.__fields__. Pydantic v2 uses User.model_fields. If you’re accessing model metadata, update the attribute name.
json_encoders migration — v1’s json_encoders in Config is replaced by @field_serializer decorators in v2:
from pydantic import field_serializer
from datetime import datetime
class Event(BaseModel):
created_at: datetime
@field_serializer('created_at')
def serialize_dt(self, dt: datetime) -> str:
return dt.isoformat()For related Python issues, see Fix: Python Type Hint Error (mypy) and Fix: FastAPI Dependency Injection Error.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Pydantic ValidationError — Field Required / Value Not Valid
How to fix Pydantic ValidationError in Python — missing required fields, wrong types, custom validators, handling optional fields, v1 vs v2 API differences, and debugging complex nested models.
Fix: FastAPI BackgroundTasks Not Working — Task Not Running or Dependency Errors
How to fix FastAPI BackgroundTasks — task not executing, dependency injection in tasks, error handling, Celery for heavy tasks, and lifespan-managed background workers.
Fix: FastAPI Dependency Injection Errors — Dependencies Not Working
How to fix FastAPI dependency injection errors — async dependencies, database sessions, sub-dependencies, dependency overrides in tests, and common DI mistakes.
Fix: Python asyncio Blocking the Event Loop — Mixing Sync and Async Code
How to fix Python asyncio event loop blocking — using run_in_executor for sync calls, asyncio.to_thread, avoiding blocking I/O in coroutines, and detecting event loop stalls.