Какой тип использовать для declarative_base SQLalchemy?

Я хотел бы использовать Type для своих базовых классов SQLAlchemy, но не могу найти определения типов.

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)

Теперь, когда я ссылаюсь на этот класс User, я не знаю, какой тип использовать. Кроме того, если бы я хотел иметь словарь, который содержит table_name : сопоставление модели, например:

from typing import Any, Dict

table_model_map: Dict[str, Any] = {
    "users": User,
    "another_table": AnotherTableModel,
}

Как мне определить типы для этого Dict

Большое спасибо, что помогли мне понять это!


person Panda    schedule 10.10.2019    source источник


Ответы (2)


Base — это тип, от которого наследуются все ваши модели (которые также являются типами), поэтому

Dict[str, Base]
person Ilja Everilä    schedule 17.10.2019

Илья прав в том, что если все классы вашей модели являются подклассами Base, то это подходящий вариант для типа, хинтующего ваш дикт.

Если, однако, вы хотите ввести hint что-то, что получает Base в качестве аргумента, до того, как вы его создали, то есть что-то вроде следующего:

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)

Это немного сложнее... Я немного покопался и вот что нашел для sqlalchemy-1.4.17.

В 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)

Таким образом, declarative_base вернет экземпляр переданного аргумента ключевого слова metaclass. Таким образом, Base = declarative_base(metaclass=MyNewMetaclass) приведет к type(Base) == MyNewMetaclass.

Кроме того, как видно из registry.generate_base, все атрибуты нового экземпляра метакласса (например, metadata) содержатся в class_dict. Они неявно добавляются как атрибуты к экземпляру во время создания, поэтому они недоступны во время подсказки типа.

(Примечание: я провел небольшое исследование, и оказалось, что, поскольку DeclarativeMeta подклассы type, неявно вызывается type.__new__, который затем добавляет значения в словаре class_dict в качестве атрибутов к экземпляру DeclarativeMeta. Я приведу ссылки на некоторые ресурсы для дальнейшего чтения по адресу конец этого ответа.)

Поэтому, даже если вы наберете подсказку о своей функции, например:


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)

В create_metadata подсказка вашего типа будет предупреждать Unresolved attribute reference 'metadata' for class 'DeclarativeMeta'.

Таким образом, на самом деле нет хорошего способа правильно ввести подсказку declarative_base. Мой подход состоял бы в том, чтобы просто добавить его в строку документации.

Надеюсь, это поможет будущим читателям! :)

Вот еще немного информации о метаклассах type/:

person dylanmorroll    schedule 03.06.2021