From 4935758e2dba530e6345e9e31d330a981fb8c681 Mon Sep 17 00:00:00 2001 From: antonio_antuan Date: Sat, 16 Mar 2019 11:08:01 +0300 Subject: [PATCH 1/6] use contextvars backport --- Makefile | 2 +- sentry_sdk/integrations/aiohttp.py | 7 +++---- sentry_sdk/integrations/sanic.py | 9 +++++---- sentry_sdk/utils.py | 6 +++++- test-requirements.txt | 1 + tests/integrations/sanic/test_sanic.py | 8 +++++++- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 09d05424a4..1115f16043 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ format: .venv .PHONY: format test: .venv - @$(VENV_PATH)/bin/tox -e py2.7,py3.7 + @$(VENV_PATH)/bin/tox -e py2.7,py3.7,py3.6 .PHONY: test test-all: .venv diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 5d0afc0869..a10d750150 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -27,12 +27,11 @@ class AioHttpIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7): + if sys.version_info < (3, 7) and 'aiocontextvars' not in sys.modules: # We better have contextvars or we're going to leak state between # requests. - raise RuntimeError( - "The aiohttp integration for Sentry requires Python 3.7+" - ) + raise RuntimeError("The aiohttp integration for Sentry requires Python 3.7+ " + " or aiocontextvars package") ignore_logger("aiohttp.server") diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index e0f14b5576..862c759f82 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -34,10 +34,11 @@ class SanicIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7): - # Sanic is async. We better have contextvars or we're going to leak - # state between requests. - raise RuntimeError("The sanic integration for Sentry requires Python 3.7+") + if sys.version_info < (3, 7) and 'aiocontextvars' not in sys.modules: + # We better have contextvars or we're going to leak state between + # requests. + raise RuntimeError("The sanic integration for Sentry requires Python 3.7+ " + " or aiocontextvars package") # Sanic 0.8 and older creates a logger named "root" and puts a # stringified version of every exception in there (without exc_info), diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index bccade6400..8793ba7468 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -867,8 +867,12 @@ def realign_remark(remark): value=rv, metadata={"len": rv_original_length, "rem": rv_remarks} ) - try: + if not PY2 and sys.version_info < (3, 7): + try: + import aiocontextvars + except ImportError: + pass from contextvars import ContextVar # type: ignore except ImportError: from threading import local diff --git a/test-requirements.txt b/test-requirements.txt index 36cacfa846..bdd046ff9d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,3 +5,4 @@ tox==3.7.0 Werkzeug==0.14.1 pytest-localserver==0.4.1 pytest-cov==2.6.0 +aiocontextvars==0.2.1 \ No newline at end of file diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index 5c59410740..00d696ccc8 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -1,3 +1,5 @@ +import sys + import random import asyncio @@ -156,7 +158,11 @@ async def task(i): async def runner(): await asyncio.gather(*(task(i) for i in range(1000))) - asyncio.run(runner()) + if sys.version_info < (3, 7): + loop = asyncio.get_event_loop() + loop.run_until_complete(runner()) + else: + asyncio.run(runner()) with configure_scope() as scope: assert not scope._tags From 2dd681763f11d28c057789764ff48f92ef49760c Mon Sep 17 00:00:00 2001 From: antonio_antuan Date: Mon, 18 Mar 2019 22:57:26 +0300 Subject: [PATCH 2/6] use var for contextvars backport detection --- Makefile | 2 +- sentry_sdk/integrations/aiohttp.py | 11 +++++++---- sentry_sdk/integrations/sanic.py | 8 ++++++-- sentry_sdk/utils.py | 3 +++ test-requirements.txt | 3 +-- tests/integrations/aiohttp/test_aiohttp.py | 2 +- tests/integrations/sanic/test_sanic.py | 5 +++-- tox.ini | 3 ++- 8 files changed, 24 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 1115f16043..09d05424a4 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ format: .venv .PHONY: format test: .venv - @$(VENV_PATH)/bin/tox -e py2.7,py3.7,py3.6 + @$(VENV_PATH)/bin/tox -e py2.7,py3.7 .PHONY: test test-all: .venv diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index a10d750150..efaa352de9 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -6,9 +6,12 @@ from sentry_sdk.integrations import Integration from sentry_sdk.integrations.logging import ignore_logger from sentry_sdk.integrations._wsgi_common import _filter_headers -from sentry_sdk.utils import capture_internal_exceptions, event_from_exception +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, + CONTEXTVARS_BACKPORT_ENABLED, +) -import asyncio from aiohttp.web import Application, HTTPException # type: ignore if False: @@ -27,7 +30,7 @@ class AioHttpIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7) and 'aiocontextvars' not in sys.modules: + if sys.version_info < (3, 7) and not CONTEXTVARS_BACKPORT_ENABLED: # We better have contextvars or we're going to leak state between # requests. raise RuntimeError("The aiohttp integration for Sentry requires Python 3.7+ " @@ -60,7 +63,7 @@ async def inner(): return response - return await asyncio.create_task(inner()) + return await inner() Application._handle = sentry_app_handle diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index 862c759f82..021c8486db 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -4,7 +4,11 @@ from sentry_sdk._compat import urlparse, reraise from sentry_sdk.hub import Hub -from sentry_sdk.utils import capture_internal_exceptions, event_from_exception +from sentry_sdk.utils import ( + capture_internal_exceptions, + event_from_exception, + CONTEXTVARS_BACKPORT_ENABLED, +) from sentry_sdk.integrations import Integration from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers from sentry_sdk.integrations.logging import ignore_logger @@ -34,7 +38,7 @@ class SanicIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7) and 'aiocontextvars' not in sys.modules: + if sys.version_info < (3, 7) and not CONTEXTVARS_BACKPORT_ENABLED: # We better have contextvars or we're going to leak state between # requests. raise RuntimeError("The sanic integration for Sentry requires Python 3.7+ " diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 8793ba7468..97019abbde 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -867,10 +867,13 @@ def realign_remark(remark): value=rv, metadata={"len": rv_original_length, "rem": rv_remarks} ) + +CONTEXTVARS_BACKPORT_ENABLED = False try: if not PY2 and sys.version_info < (3, 7): try: import aiocontextvars + CONTEXTVARS_BACKPORT_ENABLED = True except ImportError: pass from contextvars import ContextVar # type: ignore diff --git a/test-requirements.txt b/test-requirements.txt index bdd046ff9d..79872381ae 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,5 +4,4 @@ pytest-xdist==1.23.0 tox==3.7.0 Werkzeug==0.14.1 pytest-localserver==0.4.1 -pytest-cov==2.6.0 -aiocontextvars==0.2.1 \ No newline at end of file +pytest-cov==2.6.0 \ No newline at end of file diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index d357a02d58..4e18cc6400 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -28,7 +28,7 @@ async def hello(request): assert request["env"] == {"REMOTE_ADDR": "127.0.0.1"} assert request["method"] == "GET" assert request["query_string"] == "" - assert request["url"] == f"http://{host}/" + assert request["url"] == "http://{host}/".format(host=host) assert request["headers"] == { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index 00d696ccc8..c4978a13ae 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -142,7 +142,7 @@ async def task(i): await app.handle_request( request.Request( - url_bytes=f"http://localhost/context-check/{i}".encode("ascii"), + url_bytes="http://localhost/context-check/{i}".format(i=i).encode("ascii"), headers={}, version="1.1", method="GET", @@ -159,7 +159,8 @@ async def runner(): await asyncio.gather(*(task(i) for i in range(1000))) if sys.version_info < (3, 7): - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) loop.run_until_complete(runner()) else: asyncio.run(runner()) diff --git a/tox.ini b/tox.ini index c4646511fc..13e2913382 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ envlist = {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-flask-{1.0,0.11,0.12,dev} - py3.7-sanic-0.8 + {py3.5,py3.6,py3.7}-sanic-0.8 {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-celery-{4.1,4.2} {pypy,py2.7}-celery-3 @@ -62,6 +62,7 @@ deps = flask-dev: git+https://github.com/pallets/flask.git#egg=flask sanic-0.8: sanic>=0.8,<0.9 + {py3.5,py3.6}-sanic-0.8: aiocontextvars==0.2.1 sanic: aiohttp celery-3: Celery>=3.1,<4.0 From ac6ae0cc4d28b23c1c8bcc0792b85262d7bf0844 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 18 Mar 2019 21:36:17 +0100 Subject: [PATCH 3/6] fix: Fix logic for contextvar check --- sentry_sdk/integrations/aiohttp.py | 4 ++-- sentry_sdk/integrations/sanic.py | 4 ++-- sentry_sdk/utils.py | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index efaa352de9..f4f62bf478 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -9,7 +9,7 @@ from sentry_sdk.utils import ( capture_internal_exceptions, event_from_exception, - CONTEXTVARS_BACKPORT_ENABLED, + HAS_REAL_CONTEXTVARS, ) from aiohttp.web import Application, HTTPException # type: ignore @@ -30,7 +30,7 @@ class AioHttpIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7) and not CONTEXTVARS_BACKPORT_ENABLED: + if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. raise RuntimeError("The aiohttp integration for Sentry requires Python 3.7+ " diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index 021c8486db..543d8fa061 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -7,7 +7,7 @@ from sentry_sdk.utils import ( capture_internal_exceptions, event_from_exception, - CONTEXTVARS_BACKPORT_ENABLED, + HAS_REAL_CONTEXTVARS, ) from sentry_sdk.integrations import Integration from sentry_sdk.integrations._wsgi_common import RequestExtractor, _filter_headers @@ -38,7 +38,7 @@ class SanicIntegration(Integration): @staticmethod def setup_once(): # type: () -> None - if sys.version_info < (3, 7) and not CONTEXTVARS_BACKPORT_ENABLED: + if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. raise RuntimeError("The sanic integration for Sentry requires Python 3.7+ " diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 97019abbde..823cb08124 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -868,16 +868,16 @@ def realign_remark(remark): ) -CONTEXTVARS_BACKPORT_ENABLED = False +HAS_REAL_CONTEXTVARS = True + try: - if not PY2 and sys.version_info < (3, 7): - try: - import aiocontextvars - CONTEXTVARS_BACKPORT_ENABLED = True - except ImportError: - pass from contextvars import ContextVar # type: ignore + + if not PY2 and sys.version_info < (3, 7): + import aiocontextvars # noqa except ImportError: + HAS_REAL_CONTEXTVARS = False + from threading import local class ContextVar(object): # type: ignore From fcebdfe106dce8bc3c03b25d42c383490a35c135 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 18 Mar 2019 21:37:52 +0100 Subject: [PATCH 4/6] fix: Formatting --- sentry_sdk/integrations/aiohttp.py | 6 ++++-- sentry_sdk/integrations/sanic.py | 6 ++++-- tests/integrations/sanic/test_sanic.py | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index f4f62bf478..02f825bef7 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -33,8 +33,10 @@ def setup_once(): if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. - raise RuntimeError("The aiohttp integration for Sentry requires Python 3.7+ " - " or aiocontextvars package") + raise RuntimeError( + "The aiohttp integration for Sentry requires Python 3.7+ " + " or aiocontextvars package" + ) ignore_logger("aiohttp.server") diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index 543d8fa061..79f4ceb4c2 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -41,8 +41,10 @@ def setup_once(): if not HAS_REAL_CONTEXTVARS: # We better have contextvars or we're going to leak state between # requests. - raise RuntimeError("The sanic integration for Sentry requires Python 3.7+ " - " or aiocontextvars package") + raise RuntimeError( + "The sanic integration for Sentry requires Python 3.7+ " + " or aiocontextvars package" + ) # Sanic 0.8 and older creates a logger named "root" and puts a # stringified version of every exception in there (without exc_info), diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index c4978a13ae..beb60d37bd 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -142,7 +142,9 @@ async def task(i): await app.handle_request( request.Request( - url_bytes="http://localhost/context-check/{i}".format(i=i).encode("ascii"), + url_bytes="http://localhost/context-check/{i}".format(i=i).encode( + "ascii" + ), headers={}, version="1.1", method="GET", From 4499fd7ba39ecc7a334be2234db4267adcc70869 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 18 Mar 2019 21:48:07 +0100 Subject: [PATCH 5/6] fix: Workaround for asyncio.create_task --- sentry_sdk/integrations/aiohttp.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 02f825bef7..ae8f49b79b 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -12,6 +12,7 @@ HAS_REAL_CONTEXTVARS, ) +import asyncio from aiohttp.web import Application, HTTPException # type: ignore if False: @@ -65,7 +66,10 @@ async def inner(): return response - return await inner() + # Explicitly wrap in task such that current contextvar context is + # copied. Just doing `return await inner()` will leak scope data + # between requests. + return await asyncio.get_event_loop().create_task(inner()) Application._handle = sentry_app_handle From 7c519ef2c380d24ffc38e8463a9ac2c78d1e9812 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Tue, 19 Mar 2019 22:52:49 +0100 Subject: [PATCH 6/6] fix: Linters --- sentry_sdk/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 823cb08124..d11d79073e 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -874,7 +874,7 @@ def realign_remark(remark): from contextvars import ContextVar # type: ignore if not PY2 and sys.version_info < (3, 7): - import aiocontextvars # noqa + import aiocontextvars # type: ignore # noqa except ImportError: HAS_REAL_CONTEXTVARS = False