sqlalchemy2-stubs icon indicating copy to clipboard operation
sqlalchemy2-stubs copied to clipboard

Column wrapping functions not handled correctly

Open alxdb opened this issue 4 years ago • 4 comments
trafficstars

Describe the bug If columns are created with functions wrapping sqlalchemy.Column (e.g. NotNullColumn = functools.partial(sqlalchemy.Column, nullable=False)) they are not recognised by the type checker in places such as initialisation.

Expected behavior A function wrapping the creation of a sqlalchemy.Column object, should be treated the same by the type checker as the constructor of sqlalchemy.Column.

To Reproduce

from sqlalchemy import Column, Integer, orm

Base = orm.declarative_base()

def MyColumn(*args, **kwargs) -> Column:
    return Column(*args, **kwargs)

class Foo(Base):
    bar = MyColumn(Integer)

foo = Foo(bar=1)

Error

example.py:11: error: Unexpected keyword argument "bar" for "Foo"
Found 1 error in 1 file (checked 1 source file)

Versions.

  • OS: MacOS
  • Python: 3.8.2
  • SQLAlchemy: 1.4.2
  • Database: NA
  • DBAPI: NA

alxdb avatar Mar 24 '21 11:03 alxdb

hey there -

i can try to improve this and perhaps have your Column considered to be reutrning "Any", but I can't derive that this Column is against Integer because your function doesn't indicate this:

from sqlalchemy import Column, Integer, orm

def MyColumn(*args, **kwargs) -> Column:
    return Column(*args, **kwargs)

x = MyColumn(Integer)
$ mypy test3.py --strict
test3.py:3: error: Function is missing a type annotation for one or more arguments
test3.py:3: error: Missing type parameters for generic type "Column"

I'm pretty backlogged now but will try to get it to work at least as:

class Foo(Base):
    bar: int = MyColumn(Integer)

zzzeek avatar Mar 24 '21 13:03 zzzeek

ah okay, I see the issue now. Would there be a way to annotate the wrapper function with a generic type? something like def MyColumn(col_type: T, *args, **kwargs) -> Column[T]:?

PS: Thanks so much for your work on this great project 💯 this isn't a serious issue for me atm, so I'm happy to wait a while for this to be addressed

alxdb avatar Mar 24 '21 13:03 alxdb

Yes I think ideally your function would have to use a generic like that, then I could pick up the "T" part.

however great news! there's a workaround you can use for now, which is the _mypy_mapped_attrs attribute, this passes the SQLAlchemy part of this (the --strict thing above is still separate):

class Foo(Base):
    bar: int = MyColumn(Integer)

    _mypy_mapped_attrs = [bar]



foo = Foo(bar=1)

zzzeek avatar Mar 24 '21 13:03 zzzeek

Another solution is to do

class MyInteger(Integer):
    pass

class Foo(Base):
    bar: int = Column(MyInteger())

And for isinstance checks you can use isinstance(col.type, MyInteger)

peteris-zealid avatar Jan 12 '22 14:01 peteris-zealid