From 4b4ffc05795130c8a95577074a29462c2a512d66 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Mon, 17 May 2021 11:32:46 +0200 Subject: [PATCH 01/31] fix(transport): Unified hook for capturing metric about dropped events (#1100) --- sentry_sdk/transport.py | 31 +++++++++++++++++++++++-------- sentry_sdk/worker.py | 9 +++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index 5fdfdfbdc1..a254b4f6ee 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -150,12 +150,14 @@ def _update_rate_limits(self, response): # no matter of the status code to update our internal rate limits. header = response.headers.get("x-sentry-rate-limits") if header: + logger.warning("Rate-limited via x-sentry-rate-limits") self._disabled_until.update(_parse_rate_limits(header)) # old sentries only communicate global rate limit hits via the # retry-after header on 429. This header can also be emitted on new # sentries if a proxy in front wants to globally slow things down. elif response.status == 429: + logger.warning("Rate-limited via 429") self._disabled_until[None] = datetime.utcnow() + timedelta( seconds=self._retry.get_retry_after(response) or 60 ) @@ -173,12 +175,16 @@ def _send_request( "X-Sentry-Auth": str(self._auth.to_header()), } ) - response = self._pool.request( - "POST", - str(self._auth.get_api_url(endpoint_type)), - body=body, - headers=headers, - ) + try: + response = self._pool.request( + "POST", + str(self._auth.get_api_url(endpoint_type)), + body=body, + headers=headers, + ) + except Exception: + self.on_dropped_event("network") + raise try: self._update_rate_limits(response) @@ -186,6 +192,7 @@ def _send_request( if response.status == 429: # if we hit a 429. Something was rate limited but we already # acted on this in `self._update_rate_limits`. + self.on_dropped_event("status_429") pass elif response.status >= 300 or response.status < 200: @@ -194,9 +201,14 @@ def _send_request( response.status, response.data, ) + self.on_dropped_event("status_{}".format(response.status)) finally: response.close() + def on_dropped_event(self, reason): + # type: (str) -> None + pass + def _check_disabled(self, category): # type: (str) -> bool def _disabled(bucket): @@ -212,6 +224,7 @@ def _send_event( # type: (...) -> None if self._check_disabled("error"): + self.on_dropped_event("self_rate_limits") return None body = io.BytesIO() @@ -325,7 +338,8 @@ def send_event_wrapper(): with capture_internal_exceptions(): self._send_event(event) - self._worker.submit(send_event_wrapper) + if not self._worker.submit(send_event_wrapper): + self.on_dropped_event("full_queue") def capture_envelope( self, envelope # type: Envelope @@ -339,7 +353,8 @@ def send_envelope_wrapper(): with capture_internal_exceptions(): self._send_envelope(envelope) - self._worker.submit(send_envelope_wrapper) + if not self._worker.submit(send_envelope_wrapper): + self.on_dropped_event("full_queue") def flush( self, diff --git a/sentry_sdk/worker.py b/sentry_sdk/worker.py index a8e2fe1ce6..47272b81c0 100644 --- a/sentry_sdk/worker.py +++ b/sentry_sdk/worker.py @@ -109,16 +109,13 @@ def _wait_flush(self, timeout, callback): logger.error("flush timed out, dropped %s events", pending) def submit(self, callback): - # type: (Callable[[], None]) -> None + # type: (Callable[[], None]) -> bool self._ensure_thread() try: self._queue.put_nowait(callback) + return True except Full: - self.on_full_queue(callback) - - def on_full_queue(self, callback): - # type: (Optional[Any]) -> None - logger.error("background worker queue full, dropping event") + return False def _target(self): # type: () -> None From e2d0893824481c9a5dd3141872d90d0888c4c5f8 Mon Sep 17 00:00:00 2001 From: elonzh Date: Mon, 31 May 2021 17:24:29 +0800 Subject: [PATCH 02/31] feat(integration): Add Httpx Integration (#1119) * feat(integration): Add Httpx Integration Co-authored-by: Ahmed Etefy --- sentry_sdk/integrations/httpx.py | 83 ++++++++++++++++++++++++++ setup.py | 1 + tests/integrations/httpx/__init__.py | 3 + tests/integrations/httpx/test_httpx.py | 66 ++++++++++++++++++++ tox.ini | 6 ++ 5 files changed, 159 insertions(+) create mode 100644 sentry_sdk/integrations/httpx.py create mode 100644 tests/integrations/httpx/__init__.py create mode 100644 tests/integrations/httpx/test_httpx.py diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py new file mode 100644 index 0000000000..af67315338 --- /dev/null +++ b/sentry_sdk/integrations/httpx.py @@ -0,0 +1,83 @@ +from sentry_sdk import Hub +from sentry_sdk.integrations import Integration, DidNotEnable + +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + + +try: + from httpx import AsyncClient, Client, Request, Response # type: ignore +except ImportError: + raise DidNotEnable("httpx is not installed") + +__all__ = ["HttpxIntegration"] + + +class HttpxIntegration(Integration): + identifier = "httpx" + + @staticmethod + def setup_once(): + # type: () -> None + """ + httpx has its own transport layer and can be customized when needed, + so patch Client.send and AsyncClient.send to support both synchronous and async interfaces. + """ + _install_httpx_client() + _install_httpx_async_client() + + +def _install_httpx_client(): + # type: () -> None + real_send = Client.send + + def send(self, request, **kwargs): + # type: (Client, Request, **Any) -> Response + hub = Hub.current + if hub.get_integration(HttpxIntegration) is None: + return real_send(self, request, **kwargs) + + with hub.start_span( + op="http", description="%s %s" % (request.method, request.url) + ) as span: + span.set_data("method", request.method) + span.set_data("url", str(request.url)) + for key, value in hub.iter_trace_propagation_headers(): + request.headers[key] = value + rv = real_send(self, request, **kwargs) + + span.set_data("status_code", rv.status_code) + span.set_http_status(rv.status_code) + span.set_data("reason", rv.reason_phrase) + return rv + + Client.send = send + + +def _install_httpx_async_client(): + # type: () -> None + real_send = AsyncClient.send + + async def send(self, request, **kwargs): + # type: (AsyncClient, Request, **Any) -> Response + hub = Hub.current + if hub.get_integration(HttpxIntegration) is None: + return await real_send(self, request, **kwargs) + + with hub.start_span( + op="http", description="%s %s" % (request.method, request.url) + ) as span: + span.set_data("method", request.method) + span.set_data("url", str(request.url)) + for key, value in hub.iter_trace_propagation_headers(): + request.headers[key] = value + rv = await real_send(self, request, **kwargs) + + span.set_data("status_code", rv.status_code) + span.set_http_status(rv.status_code) + span.set_data("reason", rv.reason_phrase) + return rv + + AsyncClient.send = send diff --git a/setup.py b/setup.py index eaced8dbd9..d854f87df5 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ def get_file_text(file_name): "pyspark": ["pyspark>=2.4.4"], "pure_eval": ["pure_eval", "executing", "asttokens"], "chalice": ["chalice>=1.16.0"], + "httpx": ["httpx>=0.16.0"], }, classifiers=[ "Development Status :: 5 - Production/Stable", diff --git a/tests/integrations/httpx/__init__.py b/tests/integrations/httpx/__init__.py new file mode 100644 index 0000000000..1afd90ea3a --- /dev/null +++ b/tests/integrations/httpx/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytest.importorskip("httpx") diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py new file mode 100644 index 0000000000..4623f13348 --- /dev/null +++ b/tests/integrations/httpx/test_httpx.py @@ -0,0 +1,66 @@ +import asyncio + +import httpx + +from sentry_sdk import capture_message, start_transaction +from sentry_sdk.integrations.httpx import HttpxIntegration + + +def test_crumb_capture_and_hint(sentry_init, capture_events): + def before_breadcrumb(crumb, hint): + crumb["data"]["extra"] = "foo" + return crumb + + sentry_init(integrations=[HttpxIntegration()], before_breadcrumb=before_breadcrumb) + clients = (httpx.Client(), httpx.AsyncClient()) + for i, c in enumerate(clients): + with start_transaction(): + events = capture_events() + + url = "https://httpbin.org/status/200" + if not asyncio.iscoroutinefunction(c.get): + response = c.get(url) + else: + response = asyncio.get_event_loop().run_until_complete(c.get(url)) + + assert response.status_code == 200 + capture_message("Testing!") + + (event,) = events + # send request twice so we need get breadcrumb by index + crumb = event["breadcrumbs"]["values"][i] + assert crumb["type"] == "http" + assert crumb["category"] == "httplib" + assert crumb["data"] == { + "url": url, + "method": "GET", + "status_code": 200, + "reason": "OK", + "extra": "foo", + } + + +def test_outgoing_trace_headers(sentry_init): + sentry_init(traces_sample_rate=1.0, integrations=[HttpxIntegration()]) + clients = (httpx.Client(), httpx.AsyncClient()) + for i, c in enumerate(clients): + with start_transaction( + name="/interactions/other-dogs/new-dog", + op="greeting.sniff", + # make trace_id difference between transactions + trace_id=f"012345678901234567890123456789{i}", + ) as transaction: + url = "https://httpbin.org/status/200" + if not asyncio.iscoroutinefunction(c.get): + response = c.get(url) + else: + response = asyncio.get_event_loop().run_until_complete(c.get(url)) + + request_span = transaction._span_recorder.spans[-1] + assert response.request.headers[ + "sentry-trace" + ] == "{trace_id}-{parent_span_id}-{sampled}".format( + trace_id=transaction.trace_id, + parent_span_id=request_span.span_id, + sampled=1, + ) diff --git a/tox.ini b/tox.ini index 40e322650c..728ddc793b 100644 --- a/tox.ini +++ b/tox.ini @@ -83,6 +83,8 @@ envlist = {py2.7,py3.6,py3.7,py3.8}-boto3-{1.9,1.10,1.11,1.12,1.13,1.14,1.15,1.16} + {py3.6,py3.7,py3.8,py3.9}-httpx-{0.16,0.17} + [testenv] deps = # if you change test-requirements.txt and your change is not being reflected @@ -235,6 +237,9 @@ deps = boto3-1.15: boto3>=1.15,<1.16 boto3-1.16: boto3>=1.16,<1.17 + httpx-0.16: httpx>=0.16,<0.17 + httpx-0.17: httpx>=0.17,<0.18 + setenv = PYTHONDONTWRITEBYTECODE=1 TESTPATH=tests @@ -260,6 +265,7 @@ setenv = pure_eval: TESTPATH=tests/integrations/pure_eval chalice: TESTPATH=tests/integrations/chalice boto3: TESTPATH=tests/integrations/boto3 + httpx: TESTPATH=tests/integrations/httpx COVERAGE_FILE=.coverage-{envname} passenv = From e91c6f14bc5ff95d46c5dd8c6ef28e3be93ad169 Mon Sep 17 00:00:00 2001 From: Yusuke Hayashi Date: Wed, 2 Jun 2021 03:25:44 +0900 Subject: [PATCH 03/31] fix: typo (#1120) --- sentry_sdk/integrations/redis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/redis.py b/sentry_sdk/integrations/redis.py index 0df6121a54..6475d15bf6 100644 --- a/sentry_sdk/integrations/redis.py +++ b/sentry_sdk/integrations/redis.py @@ -56,7 +56,7 @@ def setup_once(): try: _patch_rediscluster() except Exception: - logger.exception("Error occured while patching `rediscluster` library") + logger.exception("Error occurred while patching `rediscluster` library") def patch_redis_client(cls): From be67071dba2c5cf7582cc0f4b8e62a87f9d7d85b Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Tue, 1 Jun 2021 11:32:42 -0700 Subject: [PATCH 04/31] delete reference to rate being non-zero (#1065) --- sentry_sdk/tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 21269d68df..4ce25f27c2 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -666,7 +666,7 @@ def has_tracing_enabled(options): # type: (Dict[str, Any]) -> bool """ Returns True if either traces_sample_rate or traces_sampler is - non-zero/defined, False otherwise. + defined, False otherwise. """ return bool( From b9c5cd4e06b57919c2d375fd3b4046d5799ab6bd Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Tue, 1 Jun 2021 20:44:23 +0200 Subject: [PATCH 05/31] fix(ci): Fix failing CI dependencies due to Werkzeug and pytest_django (#1124) * fix(ci): Pin trytond werkzeug dependency to Werkzeug<2.0 * Pinned Wekzeug frequence for flask * Pinned pytest-django * Fixed missing DB django tests issue * fix: Formatting * Allowed database access to postgres database in django tests * Added hack to set the appropriate db decorator * Converted string version into tuple for comparison * fix: Formatting * Handled dev versions of pytest_django in hack Co-authored-by: sentry-bot --- tests/integrations/django/test_basic.py | 20 +++++++++++++++++--- tox.ini | 7 +++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 9341dc238d..09fefe6a4c 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -1,6 +1,7 @@ from __future__ import absolute_import import pytest +import pytest_django import json from werkzeug.test import Client @@ -21,6 +22,19 @@ from tests.integrations.django.myapp.wsgi import application +# Hack to prevent from experimental feature introduced in version `4.3.0` in `pytest-django` that +# requires explicit database allow from failing the test +pytest_mark_django_db_decorator = pytest.mark.django_db +try: + pytest_version = tuple(map(int, pytest_django.__version__.split("."))) + if pytest_version > (4, 2, 0): + pytest_mark_django_db_decorator = pytest.mark.django_db(databases="__all__") +except ValueError: + if "dev" in pytest_django.__version__: + pytest_mark_django_db_decorator = pytest.mark.django_db(databases="__all__") +except AttributeError: + pass + @pytest.fixture def client(): @@ -245,7 +259,7 @@ def test_sql_queries(sentry_init, capture_events, with_integration): @pytest.mark.forked -@pytest.mark.django_db +@pytest_mark_django_db_decorator def test_sql_dict_query_params(sentry_init, capture_events): sentry_init( integrations=[DjangoIntegration()], @@ -290,7 +304,7 @@ def test_sql_dict_query_params(sentry_init, capture_events): ], ) @pytest.mark.forked -@pytest.mark.django_db +@pytest_mark_django_db_decorator def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): sentry_init( integrations=[DjangoIntegration()], @@ -323,7 +337,7 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): @pytest.mark.forked -@pytest.mark.django_db +@pytest_mark_django_db_decorator def test_sql_psycopg2_placeholders(sentry_init, capture_events): sentry_init( integrations=[DjangoIntegration()], diff --git a/tox.ini b/tox.ini index 728ddc793b..5aac423c0a 100644 --- a/tox.ini +++ b/tox.ini @@ -104,6 +104,7 @@ deps = django-{1.6,1.7}: pytest-django<3.0 django-{1.8,1.9,1.10,1.11,2.0,2.1}: pytest-django<4.0 django-{2.2,3.0,3.1}: pytest-django>=4.0 + django-{2.2,3.0,3.1}: Werkzeug<2.0 django-dev: git+https://github.com/pytest-dev/pytest-django#egg=pytest-django django-1.6: Django>=1.6,<1.7 @@ -203,7 +204,7 @@ deps = trytond-5.0: trytond>=5.0,<5.1 trytond-4.6: trytond>=4.6,<4.7 - trytond-4.8: werkzeug<1.0 + trytond-{4.6,4.8,5.0,5.2,5.4}: werkzeug<2.0 redis: fakeredis @@ -303,9 +304,7 @@ commands = ; https://github.com/pytest-dev/pytest/issues/5532 {py3.5,py3.6,py3.7,py3.8,py3.9}-flask-{0.10,0.11,0.12}: pip install pytest<5 - - ; trytond tries to import werkzeug.contrib - trytond-5.0: pip install werkzeug<1.0 + {py3.6,py3.7,py3.8,py3.9}-flask-{0.11}: pip install Werkzeug<2 py.test {env:TESTPATH} {posargs} From 41749c1b5dd003bbaa21675c00e2c80dd66b31ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20B=C3=A1rta?= Date: Tue, 1 Jun 2021 20:55:12 +0200 Subject: [PATCH 06/31] fix(integration): Discard -dev when parsing required versions for bottle --- sentry_sdk/integrations/bottle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/bottle.py b/sentry_sdk/integrations/bottle.py index 8bdabda4f7..4fa077e8f6 100644 --- a/sentry_sdk/integrations/bottle.py +++ b/sentry_sdk/integrations/bottle.py @@ -57,7 +57,7 @@ def setup_once(): # type: () -> None try: - version = tuple(map(int, BOTTLE_VERSION.split("."))) + version = tuple(map(int, BOTTLE_VERSION.replace("-dev", "").split("."))) except (TypeError, ValueError): raise DidNotEnable("Unparsable Bottle version: {}".format(version)) From 4915190848b0b2d07733efdbda02486cc9cd1846 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:57:04 +0000 Subject: [PATCH 07/31] build(deps): bump sphinx from 3.5.3 to 4.0.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.3 to 4.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/commits/v4.0.2) Signed-off-by: dependabot-preview[bot] --- docs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-requirements.txt b/docs-requirements.txt index 8273d572e7..d04e38b90b 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,4 +1,4 @@ -sphinx==3.5.3 +sphinx==4.0.2 sphinx-rtd-theme sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions From 69b3f8704481611916eb1c43d4e417dfcb709d93 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 13:58:40 +0000 Subject: [PATCH 08/31] build(deps): bump flake8 from 3.9.0 to 3.9.2 Bumps [flake8](https://gitlab.com/pycqa/flake8) from 3.9.0 to 3.9.2. - [Release notes](https://gitlab.com/pycqa/flake8/tags) - [Commits](https://gitlab.com/pycqa/flake8/compare/3.9.0...3.9.2) Signed-off-by: dependabot-preview[bot] --- linter-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index 08b4795849..474bed4ff7 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -1,5 +1,5 @@ black==20.8b1 -flake8==3.9.0 +flake8==3.9.2 flake8-import-order==0.18.1 mypy==0.782 flake8-bugbear==21.3.2 From a3b71748c7b50482811241a84e5104b9f81ad145 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:43:44 +0200 Subject: [PATCH 09/31] build(deps): bump black from 20.8b1 to 21.5b2 (#1126) Bumps [black](https://github.com/psf/black) from 20.8b1 to 21.5b2. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- linter-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index 474bed4ff7..10faef6eda 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -1,4 +1,4 @@ -black==20.8b1 +black==21.5b2 flake8==3.9.2 flake8-import-order==0.18.1 mypy==0.782 From becf6db53eac242408b46120e7a2650aa2e9a67a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:22:21 +0000 Subject: [PATCH 10/31] build(deps): bump flake8-bugbear from 21.3.2 to 21.4.3 Bumps [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) from 21.3.2 to 21.4.3. - [Release notes](https://github.com/PyCQA/flake8-bugbear/releases) - [Commits](https://github.com/PyCQA/flake8-bugbear/compare/21.3.2...21.4.3) Signed-off-by: dependabot-preview[bot] --- linter-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index 10faef6eda..ddf8ad551e 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -2,5 +2,5 @@ black==21.5b2 flake8==3.9.2 flake8-import-order==0.18.1 mypy==0.782 -flake8-bugbear==21.3.2 +flake8-bugbear==21.4.3 pep8-naming==0.11.1 From e33cf0579d43410cfa76e9b8cfaf49f8d161a705 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 11 Jun 2021 18:08:33 +0300 Subject: [PATCH 11/31] ref(craft): Modernize Craft config (#1127) * ref(craft): Modernize Craft config * Add missing comments back --- .craft.yml | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/.craft.yml b/.craft.yml index 5237c9debe..e351462f72 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,18 +1,12 @@ ---- -minVersion: "0.14.0" -github: - owner: getsentry - repo: sentry-python - +minVersion: 0.23.1 targets: - name: pypi includeNames: /^sentry[_\-]sdk.*$/ - name: github - name: gh-pages - name: registry - type: sdk - config: - canonical: pypi:sentry-sdk + sdks: + pypi:sentry-sdk: - name: aws-lambda-layer includeNames: /^sentry-python-serverless-\d+(\.\d+)*\.zip$/ layerName: SentryPythonServerlessSDK @@ -29,11 +23,5 @@ targets: - python3.7 - python3.8 license: MIT - changelog: CHANGELOG.md changelogPolicy: simple - -statusProvider: - name: github -artifactProvider: - name: github From e204e1aae5bb14ca3076e6e7f0962d657356cbd1 Mon Sep 17 00:00:00 2001 From: Charles Verdad Date: Sat, 12 Jun 2021 02:08:11 +1000 Subject: [PATCH 12/31] Support China domain in lambda cloudwatch logs url (#1051) * Support china domain in lambda cloudwatch logs url * Make tests pass * trigger GitHub actions Co-authored-by: Ahmed Etefy --- sentry_sdk/integrations/aws_lambda.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index 7f823dc04e..533250efaa 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -400,13 +400,15 @@ def _get_cloudwatch_logs_url(aws_context, start_time): str -- AWS Console URL to logs. """ formatstring = "%Y-%m-%dT%H:%M:%SZ" + region = environ.get("AWS_REGION", "") url = ( - "https://console.aws.amazon.com/cloudwatch/home?region={region}" + "https://console.{domain}/cloudwatch/home?region={region}" "#logEventViewer:group={log_group};stream={log_stream}" ";start={start_time};end={end_time}" ).format( - region=environ.get("AWS_REGION"), + domain="amazonaws.cn" if region.startswith("cn-") else "aws.amazon.com", + region=region, log_group=aws_context.log_group_name, log_stream=aws_context.log_stream_name, start_time=(start_time - timedelta(seconds=1)).strftime(formatstring), From 7e63541d988b8280fd602808013c84f1ec775bcf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 14 Jun 2021 06:26:09 +0000 Subject: [PATCH 13/31] build(deps): bump black from 21.5b2 to 21.6b0 Bumps [black](https://github.com/psf/black) from 21.5b2 to 21.6b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) Signed-off-by: dependabot-preview[bot] --- linter-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index ddf8ad551e..f7076751d5 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -1,4 +1,4 @@ -black==21.5b2 +black==21.6b0 flake8==3.9.2 flake8-import-order==0.18.1 mypy==0.782 From b0658904925ec2b625b367ae86f9762b5a382d5f Mon Sep 17 00:00:00 2001 From: Karthikeyan Singaravelan Date: Mon, 14 Jun 2021 13:12:07 +0530 Subject: [PATCH 14/31] fix(worker): Set daemon attribute instead of using setDaemon method that was deprecated in Python 3.10 (#1093) --- sentry_sdk/worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/worker.py b/sentry_sdk/worker.py index 47272b81c0..a06fb8f0d1 100644 --- a/sentry_sdk/worker.py +++ b/sentry_sdk/worker.py @@ -66,7 +66,7 @@ def start(self): self._thread = threading.Thread( target=self._target, name="raven-sentry.BackgroundWorker" ) - self._thread.setDaemon(True) + self._thread.daemon = True self._thread.start() self._thread_for_pid = os.getpid() From ab0cd2c2aa1f8cbe3a43d51bb600a7c7f6ad6d6b Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Mon, 5 Jul 2021 18:53:07 +0300 Subject: [PATCH 15/31] fix(aws-lambda): Fix bug for initial handler path (#1139) * fix(aws-lambda): Fix bug for initial handler path Adds support for long initial handler paths in the format of `x.y.z` and dir paths in the format of `x/y.z` --- scripts/init_serverless_sdk.py | 55 +++++++++++++++++--- tests/integrations/aws_lambda/client.py | 28 +++++++++-- tests/integrations/aws_lambda/test_aws.py | 56 ++++++++++++--------- tests/integrations/django/myapp/settings.py | 2 +- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/scripts/init_serverless_sdk.py b/scripts/init_serverless_sdk.py index 0d3545039b..878ff6029e 100644 --- a/scripts/init_serverless_sdk.py +++ b/scripts/init_serverless_sdk.py @@ -6,6 +6,8 @@ 'sentry_sdk.integrations.init_serverless_sdk.sentry_lambda_handler' """ import os +import sys +import re import sentry_sdk from sentry_sdk._types import MYPY @@ -23,16 +25,53 @@ ) +class AWSLambdaModuleLoader: + DIR_PATH_REGEX = r"^(.+)\/([^\/]+)$" + + def __init__(self, sentry_initial_handler): + try: + module_path, self.handler_name = sentry_initial_handler.rsplit(".", 1) + except ValueError: + raise ValueError("Incorrect AWS Handler path (Not a path)") + + self.extract_and_load_lambda_function_module(module_path) + + def extract_and_load_lambda_function_module(self, module_path): + """ + Method that extracts and loads lambda function module from module_path + """ + py_version = sys.version_info + + if re.match(self.DIR_PATH_REGEX, module_path): + # With a path like -> `scheduler/scheduler/event` + # `module_name` is `event`, and `module_file_path` is `scheduler/scheduler/event.py` + module_name = module_path.split(os.path.sep)[-1] + module_file_path = module_path + ".py" + + # Supported python versions are 2.7, 3.6, 3.7, 3.8 + if py_version >= (3, 5): + import importlib.util + spec = importlib.util.spec_from_file_location(module_name, module_file_path) + self.lambda_function_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(self.lambda_function_module) + elif py_version[0] < 3: + import imp + self.lambda_function_module = imp.load_source(module_name, module_file_path) + else: + raise ValueError("Python version %s is not supported." % py_version) + else: + import importlib + self.lambda_function_module = importlib.import_module(module_path) + + def get_lambda_handler(self): + return getattr(self.lambda_function_module, self.handler_name) + + def sentry_lambda_handler(event, context): # type: (Any, Any) -> None """ Handler function that invokes a lambda handler which path is defined in - environment vairables as "SENTRY_INITIAL_HANDLER" + environment variables as "SENTRY_INITIAL_HANDLER" """ - try: - module_name, handler_name = os.environ["SENTRY_INITIAL_HANDLER"].rsplit(".", 1) - except ValueError: - raise ValueError("Incorrect AWS Handler path (Not a path)") - lambda_function = __import__(module_name) - lambda_handler = getattr(lambda_function, handler_name) - return lambda_handler(event, context) + module_loader = AWSLambdaModuleLoader(os.environ["SENTRY_INITIAL_HANDLER"]) + return module_loader.get_lambda_handler()(event, context) diff --git a/tests/integrations/aws_lambda/client.py b/tests/integrations/aws_lambda/client.py index 8273b281c3..784a4a9006 100644 --- a/tests/integrations/aws_lambda/client.py +++ b/tests/integrations/aws_lambda/client.py @@ -18,7 +18,7 @@ def get_boto_client(): def build_no_code_serverless_function_and_layer( - client, tmpdir, fn_name, runtime, timeout + client, tmpdir, fn_name, runtime, timeout, initial_handler ): """ Util function that auto instruments the no code implementation of the python @@ -45,7 +45,7 @@ def build_no_code_serverless_function_and_layer( Timeout=timeout, Environment={ "Variables": { - "SENTRY_INITIAL_HANDLER": "test_lambda.test_handler", + "SENTRY_INITIAL_HANDLER": initial_handler, "SENTRY_DSN": "https://123abc@example.com/123", "SENTRY_TRACES_SAMPLE_RATE": "1.0", } @@ -67,12 +67,27 @@ def run_lambda_function( syntax_check=True, timeout=30, layer=None, + initial_handler=None, subprocess_kwargs=(), ): subprocess_kwargs = dict(subprocess_kwargs) with tempfile.TemporaryDirectory() as tmpdir: - test_lambda_py = os.path.join(tmpdir, "test_lambda.py") + if initial_handler: + # If Initial handler value is provided i.e. it is not the default + # `test_lambda.test_handler`, then create another dir level so that our path is + # test_dir.test_lambda.test_handler + test_dir_path = os.path.join(tmpdir, "test_dir") + python_init_file = os.path.join(test_dir_path, "__init__.py") + os.makedirs(test_dir_path) + with open(python_init_file, "w"): + # Create __init__ file to make it a python package + pass + + test_lambda_py = os.path.join(tmpdir, "test_dir", "test_lambda.py") + else: + test_lambda_py = os.path.join(tmpdir, "test_lambda.py") + with open(test_lambda_py, "w") as f: f.write(code) @@ -127,8 +142,13 @@ def run_lambda_function( cwd=tmpdir, check=True, ) + + # Default initial handler + if not initial_handler: + initial_handler = "test_lambda.test_handler" + build_no_code_serverless_function_and_layer( - client, tmpdir, fn_name, runtime, timeout + client, tmpdir, fn_name, runtime, timeout, initial_handler ) @add_finalizer diff --git a/tests/integrations/aws_lambda/test_aws.py b/tests/integrations/aws_lambda/test_aws.py index 36c212c08f..0f50753be7 100644 --- a/tests/integrations/aws_lambda/test_aws.py +++ b/tests/integrations/aws_lambda/test_aws.py @@ -112,7 +112,9 @@ def lambda_runtime(request): @pytest.fixture def run_lambda_function(request, lambda_client, lambda_runtime): - def inner(code, payload, timeout=30, syntax_check=True, layer=None): + def inner( + code, payload, timeout=30, syntax_check=True, layer=None, initial_handler=None + ): from tests.integrations.aws_lambda.client import run_lambda_function response = run_lambda_function( @@ -124,6 +126,7 @@ def inner(code, payload, timeout=30, syntax_check=True, layer=None): timeout=timeout, syntax_check=syntax_check, layer=layer, + initial_handler=initial_handler, ) # for better debugging @@ -621,32 +624,39 @@ def test_serverless_no_code_instrumentation(run_lambda_function): python sdk, with no code changes sentry is able to capture errors """ - _, _, response = run_lambda_function( - dedent( - """ - import sentry_sdk + for initial_handler in [ + None, + "test_dir/test_lambda.test_handler", + "test_dir.test_lambda.test_handler", + ]: + print("Testing Initial Handler ", initial_handler) + _, _, response = run_lambda_function( + dedent( + """ + import sentry_sdk - def test_handler(event, context): - current_client = sentry_sdk.Hub.current.client + def test_handler(event, context): + current_client = sentry_sdk.Hub.current.client - assert current_client is not None + assert current_client is not None - assert len(current_client.options['integrations']) == 1 - assert isinstance(current_client.options['integrations'][0], - sentry_sdk.integrations.aws_lambda.AwsLambdaIntegration) + assert len(current_client.options['integrations']) == 1 + assert isinstance(current_client.options['integrations'][0], + sentry_sdk.integrations.aws_lambda.AwsLambdaIntegration) - raise Exception("something went wrong") - """ - ), - b'{"foo": "bar"}', - layer=True, - ) - assert response["FunctionError"] == "Unhandled" - assert response["StatusCode"] == 200 + raise Exception("something went wrong") + """ + ), + b'{"foo": "bar"}', + layer=True, + initial_handler=initial_handler, + ) + assert response["FunctionError"] == "Unhandled" + assert response["StatusCode"] == 200 - assert response["Payload"]["errorType"] != "AssertionError" + assert response["Payload"]["errorType"] != "AssertionError" - assert response["Payload"]["errorType"] == "Exception" - assert response["Payload"]["errorMessage"] == "something went wrong" + assert response["Payload"]["errorType"] == "Exception" + assert response["Payload"]["errorMessage"] == "something went wrong" - assert "sentry_handler" in response["LogResult"][3].decode("utf-8") + assert "sentry_handler" in response["LogResult"][3].decode("utf-8") diff --git a/tests/integrations/django/myapp/settings.py b/tests/integrations/django/myapp/settings.py index bea1c35bf4..cc4d249082 100644 --- a/tests/integrations/django/myapp/settings.py +++ b/tests/integrations/django/myapp/settings.py @@ -157,7 +157,7 @@ def middleware(request): USE_L10N = True -USE_TZ = True +USE_TZ = False TEMPLATE_DEBUG = True From 5563bba89f813d6df0ac6edfff3456990098ce07 Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Tue, 6 Jul 2021 13:19:59 +0300 Subject: [PATCH 16/31] doc: Updated change log for new release 1.1.1 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a5003fb4..34960169f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,15 @@ sentry-sdk==0.10.1 A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bugtracker. +## 1.1.1 + +- Fix for `AWSLambda` Integration to handle other path formats for function initial handler #1139 +- Fix for worker to set deamon attribute instead of deprecated setDaemon method #1093 +- Fix for `bottle` Integration that discards `-dev` for version extraction #1085 +- Fix for transport that adds a unified hook for capturing metrics about dropped events #1100 +- Add `Httpx` Integration #1119 +- Add support for china domains in `AWSLambda` Integration #1051 + ## 1.1.0 - Fix for `AWSLambda` integration returns value of original handler #1106 From 020bf1b99068130dca12be61b4c09a1ea6ea427d Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Tue, 6 Jul 2021 13:29:15 +0300 Subject: [PATCH 17/31] doc: Update CHANGELOG.md for release 1.2.0 (#1141) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34960169f9..92f3c9f5d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ sentry-sdk==0.10.1 A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bugtracker. -## 1.1.1 +## 1.2.0 - Fix for `AWSLambda` Integration to handle other path formats for function initial handler #1139 - Fix for worker to set deamon attribute instead of deprecated setDaemon method #1093 From 169c224b6f6b3638fb8a367ee64bf9029cd9f51e Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Tue, 6 Jul 2021 14:15:54 +0300 Subject: [PATCH 18/31] fix(docs): Add sphinx imports to docs conf to prevent circular dependency (#1142) --- docs/conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 64084a3970..6d0bde20c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,6 +5,13 @@ import typing +# prevent circular imports +import sphinx.builders.html +import sphinx.builders.latex +import sphinx.builders.texinfo +import sphinx.builders.text +import sphinx.ext.autodoc + typing.TYPE_CHECKING = True # From 861b0aefd2ea51a4f3f25acb019612be97202f83 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 6 Jul 2021 11:17:29 +0000 Subject: [PATCH 19/31] release: 1.2.0 --- docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6d0bde20c2..da68a4e8d4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ copyright = u"2019, Sentry Team and Contributors" author = u"Sentry Team and Contributors" -release = "1.1.0" +release = "1.2.0" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 824e874bbd..005d9573b5 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -99,7 +99,7 @@ def _get_default_options(): del _get_default_options -VERSION = "1.1.0" +VERSION = "1.2.0" SDK_INFO = { "name": "sentry.python", "version": VERSION, diff --git a/setup.py b/setup.py index d854f87df5..056074757d 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="1.1.0", + version="1.2.0", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python", From c6a0ea4c253c8f09b12e90574a23af87958b520e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 6 Jul 2021 13:32:31 +0200 Subject: [PATCH 20/31] Upgrade to GitHub-native Dependabot (#1103) Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- .github/dependabot.yml | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..9c69247970 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,43 @@ +version: 2 +updates: +- package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + allow: + - dependency-type: direct + - dependency-type: indirect + ignore: + - dependency-name: pytest + versions: + - "> 3.7.3" + - dependency-name: pytest-cov + versions: + - "> 2.8.1" + - dependency-name: pytest-forked + versions: + - "> 1.1.3" + - dependency-name: sphinx + versions: + - ">= 2.4.a, < 2.5" + - dependency-name: tox + versions: + - "> 3.7.0" + - dependency-name: werkzeug + versions: + - "> 0.15.5, < 1" + - dependency-name: werkzeug + versions: + - ">= 1.0.a, < 1.1" + - dependency-name: mypy + versions: + - "0.800" + - dependency-name: sphinx + versions: + - 3.4.3 +- package-ecosystem: gitsubmodule + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 From b67fe105a323b1ada052bcb137cea3508fa2e068 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Tue, 6 Jul 2021 11:32:00 +0000 Subject: [PATCH 21/31] build(deps): bump checkouts/data-schemas from `f97137d` to `f8615df` Bumps [checkouts/data-schemas](https://github.com/getsentry/sentry-data-schemas) from `f97137d` to `f8615df`. - [Release notes](https://github.com/getsentry/sentry-data-schemas/releases) - [Commits](https://github.com/getsentry/sentry-data-schemas/compare/f97137ddd16853269519de3c9ec00503a99b5da3...f8615dff7f4640ff8a1810b264589b9fc6a4684a) Signed-off-by: dependabot-preview[bot] --- checkouts/data-schemas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkouts/data-schemas b/checkouts/data-schemas index f97137ddd1..f8615dff7f 160000 --- a/checkouts/data-schemas +++ b/checkouts/data-schemas @@ -1 +1 @@ -Subproject commit f97137ddd16853269519de3c9ec00503a99b5da3 +Subproject commit f8615dff7f4640ff8a1810b264589b9fc6a4684a From dd91a8b3e30b67edb6e29c75372f278563523edc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:04:09 +0200 Subject: [PATCH 22/31] build(deps): bump sphinx from 4.0.2 to 4.0.3 (#1144) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.2...v4.0.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-requirements.txt b/docs-requirements.txt index d04e38b90b..e8239919ca 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,4 +1,4 @@ -sphinx==4.0.2 +sphinx==4.0.3 sphinx-rtd-theme sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions From 73bb478f1d2bec580af46825a763a31bcef08514 Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Thu, 8 Jul 2021 09:15:06 +0300 Subject: [PATCH 23/31] feat(integration): Add support for Sanic >=21.3 (#1146) * feat(integration): Add support for Sanic >=21.3 * PR changes requested * Fixed failing test + consistent transaction names * fix: Formatting * Trigger Build * Small refactor * Removed python 3.9 sanic 19 env due to lack of support * Added checks for splitting app name from route name Co-authored-by: sentry-bot --- sentry_sdk/integrations/sanic.py | 23 +++++++++-- tests/integrations/sanic/test_sanic.py | 53 +++++++++++++++++++++++--- tox.ini | 5 +++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index d5eb7fae87..890bb2f3e2 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -96,14 +96,29 @@ async def sentry_handle_request(self, request, *args, **kwargs): old_router_get = Router.get - def sentry_router_get(self, request): - # type: (Any, Request) -> Any - rv = old_router_get(self, request) + def sentry_router_get(self, *args): + # type: (Any, Union[Any, Request]) -> Any + rv = old_router_get(self, *args) hub = Hub.current if hub.get_integration(SanicIntegration) is not None: with capture_internal_exceptions(): with hub.configure_scope() as scope: - scope.transaction = rv[0].__name__ + if version >= (21, 3): + # Sanic versions above and including 21.3 append the app name to the + # route name, and so we need to remove it from Route name so the + # transaction name is consistent across all versions + sanic_app_name = self.ctx.app.name + sanic_route = rv[0].name + + if sanic_route.startswith("%s." % sanic_app_name): + # We add a 1 to the len of the sanic_app_name because there is a dot + # that joins app name and the route name + # Format: app_name.route_name + sanic_route = sanic_route[len(sanic_app_name) + 1 :] + + scope.transaction = sanic_route + else: + scope.transaction = rv[0].__name__ return rv Router.get = sentry_router_get diff --git a/tests/integrations/sanic/test_sanic.py b/tests/integrations/sanic/test_sanic.py index 72425abbcb..8ee19844c5 100644 --- a/tests/integrations/sanic/test_sanic.py +++ b/tests/integrations/sanic/test_sanic.py @@ -9,6 +9,7 @@ from sentry_sdk.integrations.sanic import SanicIntegration from sanic import Sanic, request, response, __version__ as SANIC_VERSION_RAW +from sanic.response import HTTPResponse from sanic.exceptions import abort SANIC_VERSION = tuple(map(int, SANIC_VERSION_RAW.split("."))) @@ -16,7 +17,12 @@ @pytest.fixture def app(): - app = Sanic(__name__) + 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) + else: + app = Sanic(__name__) @app.route("/message") def hi(request): @@ -166,11 +172,46 @@ async def task(i): if SANIC_VERSION >= (19,): kwargs["app"] = app - await app.handle_request( - request.Request(**kwargs), - write_callback=responses.append, - stream_callback=responses.append, - ) + if SANIC_VERSION >= (21, 3): + try: + app.router.reset() + app.router.finalize() + except AttributeError: + ... + + class MockAsyncStreamer: + def __init__(self, request_body): + self.request_body = request_body + self.iter = iter(self.request_body) + self.response = b"success" + + def respond(self, response): + responses.append(response) + patched_response = HTTPResponse() + patched_response.send = lambda end_stream: asyncio.sleep(0.001) + return patched_response + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self.iter) + except StopIteration: + raise StopAsyncIteration + + patched_request = request.Request(**kwargs) + patched_request.stream = MockAsyncStreamer([b"hello", b"foo"]) + + await app.handle_request( + patched_request, + ) + else: + await app.handle_request( + request.Request(**kwargs), + write_callback=responses.append, + stream_callback=responses.append, + ) (r,) = responses assert r.status == 200 diff --git a/tox.ini b/tox.ini index 5aac423c0a..68cee8e587 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,8 @@ envlist = {py3.5,py3.6,py3.7}-sanic-{0.8,18} {py3.6,py3.7}-sanic-19 + {py3.6,py3.7,py3.8}-sanic-20 + {py3.7,py3.8,py3.9}-sanic-21 # TODO: Add py3.9 {pypy,py2.7}-celery-3 @@ -139,6 +141,9 @@ deps = sanic-0.8: sanic>=0.8,<0.9 sanic-18: sanic>=18.0,<19.0 sanic-19: sanic>=19.0,<20.0 + sanic-20: sanic>=20.0,<21.0 + sanic-21: sanic>=21.0,<22.0 + {py3.7,py3.8,py3.9}-sanic-21: sanic_testing {py3.5,py3.6}-sanic: aiocontextvars==0.2.1 sanic: aiohttp py3.5-sanic: ujson<4 From a9bb245ae28bc203b252d1a8fb280203f219c93e Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Thu, 8 Jul 2021 10:17:29 +0300 Subject: [PATCH 24/31] Update changelog (#1147) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f3c9f5d8..c34bd5439b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ sentry-sdk==0.10.1 A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bugtracker. +## 1.3.0 + +- Add support for Sanic versions 20 and 21 #1146 + ## 1.2.0 - Fix for `AWSLambda` Integration to handle other path formats for function initial handler #1139 From 956101e9ba18f8c9a2e323808e0a2baacff03ca0 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 8 Jul 2021 07:18:25 +0000 Subject: [PATCH 25/31] release: 1.3.0 --- docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index da68a4e8d4..e95252c80d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ copyright = u"2019, Sentry Team and Contributors" author = u"Sentry Team and Contributors" -release = "1.2.0" +release = "1.3.0" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 005d9573b5..2d00fca7eb 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -99,7 +99,7 @@ def _get_default_options(): del _get_default_options -VERSION = "1.2.0" +VERSION = "1.3.0" SDK_INFO = { "name": "sentry.python", "version": VERSION, diff --git a/setup.py b/setup.py index 056074757d..6472c663d3 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="1.2.0", + version="1.3.0", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python", From f005c3037a0a32e8bc3a9dd8020e70aca74e7046 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jul 2021 17:11:51 +0200 Subject: [PATCH 26/31] build(deps): bump sphinx from 4.0.3 to 4.1.0 (#1149) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.3 to 4.1.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.3...v4.1.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-requirements.txt b/docs-requirements.txt index e8239919ca..1c32b7dec2 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,4 +1,4 @@ -sphinx==4.0.3 +sphinx==4.1.0 sphinx-rtd-theme sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions From 5bff724b5364ade78991874732df362e5dedfe34 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 12:40:25 +0200 Subject: [PATCH 27/31] build(deps): bump sphinx from 4.1.0 to 4.1.1 (#1152) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-requirements.txt b/docs-requirements.txt index 1c32b7dec2..e66af3de2c 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,4 +1,4 @@ -sphinx==4.1.0 +sphinx==4.1.1 sphinx-rtd-theme sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions From 06f0265a9e926b38b04529dc77d2df51fba919f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Jul 2021 12:40:36 +0200 Subject: [PATCH 28/31] build(deps): bump black from 21.6b0 to 21.7b0 (#1153) Bumps [black](https://github.com/psf/black) from 21.6b0 to 21.7b0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits) --- updated-dependencies: - dependency-name: black dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- linter-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linter-requirements.txt b/linter-requirements.txt index f7076751d5..812b929c97 100644 --- a/linter-requirements.txt +++ b/linter-requirements.txt @@ -1,4 +1,4 @@ -black==21.6b0 +black==21.7b0 flake8==3.9.2 flake8-import-order==0.18.1 mypy==0.782 From e8d45870b7354859760e498ef15928e74018e505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 27 Jul 2021 11:25:09 +0200 Subject: [PATCH 29/31] =?UTF-8?q?=F0=9F=90=9B=20Fix=20detection=20of=20con?= =?UTF-8?q?textvars=20compatibility=20with=20Gevent=2020.9.0+=20(#1157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Fix detection of contextvars compatibility with Gevent 20.9.0+ * 🐛 Improve implementation of version detection and account for Python versions * 🔥 Remove duplicated sys import * 🚨 Fix linter warnings --- sentry_sdk/utils.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 323e4ceffa..43b63b41ac 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -785,12 +785,24 @@ def _is_contextvars_broken(): Returns whether gevent/eventlet have patched the stdlib in a way where thread locals are now more "correct" than contextvars. """ try: + import gevent # type: ignore from gevent.monkey import is_object_patched # type: ignore + # Get the MAJOR and MINOR version numbers of Gevent + version_tuple = tuple([int(part) for part in gevent.__version__.split(".")[:2]]) if is_object_patched("threading", "local"): - # Gevent 20.5 is able to patch both thread locals and contextvars, - # in that case all is good. - if is_object_patched("contextvars", "ContextVar"): + # Gevent 20.9.0 depends on Greenlet 0.4.17 which natively handles switching + # context vars when greenlets are switched, so, Gevent 20.9.0+ is all fine. + # Ref: https://github.com/gevent/gevent/blob/83c9e2ae5b0834b8f84233760aabe82c3ba065b4/src/gevent/monkey.py#L604-L609 + # Gevent 20.5, that doesn't depend on Greenlet 0.4.17 with native support + # for contextvars, is able to patch both thread locals and contextvars, in + # that case, check if contextvars are effectively patched. + if ( + # Gevent 20.9.0+ + (sys.version_info >= (3, 7) and version_tuple >= (20, 9)) + # Gevent 20.5.0+ or Python < 3.7 + or (is_object_patched("contextvars", "ContextVar")) + ): return False return True From 7268cb38fd0afbe321c3582f05d67482f1aaa153 Mon Sep 17 00:00:00 2001 From: Ahmed Etefy Date: Tue, 27 Jul 2021 17:02:52 +0300 Subject: [PATCH 30/31] docs: Update changelog (#1158) --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34bd5439b..672c2ef016 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ sentry-sdk==0.10.1 A major release `N` implies the previous release `N-1` will no longer receive updates. We generally do not backport bugfixes to older versions unless they are security relevant. However, feel free to ask for backports of specific commits on the bugtracker. +## 1.3.1 + +- Fix detection of contextvars compatibility with Gevent versions >=20.9.0 #1157 + ## 1.3.0 - Add support for Sanic versions 20 and 21 #1146 From 770cd6ab13b29425d5d50531d73d066f725d818f Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Tue, 27 Jul 2021 14:03:41 +0000 Subject: [PATCH 31/31] release: 1.3.1 --- docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e95252c80d..67a32f39ae 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,7 @@ copyright = u"2019, Sentry Team and Contributors" author = u"Sentry Team and Contributors" -release = "1.3.0" +release = "1.3.1" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 2d00fca7eb..a9822e8223 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -99,7 +99,7 @@ def _get_default_options(): del _get_default_options -VERSION = "1.3.0" +VERSION = "1.3.1" SDK_INFO = { "name": "sentry.python", "version": VERSION, diff --git a/setup.py b/setup.py index 6472c663d3..bec94832c6 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="1.3.0", + version="1.3.1", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python",