Closed iskyd closed 1 year ago
Hi,
yes, that's currently expect. In v2 base will be generated from a superclass using the classic class Base(superclass):
.
That's not really fixable in the 1.4, add a type: ignore
@CaselIT adding it's not a good solution because you still get all other errors related to the fact that mypy can't create a mapping.
Consider this snippet
from sqlalchemy import String, select
from sqlalchemy.orm import declarative_base
from typing import Optional, Dict, Type
import sqlalchemy
from sqlalchemy.orm.ext import DeclarativeMeta
def get_base() -> Type[DeclarativeMeta]:
return declarative_base()
Base: Type[DeclarativeMeta] = get_base()
class User(Base): # type: ignore
__tablename__ = "user"
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
name = sqlalchemy.Column(sqlalchemy.String)
def foo(name: str):
print(name)
u = User(id=1, name="Pippo")
mypy gives this error: error: Argument 1 to "foo" has incompatible type "Column[String]"; expected "str"
It seems that without Base class using sqlalchemy plugin for mypy isn't really helpfull. Am i missing something?
the mypy plugin will allow you to use the declarative_base() function directly:
Base = declarative_base()
The plugin will convince mypy that "Base" is a class. however, it won't work if you place the call to declarative_base() inside of another function. Python typing very plainly does not support classes being returned from functions.
To use the mypy plugin with a base class that isn't built using declarative_base(), in 1.4 you can use the recipe at https://docs.sqlalchemy.org/en/14/orm/declarative_styles.html#creating-an-explicit-base-non-dynamically-for-use-with-mypy-similar .
as mentioned before, in 2.0 SQLAlchemy provides new patterns that support typing without plugins from start to finish.
@zzzeek I'm trying with the following snippet that works.
from sqlalchemy import String, select
from sqlalchemy.orm import registry
from typing import Optional, Dict, Type
import sqlalchemy
from sqlalchemy.orm.decl_api import DeclarativeMeta
mapper_registry = registry()
class Base(metaclass=DeclarativeMeta):
__abstract__ = True
registry = mapper_registry
metadata = mapper_registry.metadata
__init__ = mapper_registry.constructor
class User(Base):
__tablename__ = "user"
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
name = sqlalchemy.Column(sqlalchemy.String)
def foo(name: str):
print(name)
u = User(id=1, name="Pippo")
foo(u.name)
But I don't get how it fits with the declarative_base() returned by a function. This way I'm going to simply override Base class. Does it change the behaviour of the system? Where the declarative_base() returns is going to be used?
the declarative_base() function invokes Python code that is the equivalent operation as doing your "class Base" declaration. It uses the Python type() function to create a new type. But also it could just as well have that same "class Base" declaration inside of it, then return the class; Python typing has no way to represent that returned class as something you can subclass.
short answer no, there's nothing different. as long as your Base class is making mappings, it's doing the thing it's supposed to do.
Is there a guideline to addressing this issue in 2.0? We are on 2.0.12 and still encounter the same type checking error.
error: Invalid base class "Base" [misc]
error: Variable "DBApp.models.Base" is not valid as a type [valid-type]
with:
Base: DeclarativeMeta = declarative_base()
class Users(Base, DiffMixin):
__tablename__ = "users"
(...)
the declarative_base function is kinda deprecated and you should do as all the v2 examples in the docs, creating the base manually:
>>> from sqlalchemy.orm import DeclarativeBase
>>> class Base(DeclarativeBase):
... pass
Closing since it's solved in v2
Describe the bug mypy returns [Invald ](error: Invalid base class "Base" [misc]) when Base class is returned by a function.
To Reproduce
mypy.ini
test.py
Error
Versions.
Have a nice day!