Repository: Kinto/kinto

Issue:
Upgrade to Pyramid-tm 2.X
> 2.0
> The pyramid_tm tween has been moved over the EXCVIEW tween. This means the transaction is open during exception view execution. See https://github.com/Pylons/pyramid_tm/pull/55

http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/#id2

Related #877 and #878 

Upgrade to Pyramid-tm 2.X
> 2.0
> The pyramid_tm tween has been moved over the EXCVIEW tween. This means the transaction is open during exception view execution. See https://github.com/Pylons/pyramid_tm/pull/55

http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/#id2

Related #877 and #878 



Discussion:

Resolving patch:
diff --git a/kinto/core/events.py b/kinto/core/events.py
--- a/kinto/core/events.py
+++ b/kinto/core/events.py
@@ -3,6 +3,7 @@
 
 import transaction
 from pyramid.events import NewRequest
+import pyramid.tweens
 from enum import Enum
 
 from kinto.core.utils import strip_uri_prefix
@@ -65,6 +66,22 @@ def __init__(self, payload, impacted_records, request):
         self.impacted_records = impacted_records
 
 
+def notify_resource_events_before(handler, registry):
+    """pyramid_tm "commit veto" hook to run ResourceChanged events.
+
+    This hook being a "commit veto" let us tell pyramid_tm to abort
+    the transaction if the ResourceChanged listeners raise.
+    """
+    def tween(request):
+        response = handler(request)
+        for event in request.get_resource_events():
+            request.registry.notify(event)
+
+        return response
+
+    return tween
+
+
 def setup_transaction_hook(config):
     """
     Resource events are plugged with the transactions of ``pyramid_tm``.
@@ -72,12 +89,6 @@ def setup_transaction_hook(config):
     Once a transaction is committed, ``AfterResourceRead`` and
     ``AfterResourceChanged`` events are sent.
     """
-    def _notify_resource_events_before(request):
-        """Notify the accumulated resource events before end of transaction.
-        """
-        for event in request.get_resource_events():
-            request.registry.notify(event)
-
     def _notify_resource_events_after(success, request):
         """Notify the accumulated resource events if transaction succeeds.
         """
@@ -97,12 +108,12 @@ def on_new_request(event):
         if hasattr(event.request, 'parent'):
             return
         current = transaction.get()
-        current.addBeforeCommitHook(_notify_resource_events_before,
-                                    args=(event.request,))
         current.addAfterCommitHook(_notify_resource_events_after,
                                    args=(event.request,))
 
     config.add_subscriber(on_new_request, NewRequest)
+    config.add_tween('kinto.core.events.notify_resource_events_before',
+                     under=pyramid.tweens.EXCVIEW)
 
 
 def get_resource_events(request, after_commit=False):
diff --git a/kinto/plugins/quotas/listener.py b/kinto/plugins/quotas/listener.py
--- a/kinto/plugins/quotas/listener.py
+++ b/kinto/plugins/quotas/listener.py
@@ -13,6 +13,12 @@
 COLLECTION_QUOTA_OBJECT_ID = 'collection_info'
 
 
+def raise_insufficient_storage(message):
+    raise http_error(HTTPInsufficientStorage(),
+                     errno=ERRORS.FORBIDDEN.value,
+                     message=message)
+
+
 def get_bucket_settings(settings, bucket_id, name):
     return settings.get(
         # Bucket specific
@@ -130,9 +136,7 @@ def on_resource_changed(event):
             if new_size > max_bytes_per_item:
                 message = ("Maximum bytes per object exceeded "
                            "({} > {} Bytes.".format(new_size, max_bytes_per_item))
-                raise http_error(HTTPInsufficientStorage(),
-                                 errno=ERRORS.FORBIDDEN.value,
-                                 message=message)
+                raise_insufficient_storage(message)
 
         if action == 'create':
             bucket_info['storage_size'] += new_size
@@ -177,36 +181,28 @@ def on_resource_changed(event):
             message = ("Bucket maximum total size exceeded "
                        "({} > {} Bytes). ".format(bucket_info['storage_size'],
                                                   bucket_max_bytes))
-            raise http_error(HTTPInsufficientStorage(),
-                             errno=ERRORS.FORBIDDEN.value,
-                             message=message)
+            raise_insufficient_storage(message)
 
     if bucket_max_items is not None:
         if bucket_info['record_count'] > bucket_max_items:
             message = ("Bucket maximum number of objects exceeded "
                        "({} > {} objects).".format(bucket_info['record_count'],
                                                    bucket_max_items))
-            raise http_error(HTTPInsufficientStorage(),
-                             errno=ERRORS.FORBIDDEN.value,
-                             message=message)
+            raise_insufficient_storage(message)
 
     if collection_max_bytes is not None:
         if collection_info['storage_size'] > collection_max_bytes:
             message = ("Collection maximum size exceeded "
                        "({} > {} Bytes).".format(collection_info['storage_size'],
                                                  collection_max_bytes))
-            raise http_error(HTTPInsufficientStorage(),
-                             errno=ERRORS.FORBIDDEN.value,
-                             message=message)
+            raise_insufficient_storage(message)
 
     if collection_max_items is not None:
         if collection_info['record_count'] > collection_max_items:
             message = ("Collection maximum number of objects exceeded "
                        "({} > {} objects).".format(collection_info['record_count'],
                                                    collection_max_items))
-            raise http_error(HTTPInsufficientStorage(),
-                             errno=ERRORS.FORBIDDEN.value,
-                             message=message)
+            raise_insufficient_storage(message)
 
     storage.update(parent_id=bucket_uri,
                    collection_id=QUOTA_RESOURCE_NAME,
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,7 @@ def read_file(filename):
     'pyramid > 1.8',
     'pyramid_multiauth >= 0.8',  # User on policy selected event.
     'transaction',
-    'pyramid_tm < 2',
+    'pyramid_tm',
     'requests',
     'waitress',
     'ujson >= 1.35'


Test patch: