Skip to content

Commit 4f51ff3

Browse files
authored
fix(quart): Support quart_flask_patch (#4132)
See #2709 (comment) If `quart_flask_patch` is imported, it monkeypatches stuff so that the Quart app appears to be a Flask app. This confuses our Flask integration, which tries to enable itself and fails. This commit: - Makes the Flask integration detect that what it sees as Flask might actually be Quart. - Reorganizes the Quart test suite a little to allow to test this case (a bit tricky since `import quart_flask_patch` needs to happen before anything else due to its monkeypatching nature). Closes #2709
1 parent 4ffefe4 commit 4f51ff3

File tree

5 files changed

+71
-12
lines changed

5 files changed

+71
-12
lines changed

requirements-testing.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ socksio
1414
httpcore[http2]
1515
setuptools
1616
Brotli
17-
docker
17+
docker

scripts/populate_tox/tox.jinja

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ deps =
384384
# Quart
385385
quart: quart-auth
386386
quart: pytest-asyncio
387+
quart-{v0.19,latest}: quart-flask-patch
387388
quart-v0.16: blinker<1.6
388389
quart-v0.16: jinja2<3.1.0
389390
quart-v0.16: Werkzeug<2.1.0

sentry_sdk/integrations/flask.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ def __init__(
7272
@staticmethod
7373
def setup_once():
7474
# type: () -> None
75+
try:
76+
from quart import Quart # type: ignore
77+
78+
if Flask == Quart:
79+
# This is Quart masquerading as Flask, don't enable the Flask
80+
# integration. See https://github.com/getsentry/sentry-python/issues/2709
81+
raise DidNotEnable(
82+
"This is not a Flask app but rather Quart pretending to be Flask"
83+
)
84+
except ImportError:
85+
pass
86+
7587
version = package_version("flask")
7688
_check_minimum_version(FlaskIntegration, version)
7789

tests/integrations/quart/test_quart.py

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import importlib
12
import json
23
import threading
34
from unittest import mock
@@ -13,22 +14,22 @@
1314
from sentry_sdk.integrations.logging import LoggingIntegration
1415
import sentry_sdk.integrations.quart as quart_sentry
1516

16-
from quart import Quart, Response, abort, stream_with_context
17-
from quart.views import View
1817

19-
from quart_auth import AuthUser, login_user
20-
21-
try:
22-
from quart_auth import QuartAuth
18+
def quart_app_factory():
19+
# These imports are inlined because the `test_quart_flask_patch` testcase
20+
# tests behavior that is triggered by importing a package before any Quart
21+
# imports happen, so we can't have these on the module level
22+
from quart import Quart
2323

24-
auth_manager = QuartAuth()
25-
except ImportError:
26-
from quart_auth import AuthManager
24+
try:
25+
from quart_auth import QuartAuth
2726

28-
auth_manager = AuthManager()
27+
auth_manager = QuartAuth()
28+
except ImportError:
29+
from quart_auth import AuthManager
2930

31+
auth_manager = AuthManager()
3032

31-
def quart_app_factory():
3233
app = Quart(__name__)
3334
app.debug = False
3435
app.config["TESTING"] = False
@@ -71,6 +72,42 @@ def integration_enabled_params(request):
7172
raise ValueError(request.param)
7273

7374

75+
@pytest.mark.asyncio
76+
@pytest.mark.forked
77+
@pytest.mark.skipif(
78+
not importlib.util.find_spec("quart_flask_patch"),
79+
reason="requires quart_flask_patch",
80+
)
81+
async def test_quart_flask_patch(sentry_init, capture_events, reset_integrations):
82+
# This testcase is forked because `import quart_flask_patch` needs to run
83+
# before anything else Quart-related is imported (since it monkeypatches
84+
# some things) and we don't want this to affect other testcases.
85+
#
86+
# It's also important this testcase be run before any other testcase
87+
# that uses `quart_app_factory`.
88+
import quart_flask_patch # noqa: F401
89+
90+
app = quart_app_factory()
91+
sentry_init(
92+
integrations=[quart_sentry.QuartIntegration()],
93+
)
94+
95+
@app.route("/")
96+
async def index():
97+
1 / 0
98+
99+
events = capture_events()
100+
101+
client = app.test_client()
102+
try:
103+
await client.get("/")
104+
except ZeroDivisionError:
105+
pass
106+
107+
(event,) = events
108+
assert event["exception"]["values"][0]["mechanism"]["type"] == "quart"
109+
110+
74111
@pytest.mark.asyncio
75112
async def test_has_context(sentry_init, capture_events):
76113
sentry_init(integrations=[quart_sentry.QuartIntegration()])
@@ -213,6 +250,8 @@ async def test_quart_auth_configured(
213250
monkeypatch,
214251
integration_enabled_params,
215252
):
253+
from quart_auth import AuthUser, login_user
254+
216255
sentry_init(send_default_pii=send_default_pii, **integration_enabled_params)
217256
app = quart_app_factory()
218257

@@ -368,6 +407,8 @@ async def error_handler(err):
368407

369408
@pytest.mark.asyncio
370409
async def test_bad_request_not_captured(sentry_init, capture_events):
410+
from quart import abort
411+
371412
sentry_init(integrations=[quart_sentry.QuartIntegration()])
372413
app = quart_app_factory()
373414
events = capture_events()
@@ -385,6 +426,8 @@ async def index():
385426

386427
@pytest.mark.asyncio
387428
async def test_does_not_leak_scope(sentry_init, capture_events):
429+
from quart import Response, stream_with_context
430+
388431
sentry_init(integrations=[quart_sentry.QuartIntegration()])
389432
app = quart_app_factory()
390433
events = capture_events()
@@ -514,6 +557,8 @@ async def error():
514557

515558
@pytest.mark.asyncio
516559
async def test_class_based_views(sentry_init, capture_events):
560+
from quart.views import View
561+
517562
sentry_init(integrations=[quart_sentry.QuartIntegration()])
518563
app = quart_app_factory()
519564
events = capture_events()

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ deps =
501501
# Quart
502502
quart: quart-auth
503503
quart: pytest-asyncio
504+
quart-{v0.19,latest}: quart-flask-patch
504505
quart-v0.16: blinker<1.6
505506
quart-v0.16: jinja2<3.1.0
506507
quart-v0.16: Werkzeug<2.1.0

0 commit comments

Comments
 (0)