如何创建从外部库

时间:2016-06-03 18:43:40

标签: python inheritance sqlalchemy polymorphism

我没有创建模型继承的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的成员。期望类型的对象或此类型的多态子类。如果是子类,请配置mapper Mapper|Foo|foo以多态方式加载此子类型,或设置enable_typechecks=False以允许接受任何子类型进行刷新。

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

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

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

1 个答案:

答案 0 :(得分:3)

这里有三个问题:

  1. 当您撰写foo_bar = relationship(FooBar, backref=backref("foos"))时,FooBar需要引用子类FooBar,而不是BaseFooBar
  2. Bar需要继承Foo才能使继承机制正常工作;它无法继承BaseFoo
  3. 您的基类不应该附加映射器;否则继承机制就会失控。
  4. 解决这些问题的依据是:

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

      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(通过继承)。

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