Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sentry-sdk for openSUSE:Factory checked in at 2021-08-27 21:44:03 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sentry-sdk (Old) and /work/SRC/openSUSE:Factory/.python-sentry-sdk.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sentry-sdk" Fri Aug 27 21:44:03 2021 rev:16 rq:914559 version:1.3.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sentry-sdk/python-sentry-sdk.changes 2021-05-07 16:46:07.164217695 +0200 +++ /work/SRC/openSUSE:Factory/.python-sentry-sdk.new.1899/python-sentry-sdk.changes 2021-08-27 21:45:15.238065326 +0200 @@ -1,0 +2,20 @@ +Wed Aug 25 12:00:44 UTC 2021 - Martin Hauke <mar...@gmx.de> + +- Update to 1.3.1 + * Fix detection of contextvars compatibility with Gevent + versions >=20.9.0 . +- Update to 1.3.0 + * Add support for Sanic versions 20 and 21 . +- Update to 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 + * 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 + +------------------------------------------------------------------- Old: ---- sentry-python-1.1.0.tar.gz New: ---- sentry-python-1.3.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sentry-sdk.spec ++++++ --- /var/tmp/diff_new_pack.HXolb6/_old 2021-08-27 21:45:15.770065960 +0200 +++ /var/tmp/diff_new_pack.HXolb6/_new 2021-08-27 21:45:15.774065965 +0200 @@ -20,7 +20,7 @@ # nothing provides python2-venusian >= 1.0 needed by python2-pyramid %define skip_python2 1 Name: python-sentry-sdk -Version: 1.1.0 +Version: 1.3.1 Release: 0 Summary: Python SDK for Sentry.io License: BSD-2-Clause ++++++ sentry-python-1.1.0.tar.gz -> sentry-python-1.3.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/.craft.yml new/sentry-python-1.3.1/.craft.yml --- old/sentry-python-1.1.0/.craft.yml 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/.craft.yml 2021-07-27 16:03:41.000000000 +0200 @@ -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 @@ - python3.7 - python3.8 license: MIT - changelog: CHANGELOG.md changelogPolicy: simple - -statusProvider: - name: github -artifactProvider: - name: github diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/.github/dependabot.yml new/sentry-python-1.3.1/.github/dependabot.yml --- old/sentry-python-1.1.0/.github/dependabot.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/sentry-python-1.3.1/.github/dependabot.yml 2021-07-27 16:03:41.000000000 +0200 @@ -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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/CHANGELOG.md new/sentry-python-1.3.1/CHANGELOG.md --- old/sentry-python-1.1.0/CHANGELOG.md 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/CHANGELOG.md 2021-07-27 16:03:41.000000000 +0200 @@ -20,6 +20,23 @@ 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 + +## 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 +- 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/docs/conf.py new/sentry-python-1.3.1/docs/conf.py --- old/sentry-python-1.1.0/docs/conf.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/docs/conf.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 # @@ -22,7 +29,7 @@ copyright = u"2019, Sentry Team and Contributors" author = u"Sentry Team and Contributors" -release = "1.1.0" +release = "1.3.1" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/docs-requirements.txt new/sentry-python-1.3.1/docs-requirements.txt --- old/sentry-python-1.1.0/docs-requirements.txt 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/docs-requirements.txt 2021-07-27 16:03:41.000000000 +0200 @@ -1,4 +1,4 @@ -sphinx==3.5.3 +sphinx==4.1.1 sphinx-rtd-theme sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/linter-requirements.txt new/sentry-python-1.3.1/linter-requirements.txt --- old/sentry-python-1.1.0/linter-requirements.txt 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/linter-requirements.txt 2021-07-27 16:03:41.000000000 +0200 @@ -1,6 +1,6 @@ -black==20.8b1 -flake8==3.9.0 +black==21.7b0 +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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/scripts/init_serverless_sdk.py new/sentry-python-1.3.1/scripts/init_serverless_sdk.py --- old/sentry-python-1.1.0/scripts/init_serverless_sdk.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/scripts/init_serverless_sdk.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/consts.py new/sentry-python-1.3.1/sentry_sdk/consts.py --- old/sentry-python-1.1.0/sentry_sdk/consts.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/consts.py 2021-07-27 16:03:41.000000000 +0200 @@ -99,7 +99,7 @@ del _get_default_options -VERSION = "1.1.0" +VERSION = "1.3.1" SDK_INFO = { "name": "sentry.python", "version": VERSION, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/integrations/aws_lambda.py new/sentry-python-1.3.1/sentry_sdk/integrations/aws_lambda.py --- old/sentry-python-1.1.0/sentry_sdk/integrations/aws_lambda.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/integrations/aws_lambda.py 2021-07-27 16:03:41.000000000 +0200 @@ -400,13 +400,15 @@ 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), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/integrations/bottle.py new/sentry-python-1.3.1/sentry_sdk/integrations/bottle.py --- old/sentry-python-1.1.0/sentry_sdk/integrations/bottle.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/integrations/bottle.py 2021-07-27 16:03:41.000000000 +0200 @@ -57,7 +57,7 @@ # 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)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/integrations/httpx.py new/sentry-python-1.3.1/sentry_sdk/integrations/httpx.py --- old/sentry-python-1.1.0/sentry_sdk/integrations/httpx.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sentry-python-1.3.1/sentry_sdk/integrations/httpx.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/integrations/redis.py new/sentry-python-1.3.1/sentry_sdk/integrations/redis.py --- old/sentry-python-1.1.0/sentry_sdk/integrations/redis.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/integrations/redis.py 2021-07-27 16:03:41.000000000 +0200 @@ -56,7 +56,7 @@ 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): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/integrations/sanic.py new/sentry-python-1.3.1/sentry_sdk/integrations/sanic.py --- old/sentry-python-1.1.0/sentry_sdk/integrations/sanic.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/integrations/sanic.py 2021-07-27 16:03:41.000000000 +0200 @@ -96,14 +96,29 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/tracing.py new/sentry-python-1.3.1/sentry_sdk/tracing.py --- old/sentry-python-1.1.0/sentry_sdk/tracing.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/tracing.py 2021-07-27 16:03:41.000000000 +0200 @@ -666,7 +666,7 @@ # 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( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/transport.py new/sentry-python-1.3.1/sentry_sdk/transport.py --- old/sentry-python-1.1.0/sentry_sdk/transport.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/transport.py 2021-07-27 16:03:41.000000000 +0200 @@ -150,12 +150,14 @@ # 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 @@ "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 @@ 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 @@ 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 @@ # type: (...) -> None if self._check_disabled("error"): + self.on_dropped_event("self_rate_limits") return None body = io.BytesIO() @@ -325,7 +338,8 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/utils.py new/sentry-python-1.3.1/sentry_sdk/utils.py --- old/sentry-python-1.1.0/sentry_sdk/utils.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/utils.py 2021-07-27 16:03:41.000000000 +0200 @@ -785,12 +785,24 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/sentry_sdk/worker.py new/sentry-python-1.3.1/sentry_sdk/worker.py --- old/sentry-python-1.1.0/sentry_sdk/worker.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/sentry_sdk/worker.py 2021-07-27 16:03:41.000000000 +0200 @@ -66,7 +66,7 @@ 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() @@ -109,16 +109,13 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/setup.py new/sentry-python-1.3.1/setup.py --- old/sentry-python-1.1.0/setup.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/setup.py 2021-07-27 16:03:41.000000000 +0200 @@ -21,7 +21,7 @@ setup( name="sentry-sdk", - version="1.1.0", + version="1.3.1", author="Sentry Team and Contributors", author_email="he...@sentry.io", url="https://github.com/getsentry/sentry-python", @@ -53,6 +53,7 @@ "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/aws_lambda/client.py new/sentry-python-1.3.1/tests/integrations/aws_lambda/client.py --- old/sentry-python-1.1.0/tests/integrations/aws_lambda/client.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tests/integrations/aws_lambda/client.py 2021-07-27 16:03:41.000000000 +0200 @@ -18,7 +18,7 @@ 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 @@ Timeout=timeout, Environment={ "Variables": { - "SENTRY_INITIAL_HANDLER": "test_lambda.test_handler", + "SENTRY_INITIAL_HANDLER": initial_handler, "SENTRY_DSN": "https://123...@example.com/123", "SENTRY_TRACES_SAMPLE_RATE": "1.0", } @@ -67,12 +67,27 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/aws_lambda/test_aws.py new/sentry-python-1.3.1/tests/integrations/aws_lambda/test_aws.py --- old/sentry-python-1.1.0/tests/integrations/aws_lambda/test_aws.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tests/integrations/aws_lambda/test_aws.py 2021-07-27 16:03:41.000000000 +0200 @@ -112,7 +112,9 @@ @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 @@ timeout=timeout, syntax_check=syntax_check, layer=layer, + initial_handler=initial_handler, ) # for better debugging @@ -621,32 +624,39 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/django/myapp/settings.py new/sentry-python-1.3.1/tests/integrations/django/myapp/settings.py --- old/sentry-python-1.1.0/tests/integrations/django/myapp/settings.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tests/integrations/django/myapp/settings.py 2021-07-27 16:03:41.000000000 +0200 @@ -157,7 +157,7 @@ USE_L10N = True -USE_TZ = True +USE_TZ = False TEMPLATE_DEBUG = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/django/test_basic.py new/sentry-python-1.3.1/tests/integrations/django/test_basic.py --- old/sentry-python-1.1.0/tests/integrations/django/test_basic.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tests/integrations/django/test_basic.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 @@ @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 @@ ], ) @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 @@ @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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/httpx/__init__.py new/sentry-python-1.3.1/tests/integrations/httpx/__init__.py --- old/sentry-python-1.1.0/tests/integrations/httpx/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sentry-python-1.3.1/tests/integrations/httpx/__init__.py 2021-07-27 16:03:41.000000000 +0200 @@ -0,0 +1,3 @@ +import pytest + +pytest.importorskip("httpx") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/httpx/test_httpx.py new/sentry-python-1.3.1/tests/integrations/httpx/test_httpx.py --- old/sentry-python-1.1.0/tests/integrations/httpx/test_httpx.py 1970-01-01 01:00:00.000000000 +0100 +++ new/sentry-python-1.3.1/tests/integrations/httpx/test_httpx.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tests/integrations/sanic/test_sanic.py new/sentry-python-1.3.1/tests/integrations/sanic/test_sanic.py --- old/sentry-python-1.1.0/tests/integrations/sanic/test_sanic.py 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tests/integrations/sanic/test_sanic.py 2021-07-27 16:03:41.000000000 +0200 @@ -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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sentry-python-1.1.0/tox.ini new/sentry-python-1.3.1/tox.ini --- old/sentry-python-1.1.0/tox.ini 2021-05-06 18:58:30.000000000 +0200 +++ new/sentry-python-1.3.1/tox.ini 2021-07-27 16:03:41.000000000 +0200 @@ -39,6 +39,8 @@ {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 @@ -83,6 +85,8 @@ {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 @@ -102,6 +106,7 @@ 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 @@ -136,6 +141,9 @@ 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 @@ -201,7 +209,7 @@ 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 @@ -235,6 +243,9 @@ 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 +271,7 @@ 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 = @@ -297,9 +309,7 @@ ; 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}