Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bidirectional relationship with association_proxy

I have two models, Word and Sentence, which have a bidirectional many-to-many relationship. In order to store additional information, I have an association object, WordInSentence.

class WordInSentence(Base):
    __tablename__ = "word_in_sentence"
    word_id = Column(Integer, ForeignKey('word.id'),
        primary_key=True)
    sentence_id = Column(Integer, ForeignKey('sentence.id'),
        primary_key=True)
    space_after = Column(String)
    tag = Column(String)
    position = Column(Integer)

    word = relationship("Word",
        backref=backref("word_sentences", lazy="dynamic"))
    sentence = relationship("Sentence",
        backref=backref("sentence_words", lazy="dynamic"))

class Sentence(Base):
    text = Column(Text, index = True)

    words = association_proxy("sentence_words", "word",
        creator=lambda word: WordInSentence(word=word))

class Word(Base):

    word = Column(String, index = True)
    sentences = association_proxy("word_sentences", "sentence",
        creator=lambda sent: WordInSentence(sentence=sent))

    def __repr__(self):
        return "<Word: " + str(self.word) + ">"

I want to be able to do things like this:

w = Word()
s = Sentence()
w.sentences = [s]

However, I get errors like this:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/plasma/project/venv/lib/python2.7/site-packages/sqlalchemy/ext/associationproxy.py", line 274, in __set__
    proxy.clear()
  File "/home/plasma/project/venv/lib/python2.7/site-packages/sqlalchemy/ext/associationproxy.py", line 629, in clear
    del self.col[0:len(self.col)]
TypeError: object of type 'AppenderBaseQuery' has no len()

I also noticed this example in the docs, but I'm not sure how to make it bidirectional and a list.

like image 593
Plasma Avatar asked Dec 03 '22 19:12

Plasma


2 Answers

The reason you were getting the error TypeError: object of type 'AppenderBaseQuery' has no len() is because your relationships are set to lazy="dynamic". If you actually inspect the object, it is simply an SQL query. That's why you can't iterate over it - it has to be executed first.

You can do that by using the standard functions available to all queries - calling filter(<conditions>) on the object, or all() if you want everything.

If you don't want the extra step of executing a dynamic query every time you access it, another option -- if the number of child items in the relationship is not large -- is to change the lazy setting to 'select'. This will then run the association query at the same time as the query for the parent object, which is of course impractical for a huge child database, but not unreasonable for a smaller one. Then it can be iterated over as you expect.

like image 157
Aaron D Avatar answered Dec 05 '22 10:12

Aaron D


Hopes this fragment of code can help you. I'm just changing the creation sequence and adding engine and session. Here the working code.

from sqlalchemy.engine import create_engine
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm import relationship, backref
from sqlalchemy.orm.session import Session
from sqlalchemy.sql.schema import Column, ForeignKey
from sqlalchemy.sql.sqltypes import Integer, String, Float, Date

Base = declarative_base()

class Sentence(Base):
    __tablename__ = 'sentence'

    id = Column(Integer, primary_key=True)
    text = Column(String, index = True)

    sentence_words = relationship('WordInSentence')
    words = association_proxy("sentence_words", "word")

class Word(Base):
    __tablename__ = 'word'

    id = Column(Integer, primary_key=True)
    word = Column(String, index = True)

    word_sentences = relationship('WordInSentence')
    sentences = association_proxy("word_sentences", "sentence")

    def __repr__(self):
        return "<Word: " + str(self.word) + ">"


class WordInSentence(Base):
    __tablename__ = "word_in_sentence"

    word_id = Column(Integer, ForeignKey(Word.id),
        primary_key=True)
    sentence_id = Column(Integer, ForeignKey(Sentence.id),
        primary_key=True)
    space_after = Column(String)
    tag = Column(String)
    position = Column(Integer)

    word = relationship(Word)
        #backref=backref("word_sentences", lazy="dynamic"))
    sentence = relationship(Sentence)
        #backref=backref("sentence_words", lazy="dynamic"))


engine = create_engine('sqlite://')
Base.metadata.create_all(engine)

session = Session(engine)

Test:

>>> 
>>> w = Word(word='something new')
>>> w.sentences = [Sentence(text='there is somethine new')]
>>> session.add(w)
>>> session.commit()
>>> session.query(WordInSentence).one().word_id
1
>>> session.query(Word).one().word
'something new'
>>> session.query(Sentence).one().text
'there is somethine new'
>>> 
like image 34
Tok Soegiharto Avatar answered Dec 05 '22 08:12

Tok Soegiharto