python - 如何创建从外部库中的 sqlalchemy 模型继承的自定义类

标签 python inheritance sqlalchemy polymorphism

我有一个用例,需要我以相反的方式配置类,而不是创建模型继承的混合类。通常是混合类的类需要是从模型继承的类以及创建模型对象的类。这是因为模型和映射器配置位于主存储库的外部库中。在加载任何模型之前,我需要将引擎的主机从主存储库传递到模型库,以便它们可以加载已配置的声明性基础。传入引擎信息后, session 、基类和所有内容都会在模型继承的基类中创建。这是一个简化的示例:

class SQLAlchemyBase(object):

    metadata = None
    Session = None
    Base = object
    sessionfactory = sessionmaker()

    def initialize(self, host):
        engine = create_engine(host)
        self.metadata = MetaData(bind=engine)
        self.Session = scoped_session(self.sessionfactory)
        self.Base = declarative_base(metadata=self.metadata)

models = SQLAlchemyBase()

(模型继承自models.Base)

因此,SQLAlchemyBase 将被导入主存储库,将调用初始化方法,传入引擎的主机,然后可以导入模型。主存储库有自己的类,其名称与模型相同,并且具有普通 mixin 类必须扩展功能的附加方法。但是,我无法使用主存储库中的类创建模型对象,因为我无法让映射器很好地处理从外部模型库扩展的这种不寻常的继承。此外,在模型库中,存在具有多级继承多态关系的模型。下面是一个类似于更基本的继承多态关系之一的示例:

模型库

class Foo(models.Base):

    __tablename__ = "foo"
    id = Column(Integer, primary_key=True)
    type = Column(String)
    foo_bar_id = Column(Integer, ForeignKey("foo_bar.id"))
    foo_bar = relationship(Foo, backref=backref("foos"))

    __mapper_args__ = {"polymorphic_on": type}


class Bar(Foo):

    __mapper_args__ = {"polymorphic_identity": "bar"}


class FooBar(models.Base):

    __tablename__ = "foo_bar"
    id = Column(Integer, primary_key=True)

主存储库

from separate_library.models import models, Foo as BaseFoo, Bar as BaseBar, FooBar as BaseFooBar


class Foo(BaseFoo):

    @classmethod
    def custom_create_method(cls, **kw):
        foo_obj = cls(**kw)
        models.session.add(foo_obj)
        models.session.flush()


class Bar(BaseBar):
    pass


class FooBar(BaseFooBar):
    pass

我收到的原始错误是这样的:

InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers.
Original exception was: Multiple classes found for path Foo in the registry of this declarative base. Please use a fully module-qualified path.

所以我尝试将完整路径放入关系中。然后它开始给我一个这样的错误:

FlushError: Attempting to flush an item of type as a member of collection FooBar.foos. Expected an object of type or a polymorphic subclass of this type. If is a subclass of , configure mapper Mapper|Foo|foo to load this subtype polymorphically, or set enable_typechecks=False to allow any subtype to be accepted for flush.

本质上,主要问题是让主模块中的类指向模型类并表现得像模型类。例如,当我尝试创建关系时,它说它需要一个 separate_library.models.Foo 类型的对象,而不是 main_module.models.Foo。此外,在多态关系中,我无法为 polymorphic_on 列填充 polymorphic_identity。例如,在最初创建对象时,主存储库中的 Bar 的 type 列将为空。

我尝试的一个想法是向模型库中的声明性基添加一个元类,并在初始化期间修改 __init__ 方法中的映射器。我通过这种方式取得了进展,但还没有完全发挥作用。

抱歉,解释很复杂,但这是一个复杂的问题。不幸的是,我无法更改有关模型或用例的任何内容。我必须在这些限制下工作。如果有人可以提供有关如何为主存储库中的类配置映射器以使其像模型库中的模型一样工作的想法,我将非常感激。

最佳答案

这里存在三个问题:

  1. 当您编写 foo_bar =lationship(FooBar, backref=backref("foos")) 时,FooBar 需要引用子类 FooBar,而不是 BaseFooBar
  2. Bar 需要继承自 Foo 才能使继承机制发挥作用;它不能从 BaseFoo 继承。
  3. 您的基类不应附加映射器;否则继承机制就会失常。

这些问题的解决方案,按顺序:

  1. 使用字符串来引用类名。这限制了消费者以某种方式命名他们的类。我们暂时接受此限制。
  2. 我们可以使用元类来动态更改基类。元类需要从 Base 的元类派生,因为 SQLAlchemy 的声明性扩展充分利用了元类。我们将看到元类方法也可以灵活地解决问题 1。
  3. 使用__abstract__ = True

最简单的示例:

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base, declared_attr, DeclarativeMeta

class BaseMeta(DeclarativeMeta):
    def __new__(cls, name, bases, attrs):
        if not attrs.get("__abstract__"):
            if len(bases) != 1:
                # you'll need to have multiple inheritance if you have that
                # as well
                raise NotImplementedError()
            base, = bases
            extra_bases = tuple(b._impl for b in base.__bases__
                                if hasattr(b, "_impl"))
            bases += extra_bases
            self = super(BaseMeta, cls).__new__(cls, name, bases, attrs)
            if getattr(base, "__abstract__", False):
                base._impl = self
            return self
        else:
            return super(BaseMeta, cls).__new__(cls, name, bases, attrs)

Base = declarative_base(metaclass=BaseMeta)

class BaseFoo(Base):
    __abstract__ = True

    __tablename__ = "foo"
    id = Column(Integer, primary_key=True)
    type = Column(String)

    @declared_attr
    def foo_bar_id(cls):
        return Column(Integer, ForeignKey("foo_bar.id"))

    @declared_attr
    def foo_bar(cls):
        return relationship(lambda: BaseFooBar._impl, backref=backref("foos"))

    __mapper_args__ = {"polymorphic_on": type}

class BaseBar(BaseFoo):
    __abstract__ = True

    __mapper_args__ = {"polymorphic_identity": "bar"}

class BaseFooBar(Base):
    __abstract__ = True

    __tablename__ = "foo_bar"
    id = Column(Integer, primary_key=True)

class Foo(BaseFoo):
    @classmethod
    def custom_create_method(cls, **kw):
        foo_obj = cls(**kw)
        models.session.add(foo_obj)
        models.session.flush()

class Bar(BaseBar):
    pass

class FooBar(BaseFooBar):
    pass

print(Bar.__bases__)  # (<class '__main__.BaseBar'>, <class '__main__.Foo'>)

元类的基本思想是将类 Foo 注入(inject)到 Bar 的基类中,基于 BaseBar 继承自BaseFoo,以及 Foo 实现 BaseFoo(通过继承)的事实。

您可以在顶部添加更复杂的内容,例如多重继承支持或优雅的错误处理(例如,警告用户他缺少您拥有的每个基类的子类,或者他为同一基类提供了多个子类)。

关于python - 如何创建从外部库中的 sqlalchemy 模型继承的自定义类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37621423/

相关文章:

java - 为什么 protected clone()方法无法使用同一包中的继承来访问

java - 如何将参数传递给二级父类(super class)

python - 在 SQLAlchemy 中使用 PostgresQL INTERVAL,其中持续时间动态存储在数据库中并且不是参数

python - 导入错误 : No module named google. oauth2

python - 替换部分 Python AST 树

C++ 继承与重载无法编译?

postgresql - SQLAlchemy 在没有 to_tsvector 的 ts_vector 列上搜索

python - Pandas 数据框中的计算列链

python - (OpenCV) 将日期放在文件名中

python - SqlAlchemy 中的有效批处理 "update-or-insert"