我有一个用例,需要我以相反的方式配置类,而不是创建模型继承的混合类。通常是混合类的类需要是从模型继承的类以及创建模型对象的类。这是因为模型和映射器配置位于主存储库的外部库中。在加载任何模型之前,我需要将引擎的主机从主存储库传递到模型库,以便它们可以加载已配置的声明性基础。传入引擎信息后, 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 pathFoo
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 collectionFooBar.foos
. Expected an object of type or a polymorphic subclass of this type. If is a subclass of , configure mapperMapper|Foo|foo
to load this subtype polymorphically, or setenable_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__
方法中的映射器。我通过这种方式取得了进展,但还没有完全发挥作用。
抱歉,解释很复杂,但这是一个复杂的问题。不幸的是,我无法更改有关模型或用例的任何内容。我必须在这些限制下工作。如果有人可以提供有关如何为主存储库中的类配置映射器以使其像模型库中的模型一样工作的想法,我将非常感激。
最佳答案
这里存在三个问题:
- 当您编写
foo_bar =lationship(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
注入(inject)到 Bar
的基类中,基于 BaseBar
继承自BaseFoo
,以及 Foo
实现 BaseFoo
(通过继承)的事实。
您可以在顶部添加更复杂的内容,例如多重继承支持或优雅的错误处理(例如,警告用户他缺少您拥有的每个基类的子类,或者他为同一基类提供了多个子类)。
关于python - 如何创建从外部库中的 sqlalchemy 模型继承的自定义类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37621423/