3 Stimmen

Verwendung eines Tupels von Werten in einer Sqlalchemy gemappten Sammlung

In einer Viele-zu-Viele-Beziehung habe ich einige zusätzliche Daten in der Zuordnungstabelle, um die Beziehung zu beschreiben (eine Menge und ein boolescher Wert). Ich möchte eine zugeordnete Auflistung verwenden, um zu vermeiden, dass ich direkt mit den Assoziationsobjekten arbeite, aber ich kann nicht herausfinden, wie ich ein Tupel für die Werte in der Zuordnung verwenden kann. Soweit ich das beurteilen kann, Attribute als Diktat von Listen unter Verwendung einer Middleman-Tabelle mit SQLAlchemy ist ähnlich, nur andersherum.

Um dies zu veranschaulichen, möchte ich etwas Ähnliches tun:

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

Das... funktioniert... aber schließlich versucht SQLAlchemy, das Tupel in die Datenbank zu stellen (ich werde versuchen, das in ein bisschen nachzustellen).

Ich habe auch versucht, Tupel im Schlüssel zu verwenden (das verwandte Objekt und eine der anderen Spalten), aber das sieht schrecklich aus, und es funktioniert nicht:

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

I kann den Elementnamen und einen Wert problemlos in die zugeordnete Sammlung aufnehmen: Ich hatte eine andere (strukturell identische) Form dieser Beziehung, die ich gelöst habe, indem ich zwei Beziehungen (und Assoziations-Proxys) erstellt habe, die die Assoziationstabelle auf der Grundlage des booleschen Wertes geteilt haben, und deren Erstellerfunktionen haben den booleschen Wert ohne weitere Eingriffe korrekt gesetzt. Leider gab der Boolesche Wert in diesem Fall einen geringfügigen semantischen Unterschied an (der Anwendungscode muss die Elemente als Gruppe behandeln), während es sich bei dem aktuellen Problem um einen nicht unbedeutenden kosmetischen Unterschied handelt (der Anwendungscode sollte die Elemente nicht als Gruppen behandeln, aber der Wert ändert, wie das Element angezeigt wird, und wird daher benötigt).

7voto

zzzeek Punkte 66340

Die verknüpfte Antwort enthält alle Komponenten. attribute_mapped_collection und association_proxy können zusammen viel leisten. Hier ist zunächst das Wörterbuch von string->tuple(int, boolean) (aktualisiert für 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

Hier ist es andersherum, mit den Tupeln auf der Assoziation und dem Namen auf dem Endpunkt:

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

Das ist wahrscheinlich alles, was Sie brauchen. Wenn Sie Postgresql verwenden, das SQL-Tupel unterstützt, können Sie die obigen Angaben durch Hybride plus tuple_() , so dass as_tuple kann auch auf der SQL-Ebene verwendet werden (im Folgenden wird auch One-to-many anstelle von Assoziationsobjekt verwendet, nur als Beispiel):

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:tiger@localhost/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()

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X