fastapi / sqlmodel

SQL databases in Python, designed for simplicity, compatibility, and robustness.
https://sqlmodel.tiangolo.com/
MIT License
14.44k stars 658 forks source link

Pyright cannot recognize the type of `SQLModel.__tablename__` #98

Open Ma233 opened 3 years ago

Ma233 commented 3 years ago

First Check

Commit to Help

Example Code

from sqlmodel import SQLModel

reveal_type(SQLModel.__tablename__)

# mypy(0.910) output: Revealed type is "Union[builtins.str, def (*Any, **Any) -> builtins.str]"
# pyright(1.1.166) output: Type of "SQLModel.__tablename__" is "declared_attr"

class User(SQLModel, table=True):  # pyright error: Instance variable "__name__" overrides class variable of same name in class "SQLModel"
    __tablename__ = "users"  # pyright error: Expression of type "Literal['users']" cannot be assigned to declared type "declared_attr"
    name: str

Description

This will cause a type error when you declare __tablename__ with pyright as type checker. Like:

Expression of type "Literal['users']" cannot be assigned to declared type "declared_attr"

Operating System

Linux, macOS

Operating System Details

No response

SQLModel Version

0.0.4

Python Version

3.8.6

Additional Context

No response

Chedi commented 3 years ago

this is my workaround to silence that type of errors (not my proudest hack but it work)

from sqlmodel import SQLModel
from sqlalchemy.orm import declared_attr

class User(SQLModel, table=True): 
    name: str

    @declared_attr
    def __tablename__(cls):  # noqa: N805
        return 'users'

inspired by the declared_attr documentation, tested with pyright 1.1.172 (Linux), python 3.9.7, sqlmodel 0.0.4

Ma233 commented 3 years ago

@Chedi Hi, of my own workaround is adding a # type: ignore after __tablename__ = "users".

mudassirkhan19 commented 3 years ago

With @Chedi 's workaround, I still get an error on Mypy: error: Signature of "__tablename__" incompatible with supertype "SQLModel". Only # type: ignore works.

indivisible commented 2 years ago

another really easy workaround is just specifying the type: __tablename__: str = 'poop'

ajlive commented 2 years ago

another really easy workaround is just specifying the type: __tablename__: str = 'poop'

Note that Pyright complains at this:

"__tablename__" incorrectly overrides property of same name in class "SQLModel" Pylance(reportIncompatibleMethodOverride)

To satisfy the type checker you need to use the declared_attr approach, eg:

class Lineitem(SQLModel, table=True):
    @declared_attr
    def __tablename__(cls):
        return "invoice_lineitems"

This could be mentioned in the docs, and maybe in a future version a more intuitive approach could be used, such as putting it in a Meta as ormar does:

class Lineitem(SQLModel, table=True):
    class Meta:
        tablename: str = "invoice_lineitems"

I thought about making a request for this but I'm guessing there's already a lot on the roadmap.

ajlive commented 2 years ago

Since I posted https://github.com/tiangolo/sqlmodel/issues/98#issuecomment-1146535179, I've started simply doing

    __tablename__: str = "invoices_legacy"  #  type: ignore

I think this is by far the best solution for people still getting the "incorrect override" error for the line without type: ignore. As I said when I mentioned this in https://github.com/tiangolo/sqlmodel/issues/159,

I choose this last method because the others are far too verbose for something as simple as declaring the table name. This may seem hacky, but although I have to tell Pyright to ignore a number of things in sqlmodel, it's not half the file like I have to type: ignore with sqlalchemy.