Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What Type do I use for SQLalchemy declarative_base?

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!

like image 528
Panda Avatar asked Nov 15 '22 23:11

Panda


2 Answers

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:

  • SqlAlchemy metaclass confusion
  • What is the difference between type and type.__new__ in python?
  • Understanding Object Instantiation and Metaclasses in Python
like image 186
dylanmorroll Avatar answered Dec 29 '22 17:12

dylanmorroll


Base is a type that all your models (that are also types) inherit from, so

Dict[str, Base]
like image 28
Ilja Everilä Avatar answered Dec 29 '22 15:12

Ilja Everilä