Skip to content

Commit fdb6861

Browse files
Anton Spitsynuntitaker
Anton Spitsyn
authored andcommitted
use contextvars backport for aiohttp and sanic support in py3.6 (#293)
* use contextvars backport * use var for contextvars backport detection * fix: Fix logic for contextvar check * fix: Formatting * fix: Workaround for asyncio.create_task * fix: Linters
1 parent afe0232 commit fdb6861

File tree

7 files changed

+46
-14
lines changed

7 files changed

+46
-14
lines changed

sentry_sdk/integrations/aiohttp.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from sentry_sdk.integrations import Integration
77
from sentry_sdk.integrations.logging import ignore_logger
88
from sentry_sdk.integrations._wsgi_common import _filter_headers
9-
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
9+
from sentry_sdk.utils import (
10+
capture_internal_exceptions,
11+
event_from_exception,
12+
HAS_REAL_CONTEXTVARS,
13+
)
1014

1115
import asyncio
1216
from aiohttp.web import Application, HTTPException # type: ignore
@@ -27,11 +31,12 @@ class AioHttpIntegration(Integration):
2731
@staticmethod
2832
def setup_once():
2933
# type: () -> None
30-
if sys.version_info < (3, 7):
34+
if not HAS_REAL_CONTEXTVARS:
3135
# We better have contextvars or we're going to leak state between
3236
# requests.
3337
raise RuntimeError(
34-
"The aiohttp integration for Sentry requires Python 3.7+"
38+
"The aiohttp integration for Sentry requires Python 3.7+ "
39+
" or aiocontextvars package"
3540
)
3641

3742
ignore_logger("aiohttp.server")
@@ -61,7 +66,10 @@ async def inner():
6166

6267
return response
6368

64-
return await asyncio.create_task(inner())
69+
# Explicitly wrap in task such that current contextvar context is
70+
# copied. Just doing `return await inner()` will leak scope data
71+
# between requests.
72+
return await asyncio.get_event_loop().create_task(inner())
6573

6674
Application._handle = sentry_app_handle
6775

sentry_sdk/integrations/sanic.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
from sentry_sdk._compat import urlparse, reraise
66
from sentry_sdk.hub import Hub
7-
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
7+
from sentry_sdk.utils import (
8+
capture_internal_exceptions,
9+
event_from_exception,
10+
HAS_REAL_CONTEXTVARS,
11+
)
812
from sentry_sdk.integrations import Integration
913
from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers
1014
from sentry_sdk.integrations.logging import ignore_logger
@@ -34,10 +38,13 @@ class SanicIntegration(Integration):
3438
@staticmethod
3539
def setup_once():
3640
# type: () -> None
37-
if sys.version_info < (3, 7):
38-
# Sanic is async. We better have contextvars or we're going to leak
39-
# state between requests.
40-
raise RuntimeError("The sanic integration for Sentry requires Python 3.7+")
41+
if not HAS_REAL_CONTEXTVARS:
42+
# We better have contextvars or we're going to leak state between
43+
# requests.
44+
raise RuntimeError(
45+
"The sanic integration for Sentry requires Python 3.7+ "
46+
" or aiocontextvars package"
47+
)
4148

4249
# Sanic 0.8 and older creates a logger named "root" and puts a
4350
# stringified version of every exception in there (without exc_info),

sentry_sdk/utils.py

+7
Original file line numberDiff line numberDiff line change
@@ -868,9 +868,16 @@ def realign_remark(remark):
868868
)
869869

870870

871+
HAS_REAL_CONTEXTVARS = True
872+
871873
try:
872874
from contextvars import ContextVar # type: ignore
875+
876+
if not PY2 and sys.version_info < (3, 7):
877+
import aiocontextvars # type: ignore # noqa
873878
except ImportError:
879+
HAS_REAL_CONTEXTVARS = False
880+
874881
from threading import local
875882

876883
class ContextVar(object): # type: ignore

test-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pytest-xdist==1.23.0
44
tox==3.7.0
55
Werkzeug==0.14.1
66
pytest-localserver==0.4.1
7-
pytest-cov==2.6.0
7+
pytest-cov==2.6.0

tests/integrations/aiohttp/test_aiohttp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async def hello(request):
2828
assert request["env"] == {"REMOTE_ADDR": "127.0.0.1"}
2929
assert request["method"] == "GET"
3030
assert request["query_string"] == ""
31-
assert request["url"] == f"http://{host}/"
31+
assert request["url"] == "http://{host}/".format(host=host)
3232
assert request["headers"] == {
3333
"Accept": "*/*",
3434
"Accept-Encoding": "gzip, deflate",

tests/integrations/sanic/test_sanic.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
import random
24
import asyncio
35

@@ -140,7 +142,9 @@ async def task(i):
140142

141143
await app.handle_request(
142144
request.Request(
143-
url_bytes=f"http://localhost/context-check/{i}".encode("ascii"),
145+
url_bytes="http://localhost/context-check/{i}".format(i=i).encode(
146+
"ascii"
147+
),
144148
headers={},
145149
version="1.1",
146150
method="GET",
@@ -156,7 +160,12 @@ async def task(i):
156160
async def runner():
157161
await asyncio.gather(*(task(i) for i in range(1000)))
158162

159-
asyncio.run(runner())
163+
if sys.version_info < (3, 7):
164+
loop = asyncio.new_event_loop()
165+
asyncio.set_event_loop(loop)
166+
loop.run_until_complete(runner())
167+
else:
168+
asyncio.run(runner())
160169

161170
with configure_scope() as scope:
162171
assert not scope._tags

tox.ini

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ envlist =
2020

2121
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-flask-{1.0,0.11,0.12,dev}
2222

23-
py3.7-sanic-0.8
23+
{py3.5,py3.6,py3.7}-sanic-0.8
2424

2525
{pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-celery-{4.1,4.2}
2626
{pypy,py2.7}-celery-3
@@ -62,6 +62,7 @@ deps =
6262
flask-dev: git+https://github.com/pallets/flask.git#egg=flask
6363

6464
sanic-0.8: sanic>=0.8,<0.9
65+
{py3.5,py3.6}-sanic-0.8: aiocontextvars==0.2.1
6566
sanic: aiohttp
6667

6768
celery-3: Celery>=3.1,<4.0

0 commit comments

Comments
 (0)