我没有创建模型继承的mixin类,而是有一个用例,要求我以相反的方式配置类。通常是mixin类的类需要是从模型继承的类以及从中创建模型对象的类。这是因为模型和映射器配置位于主存储库的外部库中。在加载任何模型之前,我需要将引擎的主机从主存储库传递到模型库,以便它们可以使用已配置的声明性基础进行加载。传入引擎信息后,会话,Base类和所有内容都在模型继承的基类中创建。这是一个简化的例子:
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将被导入主存储库,将调用initialize方法,传入引擎的主机,然后可以导入模型。主存储库有自己的类,其名称与模型相同,并且具有普通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
:一个或多个映射器无法初始化 - 无法继续初始化其他映射器。
原始异常是:在此声明性基础的注册表中找到路径Foo
的多个类。请使用完全模块限定的路径。
所以我尝试将完整路径放在关系中。然后它开始给我一个这样的错误:
FlushError
:尝试将类型项清除为集合FooBar.foos
的成员。期望类型的对象或此类型的多态子类。如果是子类,请配置mapperMapper|Foo|foo
以多态方式加载此子类型,或设置enable_typechecks=False
以允许接受任何子类型进行刷新。
本质上,主要问题是让主模块中的类指向并充当模型类。例如,当我尝试创建关系时,它表示期望类型为separate_library.models.Foo
的对象而不是main_module.models.Foo
。另外,在多态关系中,我无法为polymorphic_on列填充polymorphic_identity。例如,在最初创建对象时,主存储库中的Bar将type
列为空。
我尝试过的一个想法是在模型库中向声明性基础添加元类,并在初始化期间修改__init__
方法中的映射器。我以这种方式取得了进步,但还没有完全发挥作用。
对于复杂的解释感到抱歉,但这是一个复杂的问题。遗憾的是,我无法改变模型或用例的任何内容。我必须在这些限制范围内工作。如果有人可以提供关于如何为主存储库中的类配置映射器的想法,就像模型库中的模型一样,我将非常感激。
答案 0 :(得分:3)
这里有三个问题:
foo_bar = relationship(FooBar, backref=backref("foos"))
时,FooBar
需要引用子类FooBar
,而不是BaseFooBar
。Bar
需要继承Foo
才能使继承机制正常工作;它无法继承BaseFoo
。解决这些问题的依据是:
Base
的元类派生,因为SQLAlchemy的声明性扩展使得自由使用元类。我们将看到元类方法也可以灵活地解决问题1。__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
继承自Bar
的事实,将类BaseBar
注入BaseFoo
的基础,以及事实Foo
实现BaseFoo
(通过继承)。
您可以在顶部添加更复杂的内容,例如多重继承支持或优雅的错误处理(例如,警告用户他缺少您拥有的每个基类的子类,或者他提供了多个同一基类的子类。)