SQLAlchemy级联多态列更新

时间:2016-01-07 19:16:31

标签: python postgresql sqlalchemy

我有一个父Employee表和一个子Engineer表。从客户端的角度来看,我只想与Employee模型进行交互。这对于READ和DELETE很容易实现,但在尝试UPDATE或INSERT时会出现问题。

sqlalchemy docs州:

  

警告

     

目前,通常只能在层次结构中最基类的类上设置一个鉴别器列。 “Cascading”多态列尚不支持。

所以看起来默认这不会起作用。我正在寻找有关如何使这项工作的想法。

这是使用带有psycopg2的postgres的完整测试设置。 SQL可能适用于其他SQL数据库,但我测试了其他任何SQL数据库。

用于创建测试数据库(testdb)和表(员工,工程师)的SQL脚本:

CREATE DATABASE testdb;
\c testdb;

CREATE TABLE employee(
  id    INT   PRIMARY KEY   NOT NULL,
  name  TEXT,
  type  TEXT
  );

CREATE TABLE engineer(
  id            INT   PRIMARY KEY   NOT NULL,
  engineer_name TEXT,
  employee_id INT REFERENCES employee(id)
    ON UPDATE CASCADE
    ON DELETE CASCADE
  );

Python测试脚本:

原样,INSERT测试将失败,但DELETE将通过。如果您更改代码(注释/取消注释)以使用子工程师模型,它将通过两种情况。

import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import (
    Column,
    ForeignKey,
    Integer,
    Text,
    )

Base = declarative_base()

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(Text(), default='John')
    type = Column(Text, default='engineer')

    __mapper_args__ = {
        'polymorphic_identity':'employee',
        'polymorphic_on':type,
        'with_polymorphic': '*',
    }

class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id',
                ondelete='CASCADE', onupdate='CASCADE'), primary_key=True)
    engineer_name = Column(Text(), default='Eugine')

    __mapper_args__ = {
        'polymorphic_identity':'engineer',
    }


def count(session, Model):
  query = session.query(Model)
  count = len(query.all())
  return count

url = 'postgresql+psycopg2://postgres@localhost/testdb'
engine = sa.create_engine(url)
Base.metadata.bind = engine
Base.metadata.create_all()
Session = orm.sessionmaker(engine)
session = Session()

if __name__ == '__main__':
  id=0
  print '#'*30, 'INSERT', '#'*30
  id += id
  # I only want to interact with the Employee table
  e = Employee(id=id)
  # Use the child model to see the INSERT test pass
  # e = Engineer(id=id)
  session.add(e)
  session.commit()
  print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail'

  print '#'*30, 'DELETE', '#'*30
  # e = session.query(Employee).first()
  session.delete(e);
  session.commit();
  print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail'

  session.flush()

有关如何通过sqlalchemy模型定义实现此目的的任何想法,而不必使用显式控制器代码?

谢谢!

修改 好吧,我对这个没有任何爱。任何人都有关于如何使用控制器代码完成的想法?

1 个答案:

答案 0 :(得分:0)

使用控制器逻辑可以通过getting the polymorphic subclass using the polymorphic identity完成。

我添加了两个函数来封装一些基本逻辑。

def get_polymorphic_class(klass, data):
  column = klass.__mapper__.polymorphic_on
  if column is None:
    # The column is not polymorphic so the Class can be returned as-is
    return klass
  identity = data.get(column.name)
  if not identity:
    raise ValueError('Missing value for "' + column.name + '"', data)
  mapper = klass.__mapper__.polymorphic_map.get(identity)
  if mapper:
    return mapper.class_
  else:
    raise ValueError('Missing polymorphic_identity definition for "' + identity + '"')
  return klass

def insert(klass, data):
  klass = get_polymorphic_class(klass, data)
  e = klass(**data)
  session.add(e)
  session.commit()
  return e

现在我更新main以使用insert功能,一切都按预期工作:

if __name__ == '__main__':
  id=0
  print '#'*30, 'INSERT', '#'*30
  id += id
  e = insert(Employee, {'id': id, 'type': 'engineer'})
  print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail'

  print '#'*30, 'DELETE', '#'*30
  session.delete(e);
  session.commit();
  print 'pass' if count(session, Employee) == count(session, Engineer) else 'fail'
  session.flush()

我的封装中有一些额外的代码用于可重用性,但重要的是执行Employee.__mapper__.polymorphic_map['engineer'].class_,返回Engineer类,以便我们可以进行适当的级联INSERT。