On Sat, Mar 19, 2016 at 3:44 AM, Zsolt Ero <zsolt....@gmail.com> wrote:
> My project is based on standard SQLAlchemy scaffold, ZTE, using ORM. > Pyramid 1.5.8. > > I'm trying to catch an IntegrityError (unique key constraint), I've made a > very minimal example > > @view_config(route_name='exception_test', renderer='json') > def exception_test(request): > user = DBSession.query(User).filter(User.id == 1).first() > user.tutorial_done = True > > DBSession.query(Map).first() > > map_obj = Map(user=request.user) > map_obj.name = u'a' > map_obj.slug = 'a' > > try: > DBSession.add(map_obj) > DBSession.flush() > > except Exception: > return {'success': False} > > return { > 'id': map_obj.id, > 'success': True > } > > > This results in a InvalidRequestError, which cannot be catched, even with > an except Exception. > > 04:34:25,180 ERROR [waitress] Exception when serving /exception_test > Traceback (most recent call last): > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/channel.py", > line 336, in service > task.service() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/task.py", > line 169, in service > self.execute() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/task.py", > line 388, in execute > app_iter = self.channel.server.application(env, start_response) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/paste/translogger.py", > line 69, in __call__ > return self.application(environ, replacement_start_response) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/router.py", > line 242, in __call__ > response = self.invoke_subrequest(request, use_tweens=True) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/router.py", > line 217, in invoke_subrequest > response = handle_request(request) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/tweens.py", > line 21, in excview_tween > response = handler(request) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid_tm/__init__.py", > line 101, in tm_tween > reraise(*exc_info) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid_tm/__init__.py", > line 90, in tm_tween > manager.commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_manager.py", > line 111, in commit > return self.get().commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 280, in commit > reraise(t, v, tb) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 271, in commit > self._commitResources() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 417, in _commitResources > reraise(t, v, tb) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 394, in _commitResources > rm.tpc_vote(self) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/zope/sqlalchemy/datamanager.py", > line 103, in tpc_vote > self.tx.commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/sqlalchemy/orm/session.py", > line 390, in commit > self._assert_active(prepared_ok=True) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/sqlalchemy/orm/session.py", > line 214, in _assert_active > % self._rollback_exception > InvalidRequestError: This Session's transaction has been rolled back due > to a previous exception during flush. To begin a new transaction with this > Session, first issue Session.rollback(). Original exception was: > (psycopg2.IntegrityError) duplicate key value violates unique constraint > "uq_maps_user_id" > DETAIL: Key (user_id, slug)=(1, a) already exists. > > If I put a DBSession.rollback() in the exception block, it results in: > > 04:37:58,625 ERROR [waitress] Exception when serving /exception_test > Traceback (most recent call last): > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/channel.py", > line 336, in service > task.service() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/task.py", > line 169, in service > self.execute() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/waitress/task.py", > line 388, in execute > app_iter = self.channel.server.application(env, start_response) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/paste/translogger.py", > line 69, in __call__ > return self.application(environ, replacement_start_response) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/router.py", > line 242, in __call__ > response = self.invoke_subrequest(request, use_tweens=True) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/router.py", > line 217, in invoke_subrequest > response = handle_request(request) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid/tweens.py", > line 21, in excview_tween > response = handler(request) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid_tm/__init__.py", > line 101, in tm_tween > reraise(*exc_info) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/pyramid_tm/__init__.py", > line 90, in tm_tween > manager.commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_manager.py", > line 111, in commit > return self.get().commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 280, in commit > reraise(t, v, tb) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 271, in commit > self._commitResources() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 417, in _commitResources > reraise(t, v, tb) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/transaction/_transaction.py", > line 394, in _commitResources > rm.tpc_vote(self) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/zope/sqlalchemy/datamanager.py", > line 103, in tpc_vote > self.tx.commit() > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/sqlalchemy/orm/session.py", > line 390, in commit > self._assert_active(prepared_ok=True) > File > "/Users/user/.virtualenvs/maphub_web/lib/python2.7/site-packages/sqlalchemy/orm/session.py", > line 223, in _assert_active > raise sa_exc.ResourceClosedError(closed_msg) > ResourceClosedError: This transaction is closed > > > Moreover, the weird part is that it all seems to depend on seemingly > meaningless (to me at least) order of the queries: > > Moving the query(Map) to be before user.tutorial_done > > user = DBSession.query(User).filter(User.id == 1).first() > DBSession.query(Map).first() > user.tutorial_done = True > > Results in the correct, catchable IntegrityError. > > However, removing the .filter() part makes it broken again: > > user = DBSession.query(User).first() > DBSession.query(Map).first() > user.tutorial_done = True > > What is happening here? I'm totally confused. Is this a bug in ZTE or > SQLAlchemy? How can I possibly run into an uncatchable exception in a view, > and if I do, what am I supposed to do? > It's difficult to tell without a sample that we can run, but I think the exception is being raised after your view function returns. The zope transaction package (in combination with the pyramid_tm and pyramid_sqlalchemy packages) wraps each request to ensure that a transaction is either committed or rolled back at the end of the request. Your IntegrityError in the middle of the view is causing the transaction to be left in a broken state, but you aren't signalling that to the transaction manager. It has no way of knowing that an error has occurred in the view, so it tries to commit the transaction. Since this happens outside your view altogether, you can't catch it. The reason you get different behaviour when you order the statements differently is likely to be autoflush. By default, pending changes are flushed to the database whenever you issue a query (so in your original example, the call to DBSession.query(Map).first() will flush the user.tutorial_done change to the database). Hope that helps, Simon -- You received this message because you are subscribed to the Google Groups "pylons-devel" group. To unsubscribe from this group and stop receiving emails from it, send an email to pylons-devel+unsubscr...@googlegroups.com. To post to this group, send email to pylons-devel@googlegroups.com. Visit this group at https://groups.google.com/group/pylons-devel. For more options, visit https://groups.google.com/d/optout.