2011-05-23 46 views
3

在多對多關係中,我在關聯表上有一些額外數據來描述關係(數量和布爾值)。我想使用映射集合來避免直接使用關聯對象,但我無法弄清楚如何在映射中使用元組作爲值。據我所知,Attribute as dict of lists using middleman table with SQLAlchemy是相似的,但倒退。在sqlalchemy映射集合中使用值的元組

爲了說明這一點,我希望做這樣的事情:

>>> collection.items[item] = (3, True) 
>>> collection.items[item] = (1, False) 
>>> colletion.items 
{"item name": (3, True), "item name": (1, False)} 

這...作品...但最終的SQLAlchemy試圖把元組到數據庫(我會嘗試重新創建在一點)。

我也試圖在關鍵(相關對象和另一列之一)使用元組,但它看起來很可怕,這是行不通的:

>>> collection.items[item, True] = 3 
>>> collection.items[item, False] = 1 
>>> collection.items 
{(<item>, True): 3, (<item>, False): 1} 

可以把項目名和一個值放在映射集合中沒有問題:我有另一種(結構相同的)這種關係的形式,我通過建立兩個關係(和關聯代理)來根據布爾值劃分它們之間的關聯表,並且他們的創建者函數正確設置了布爾值,而沒有任何進一步干擾。不幸的是,在這種情況下,布爾值指定了較小的語義差異(應用程序代碼需要將項目作爲一個組來處理),而在當前的問題中,這是一個不小的整容差異(應用程序代碼不應將項目視爲組,值確實會改變項目的顯示方式,因此需要)。

回答

8

鏈接的答案包含所有組件。 attribute_mapped_collection和association_proxy可以一起做很多事情。首先這裏是與字符串>元組(INT,布爾)(更新M2M)的詞典:

from sqlalchemy import Integer, Boolean, String, Column, create_engine, \ 
    ForeignKey 
from sqlalchemy.orm import Session, relationship 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.ext.associationproxy import association_proxy 
from sqlalchemy.orm.collections import attribute_mapped_collection 

Base = declarative_base() 

class SomeClass(Base): 
    __tablename__ = 'sometable' 

    id = Column(Integer, primary_key=True) 
    tuple_elements = relationship(
       "TupleAssociation", 
       collection_class=attribute_mapped_collection("name"), 
       cascade="all, delete-orphan" 
      ) 
    items = association_proxy("tuple_elements", "as_tuple") 

class TupleAssociation(Base): 
    __tablename__ = 'tuple_association' 
    parent_id = Column(Integer, ForeignKey('sometable.id'), primary_key=True) 
    tuple_id = Column(Integer, ForeignKey("tuple_data.id"), primary_key=True) 
    name = Column(String) 

    tuple_element = relationship("TupleElement") 

    def __init__(self, key, tup): 
     self.name = key 
     self.tuple_element = TupleElement(tup) 

    @property 
    def as_tuple(self): 
     return self.tuple_element.as_tuple 

class TupleElement(Base): 
    __tablename__ = 'tuple_data' 

    id = Column(Integer, primary_key=True) 
    col1 = Column(Integer) 
    col2 = Column(Boolean) 

    def __init__(self, tup): 
     self.col1, self.col2 = tup 

    @property 
    def as_tuple(self): 
     return self.col1, self.col2 


e = create_engine('sqlite://') 
Base.metadata.create_all(e) 
s = Session(e) 

collection = SomeClass() 
collection.items["item name 1"] = (3, True) 
collection.items["item name 2"] = (1, False) 
print collection.items 

s.add(collection) 
s.commit() 

collection = s.query(SomeClass).first() 
print collection.items 

這周圍的上述關聯,元組和端點上的名稱的其他方式:

from sqlalchemy import Integer, Boolean, String, Column, create_engine, \ 
    ForeignKey 
from sqlalchemy.orm import Session, relationship 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.ext.associationproxy import association_proxy 
from sqlalchemy.orm.collections import attribute_mapped_collection 

Base = declarative_base() 

class SomeClass(Base): 
    __tablename__ = 'sometable' 

    id = Column(Integer, primary_key=True) 
    tuple_elements = relationship(
       "TupleAssociation", 
       collection_class=attribute_mapped_collection("name"), 
       cascade="all, delete-orphan" 
      ) 
    items = association_proxy("tuple_elements", "as_tuple") 

class TupleAssociation(Base): 
    __tablename__ = 'tuple_association' 
    parent_id = Column(Integer, ForeignKey('sometable.id'), primary_key=True) 
    name_id = Column(Integer, ForeignKey("name_data.id"), primary_key=True) 

    col1 = Column(Integer) 
    col2 = Column(Boolean) 

    name_element = relationship("NameElement") 

    def __init__(self, key, tup): 
     self.name_element = NameElement(name=key) 
     self.col1, self.col2 = tup 

    @property 
    def name(self): 
     return self.name_element.name 

    @property 
    def as_tuple(self): 
     return self.col1, self.col2 

class NameElement(Base): 
    __tablename__ = 'name_data' 

    id = Column(Integer, primary_key=True) 
    name = Column(String) 


e = create_engine('sqlite://', echo=True) 
Base.metadata.create_all(e) 
s = Session(e) 

collection = SomeClass() 
collection.items["item name 1"] = (3, True) 
collection.items["item name 2"] = (1, False) 
print collection.items 

s.add(collection) 
s.commit() 

collection = s.query(SomeClass).first() 
print collection.items 

這可能是你所需要的。如果您使用支持SQL元組的Postgresql,則可以使用混合加tuple_()來添加更多內容,以便as_tuple也可以在SQL級別使用(以下還使用一對多而不是關聯對象,僅用於例如):

from sqlalchemy import Integer, Boolean, String, Column, create_engine, \ 
    ForeignKey 
from sqlalchemy.orm import Session, relationship 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.ext.associationproxy import association_proxy 
from sqlalchemy.orm.collections import attribute_mapped_collection 
from sqlalchemy.ext import hybrid 
from sqlalchemy.sql import tuple_ 

Base = declarative_base() 

class SomeClass(Base): 
    __tablename__ = 'sometable' 

    id = Column(Integer, primary_key=True) 
    tuple_elements = relationship(
       "TupleElement", 
       collection_class=attribute_mapped_collection("name"), 
       cascade="all, delete-orphan" 
      ) 
    items = association_proxy("tuple_elements", "as_tuple") 

class TupleElement(Base): 
    __tablename__ = 'tuple_data' 

    id = Column(Integer, primary_key=True) 
    parent_id = Column(Integer, ForeignKey('sometable.id'), nullable=False) 
    name = Column(String) 
    col1 = Column(Integer) 
    col2 = Column(Boolean) 

    def __init__(self, key, tup): 
     self.name = key 
     self.col1, self.col2 = tup 

    @hybrid.hybrid_property 
    def as_tuple(self): 
     return self.col1, self.col2 

    @as_tuple.expression 
    def as_tuple(self): 
     return tuple_(self.col1, self.col2) 

e = create_engine('postgresql://scott:[email protected]/test', echo=True) 
Base.metadata.drop_all(e) 
Base.metadata.create_all(e) 
s = Session(e) 

collection = SomeClass() 
collection.items["item name 1"] = (3, True) 
collection.items["item name 2"] = (1, False) 
print collection.items 

s.add(collection) 
s.commit() 

q = s.query(SomeClass).join(SomeClass.tuple_elements) 
assert q.filter(TupleElement.as_tuple == (3, True)).first() is collection 
assert q.filter(TupleElement.as_tuple == (5, False)).first() is None 
print s.query(TupleElement.as_tuple).all() 
+0

啊,很多很多。一會兒 – zzzeek 2011-05-24 00:43:31

+0

這讓它工作。但是,必須爲as_tuple添加一個setter,並且我已經用自己的名字映射了自己(這不是唯一的)。我必須解決這個問題,但我現在應該可以輕鬆完成。謝謝您的幫助。 – 2011-05-24 18:57:52