pytest-flask-sqlalchemy icon indicating copy to clipboard operation
pytest-flask-sqlalchemy copied to clipboard

Instance XXX has been deleted. Use the make_transient() function to send this object back to the transient state. Again.

Open materemias opened this issue 5 years ago • 4 comments

just like in #5 this happens again with latest versions, unfortunately #6 did not resolve this completely.

When I delete an object via a client call I receive the notorious error:

self = <sqlalchemy.orm.session.SignallingSession object at 0x7f67f0d99510>
state = <sqlalchemy.orm.state.InstanceState object at 0x7f67f0cb6390>
revert_deletion = False

    def _update_impl(self, state, revert_deletion=False):
        if state.key is None:
            raise sa_exc.InvalidRequestError(
                "Instance '%s' is not persisted" % state_str(state)
            )
    
        if state._deleted:
            if revert_deletion:
                if not state._attached:
                    return
                del state._deleted
            else:
                raise sa_exc.InvalidRequestError(
                    "Instance '%s' has been deleted.  "
                    "Use the make_transient() "
                    "function to send this object back "
>                   "to the transient state." % state_str(state)
                )
E               sqlalchemy.exc.InvalidRequestError: Instance '<YYY at 0x7f67f0cb6a50>' has been deleted.  Use the make_transient() function to send this object back to the transient state.
flask-sqlalchemy==2.4.1
pytest-flask-sqlalchemy==1.0.2
sqlalchemy==1.3.11
sqlalchemy-continuum==1.3.9
sqlalchemy-utils==0.36.0

@jeancochrane could you please have a look at it?

materemias avatar Dec 12 '19 12:12 materemias

Also suffering from this. It wasn't a problem until i started using begin_nested() in my codebase.

tomthorogood avatar Feb 20 '20 00:02 tomthorogood

So, in teardown_transaction, the call to session.remove() is leading to SQLAlchemy to call expunge_all(), which detaches objects from the session. That's great.

Except, reyhydrate_object is listening for those detachments, and trying to re-add them to the session that is currently being destroyed.

https://github.com/jeancochrane/pytest-flask-sqlalchemy/blob/master/pytest_flask_sqlalchemy/fixtures.py#L70-L84

Do I have this right? Is there a way to remove those event listeners before the call to session.remove()?

(I'm looking into this.)

tomthorogood avatar Feb 20 '20 15:02 tomthorogood

Yes, I was able to fix this issue by updating teardown_transaction to look like:

    @request.addfinalizer
    def teardown_transaction():
        # Delete the session
        sa.event.remove(session, 'persistent_to_detached', rehydrate_object)
        sa.event.remove(session, 'deleted_to_detached', rehydrate_object)
        session.remove()

        # Rollback the transaction and return the connection to the pool
        transaction.force_rollback()
        connection.force_close()

    return connection, transaction, session

I am not sure whether this is a sane solution, or if it will have other side effects, though. It'd be great to have some feedback.

tomthorogood avatar Feb 20 '20 15:02 tomthorogood

I've just run into this problem myself. In particular if you're using Flask's test_client() to make requests and you hit something that deletes a row then commits it, things break in this way.

I'm not sure I understand the case for adding deleted things back into the session anyway. I literally removed the line

@sa.event.listens_for(session, 'deleted_to_detached')

So that this code only fixed on persistent_to_detached and that definitely solved the issue for me. Does anyone know a good use case for having that behaviour left in there?

martynsmith avatar Jun 06 '20 23:06 martynsmith