Skip to content

Field(allow_mutation=False) doesn't have same behavior as pydantic #262

Closed

Description

@winglian

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the SQLModel documentation, with the integrated search.
  • I already searched in Google "How to X in SQLModel" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to SQLModel but to Pydantic.
  • I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

import pytest
import sqlmodel
import pydantic


class MyPydanticModel(pydantic.BaseModel):
    id: str
    data: str = pydantic.Field(allow_mutation=False)

    class Config:
        validate_assignment = True


class MySqlmodelModel(sqlmodel.SQLModel):
    id: str
    data: str = sqlmodel.Field(allow_mutation=False)

    class Config:
        validate_assignment = True


class TestSuite:
    def test_pydantic_can_create(self):
        MyPydanticModel(id='abc', data="foo")

    def test_pydantic_raises_on_update(self):
        my_pyd_mdl = MyPydanticModel(id='abc', data="foo")
        with pytest.raises(TypeError):
            my_pyd_mdl.data = "bar"

    def test_sqlmodel_can_create(self):
        MySqlmodelModel(id='abc', data="foo")

    def test_sqlmodel_raises_on_update(self):
        my_sqlmdl_mdl = MySqlmodelModel(id='abc', data="foo")
        with pytest.raises(TypeError):
            my_sqlmdl_mdl.data = "bar"

Description

When setting a Field(allow_mutation=False) in combination with the required validate_assignment = True configuration on a SQLModel, the framework doesn't even allow the field to be set in the initial construction of the model. This is allowed in pydantic. I've attached a test suite that we should expect to pass, but it fails both the SQLModel specific tests.

Operating System

macOS

Operating System Details

Big Sur 11.3.1

SQLModel Version

0.0.6

Python Version

3.9.9

Additional Context

No response

Activity

  1. byrman commented on Mar 6, 2022

    @byrman
    Contributor

    Several issues have been reported about inconsistencies between sqlmodel and pydantic: #87, #230, etc. This particular issue is caused by these lines in main.py:

    # Do not set values as in Pydantic, pass them through setattr, so SQLAlchemy
    # can handle them
    # object.__setattr__(__pydantic_self__, '__dict__', values)
    object.__setattr__(__pydantic_self__, "__fields_set__", fields_set)
    for key, value in values.items():
        setattr(__pydantic_self__, key, value)
    

    The last line is assignment, which is forbidden by allow_mutation=False. Unfortunately, the problem is not so easily fixed, because the handling of attributes is rather complex in sqlmodel. I have fixed it here, but it feels like duct tape.

  2. WalkerWalker commented on Jan 14, 2023

    @WalkerWalker

    I also hope the immutatable model can be possible in sqlmodel. The same problem is reported in stackoverflow here

    The problem still exists in 0.0.8

  3. tepelbaum commented on Jan 14, 2024

    @tepelbaum

    Hi! Does the recent version of SQLModel (0.0.14) solves the issue? :)

  4. EkremDincel commented on Apr 13, 2025

    @EkremDincel

    I am having the same problem at version 0.0.24, and even setting validate_assignment = False doesn't work anymore. Actually, my problem has to do with frozen=True argument instead of the allow_mutation=False argument, as the later is deprecated at the pydantic side and doesn't even do anything anymore (which also means that the SQLModel API interface should be updated as well).

    Below is an example stacktrace, where UserDB is a SQLModel with a table=True, and has a field like this:

    	date_of_creation: datetime = Field(
    		# allow_mutation=True, # This seems meaningless as it is a pydantic v1 argument
    		default_factory=datetime.now,
    		schema_extra={"frozen": True} # see https://github.com/fastapi/sqlmodel/issues/262#issuecomment-1060041950
    	)

    The stacktrace is as follows:

      File "D:\Projects\server\backend\source\data\models\user.py", line 75, in sql
        return UserDB(**self.dict())
               ^^^^^^^^^^^^^^^^^^^^^
      File "<string>", line 4, in __init__
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlalchemy\orm\state.py", line 571, in _initialize_instance
        with util.safe_reraise():
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlalchemy\util\langhelpers.py", line 146, in __exit__
        raise exc_value.with_traceback(exc_tb)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlalchemy\orm\state.py", line 569, in _initialize_instance
        manager.original_init(*mixed[1:], **kwargs)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlmodel\main.py", line 814, in __init__
        sqlmodel_init(self=__pydantic_self__, data=data)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlmodel\_compat.py", line 356, in sqlmodel_init
        sqlmodel_table_construct(
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlmodel\_compat.py", line 275, in sqlmodel_table_construct
        setattr(self_instance, key, value)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\sqlmodel\main.py", line 827, in __setattr__
        super().__setattr__(name, value)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\pydantic\main.py", line 875, in __setattr__
        self._check_frozen(name, value)
      File "D:\Projects\server\backend\.venv\Lib\site-packages\pydantic\main.py", line 939, in _check_frozen
        raise pydantic_core.ValidationError.from_exception_data(self.__class__.__name__, [error])
    pydantic_core._pydantic_core.ValidationError: 1 validation error for UserDB
    date_of_creation
      Field is frozen [type=frozen_field, input_value=datetime.datetime(2025, 4, 13, 18, 42, 57, 637768), input_type=datetime]
        For further information visit https://errors.pydantic.dev/2.9/v/frozen_field
    

    The last callstack at the SQLModel side is here. I wanted to fix it, but it seems so deep down in the callstack, and is nested inside multiple dunder method calls, so I have no idea how to even start fixing that.

    Also, can we get a bug label for this issue please?

  5. YuriiMotov commented on Aug 27, 2025

    @YuriiMotov
    Member

    I just tested it with SQLAlchemy 0.0.24, Pydantic 1.10.22 / 2.11.7 and tests pass.

    Here is adapted test (in order to work with both, Pydantic V1 and V2) (in the details):

    Details
    import pytest
    import sqlmodel
    import pydantic
    
    
    if pydantic.VERSION.startswith("2."):
    
        class MyPydanticModel(pydantic.BaseModel):
            id: str
            data: str = pydantic.Field(frozen=True)
    
            model_config = {"validate_assignment": True}
    
    
        class MySqlmodelModel(sqlmodel.SQLModel):
            id: str
            data: str = sqlmodel.Field(schema_extra={"frozen": True})
    
            model_config = {"validate_assignment": True}
    
    else:
    
        class MyPydanticModel(pydantic.BaseModel):
            id: str
            data: str = pydantic.Field(allow_mutation=False)
    
            class Config:
                validate_assignment = True
    
    
        class MySqlmodelModel(sqlmodel.SQLModel):
            id: str
            data: str = sqlmodel.Field(allow_mutation=False)
    
            class Config:
                validate_assignment = True
    
    
    class TestSuite:
        def test_pydantic_can_create(self):
            MyPydanticModel(id='abc', data="foo")
    
        def test_pydantic_raises_on_update(self):
            my_pyd_mdl = MyPydanticModel(id='abc', data="foo")
            with pytest.raises((pydantic.ValidationError, TypeError)):
                my_pyd_mdl.data = "bar"
    
        def test_sqlmodel_can_create(self):
            MySqlmodelModel(id='abc', data="foo")
    
        def test_sqlmodel_raises_on_update(self):
            my_sqlmdl_mdl = MySqlmodelModel(id='abc', data="foo")
            with pytest.raises((pydantic.ValidationError, TypeError)):
                my_sqlmdl_mdl.data = "bar"

    Use case of @EkremDincel is a bit different. And it still doesn't work.
    It shows the inability to instantiate model with table=True and immutable field:

    import sqlmodel
    
    class MySqlmodelModel(sqlmodel.SQLModel, table=True):
        id: str = sqlmodel.Field(primary_key=True)
        data: str = sqlmodel.Field(schema_extra={"frozen": True})
    
        model_config = {"validate_assignment": True}
    
    class TestSuite:
        def test_sqlmodel_can_create(self):
            MySqlmodelModel(id="abc", data="foo")
    
        #  1 validation error for MySqlmodelModel
        #        data
        #          Field is frozen
  6. locked and limited conversation to collaborators on May 18, 2026
  7. converted this issue into a discussion #1949 on May 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions