pydantic/pydantic · error · ValidationError
frozen_instance
frozen_instance
Error message
frozen_instance
What it means
Raised by _check_frozen (main.py:96-106) when model_config has `frozen=True` (class-wide) and any attribute assignment is attempted. It surfaces as a ValidationError with error type 'frozen_instance' and the offending field name/value in loc/input. This is the same machinery dataclasses use to make instances hashable/immutable.
Source
Thrown at pydantic/main.py:104
Rebuilding a model isn't thread-safe (the class attributes are mutated during the rebuild,
while other threads may be reading them to perform validation/serialization), so `model_rebuild()`
calls are serialized using this lock. The lock is reentrant, as rebuilding a model can trigger the
rebuild of another one (e.g. when a generic origin is rebuilt during parametrization in
`__class_getitem__()`). For the same reason, the lock is global and not per-class: two threads
holding their own class's lock could otherwise request the other's and deadlock.
"""
def _check_frozen(model_cls: type[BaseModel], name: str, value: Any) -> None:
if model_cls.model_config.get('frozen'):
error_type = 'frozen_instance'
elif getattr(model_cls.__pydantic_fields__.get(name), 'frozen', False):
error_type = 'frozen_field'
else:
return
raise ValidationError.from_exception_data(
model_cls.__name__, [{'type': error_type, 'loc': (name,), 'input': value}]
)
def _model_field_setattr_handler(model: BaseModel, name: str, val: Any) -> None:
model.__dict__[name] = val # pyright: ignore[reportIndexIssue] (https://github.com/microsoft/pyright/issues/11548)
model.__pydantic_fields_set__.add(name)
def _private_setattr_handler(model: BaseModel, name: str, val: Any) -> None:
if getattr(model, '__pydantic_private__', None) is None:
# While the attribute should be present at this point, this may not be the case if
# users do unusual stuff with `model_post_init()` (which is where the `__pydantic_private__`
# is initialized, by wrapping the user-defined `model_post_init()`), e.g. if they mock
# the `model_post_init()` call. Ideally we should find a better way to init private attrs.
object.__setattr__(model, '__pydantic_private__', {})
model.__pydantic_private__[name] = val # pyright: ignore[reportOptionalSubscript]
View on GitHub (pinned to 2e5f0e2b42)
Solutions
- Use model_copy(update={'field': value}) to return a new immutable instance instead of mutating.
- If mutation is genuinely required, remove `frozen=True` from model_config (and reconsider why it was set).
- For per-field immutability, set `Field(..., frozen=True)` only on specific fields instead of freezing the whole model.
Example fix
# before
from pydantic import BaseModel, ConfigDict
class M(BaseModel):
model_config = ConfigDict(frozen=True)
x: int
m = M(x=1)
m.x = 2 # raises frozen_instance ValidationError
# after
m2 = m.model_copy(update={'x': 2}) Defensive patterns
Strategy: validation
Validate before calling
from pydantic import BaseModel
def safe_set(model: BaseModel, name: str, value):
if model.model_config.get('frozen'):
return model.model_copy(update={name: value})
setattr(model, name, value)
return model Type guard
from pydantic import BaseModel
def is_frozen(model: BaseModel) -> bool:
return bool(model.model_config.get('frozen')) Try / catch
from pydantic import ValidationError
try:
m.x = 2
except ValidationError as e:
if any(err['type'] == 'frozen_instance' for err in e.errors()):
m = m.model_copy(update={'x': 2})
else:
raise Prevention
- Treat frozen models as immutable values; always use model_copy(update=...).
- Only set frozen=True when you need hashability, and audit call sites that mutate.
- For per-field immutability prefer Field(frozen=True) over class-wide frozen.
When it happens
Trigger: Defining `model_config = ConfigDict(frozen=True)` then doing `instance.field = value`, or calling setattr()/copy-with-update on a frozen model. Triggered inside __setattr__ via _check_frozen before any field write.
Common situations: Making models hashable so they can go in sets/dicts (requires frozen=True); ORM-style mutation code that expects to update fields in place; migrating from mutable v1 models to frozen v2 models.
Related errors
- "{self.__class__.__name__}" is immutable and does not suppor
- {name!r} is a ClassVar of `{cls.__name__}` and cannot be set
- "{cls.__name__}" object has no field "{name}"
- "{self.__class__.__name__}" object "{name}" field is final a
- model-config-invalid-field-name
AI-assisted analysis of pydantic/pydantic@2e5f0e2b42 (2026-08-04).
Data as JSON: /data/errors/4ce926a86594c63a.json.
Report an issue: GitHub.