SQLAlchemy 多态进阶(__mapper_args__ )、多对多标签
什么是polymorphic
熟悉SQLAlchemy的人往往知道polymorphic(多態)的模型定義,如果你恰好不熟悉SQLAlchemy,這里簡單的舉一個例子:
class Employee(Base): __tablename__ = 'employee'id = Column(Integer, primary_key=True)name = Column(String(50))type = Column(String(50))__mapper_args__ = {'polymorphic_identity':'employee','polymorphic_on':type}這里定義了雇員Employee 模型,指定type字段為多態所在字段,并且對于這個模型,當type字段為'employee'時,即為一個雇員
再看下面這兩個繼承之后的模型
class Engineer(Employee): __tablename__ = 'engineer'id = Column(Integer, ForeignKey('employee.id'), primary_key=True)engineer_name = Column(String(30))__mapper_args__ = {'polymorphic_identity':'engineer',}class Manager(Employee): __tablename__ = 'manager'id = Column(Integer, ForeignKey('employee.id'), primary_key=True)manager_name = Column(String(30))__mapper_args__ = {'polymorphic_identity':'manager',}這里又定義了兩個模型,Engineer,Manager,
并對應了兩張表,這兩張表的結構除了有少許不同,類似的,polymorphic_identity指定了這兩種模型對應的type字段值,
在上面的基礎上,可以提出的問題:
兩者的答案顯然是肯定的。 對于第一個問題,只需要使得后兩者的__tablename__ = None,并且不指定額外的字段即可。
第二個問題,即這幾天我的重構的探索
如何設置多對多模型
對于一個多對多的關系表,按照?SQLAlchemy?文檔:
association_table = Table('association', Base.metadata, Column('left_id', Integer, ForeignKey('left.id')),Column('right_id', Integer, ForeignKey('right.id')) )class Parent(Base): __tablename__ = 'left'id = Column(Integer, primary_key=True)children = relationship("Child",secondary=association_table)class Child(Base): __tablename__ = 'right'id = Column(Integer, primary_key=True)(雖然我們的基本不會按照SQLAlchemy那樣定義ForeignKey了,萬惡的ForeignKey。。)
關鍵在于應當有第三張表,存放M2M的關系。上面的association,就是這樣的一張M2M表,有兩個字段left_id和right_id
而且顯然的,我們可以輕松地想象出取出M2M關系的SQL:
select left.id,right.id from left join association on left.id=association.left_id join right on association.right_id=right.id是借助association實現兩個表的JOIN關系
SQLAlchemy 的對應操作這里就不贅述了,大家看文檔吧咩哈哈
M2M和多態
此次重構遇到的問題就是:如果我們的M2M的關系,如果是在多態上進行的,例如上面的Child,如果我不僅僅有Child,還分Boy和Girl,如何在這一張association_table進行控制呢? 上面代碼先稍作修改:
class Association(Base): left_id = Column('left_id', Integer, ForeignKey('left.id')),right_id = Column('right_id', Integer, ForeignKey('right.id'))gender = Column('gender', Boolean)__mapper_args__ = {"polymorphic_on": gender}增加了gender字段,并且增加了多態聲明__mapper_args__ 我們先假設一下這樣的SQL該怎么寫吧,實際上是很簡單的哈:
select left.id,right.id from left join association on (left.id=association.left_id and association.gender) join right on association.right_id=right.idjoin的時候額外加一個字段即可。
如何讓SQLalchemy可以生成出這樣的SQL,并且還自動進行例如增刪查改的SQL聲明呢?
SQLAlchemy同樣給出了對應的?樣例
我基于這個樣例做了一定的修改:
對應的diff如下(稍微修改了字段名),稍后有完整代碼:
--- origin.py 2016-10-13 11:28:57.000000000 +0800 +++ target.py 2016-10-13 11:29:44.000000000 +0800 @@ -1,80 +1,84 @@ from sqlalchemy.ext.declarative import as_declarative, declared_attrfrom sqlalchemy import create_engine, Integer, Column, \ - String, ForeignKey -from sqlalchemy.orm import Session, relationship, backref + String, and_ +from sqlalchemy.orm import Session, foreign, relationship, backref from sqlalchemy.ext.associationproxy import association_proxyclass AddressAssociation(Base):"""Associates a collection of Address objectswith a particular parent."""__tablename__ = "address_association" - - discriminator = Column(String) + addr_id = Column(Integer, + primary_key=True, + ) + order_id = Column(Integer, + primary_key=True, + ) + discriminator = Column(String, primary_key=True) """Refers to the type of parent."""__mapper_args__ = {"polymorphic_on": discriminator}class Address(Base):"""The Address class.This represents all address records in asingle table.""" - association_id = Column(Integer, ForeignKey("address_association.id")) + id = Column(Integer, primary_key=True) street = Column(String)city = Column(String)zip = Column(String) - association = relationship("AddressAssociation", backref="addresses") - - parent = association_proxy("association", "parent")def __repr__(self):return "%s(street=%r, city=%r, zip=%r)" % \(self.__class__.__name__, self.street,self.city, self.zip)class HasAddresses(object):"""HasAddresses mixin, creates a relationship tothe address_association table for each parent."""@declared_attr - def address_association_id(cls): - return Column(Integer, ForeignKey("address_association.id")) - - @declared_attr def address_association(cls):name = cls.__name__discriminator = name.lower()assoc_cls = type("%sAddressAssociation" % name,(AddressAssociation, ),dict(__tablename__=None,__mapper_args__={"polymorphic_identity": discriminator - } - ) + }, + addresses=relationship( + Address, + primaryjoin="Address.idforeign({assoc_cls_name}.addr_id)".format(assoc_cls_name=assoc_cls_name)) + ) )cls.addresses = association_proxy("address_association", "addresses",creator=lambda addresses: assoc_cls(addresses=addresses))return relationship(assoc_cls, - backref=backref("parent", uselist=False)) + primaryjoin=and_(foreign(assoc_cls.addr_id) Address.id, + foreign(assoc_cls.order_id) == cls.id), + cascade="save-update, merge, delete, delete-orphan", + )class Customer(HasAddresses, Base):name = Column(String)class Supplier(HasAddresses, Base):company_name = Column(String)此后就可以通過Customer.addresses.append等操作M2M了
?
?
總結
以上是生活随笔為你收集整理的SQLAlchemy 多态进阶(__mapper_args__ )、多对多标签的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis Save 命令
- 下一篇: Python pip install 包