SQLAlchemy ORM:多态单表继承,如果找不到"polymorphic_identity",则回退到父类

Gui*_*ume 13 python sqlalchemy single-table-inheritance

使用Python 3.5和SQLAlchemy 1.0.14(ORM).

我有一个声明如下的项目表:

from sqlalchemy.ext.declarative.api import declarative_base

Base = declarative_base()

class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True)
    type = Column(String)
    # other non relevant attributes
Run Code Online (Sandbox Code Playgroud)

我的项目可以有许多不同的类型,存储的类型标识符type.对于其中一些对象类型,我需要具有特定的方法或属性.

为了实现这一点,我尝试使用单个表继承与几个SpecialisedItem作为Item的子类:

class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True)
    type = Column(String, index=True)
    # other non relevant attributes

    __mapper_args__ = {
        'polymorphic_on': type,
    }

class SpecialisedItem(Base):
    __mapper_args__ = {
        'polymorphic_identity': 'specialitem',
    }

    def specialised_method(self):
        return "I am special"
Run Code Online (Sandbox Code Playgroud)

现在当我加载我的项目时,我希望所有特殊项目(有type=='specialitem')都这样加载,而任何其他类型的值将导致父类Item被加载.这不起作用,我AssertionError: No such polymorphic_identity 'normal' is defined在加载项目时得到.

我想避免创建只是为了覆盖所有可能的type值而不做任何事情的继承类,而是让"unmapped" type回退到父类Item.

有没有办法达到这个效果?

最小测试用例供参考:

from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Integer, String


Base = declarative_base()

class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True)
    type = Column(String, index=True)
    # other non relevant attributes

    __mapper_args__ = {
        'polymorphic_on': type,
    }

class SpecialisedItem(Item):
    __mapper_args__ = {
        'polymorphic_identity': 'special',
    }

    specialAttribute = Column(String)

    def specialised_method(self):
        return "I am special"


engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

session.add(Item(type='normal'))
session.add(Item(type='special'))
session.commit()
# loading only specialized items works
for item in session.query(Item).filter_by(type="special"):
    print(item.specialised_method())

# loading other items fails
for item in session.query(Item):
    print(item.type)
Run Code Online (Sandbox Code Playgroud)

谢谢,

纪尧姆

r-m*_*m-n 10

"多态身份"标识符到Mapper实例的映射存储在polymorphic_map dict中.您可以创建polymorphic_map将返回父类映射器的自定义,以获取未定义的多态身份.

from sqlalchemy.engine import create_engine
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Integer, String
from sqlalchemy import event

Base = declarative_base()

class Item(Base):
    __tablename__ = 'items'

    id = Column(Integer, primary_key=True)
    type = Column(String, index=True)
    # other non relevant attributes

    __mapper_args__ = {
        'polymorphic_on': type,
    }

class SpecialisedItem(Item):
    __mapper_args__ = {
        'polymorphic_identity': 'special',
    }

    specialAttribute = Column(String)

    def specialised_method(self):
        return "I am special"

#http://docs.sqlalchemy.org/en/rel_1_1/orm/events.html#sqlalchemy.orm.events.MapperEvents.mapper_configured
@event.listens_for(Item, 'mapper_configured')
def receive_mapper_configured(mapper, class_):    
    mapper.polymorphic_map = defaultdict(lambda: mapper, mapper.polymorphic_map)

    # to prevent 'incompatible polymorphic identity' warning, not mandatory
    mapper._validate_polymorphic_identity = None

engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

session.add(Item(type='normal'))
session.add(Item(type='special'))
session.commit()
# loading only specialized items works
for item in session.query(Item).filter_by(type="special"):
    print(item.specialised_method())

# loading other items fails
for item in session.query(Item):
    print(item.type)
Run Code Online (Sandbox Code Playgroud)


Iva*_*ass 5

一个可重用的装饰器解决方案,基于@rmn答案。自定义类也被替换为collections.defaultdict实际上做同样事情的类。

def receive_mapper_configured(mapper, class_):
    mapper.polymorphic_map = defaultdict(lambda: mapper, mapper.polymorphic_map)
    # to prevent 'incompatible polymorphic identity' warning, not necessary
    mapper._validate_polymorphic_identity = None


def polymorphic_fallback(mapper_klass):
    event.listens_for(mapper_klass, 'mapper_configured')(receive_mapper_configured)
    return mapper_klass
Run Code Online (Sandbox Code Playgroud)

然后在您的代码中,您可以将此装饰器添加到基类中:

@polymorphic_fallback
class Item:
    ...

class SpecificItem(Item):
    ...
Run Code Online (Sandbox Code Playgroud)