I would like to use a Type for my SQLAlchemy base classes, but can't seem to find the type definitions.
Eg:
from sqlalchemy.types import BigInteger
from sqlalchemy.schema import Column, Index
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id_user = Column(
BigInteger, primary_key=True, autoincrement=True, nullable=False, index=True
)
username = Column(String, unique=True, index=True)
Now when referencing this User class, I don't know what type to use. Furthermore, If I wanted to have a dictionary which contains table_name : model mapping like:
from typing import Any, Dict
table_model_map: Dict[str, Any] = {
"users": User,
"another_table": AnotherTableModel,
}
How would I go about defining the types for this Dict
Thank you very much for helping me understand this!
Ilja is right in that if all your model classes subclass Base
, this is a suitable option for type hinting your dict.
If, however, you wanted to type hint something that receives Base
as an argument, before you have created it, i.e. something like the following:
class DatabaseJanitor:
def __init__(self, base: WhatTypeHere?, engine):
self._base = base
self._engine = engine
def create_metadata(self):
self._base.metadata.create_all(self._engine)
That is a bit more tricky... I did some digging and this is what I found for sqlalchemy-1.4.17
.
The following can be seen in sqlalchemy/orm/decl_api.py
:
class DeclarativeMeta(type):
def __init__(cls, classname, bases, dict_, **kw):
...
def declarative_base(
...
metaclass=DeclarativeMeta,
):
...
return registry(
_bind=bind,
metadata=metadata,
class_registry=class_registry,
constructor=constructor,
).generate_base(
mapper=mapper,
cls=cls,
name=name,
metaclass=metaclass,
)
class registry(object):
...
def generate_base(
...
metaclass=DeclarativeMeta,
):
metadata = self.metadata
bases = not isinstance(cls, tuple) and (cls,) or cls
class_dict = dict(registry=self, metadata=metadata)
if isinstance(cls, type):
class_dict["__doc__"] = cls.__doc__
if self.constructor:
class_dict["__init__"] = self.constructor
class_dict["__abstract__"] = True
if mapper:
class_dict["__mapper_cls__"] = mapper
return metaclass(name, bases, class_dict)
So declarative_base
will return an instance of the metaclass
keyword argument passed in. So Base = declarative_base(metaclass=MyNewMetaclass)
will result in type(Base) == MyNewMetaclass
.
Furthermore, as you can see in registry.generate_base
, all the attributes for the new instance of the metaclass (such as metadata
) are contained in class_dict
. These are implicitly added as attributes to the instance during creation, so they are not available during type hinting.
(Side note: I did a bit of research and it appears that because DeclarativeMeta
subclasses type
, type.__new__
is implicitly called which then adds the values in the class_dict
dictionary as attributes to the instance of DeclarativeMeta
. I'll link some resources for further reading at the end of this answer.)
So even if you type hint your function like so:
class DatabaseJanitor:
def __init__(self, base: DeclarativeMeta, engine):
self._base = base
self._engine = engine
def create_metadata(self):
self._base.metadata.create_all(self._engine)
In create_metadata
, your type hinting will warn
Unresolved attribute reference 'metadata' for class 'DeclarativeMeta'
.
So there's not really a good way to type hint declarative_base
properly. My approach would be to just add it to the docstring for now.
Hope this helps for any future readers! :)
Here's some further reading on type/ metaclasses:
Base
is a type that all your models (that are also types) inherit from, so
Dict[str, Base]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With