Repository: getsentry/sentry-python

Issue:
Support for Sanic v21.12.0
Raising custom exceptions in Sanic's latest version (which shouldn't be logged to Sentry), getting **IndexError: pop from empty list**

```
File "/app/.heroku/python/lib/python3.9/site-packages/sentry_sdk/integrations/sanic.py", line 184, in _hub_exit
     request.ctx._sentry_hub.__exit__(None, None, None)
File "/app/.heroku/python/lib/python3.9/site-packages/sentry_sdk/hub.py", line 247, in __exit__
     old = self._old_hubs.pop()
IndexError: pop from empty list
```




Discussion:
@Basit-qc Do you know how to trigger this? Is it when raising _any_ exception that derives from `SanicException`?
Okay, I think I see what is happening. It is because this was included in the LTS: https://github.com/sanic-org/sanic/issues/2297
Yes @ahopkins, I got this raising Unauthorized, NotFound, InvalidUsage exceptions.

Resolving patch:
diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py
--- a/sentry_sdk/integrations/sanic.py
+++ b/sentry_sdk/integrations/sanic.py
@@ -222,7 +222,7 @@ async def sentry_wrapped_error_handler(request, exception):
         finally:
             # As mentioned in previous comment in _startup, this can be removed
             # after https://github.com/sanic-org/sanic/issues/2297 is resolved
-            if SanicIntegration.version >= (21, 9):
+            if SanicIntegration.version == (21, 9):
                 await _hub_exit(request)
 
     return sentry_wrapped_error_handler


Test patch:
diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py
--- a/tests/integrations/sanic/test_sanic.py
+++ b/tests/integrations/sanic/test_sanic.py
@@ -2,6 +2,7 @@
 
 import random
 import asyncio
+from unittest.mock import Mock
 
 import pytest
 
@@ -10,7 +11,7 @@
 
 from sanic import Sanic, request, response, __version__ as SANIC_VERSION_RAW
 from sanic.response import HTTPResponse
-from sanic.exceptions import abort
+from sanic.exceptions import SanicException
 
 SANIC_VERSION = tuple(map(int, SANIC_VERSION_RAW.split(".")))
 
@@ -20,9 +21,9 @@ def app():
     if SANIC_VERSION >= (20, 12):
         # Build (20.12.0) adds a feature where the instance is stored in an internal class
         # registry for later retrieval, and so add register=False to disable that
-        app = Sanic(__name__, register=False)
+        app = Sanic("Test", register=False)
     else:
-        app = Sanic(__name__)
+        app = Sanic("Test")
 
     @app.route("/message")
     def hi(request):
@@ -90,7 +91,7 @@ def test_bad_request_not_captured(sentry_init, app, capture_events):
 
     @app.route("/")
     def index(request):
-        abort(400)
+        raise SanicException("...", status_code=400)
 
     request, response = app.test_client.get("/")
     assert response.status == 400
@@ -178,7 +179,12 @@ class MockAsyncStreamer:
                 def __init__(self, request_body):
                     self.request_body = request_body
                     self.iter = iter(self.request_body)
-                    self.response = b"success"
+
+                    if SANIC_VERSION >= (21, 12):
+                        self.response = None
+                        self.stage = Mock()
+                    else:
+                        self.response = b"success"
 
                 def respond(self, response):
                     responses.append(response)