Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to dynamically change the mutability of a pydantic class?

I was thinking about using a pydantic class to support a builder pattern, where the object values are set, one attribute at a time, and then at the end, the object values are frozen.

To that end, is it possible in pydantic to make an object immutable after starting out mutable?

like image 730
morfys Avatar asked Sep 20 '25 08:09

morfys


1 Answers

If the class is subclassed from BaseModel, then mutability/immutability is configured by adding a Model Config inside the class with an allow_mutation attribute set to either True/False.

Sample Code:

from pydantic import BaseModel, NonNegativeInt

class Person(BaseModel):
    name: str
    age: NonNegativeInt

    class Config:
        allow_mutation = False

p = Person(name='Jack', age=15)
p.name = 'Jill'

Sample Output:

TypeError: "Person" is immutable and does not support item assignment

Now, this Config object and allow_mutation attribute (or any of the other Model Config attributes) can also be accessed and toggled from methods of the class as:

self.__config__.allow_mutation

Sample Code:

class Person(BaseModel):
    name: str
    age: NonNegativeInt

    def __init__(self, **data) -> None:
        super().__init__(**data)
        self.__config__.allow_mutation = False

    def is_mutable(self):
        print(self.__config__, self.__config__.allow_mutation)

p = Person(name='Jack', age=15)
p.is_mutable()
p.name = 'Jill'

Sample Output:

<class '__main__.Config'> False
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    p.name = 'Jill'
  File "pydantic/main.py", line 418, in pydantic.main.BaseModel.__setattr__
TypeError: "Person" is immutable and does not support item assignment

Putting that together, you can make a class initially mutable (allow_mutation is True), then set its attributes one at a time (directly or via a method), then at the end, call some method to make it immutable (allow_mutation is False):

Sample Code:

from pydantic import BaseModel, NonNegativeInt

class Person(BaseModel):
    name: str
    age: NonNegativeInt

    def __init__(self, **data) -> None:
        super().__init__(**data)
        self.__config__.allow_mutation = True

    def build(self):
        self.__config__.allow_mutation = False

print('-- Create a Person --')
p = Person(name='Jack', age=15)
print(p)

print('-- It is initially mutable, can set its attributes --')
p.name = 'Jill'
p.age = 17
print(p)

print('-- Making it immutable --')
p.build()

p.name = 'Jack'

Sample Output:

-- Create a Person --
name='Jack' age=15
-- It is initially mutable, can set its attributes --
name='Jill' age=17
-- Making it immutable --
Traceback (most recent call last):
  File "test.py", line 29, in <module>
    p.name = 'Jack'
  File "pydantic/main.py", line 418, in pydantic.main.BaseModel.__setattr__
TypeError: "Person" is immutable and does not support item assignment

Some notes about this:

  1. Accessing the private/internal __config__ object may not be a future-proof solution. If it gets renamed or how the Config object is used changes, then this solution breaks. It is also not mentioned in the ModelConfig section of the docs, so it's probably not intended to be used like this.

  2. Pydantic mentions that this allow_mutation is just "faux immutability". It basically aborts the __setattr__ call if allow_mutation is False:

    # In pydantic/main.py > BaseModel
    
        elif not self.__config__.allow_mutation or self.__config__.frozen:
            raise TypeError(f'"{self.__class__.__name__}" is immutable and does not support item assignment')
    
like image 173
Gino Mempin Avatar answered Sep 21 '25 23:09

Gino Mempin