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: