2
from sqlmodel import SQLModel


class Foo(SQLModel):
    bar: str
        
    class Config:
        """
        Forbid mutation in order to freeze the inheriting classes
        """
        allow_mutation = False


foo = Foo(bar='bar')

Produces

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_3092/4259691966.py in <module>
----> 1 foo = Foo(bar='bar')

/usr/local/lib/python3.8/site-packages/sqlmodel/main.py in __init__(__pydantic_self__, **data)
    512         object.__setattr__(__pydantic_self__, "__fields_set__", fields_set)
    513         for key, value in values.items():
--> 514             setattr(__pydantic_self__, key, value)
    515         non_pydantic_keys = data.keys() - values.keys()
    516         for key in non_pydantic_keys:

/usr/local/lib/python3.8/site-packages/sqlmodel/main.py in __setattr__(self, name, value)
    530             # non relationship values
    531             if name not in self.__sqlmodel_relationships__:
--> 532                 super().__setattr__(name, value)
    533 
    534     @classmethod

/usr/local/lib/python3.8/site-packages/pydantic/main.cpython-38-x86_64-linux-gnu.so in pydantic.main.BaseModel.__setattr__()

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

Question: Is it possible to forbid mutation for a SQLModel class?

Context: I have a code base with all data model classes built upon frozen Pydantic classes and want to migrate from BaseModel to SQLModel in order to store inheriting table classes.

Of course allow_mutation=True works like a charm.

Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41
solknar
  • 31
  • 5

1 Answers1

2

This is arguably a bug in the current (0.0.8) implementation of SQLModel.__init__.

It calls its own __setattr__, which in turn calls the BaseModel.__setattr__. Since mutation is forbidden, you get the error. This has been mentioned here already. I did not see a PR for fixing it yet, so it may take a while.

EDIT:

The code below is not a usable workaround. BaseModel.__config__ is a class variable, which means that with this code you effectively still set allow_mutation=False after the first initialization. Maybe someone can come up with an idea for how to work around this issue for now.

Original section:

In the meantime, here is a possible workaround:

from sqlmodel import SQLModel


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


class Foo(ImmutableSQLModel):
    bar: str


if __name__ == "__main__":
    foo = Foo(bar="bar")
    print(foo)
    try:
        foo.bar = "baz"
    except Exception as e:
        print(repr(e))

Output:

bar='bar'
TypeError('"Foo" is immutable and does not support item assignment')
Daniil Fajnberg
  • 12,753
  • 2
  • 10
  • 41
  • Thanks a lot for this clear answer! I'll use the workaround until a better solution is found (I see that things are taking time on sqlmodel :'(. This other problem that I had https://github.com/tiangolo/sqlmodel/issues/164, though very simple to solve https://github.com/tiangolo/sqlmodel/pull/442, is still not merged). – solknar Dec 10 '22 at 10:48
  • Nevermind, in your case if you do ```python toto = Foo(bar='bar') titi = Foo(bar='baz') ``` you also get the error :'( – solknar Dec 10 '22 at 12:10
  • 1
    @solknar You're right. My bad. `__config__` is a class variable. I missed that. – Daniil Fajnberg Dec 10 '22 at 12:12
  • @solknar I tried a few tricks, but it seems harder than I thought to work around this issue due to the limitations on Pydantic model attributes. Maybe someone can come up with a better way. I edited my answer accordingly. – Daniil Fajnberg Dec 10 '22 at 12:28
  • Thanks again for your time! :). In the meantime I will stick with unfrozing my models, but I am not thrilled by it :'(. – solknar Dec 10 '22 at 12:33