sqlmodel icon indicating copy to clipboard operation
sqlmodel copied to clipboard

Pyright cannot recognize the type of `SQLModel.__tablename__`

Open Ma233 opened this issue 4 years ago • 11 comments

First Check

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

Commit to Help

  • [X] I commit to help with one of those options 👆

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

Ma233 avatar Sep 14 '21 16:09 Ma233

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

Chedi avatar Sep 30 '21 09:09 Chedi

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

Ma233 avatar Oct 07 '21 02:10 Ma233

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

mudassirkhan19 avatar Oct 28 '21 16:10 mudassirkhan19

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

indivisible avatar Dec 17 '21 11:12 indivisible

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.

cassieopea avatar Jun 04 '22 04:06 cassieopea

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.

cassieopea avatar Jun 21 '22 05:06 cassieopea