SQLALchemy “after_insert” doesn't update target object fields

戏子无情 提交于 2019-12-19 08:09:29

问题


I have a model (see code below) on which I want to execute a function after an object is inserted that will update one of the object's fields. I'm using the after_insert Mapper Event to do this.

I've confirmed that the after_insert properly calls the event_extract_audio_text() handler, and the target is getting updated with the correct audio_text value. However, once the event handler finishes executing, the text value is not set for the object in the database.

Code

# Event handler
def event_extract_audio_text(mapper, connect, target):

    # Extract text from audio file
    audio_text = compute_text_from_audio_file(target.filename)

    # Update the 'text' field with extracted text
    target.audio_text = audio_text

# Model
class SoundsRaw(db.Model):
    __tablename__ = 'soundsraw'

    id = db.Column(db.BigInteger(), primary_key=True, autoincrement=True)
    filename = db.Column(db.String(255))
    audio_text = db.Column(db.Text())

# Event listener
event.listen(SoundsRaw, 'after_insert', event_extract_audio_text)

I've also tried calling db.session.commit() to try to update the object with the text value, but then I get the following stack trace:

File "/Users/alexmarse/.virtualenvs/techmuseum/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 219, in _assert_active
raise sa_exc.ResourceClosedError(closed_msg)

ResourceClosedError: This transaction is closed

Any ideas?

Software versions

SQLAlchemy 0.9.4 Flask 0.10.1 Flask-SQLAlchemy 1.0


回答1:


The thing with 'after_insert' kind of handlers is to use the connection directly. Here's how I did it:

class Link(db.Model):
    "News link data."

    __tablename__ = 'news_links'

    id = db.Column(db.BigInteger, primary_key=True)
    slug = db.Column(db.String, unique=True) #, nullable=False 

    url = db.Column(db.String, nullable=False, unique=True)
    title = db.Column(db.String)
    image_url = db.Column(db.String)
    description = db.Column(db.String)

@db.event.listens_for(Link, "after_insert")
def after_insert(mapper, connection, target):
    link_table = Link.__table__
    if target.slug is None:
        connection.execute(
            link_table.update().
            where(link_table.c.id==target.id).
            values(slug=slugify(target.id))
        )



回答2:


I ended up solving this by ditching the Mapper Event approach and using Flask's Signalling Support instead.

Basically, you can register "signals" on your model, which are essentially callback functions that are called whenever a specific kind of event happens. In my case, the event is an "update" on my model.

To configure the signals, I added this method to my app.py file:

def on_models_committed(sender, changes):
    """Handler for model change signals"""
    for model, change in changes:
        if change == 'insert' and hasattr(model, '__commit_insert__'):
            model.__commit_insert__()
        if change == 'update' and hasattr(model, '__commit_update__'):
            model.__commit_update__()
        if change == 'delete' and hasattr(model, '__commit_delete__'):
            model.__commit_delete__()

Then, on my model, I added this function to handle the update event:

 # Event methods
def __commit_update__(self):

    # create a new db session, which avoids the ResourceClosedError
    session = create_db_session()

    from techmuseum.modules.sensors.models import SoundsRaw
    # Get the SoundsRaw record by uuid (self contains the object being updated, 
    # but we can't just update/commit self -- we'd get a ResourceClosedError)
    sound = session.query(SoundsRaw).filter_by(uuid=self.uuid).first()

    # Extract text from audio file
    audio_text = compute_text_from_audio_file(sound)

    # Update the 'text' field of the sound
    sound.text = audio_text

    # Commit the update to the sound
    session.add(sound)
    session.commit()

def create_db_session():
    # create a new Session
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    psql_url = app.config['SQLALCHEMY_DATABASE_URI']
    some_engine = create_engine(psql_url)

    # create a configured "Session" class
    session = sessionmaker(bind=some_engine)

    return session


来源:https://stackoverflow.com/questions/25792332/sqlalchemy-after-insert-doesnt-update-target-object-fields

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!