I am working with sqlalchemy scoped session and can't update an existing row properly.
Here are my models:
db = scoped_session(sessionmaker())
Base = declarative_base()
class Song(Base):
__tablename__ = 'song'
id = Column(Integer, primary_key=True)
artist_id = Column(Integer, ForeignKey('artist.id'))
artist_title = Column(Text)
title = Column(Text)
artist = relationship('Artist', backref='songs')
preview_url = Column(Text, default=None)
class Artist(Base):
__tablename__ = 'artist'
id = Column(Integer, primary_key=True)
title = Column(Text)
similar_artists = relationship('Artist',
secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=backref('followers', lazy='dynamic'),
lazy='dynamic')
Index('my_index', Artist.id, unique=True, mysql_length=255)
I have populated the db and can see that Song rows have all the columns filled with data in the debugger and also pgweb interface. In the views I make a query to get a list of songs and want to update them as follows:
# song.artist_id has an integer value
song.preview_url = preview_url # Just a string
# db.query(Song).filter(Song.id == song.id).update({'preview_url':preview_url}) #Produces same result as above
db.commit()
# song.artist_id becomes None
This adds the preview_url to a row, however, once I do that and commit the artist.id becomes None in the song instance even though I was updating completely different field. I can observe that in the debugger and pgweb interface.
UPDATE: I have tried db.commit()
right before I do any changes to the row and it still replaces song.artist_id
with None
. This implies that the row update has nothing to do with that, hence it must be preprocessing which I do on song
. Is there any way I can get rid of all changes in the session before updating and committing the row?
Has anyone encountered this behavior? Do I have to explicitly set the artist.id again because it's a foreign key?
I managed to track down this fishy behavior. The problem was indeed with the preprocessing.
Shortly, I was aggregating Artist.songs
from many artists in a list and later on I was popping from this list, however what I forgot is that this list is special, i.e. it's an Instrumented Collection in SQLAlchemy, which according to the docs:
"Instrumentation means that normal operations on the collection are tracked and result in changes being written to the database at flush time. "
by popping from this collection I was actually deleting Artist
-Song
relations, therefore the foreign key was becoming None
.
My sample code looked something like that:
artists = db.query(Artist).all()
all_songs = []
for artist in artists:
all_songs.append(artist.songs)
all_songs.pop(0) # This would delete the Artist-Song relation after the commit