This is an issue when a Pydantic model field uses Optional[Type] without providing an explicit default value, or when using
Field(…) with Optional[Type] and the ellipsis operator.
In Pydantic models, there is a common misconception about what Optional[Type] means. Many developers assume that marking a field as
Optional[Type] makes it optional during validation, but this is not the case.
The Optional[Type] type hint only indicates that a field can accept either a value of Type or None. It does
not affect whether the field is required during validation. Without an explicit default value, Pydantic will still raise a validation error if the
field is missing from the input data.
To make a field truly optional (meaning it doesn’t need to be provided during validation), you must assign a default value. This is typically
None for optional fields, but can be any appropriate default value.
A particularly problematic pattern is using Field(…) with Optional[Type]. The ellipsis (…) is Pydantic’s
way of explicitly marking a field as required. This creates a direct contradiction: the type hint says the field can be None, but
Field(…) says it must be provided.
This mismatch between developer intent and actual behavior leads to unexpected validation errors in production, confusing API consumers who receive "field required" errors for fields they reasonably expected to be optional based on the type hints.
Fields typed as Type | None, None | Type, or Union[Type, None] are compliant, with or without a default
value.
These annotations are explicit nullable type declarations and do not imply that the field may be omitted from input data.
When Optional[…] fields lack explicit default values, the application may reject requests where users omit fields they believe to be
optional. This leads to:
Add an explicit default value (typically None) to fields with Optional type hints. This makes the field truly optional
during validation while maintaining the type safety that allows None values.
For Optional[…] fields, avoid the ellipsis form (Field(…)) and provide an explicit default instead.
from typing import Optional
from pydantic import BaseModel, Field
class TwitterAccount(BaseModel):
username: str
followers: int
class UserRead(BaseModel):
name: str
twitter_account: Optional[TwitterAccount] # Noncompliant
from typing import Optional, Union
from pydantic import BaseModel, Field
class TwitterAccount(BaseModel):
username: str
followers: int
class UserRead(BaseModel):
name: str
twitter_account: Optional[TwitterAccount] = None