Summary
BaseSettings, unlike BaseModel validates field default value. This contradicts the documentation. pydantic docs state:
Validators won't run when the default value is used. This applies both to @field_validator validators and Annotated validators. You can force them to run with Field(validate_default=True).
Documentation of pydantic-settings doesn't mention exception from this behavior.
I don't know whether this behavior is intended or not (I'd guess not, because workarounds are quite ugly, see below). But even if it is, it should probably be documented in pydantic-settings docs.
Minimal working example
from zoneinfo import ZoneInfo
from pydantic import BaseModel, ConfigDict
from pydantic.functional_validators import BeforeValidator
from pydantic_settings import BaseSettings, SettingsConfigDict
from typing_extensions import Annotated
class Model(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
zone: Annotated[ZoneInfo, BeforeValidator(ZoneInfo)] = ZoneInfo("UTC")
class Settings(BaseSettings):
# arbitrary_types_allowed are not needed for BaseSettings and don't change anything
zone: Annotated[ZoneInfo, BeforeValidator(ZoneInfo)] = ZoneInfo("UTC")
print(repr(Model().zone)) # prints "zoneinfo.ZoneInfo(key='UTC')"
print(repr(Settings().zone)) # raises TypeError: expected str, bytes or os.PathLike object, not ZoneInfo
python -V
pip --list
Package Version
----------------- -------
annotated-types 0.5.0
pip 23.0.1
pydantic 2.3.0
pydantic_core 2.6.3
pydantic-settings 2.0.3
python-dotenv 1.0.0
setuptools 66.1.1
typing_extensions 4.7.1
Workarounds
- Use plain default value. This works as expected, but doesn't play well with type checkers:
class Settings(BaseSettings):
zone: Annotated[ZoneInfo, BeforeValidator(ZoneInfo)] = "UTC"
- Set
validate_default=False explicitely. This works, but is unnecessarily verbose:
class Settings(BaseSettings):
zone: Annotated[ZoneInfo, Field(validate_default=False), BeforeValidator(ZoneInfo)] = ZoneInfo("UTC")
Summary
BaseSettings, unlikeBaseModelvalidates field default value. This contradicts the documentation. pydantic docs state:Documentation of
pydantic-settingsdoesn't mention exception from this behavior.I don't know whether this behavior is intended or not (I'd guess not, because workarounds are quite ugly, see below). But even if it is, it should probably be documented in
pydantic-settingsdocs.Minimal working example
python -Vpip --listWorkarounds
validate_default=Falseexplicitely. This works, but is unnecessarily verbose: