{"record":{"id":"db3856415303f2a8","repo":"pydantic/pydantic","slug":"frozen-field","errorCode":"frozen_field","errorMessage":"Field is frozen","messagePattern":"Field is frozen","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"pydantic/main.py","lineNumber":104,"sourceCode":"\nRebuilding a model isn't thread-safe (the class attributes are mutated during the rebuild,\nwhile other threads may be reading them to perform validation/serialization), so `model_rebuild()`\ncalls are serialized using this lock. The lock is reentrant, as rebuilding a model can trigger the\nrebuild of another one (e.g. when a generic origin is rebuilt during parametrization in\n`__class_getitem__()`). For the same reason, the lock is global and not per-class: two threads\nholding their own class's lock could otherwise request the other's and deadlock.\n\"\"\"\n\n\ndef _check_frozen(model_cls: type[BaseModel], name: str, value: Any) -> None:\n    if model_cls.model_config.get('frozen'):\n        error_type = 'frozen_instance'\n    elif getattr(model_cls.__pydantic_fields__.get(name), 'frozen', False):\n        error_type = 'frozen_field'\n    else:\n        return\n\n    raise ValidationError.from_exception_data(\n        model_cls.__name__, [{'type': error_type, 'loc': (name,), 'input': value}]\n    )\n\n\ndef _model_field_setattr_handler(model: BaseModel, name: str, val: Any) -> None:\n    model.__dict__[name] = val  # pyright: ignore[reportIndexIssue] (https://github.com/microsoft/pyright/issues/11548)\n    model.__pydantic_fields_set__.add(name)\n\n\ndef _private_setattr_handler(model: BaseModel, name: str, val: Any) -> None:\n    if getattr(model, '__pydantic_private__', None) is None:\n        # While the attribute should be present at this point, this may not be the case if\n        # users do unusual stuff with `model_post_init()` (which is where the  `__pydantic_private__`\n        # is initialized, by wrapping the user-defined `model_post_init()`), e.g. if they mock\n        # the `model_post_init()` call. Ideally we should find a better way to init private attrs.\n        object.__setattr__(model, '__pydantic_private__', {})\n    model.__pydantic_private__[name] = val  # pyright: ignore[reportOptionalSubscript]\n","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/pydantic/pydantic/blob/cc13d1b8c978eaf78ed5308329cd41f03ecc3144/pydantic/main.py#L86-L122","documentation":"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.","triggerScenarios":"`id: int = Field(frozen=True)` then `instance.id = 99` after construction.","commonSituations":"Selective immutability for primary keys, hashes, or compute-once fields; protecting identity fields from accidental overwrite.","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."],"exampleFix":"# before\nclass M(BaseModel):\n    id: int = Field(frozen=True)\n    name: str\nm = M(id=1, name='a')\nm.id = 2  # raises frozen_field\n\n# after\nm = m.model_copy(update={'id': 2})","handlingStrategy":"validation","validationCode":"from pydantic import BaseModel\n\ndef frozen_field_names(model_cls) -> set[str]:\n    return {name for name, f in model_cls.model_fields.items() if getattr(f, 'frozen', False)}\n\n# before mutating:\nfrozen = frozen_field_names(type(instance))\nif name in frozen:\n    instance = instance.model_copy(update={name: value})","typeGuard":"def field_is_frozen(model_cls, name: str) -> bool:\n    return getattr(model_cls.model_fields.get(name), 'frozen', False)","tryCatchPattern":"from pydantic import ValidationError\n\ntry:\n    setattr(instance, name, value)\nexcept ValidationError as e:\n    if any(err['type'] == 'frozen_field' for err in e.errors()):\n        instance = instance.model_copy(update={name: value})\n    else:\n        raise","preventionTips":["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."],"tags":["frozen","field","immutable","validation"],"backgroundTag":null,"analyzedSha":"cc13d1b8c978eaf78ed5308329cd41f03ecc3144","analyzedAt":"2026-08-11T16:38:52.905Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}