Skip to content
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

🐛 Fix allowing using a ForeignKey directly, remove repeated column construction from SQLModelMetaclass.__init__ and upgrade minimum SQLAlchemy to >=1.4.36 #443

Merged
merged 5 commits into from Oct 23, 2023

Conversation

daniil-berg
Copy link
Contributor

@daniil-berg daniil-berg commented Sep 9, 2022

Summary

There was a bug in the SQLModelMetaclass whereby the SQLAlchemy Column objects were constructed twice for each field defined on a table model: First in the meta class' __new__ method and then again in its __init__ method.

With these changes the get_column_from_field function is called only once for each field, namely in SQLModelMetaclass.__new__.

Example

Construct a table model with a foreign key explicitly constructed via the SQLAlchemy ForeignKey class:

from sqlmodel import Field, SQLModel, create_engine
from sqlalchemy.sql.schema import ForeignKey


class User(SQLModel, table=True):
    id: int = Field(primary_key=True)


class Post(SQLModel, table=True):
    id: int = Field(primary_key=True)
    user_id: int = Field(
        sa_column_args=(ForeignKey("user.id", ondelete="CASCADE"),)
    )

Without the proposed changes, even just running these class definitions causes an error:

Traceback (most recent call last):
  File "/home/daniil/coding/sqlmodel/experiments/__init__.py", line 12, in <module>
    class Post(SQLModel, table=True):
  File "/home/daniil/coding/sqlmodel/sqlmodel/main.py", line 329, in __init__
    dict_used[field_name] = get_column_from_field(field_value)
  File "/home/daniil/coding/sqlmodel/sqlmodel/main.py", line 457, in get_column_from_field
    return Column(sa_type, *args, **kwargs)  # type: ignore
  File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/sql/schema.py", line 1765, in __init__
    self._init_items(*args)
  File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/sql/schema.py", line 144, in _init_items
    spwd(self, **kw)
  File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/sql/base.py", line 1047, in _set_parent_with_dispatch
    self._set_parent(parent, **kw)
  File "/home/daniil/.cache/pypoetry/virtualenvs/sqlmodel-TV15XYpK-py3.10/lib/python3.10/site-packages/sqlalchemy/sql/schema.py", line 2559, in _set_parent
    raise exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: This ForeignKey already has a parent !

This is fixed with this PR.

Add the following to the example:

sqlite_url = "sqlite:///:memory:"
engine = create_engine(sqlite_url, echo=True)
SQLModel.metadata.create_all(engine)

There is no more error and the SQL statements are as expected:

CREATE TABLE user (
	id INTEGER NOT NULL, 
	PRIMARY KEY (id)
)

CREATE TABLE post (
	id INTEGER NOT NULL, 
	user_id INTEGER NOT NULL, 
	PRIMARY KEY (id), 
	FOREIGN KEY(user_id) REFERENCES user (id) ON DELETE CASCADE
)

PS

The codecov bot is weird... I really don't see how coverage could possibly have decreased. The statement below is also inconsistent with the report, if you click on it. Oh well.

have `get_column_from_field` be called once for each field (in `SQLModelMetaclass.__new__`)
@codecov
Copy link

codecov bot commented Sep 9, 2022

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (ea79c47) 98.49% compared to head (5ad7c6a) 97.76%.
Report is 66 commits behind head on main.

❗ Current head 5ad7c6a differs from pull request most recent head 50a3f76. Consider uploading reports for the commit 50a3f76 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #443      +/-   ##
==========================================
- Coverage   98.49%   97.76%   -0.74%     
==========================================
  Files         185      187       +2     
  Lines        5856     6266     +410     
==========================================
+ Hits         5768     6126     +358     
- Misses         88      140      +52     
Files Coverage Δ
sqlmodel/main.py 84.83% <ø> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@github-actions
Copy link

github-actions bot commented Sep 9, 2022

📝 Docs preview for commit 5ad7c6a at: https://631b234f16d4cf27863512be--sqlmodel.netlify.app

@github-actions
Copy link

📝 Docs preview for commit eee8e80 at: https://639cdfc1a7c2ed00568a9bc6--sqlmodel.netlify.app

@Vanderhoof
Copy link

Vanderhoof commented Mar 3, 2023

Hey is there a workaround for cascade FK definition now while this fix is being investigated? 😊

UPD, probably something like this:

from sqlmodel import Field, SQLModel, create_engine
from sqlalchemy import Column, Integer
from sqlalchemy.sql.schema import ForeignKey


class User(SQLModel, table=True):
    id: int = Field(primary_key=True)


class Post(SQLModel, table=True):
    id: int = Field(primary_key=True)
    user_id: int = Field(
        sa_column=Column("user_id", Integer, ForeignKey("user.id", ondelete="CASCADE")),)
    )

@tiangolo tiangolo added the bug Something isn't working label Oct 22, 2023
@tiangolo tiangolo changed the title 🐛 Remove repeated column construction from SQLModelMetaclass.__init__ 🐛 Fix allowing using a ForeignKey directly, remove repeated column construction from SQLModelMetaclass.__init__ Oct 23, 2023
@github-actions
Copy link

📝 Docs preview for commit 50a3f76 at: https://7b43a033.sqlmodel.pages.dev

@tiangolo
Copy link
Owner

Awesome, thanks a lot for the thorough explanation @daniil-berg! 🚀

I tweaked it and updated the internals a bit.

This will be available in the next version, 0.0.9. 🎉

@tiangolo tiangolo changed the title 🐛 Fix allowing using a ForeignKey directly, remove repeated column construction from SQLModelMetaclass.__init__ 🐛 Fix allowing using a ForeignKey directly, remove repeated column construction from SQLModelMetaclass.__init__ and upgrade minimum SQLAlchemy to >=1.4.36 Oct 23, 2023
@tiangolo tiangolo merged commit 9809b5b into tiangolo:main Oct 23, 2023
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants