Mypy errors: Incompatible type for optional ids & Incompatible type for SQLModel attribute in expression #1491
-
First Check
Commit to Help
Example Codefrom typing import Optional
from sqlmodel import SQLModel, create_engine, Session, Field, select
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
engine = create_engine("sqlite:///.deleted/database.db")
SQLModel.metadata.create_all(engine)
hero_1 = Hero(name="Grey Hound")
def format_id(id: int):
return f"{id:04d}"
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
session.exec(select(Hero).where(Hero.id.in_([1, 2])))
print(format_id(hero_1.id)) DescriptionWhen executing mypy on the code above I get the following output:
So:
Thank you! Operating SystemLinux Operating System DetailsNo response SQLModel Version0.0.6 Python VersionPython 3.9.10 Additional Contextmypy version: |
Beta Was this translation helpful? Give feedback.
Replies: 10 comments
-
This might be considered an "exotic pattern", just like my #271 . 🙂 |
Beta Was this translation helpful? Give feedback.
-
@StefanBrand note that there are two different mypy issues above. One is the use of The other is the most basic use of SQLModel which is to define a model with an id field as |
Beta Was this translation helpful? Give feedback.
-
Is a bit strange though to declare a PK 'optional', as it is always there, in the db model. I guess you get this though when don't have separate models for handling POST requests for creation or whatever, but just reuse the same classes. I've been using separate create, db and view classes, like in the tutorial, and find it quite nice. Anyhow am not arguing, I guess this would be nice to solve somehow. |
Beta Was this translation helpful? Give feedback.
-
@antont this happens even when you use different models for db and creation. If you separate them, you can exclude the id field from the creation model, but when you create the db instance from the creation instance, the id is empty. Therefore as I understand it, if you don't have an optional int as the id field the Pydantic validation for the model will fail, as before saving it to the database the instance of the db model has no id yet. |
Beta Was this translation helpful? Give feedback.
-
@javivdm I autogenerate the id's at creation, so they won't be empty even though the *Create model that gets the POST data does not have them. However my current code does declare them optional in the DB class too, I had forgotten why, and had added a comment, I can try at some point if it works when that is not Optional.
In the POST handler for creation I do
|
Beta Was this translation helpful? Give feedback.
-
This is the tutorial section @javivdm is referring to: https://sqlmodel.tiangolo.com/tutorial/fastapi/multiple-models/#multiple-hero-schemas It explains why you need |
Beta Was this translation helpful? Give feedback.
-
@StefanBrand - right. The explanation is not very elaborate, though, but I guess it is because the db model is first instantiated in memory, with no value for id yet, and only when it's then written to the db, the id gets generated. Am still curious to try it out but I figure will get an error because of that. |
Beta Was this translation helpful? Give feedback.
-
I have similar errors for a not null check in a query like
(update: seems like this is #109) |
Beta Was this translation helpful? Give feedback.
-
I'm not sure about solving the first point, but to
Replace |
Beta Was this translation helpful? Give feedback.
-
The As for def format_id(id: int | None):
assert id is not None
return f"{id:04d}" Or, create one more model and re-define the type of class HeroBase(SQLModel):
name: str
class HeroDB(HeroBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
class Hero(HeroBase):
id: int Then after fetching the object from DB, convert it to session.refresh(hero_1)
hero_1_read = Hero.model_validate(hero_1)
print(format_id(hero_1_read.id)) Runnable code example in the details: from typing import Optional
from sqlmodel import Field, Session, SQLModel, col, create_engine, select
class HeroBase(SQLModel):
name: str
class HeroDB(HeroBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
class Hero(HeroBase):
id: int
engine = create_engine("sqlite:///")
SQLModel.metadata.create_all(engine)
hero_1 = HeroDB(name="Grey Hound")
def format_id(id: int):
return f"{id:04d}"
with Session(engine) as session:
session.add(hero_1)
session.commit()
session.refresh(hero_1)
with Session(engine) as session:
hero_1_db = session.exec(select(HeroDB).where(col(HeroDB.id).in_([1, 2]))).first()
hero_1_read = Hero.model_validate(hero_1_db)
print(format_id(hero_1_read.id)) |
Beta Was this translation helpful? Give feedback.
The
has no attribute "in_"
error is fixed by usingcol
as @danielunderwood pointed.As for
incompatible type "Optional[int]"; expected "int"
error, you can either make the input parameter offormat_id
optional and then assert that it's notNone
inside:Or, create one more model and re-define the type of
id
field:Then after fetching the object from DB, convert it to
Hero
: