pydantic/pydantic · error · ValidationError
frozen_field
frozen_field
Error message
Field is frozen
What it means
An individual field declared `Field(frozen=True)` cannot be reassigned on an instance; the attempt raises a ValidationError with type `frozen_field` at that field's location. Unlike a whole-model `frozen` config, only that specific field is locked — other fields stay mutable.
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 cc13d1b8c9)
Solutions
- Build a new instance with `instance.model_copy(update={...})` (note: model_copy bypasses validation, so ensure values are valid).
- Drop `frozen=True` from the field if it must be mutable.
- Reconstruct the model from `model_dump()` with the changed value.
- Reserve `frozen=True` for fields whose value should never change after init.
Example fix
# before
class M(BaseModel):
id: int = Field(frozen=True)
name: str
m = M(id=1, name='a')
m.id = 2 # raises frozen_field
# after
m = m.model_copy(update={'id': 2}) Defensive patterns
Strategy: validation
Validate before calling
from pydantic import BaseModel
def frozen_field_names(model_cls) -> set[str]:
return {name for name, f in model_cls.model_fields.items() if getattr(f, 'frozen', False)}
# before mutating:
frozen = frozen_field_names(type(instance))
if name in frozen:
instance = instance.model_copy(update={name: value}) Type guard
def field_is_frozen(model_cls, name: str) -> bool:
return getattr(model_cls.model_fields.get(name), 'frozen', False) Try / catch
from pydantic import ValidationError
try:
setattr(instance, name, value)
except ValidationError as e:
if any(err['type'] == 'frozen_field' for err in e.errors()):
instance = instance.model_copy(update={name: value})
else:
raise Prevention
- Document which fields are frozen in the model docstring.
- Use model_copy(update=...) to 'change' frozen fields.
- Add a unit test asserting frozen fields reject direct assignment.
When it happens
Trigger: `id: int = Field(frozen=True)` then `instance.id = 99` after construction.
Common situations: Selective immutability for primary keys, hashes, or compute-once fields; protecting identity fields from accidental overwrite.
Related errors
- frozen_instance
- validate-by-alias-and-name-false
- decorator-missing-field
- `@validator(..., each_item=True)` cannot be applied to field
- bytes_type
AI-assisted analysis of pydantic/pydantic@cc13d1b8c9 (2026-08-11).
Data as JSON: /api/errors/db3856415303f2a8.
Report an issue: GitHub.