Search code examples
pythonpyramidponyorm

Manage Pony session in Pyramid


The following code throws a DatabaseSessionIsOver exception, as described in this post:

@view_config(route_name='home', renderer='templates/home.jinja2')
@orm.db_session()
def home(self):
    x = models.Entity(...)
    return {'x': x}

I solved the problem using return render_to_response('templates/home.jinja2', {'x': x}), which is the Pyramid equivalent of the render_template() suggested in the post mentioned above.

Everything works well, but I think there is a better solution: I think I should tell Pyramid to manage the Pony session.

Is it possible?

How do I do it?


Solution

  • Your issue is that you're returning managed object x from the view and then the orm session is closed before the view's response is rendered. You want the session to wrap a larger portion of the request lifecycle so that your managed objects stay alive longer. For something like a database session a tween is really the best way to handle this but there are some other ways as well such as a combination of a request property request.pony_session and a finished callback that can close the session. For example (sorry I don't actually know pony's api so you have to fill in the actual methods):

    def get_pony_session(request):
        session = ...  # load a pony session somehow
        def cleanup(request):
            try:
                if request.exception:
                    session.abort()
                else:
                    session.commit()
            finally:
                session.close()
        request.add_finished_callback(cleanup)
        return session
    config.add_request_method(get_pony_session, 'pony_session', reify=True)
    

    You can look at how pyramid does things with the pyramid_tm tween and the alchemy cookiecutter as it really is the best approach for this problem. You could even write a small wrapper to hook pony's session into pyramid_tm if you wanted. To do this you basically write a data manager [1, 2] that manages the pony session and joins to the pyramid_tm transaction (this transaction is "virtual" and has nothing to do with any particular database) and a request property:

    class PonySessionManager:
        def __init__(self, session):
            self.session = session
    
        def tpc_abort(self, txn):
            self.session.abort()
    
        def tpc_commit(self, txn):
            self.session.commit()
    
        def tpc_finish(self, txn):
            self.session.close()
    
    def get_pony_session(request):
        session = ... # load a pony session somehow
        manager = PonySessionManager(session)
        # join the manager to the pyramid_tm transaction via join()
        txn = request.tm.get()
        txn.join(manager)
        return manager
    
    config.add_request_method(get_pony_session, 'pony_session', reify=True)
    

    Note that data manager is a little more simplistic than what is required but it's not difficult.

    At the end of the day you will probably find the following diagram [2] helpful to understand pyramid and what hooks you can use. It is rare that you only want to wrap the view itself and not more of the pipeline.

    [1] http://zodb.readthedocs.io/en/latest/transactions.html

    [2] http://transaction.readthedocs.io/en/latest/datamanager.html

    [3] http://docs.pylonsproject.org/projects/pyramid/en/1.8-branch/narr/router.html