Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Using Pydantic version >2.10.6, the frozen attribute is not working properly. Below is how we have set up the basic model that will be used by all the other models in our project:

class Model(BaseModel):
    """
    """
    model_config = ConfigDict(extra="forbid", validate_default=True)
    def __init__(self, **data) -> None:
        """
        Initializes the model and freezes it after validation.
        """
        super().__init__(**data)
        object.__setattr__(self, "model_config", ConfigDict(frozen=True))

But the frozen attribute is being ignored, and changing the attributes of the class works:
image

I saw that _check_frozen was modified, and the frozen attribute is now being checked at the class level, not at the instance level. Details

Do you have any idea what the problem could be, and do you have any suggestions?

**We want to freeze the model after the initialization is done.

You must be logged in to vote

Replies: 2 comments · 6 replies

Comment options

The solution you were using wasn't meant to be supported, and could lead (before and after 2.10) to a lot of unexpected behavior. You are mutating model_config — which is a class variable — from an instance.

What is your use case to have models frozen only after validation?

You must be logged in to vote
6 replies
@Viicos
Comment options

I would suggest using object.__setattr__() to overwrite the attributes to avoid having to temporarily modify the model config (which is not supported atm).

@AritaAlidemaj
Comment options

If I understood you correctly, you are suggesting adding the frozen attribute in model_config, and if needed, changing the attribute values in validators by using object.__setattr__(). This way, the model is frozen, but we can change the attribute values when needed.
image

@Viicos
Comment options

Yes exactly, we know it's not ideal but for now we decided not to commit into implementing support for this (or potentially we need to figure out a general solution that would allow temporarily modifying the config).

@AritaAlidemaj
Comment options

Great, thank you!
One more question from my side, since the solution we were using wasn't meant to be supported, shouldn't it raise an error when trying to modify the model config?

@Viicos
Comment options

We could (or maybe a warning), but it may cause a lot of churn for users relying on this where it accidentally work (https://xkcd.com/1172/).

Comment options

Thanks for the clarification about frozen being a class-level configuration and not intended to be mutated per instance. That makes perfect sense.

For our use case—where we need to perform some post-initialization mutations (e.g., normalizing fields, computing derived values) after validation but before the model becomes immutable—we’ve adopted the recommended pattern of using object.__setattr__.

To make this more ergonomic and reusable, we wrapped it into a context manager and a decorator. Here’s a minimal working example:

from pydantic import BaseModel, ConfigDict
from functools import wraps


class ForceSetAttr:
    """Context manager to temporarily allow setting attributes on frozen Pydantic models."""

    def __init__(self, obj: BaseModel):
        if not isinstance(obj, BaseModel):
            raise TypeError(
                "ForceSetAttr can only be used with Pydantic BaseModel instances."
            )
        self._obj = obj

    def __enter__(self):
        self._original_setattr = self._obj.__class__.__setattr__
        self._obj.__class__.__setattr__ = self._setattr
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._obj.__class__.__setattr__ = self._original_setattr

    def _setattr(self, name, value):
        obj = self._obj
        config = obj.model_config
        if config.get("frozen", False):
            if config.get("validate_assignment", False):
                obj.__pydantic_validator__.validate_assignment(obj, name, value)
            else:
                object.__setattr__(obj, name, value)
        else:
            setattr(obj, name, value)


def force_set_attr(method):
    """Decorator to force attribute setting on frozen Pydantic models."""

    @wraps(method)
    def wrapper(self, *args, **kwargs):
        with ForceSetAttr(self):
            return method(self, *args, **kwargs)

    return wrapper


class TestModel(BaseModel, frozen=True):
    id: int
    name: str
    value: float

    model_config = ConfigDict(validate_assignment=True)

    @force_set_attr
    def model_post_init(self, context):
        self.name = self.name.upper()
        self.value = self.value * 2
        self.id = 1.0


print(TestModel(id=1, name="example", value=10.0))

# Output: id=1 name='EXAMPLE' value=20.0

It is worth mentioning that when both frozen and validate_assignment are set to True, the forced assignment of the model will not skip the field validation.
We’re happy with this solution and hope it might also help others facing similar needs. Thanks again for your guidance!

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.