Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
8 tasks done
winglian opened this issue Mar 5, 2022 · 4 comments
Open
8 tasks done
Labels
question Further information is requested

Comments

@winglian
Copy link

winglian commented Mar 5, 2022

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

@winglian winglian added the question Further information is requested label Mar 5, 2022
@byrman
Copy link
Contributor

byrman commented Mar 6, 2022

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.

@WalkerWalker
Copy link

WalkerWalker commented Jan 14, 2023

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

@tepelbaum
Copy link

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

@EkremDincel
Copy link

EkremDincel commented Apr 13, 2025

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants