Hello community,

here is the log from the commit of package python-sentry-sdk for 
openSUSE:Factory checked in at 2020-06-19 17:24:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-sentry-sdk (Old)
 and      /work/SRC/openSUSE:Factory/.python-sentry-sdk.new.3606 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-sentry-sdk"

Fri Jun 19 17:24:36 2020 rev:12 rq:815872 version:0.14.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-sentry-sdk/python-sentry-sdk.changes      
2020-03-26 23:35:14.882817805 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-sentry-sdk.new.3606/python-sentry-sdk.changes
    2020-06-19 17:25:24.996246508 +0200
@@ -1,0 +2,15 @@
+Thu Jun 18 16:32:19 UTC 2020 - Martin Hauke <[email protected]>
+
+- update to 0.14.4
+  * Fix bugs in transport rate limit enforcement for specific data
+    categories.
+  * The bug should not have affected anybody because we do not yet
+    emit rate limits for specific event types/data categories.
+  * Fix a bug in capture_event where it would crash if given
+    additional kwargs.
+  * Fix a bug where contextvars from the request handler were
+    inaccessible in AIOHTTP error handlers.
+  * Fix a bug where the Celery integration would crash if newrelic
+    instrumented Celery as well.
+
+-------------------------------------------------------------------

Old:
----
  python-sentry-sdk-0.14.3.tar.gz

New:
----
  python-sentry-sdk-0.14.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-sentry-sdk.spec ++++++
--- /var/tmp/diff_new_pack.5po1ZU/_old  2020-06-19 17:25:28.284256129 +0200
+++ /var/tmp/diff_new_pack.5po1ZU/_new  2020-06-19 17:25:28.288256140 +0200
@@ -20,7 +20,7 @@
 # nothing provides python2-venusian >= 1.0 needed by python2-pyramid
 %define skip_python2 1
 Name:           python-sentry-sdk
-Version:        0.14.3
+Version:        0.14.4
 Release:        0
 Summary:        Python SDK for Sentry.io
 License:        BSD-2-Clause
@@ -76,9 +76,10 @@
 export PYTEST_ADDOPTS="-W ignore::DeprecationWarning"
 # do not test integration:
 rm -r tests/integrations
-# test_transport_works stucks
+# test_transport_works / test_transport_infinite_loop / 
test_simple_rate_limits/ test_data_category_limits / 
test_complex_limits_without_data_category stucks
+
 # test_auto_enabling_integrations_catches_import_error asert False where False 
= ..., not sure
-%pytest -k 'not (test_transport_works or 
test_auto_enabling_integrations_catches_import_error or test_filename)'
+%pytest -k 'not (test_transport_works or 
test_auto_enabling_integrations_catches_import_error or test_filename or 
test_transport_infinite_loop or test_simple_rate_limits or 
test_data_category_limits or test_complex_limits_without_data_category)'
 
 %files %{python_files}
 %doc README.md CHANGES.md

++++++ python-sentry-sdk-0.14.3.tar.gz -> python-sentry-sdk-0.14.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/CHANGES.md 
new/sentry-python-0.14.4/CHANGES.md
--- old/sentry-python-0.14.3/CHANGES.md 2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/CHANGES.md 2020-05-13 13:03:55.000000000 +0200
@@ -27,6 +27,18 @@
 
 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.
 
+## 0.14.4
+
+* Fix bugs in transport rate limit enforcement for specific data categories.
+  The bug should not have affected anybody because we do not yet emit rate
+  limits for specific event types/data categories.
+* Fix a bug in `capture_event` where it would crash if given additional kwargs.
+  Thanks to Tatiana Vasilevskaya!
+* Fix a bug where contextvars from the request handler were inaccessible in
+  AIOHTTP error handlers.
+* Fix a bug where the Celery integration would crash if newrelic instrumented 
Celery as well.
+
+
 ## 0.14.3
 
 * Attempt to use a monotonic clock to measure span durations in 
Performance/APM.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/docs/conf.py 
new/sentry-python-0.14.4/docs/conf.py
--- old/sentry-python-0.14.3/docs/conf.py       2020-03-20 14:19:55.000000000 
+0100
+++ new/sentry-python-0.14.4/docs/conf.py       2020-05-13 13:03:55.000000000 
+0200
@@ -22,7 +22,7 @@
 copyright = u"2019, Sentry Team and Contributors"
 author = u"Sentry Team and Contributors"
 
-release = "0.14.3"
+release = "0.14.4"
 version = ".".join(release.split(".")[:2])  # The short X.Y version.
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/docs-requirements.txt 
new/sentry-python-0.14.4/docs-requirements.txt
--- old/sentry-python-0.14.3/docs-requirements.txt      2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/docs-requirements.txt      2020-05-13 
13:03:55.000000000 +0200
@@ -1,4 +1,4 @@
-sphinx==2.3.1
+sphinx==3.0.3
 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-0.14.3/sentry_sdk/_compat.py 
new/sentry-python-0.14.4/sentry_sdk/_compat.py
--- old/sentry-python-0.14.3/sentry_sdk/_compat.py      2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/_compat.py      2020-05-13 
13:03:55.000000000 +0200
@@ -87,6 +87,6 @@
                 "We detected the use of uwsgi with disabled threads.  "
                 "This will cause issues with the transport you are "
                 "trying to use.  Please enable threading for uwsgi.  "
-                '(Enable the "enable-threads" flag).'
+                '(Add the "enable-threads" flag).'
             )
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/_functools.py 
new/sentry-python-0.14.4/sentry_sdk/_functools.py
--- old/sentry-python-0.14.3/sentry_sdk/_functools.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/_functools.py   2020-05-13 
13:03:55.000000000 +0200
@@ -0,0 +1,66 @@
+"""
+A backport of Python 3 functools to Python 2/3. The only important change
+we rely upon is that `update_wrapper` handles AttributeError gracefully.
+"""
+
+from functools import partial
+
+from sentry_sdk._types import MYPY
+
+if MYPY:
+    from typing import Any
+    from typing import Callable
+
+
+WRAPPER_ASSIGNMENTS = (
+    "__module__",
+    "__name__",
+    "__qualname__",
+    "__doc__",
+    "__annotations__",
+)
+WRAPPER_UPDATES = ("__dict__",)
+
+
+def update_wrapper(
+    wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES
+):
+    # type: (Any, Any, Any, Any) -> Any
+    """Update a wrapper function to look like the wrapped function
+
+       wrapper is the function to be updated
+       wrapped is the original function
+       assigned is a tuple naming the attributes assigned directly
+       from the wrapped function to the wrapper function (defaults to
+       functools.WRAPPER_ASSIGNMENTS)
+       updated is a tuple naming the attributes of the wrapper that
+       are updated with the corresponding attribute from the wrapped
+       function (defaults to functools.WRAPPER_UPDATES)
+    """
+    for attr in assigned:
+        try:
+            value = getattr(wrapped, attr)
+        except AttributeError:
+            pass
+        else:
+            setattr(wrapper, attr, value)
+    for attr in updated:
+        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
+    # Issue #17482: set __wrapped__ last so we don't inadvertently copy it
+    # from the wrapped function when updating __dict__
+    wrapper.__wrapped__ = wrapped
+    # Return the wrapper so this can be used as a decorator via partial()
+    return wrapper
+
+
+def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
+    # type: (Callable[..., Any], Any, Any) -> Callable[[Callable[..., Any]], 
Callable[..., Any]]
+    """Decorator factory to apply update_wrapper() to a wrapper function
+
+       Returns a decorator that invokes update_wrapper() with the decorated
+       function as the wrapper argument and the arguments to wraps() as the
+       remaining arguments. Default arguments are as for update_wrapper().
+       This is a convenience function to simplify applying partial() to
+       update_wrapper().
+    """
+    return partial(update_wrapper, wrapped=wrapped, assigned=assigned, 
updated=updated)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/client.py 
new/sentry-python-0.14.4/sentry_sdk/client.py
--- old/sentry-python-0.14.3/sentry_sdk/client.py       2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/client.py       2020-05-13 
13:03:55.000000000 +0200
@@ -2,6 +2,7 @@
 import uuid
 import random
 from datetime import datetime
+from itertools import islice
 import socket
 
 from sentry_sdk._compat import string_types, text_type, iteritems
@@ -99,10 +100,15 @@
         def _send_sessions(sessions):
             # type: (List[Any]) -> None
             transport = self.transport
-            if sessions and transport:
+            if not transport or not sessions:
+                return
+            sessions_iter = iter(sessions)
+            while True:
                 envelope = Envelope()
-                for session in sessions:
+                for session in islice(sessions_iter, 100):
                     envelope.add_session(session)
+                if not envelope.items:
+                    break
                 transport.capture_envelope(envelope)
 
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/consts.py 
new/sentry-python-0.14.4/sentry_sdk/consts.py
--- old/sentry-python-0.14.3/sentry_sdk/consts.py       2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/consts.py       2020-05-13 
13:03:55.000000000 +0200
@@ -89,7 +89,7 @@
 del _get_default_options
 
 
-VERSION = "0.14.3"
+VERSION = "0.14.4"
 SDK_INFO = {
     "name": "sentry.python",
     "version": VERSION,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/envelope.py 
new/sentry-python-0.14.4/sentry_sdk/envelope.py
--- old/sentry-python-0.14.3/sentry_sdk/envelope.py     2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/envelope.py     2020-05-13 
13:03:55.000000000 +0200
@@ -86,7 +86,7 @@
         self, f  # type: Any
     ):
         # type: (...) -> None
-        f.write(json.dumps(self.headers).encode("utf-8"))
+        f.write(json.dumps(self.headers, allow_nan=False).encode("utf-8"))
         f.write(b"\n")
         for item in self.items:
             item.serialize_into(f)
@@ -142,7 +142,7 @@
                 with open(self.path, "rb") as f:
                     self.bytes = f.read()
             elif self.json is not None:
-                self.bytes = json.dumps(self.json).encode("utf-8")
+                self.bytes = json.dumps(self.json, 
allow_nan=False).encode("utf-8")
             else:
                 self.bytes = b""
         return self.bytes
@@ -256,7 +256,7 @@
         headers = dict(self.headers)
         length, writer = self.payload._prepare_serialize()
         headers["length"] = length
-        f.write(json.dumps(headers).encode("utf-8"))
+        f.write(json.dumps(headers, allow_nan=False).encode("utf-8"))
         f.write(b"\n")
         writer(f)
         f.write(b"\n")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/hub.py 
new/sentry-python-0.14.4/sentry_sdk/hub.py
--- old/sentry-python-0.14.3/sentry_sdk/hub.py  2020-03-20 14:19:55.000000000 
+0100
+++ new/sentry-python-0.14.4/sentry_sdk/hub.py  2020-05-13 13:03:55.000000000 
+0200
@@ -67,7 +67,7 @@
             final_scope.update_from_scope(scope_change)
     elif scope_kwargs:
         final_scope = copy.copy(base)
-        final_scope.update_from_kwargs(scope_kwargs)
+        final_scope.update_from_kwargs(**scope_kwargs)
     else:
         final_scope = base
     return final_scope
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/sentry_sdk/integrations/aiohttp.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/aiohttp.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/aiohttp.py 2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/aiohttp.py 2020-05-13 
13:03:55.000000000 +0200
@@ -71,46 +71,41 @@
 
         async def sentry_app_handle(self, request, *args, **kwargs):
             # type: (Any, Request, *Any, **Any) -> Any
-            async def inner():
-                # type: () -> Any
-                hub = Hub.current
-                if hub.get_integration(AioHttpIntegration) is None:
-                    return await old_handle(self, request, *args, **kwargs)
-
-                weak_request = weakref.ref(request)
-
-                with Hub(Hub.current) as hub:
-                    with hub.configure_scope() as scope:
-                        scope.clear_breadcrumbs()
-                        
scope.add_event_processor(_make_request_processor(weak_request))
-
-                    span = Span.continue_from_headers(request.headers)
-                    span.op = "http.server"
-                    # If this transaction name makes it to the UI, AIOHTTP's
-                    # URL resolver did not find a route or died trying.
-                    span.transaction = "generic AIOHTTP request"
-
-                    with hub.start_span(span):
-                        try:
-                            response = await old_handle(self, request)
-                        except HTTPException as e:
-                            span.set_http_status(e.status_code)
-                            raise
-                        except asyncio.CancelledError:
-                            span.set_status("cancelled")
-                            raise
-                        except Exception:
-                            # This will probably map to a 500 but seems like we
-                            # have no way to tell. Do not set span status.
-                            reraise(*_capture_exception(hub))
-
-                        span.set_http_status(response.status)
-                        return response
-
-            # Explicitly wrap in task such that current contextvar context is
-            # copied. Just doing `return await inner()` will leak scope data
-            # between requests.
-            return await asyncio.get_event_loop().create_task(inner())
+            hub = Hub.current
+            if hub.get_integration(AioHttpIntegration) is None:
+                return await old_handle(self, request, *args, **kwargs)
+
+            weak_request = weakref.ref(request)
+
+            with Hub(Hub.current) as hub:
+                # Scope data will not leak between requests because aiohttp
+                # create a task to wrap each request.
+                with hub.configure_scope() as scope:
+                    scope.clear_breadcrumbs()
+                    
scope.add_event_processor(_make_request_processor(weak_request))
+
+                span = Span.continue_from_headers(request.headers)
+                span.op = "http.server"
+                # If this transaction name makes it to the UI, AIOHTTP's
+                # URL resolver did not find a route or died trying.
+                span.transaction = "generic AIOHTTP request"
+
+                with hub.start_span(span):
+                    try:
+                        response = await old_handle(self, request)
+                    except HTTPException as e:
+                        span.set_http_status(e.status_code)
+                        raise
+                    except asyncio.CancelledError:
+                        span.set_status("cancelled")
+                        raise
+                    except Exception:
+                        # This will probably map to a 500 but seems like we
+                        # have no way to tell. Do not set span status.
+                        reraise(*_capture_exception(hub))
+
+                    span.set_http_status(response.status)
+                    return response
 
         Application._handle = sentry_app_handle
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/integrations/asgi.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/asgi.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/asgi.py    2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/asgi.py    2020-05-13 
13:03:55.000000000 +0200
@@ -5,10 +5,10 @@
 """
 
 import asyncio
-import functools
 import inspect
 import urllib
 
+from sentry_sdk._functools import partial
 from sentry_sdk._types import MYPY
 from sentry_sdk.hub import Hub, _should_send_default_pii
 from sentry_sdk.integrations._wsgi_common import _filter_headers
@@ -92,9 +92,7 @@
                 with hub.configure_scope() as sentry_scope:
                     sentry_scope.clear_breadcrumbs()
                     sentry_scope._name = "asgi"
-                    processor = functools.partial(
-                        self.event_processor, asgi_scope=scope
-                    )
+                    processor = partial(self.event_processor, asgi_scope=scope)
                     sentry_scope.add_event_processor(processor)
 
                 if scope["type"] in ("http", "websocket"):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/integrations/beam.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/beam.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/beam.py    2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/beam.py    2020-05-13 
13:03:55.000000000 +0200
@@ -2,7 +2,7 @@
 
 import sys
 import types
-from functools import wraps
+from sentry_sdk._functools import wraps
 
 from sentry_sdk.hub import Hub
 from sentry_sdk._compat import reraise
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/sentry_sdk/integrations/celery.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/celery.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/celery.py  2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/celery.py  2020-05-13 
13:03:55.000000000 +0200
@@ -1,6 +1,5 @@
 from __future__ import absolute_import
 
-import functools
 import sys
 
 from sentry_sdk.hub import Hub
@@ -10,6 +9,7 @@
 from sentry_sdk.integrations import Integration, DidNotEnable
 from sentry_sdk.integrations.logging import ignore_logger
 from sentry_sdk._types import MYPY
+from sentry_sdk._functools import wraps
 
 if MYPY:
     from typing import Any
@@ -87,7 +87,7 @@
 
 def _wrap_apply_async(task, f):
     # type: (Any, F) -> F
-    @functools.wraps(f)
+    @wraps(f)
     def apply_async(*args, **kwargs):
         # type: (*Any, **Any) -> Any
         hub = Hub.current
@@ -118,7 +118,7 @@
     # This is the reason we don't use signals for hooking in the first place.
     # Also because in Celery 3, signal dispatch returns early if one handler
     # crashes.
-    @functools.wraps(f)
+    @wraps(f)
     def _inner(*args, **kwargs):
         # type: (*Any, **Any) -> Any
         hub = Hub.current
@@ -157,7 +157,7 @@
     # functools.wraps is important here because celery-once looks at this
     # method's name.
     # https://github.com/getsentry/sentry-python/issues/421
-    @functools.wraps(f)
+    @wraps(f)
     def _inner(*args, **kwargs):
         # type: (*Any, **Any) -> Any
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/sentry_sdk/integrations/django/middleware.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/django/middleware.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/django/middleware.py       
2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/django/middleware.py       
2020-05-13 13:03:55.000000000 +0200
@@ -2,19 +2,17 @@
 Create spans from Django middleware invocations
 """
 
-from functools import wraps
-
 from django import VERSION as DJANGO_VERSION
 
 from sentry_sdk import Hub
+from sentry_sdk._functools import wraps
+from sentry_sdk._types import MYPY
 from sentry_sdk.utils import (
     ContextVar,
     transaction_from_function,
     capture_internal_exceptions,
 )
 
-from sentry_sdk._types import MYPY
-
 if MYPY:
     from typing import Any
     from typing import Callable
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/sentry_sdk/integrations/logging.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/logging.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/logging.py 2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/logging.py 2020-05-13 
13:03:55.000000000 +0200
@@ -24,7 +24,13 @@
 DEFAULT_LEVEL = logging.INFO
 DEFAULT_EVENT_LEVEL = logging.ERROR
 
-_IGNORED_LOGGERS = set(["sentry_sdk.errors"])
+# Capturing events from those loggers causes recursion errors. We cannot allow
+# the user to unconditionally create events from those loggers under any
+# circumstances.
+#
+# Note: Ignoring by logger name here is better than mucking with thread-locals.
+# We do not necessarily know whether thread-locals work 100% correctly in the 
user's environment.
+_IGNORED_LOGGERS = set(["sentry_sdk.errors", "urllib3.connectionpool"])
 
 
 def ignore_logger(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/sentry_sdk/integrations/serverless.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/serverless.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/serverless.py      
2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/serverless.py      
2020-05-13 13:03:55.000000000 +0200
@@ -1,9 +1,9 @@
-import functools
 import sys
 
 from sentry_sdk.hub import Hub
 from sentry_sdk.utils import event_from_exception
 from sentry_sdk._compat import reraise
+from sentry_sdk._functools import wraps
 
 
 from sentry_sdk._types import MYPY
@@ -42,7 +42,7 @@
     # type: (Optional[F], bool) -> Union[F, Callable[[F], F]]
     def wrapper(f):
         # type: (F) -> F
-        @functools.wraps(f)
+        @wraps(f)
         def inner(*args, **kwargs):
             # type: (*Any, **Any) -> Any
             with Hub(Hub.current) as hub:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/integrations/wsgi.py 
new/sentry-python-0.14.4/sentry_sdk/integrations/wsgi.py
--- old/sentry-python-0.14.3/sentry_sdk/integrations/wsgi.py    2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/integrations/wsgi.py    2020-05-13 
13:03:55.000000000 +0200
@@ -1,6 +1,6 @@
-import functools
 import sys
 
+from sentry_sdk._functools import partial
 from sentry_sdk.hub import Hub, _should_send_default_pii
 from sentry_sdk.utils import (
     ContextVar,
@@ -121,9 +121,7 @@
                         try:
                             rv = self.app(
                                 environ,
-                                functools.partial(
-                                    _sentry_start_response, start_response, 
span
-                                ),
+                                partial(_sentry_start_response, 
start_response, span),
                             )
                         except BaseException:
                             reraise(*_capture_exception(hub))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/scope.py 
new/sentry-python-0.14.4/sentry_sdk/scope.py
--- old/sentry-python-0.14.3/sentry_sdk/scope.py        2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/scope.py        2020-05-13 
13:03:55.000000000 +0200
@@ -1,10 +1,10 @@
 from copy import copy
 from collections import deque
-from functools import wraps
 from itertools import chain
 
-from sentry_sdk.utils import logger, capture_internal_exceptions
+from sentry_sdk._functools import wraps
 from sentry_sdk._types import MYPY
+from sentry_sdk.utils import logger, capture_internal_exceptions
 
 if MYPY:
     from typing import Any
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/sessions.py 
new/sentry-python-0.14.4/sentry_sdk/sessions.py
--- old/sentry-python-0.14.3/sentry_sdk/sessions.py     2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/sessions.py     2020-05-13 
13:03:55.000000000 +0200
@@ -170,6 +170,7 @@
         sid=None,  # type: Optional[Union[str, uuid.UUID]]
         did=None,  # type: Optional[str]
         timestamp=None,  # type: Optional[datetime]
+        started=None,  # type: Optional[datetime]
         duration=None,  # type: Optional[float]
         status=None,  # type: Optional[SessionStatus]
         release=None,  # type: Optional[str]
@@ -194,6 +195,8 @@
         if timestamp is None:
             timestamp = datetime.utcnow()
         self.timestamp = timestamp
+        if started is not None:
+            self.started = started
         if duration is not None:
             self.duration = duration
         if release is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/sentry_sdk/transport.py 
new/sentry-python-0.14.4/sentry_sdk/transport.py
--- old/sentry-python-0.14.3/sentry_sdk/transport.py    2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/sentry_sdk/transport.py    2020-05-13 
13:03:55.000000000 +0200
@@ -15,17 +15,22 @@
 from sentry_sdk._types import MYPY
 
 if MYPY:
-    from typing import Type
     from typing import Any
-    from typing import Optional
+    from typing import Callable
     from typing import Dict
+    from typing import Iterable
+    from typing import Optional
+    from typing import Tuple
+    from typing import Type
     from typing import Union
-    from typing import Callable
+
     from urllib3.poolmanager import PoolManager  # type: ignore
     from urllib3.poolmanager import ProxyManager
 
     from sentry_sdk._types import Event
 
+    DataCategory = Optional[str]
+
 try:
     from urllib.request import getproxies
 except ImportError:
@@ -94,6 +99,21 @@
             pass
 
 
+def _parse_rate_limits(header, now=None):
+    # type: (Any, Optional[datetime]) -> Iterable[Tuple[DataCategory, 
datetime]]
+    if now is None:
+        now = datetime.utcnow()
+
+    for limit in header.split(","):
+        try:
+            retry_after, categories, _ = limit.strip().split(":", 2)
+            retry_after = now + timedelta(seconds=int(retry_after))
+            for category in categories and categories.split(";") or (None,):
+                yield category, retry_after
+        except (LookupError, ValueError):
+            continue
+
+
 class HttpTransport(Transport):
     """The default HTTP transport."""
 
@@ -107,7 +127,7 @@
         assert self.parsed_dsn is not None
         self._worker = BackgroundWorker()
         self._auth = self.parsed_dsn.to_auth("sentry.python/%s" % VERSION)
-        self._disabled_until = {}  # type: Dict[Any, datetime]
+        self._disabled_until = {}  # type: Dict[DataCategory, datetime]
         self._retry = urllib3.util.Retry()
         self.options = options
 
@@ -127,18 +147,9 @@
 
         # new sentries with more rate limit insights.  We honor this header
         # no matter of the status code to update our internal rate limits.
-        header = response.headers.get("x-sentry-rate-limit")
+        header = response.headers.get("x-sentry-rate-limits")
         if header:
-            for limit in header.split(","):
-                try:
-                    retry_after, categories, _ = limit.strip().split(":", 2)
-                    retry_after = datetime.utcnow() + timedelta(
-                        seconds=int(retry_after)
-                    )
-                    for category in categories.split(";") or (None,):
-                        self._disabled_until[category] = retry_after
-                except (LookupError, ValueError):
-                    continue
+            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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/setup.py 
new/sentry-python-0.14.4/setup.py
--- old/sentry-python-0.14.3/setup.py   2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/setup.py   2020-05-13 13:03:55.000000000 +0200
@@ -12,7 +12,7 @@
 
 setup(
     name="sentry-sdk",
-    version="0.14.3",
+    version="0.14.4",
     author="Sentry Team and Contributors",
     author_email="[email protected]",
     url="https://github.com/getsentry/sentry-python";,
@@ -32,7 +32,7 @@
         "sanic": ["sanic>=0.8"],
         "celery": ["celery>=3"],
         "beam": ["beam>=2.12"],
-        "rq": ["0.6"],
+        "rq": ["rq>=0.6"],
         "aiohttp": ["aiohttp>=3.5"],
         "tornado": ["tornado>=5"],
         "sqlalchemy": ["sqlalchemy>=1.2"],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/test-requirements.txt 
new/sentry-python-0.14.4/test-requirements.txt
--- old/sentry-python-0.14.3/test-requirements.txt      2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/test-requirements.txt      2020-05-13 
13:03:55.000000000 +0200
@@ -6,3 +6,4 @@
 pytest-cov==2.8.1
 gevent
 eventlet
+newrelic
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sentry-python-0.14.3/tests/integrations/celery/test_celery.py 
new/sentry-python-0.14.4/tests/integrations/celery/test_celery.py
--- old/sentry-python-0.14.3/tests/integrations/celery/test_celery.py   
2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/tests/integrations/celery/test_celery.py   
2020-05-13 13:03:55.000000000 +0200
@@ -309,3 +309,30 @@
 
     # if this is nonempty, the worker never really forked
     assert not runs
+
+
[email protected]
[email protected]("newrelic_order", ["sentry_first", "sentry_last"])
+def test_newrelic_interference(init_celery, newrelic_order, celery_invocation):
+    def instrument_newrelic():
+        import celery.app.trace as celery_mod
+        from newrelic.hooks.application_celery import 
instrument_celery_execute_trace
+
+        assert hasattr(celery_mod, "build_tracer")
+        instrument_celery_execute_trace(celery_mod)
+
+    if newrelic_order == "sentry_first":
+        celery = init_celery()
+        instrument_newrelic()
+    elif newrelic_order == "sentry_last":
+        instrument_newrelic()
+        celery = init_celery()
+    else:
+        raise ValueError(newrelic_order)
+
+    @celery.task(name="dummy_task", bind=True)
+    def dummy_task(self, x, y):
+        return x / y
+
+    assert dummy_task.apply(kwargs={"x": 1, "y": 1}).wait() == 1
+    assert celery_invocation(dummy_task, 1, 1)[0].wait() == 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/tests/test_basics.py 
new/sentry-python-0.14.4/tests/test_basics.py
--- old/sentry-python-0.14.3/tests/test_basics.py       2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/tests/test_basics.py       2020-05-13 
13:03:55.000000000 +0200
@@ -6,6 +6,7 @@
     Client,
     push_scope,
     configure_scope,
+    capture_event,
     capture_exception,
     capture_message,
     add_breadcrumb,
@@ -312,3 +313,12 @@
     (event,) = events
 
     assert event["message"] == "hifoobarbaz"
+
+
+def test_capture_event_with_scope_kwargs(sentry_init, capture_events):
+    sentry_init(debug=True)
+    events = capture_events()
+    capture_event({}, level="info", extras={"foo": "bar"})
+    (event,) = events
+    assert event["level"] == "info"
+    assert event["extra"]["foo"] == "bar"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/tests/test_transport.py 
new/sentry-python-0.14.4/tests/test_transport.py
--- old/sentry-python-0.14.3/tests/test_transport.py    2020-03-20 
14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/tests/test_transport.py    2020-05-13 
13:03:55.000000000 +0200
@@ -2,11 +2,13 @@
 import logging
 import pickle
 
-from datetime import datetime
+from datetime import datetime, timedelta
 
 import pytest
 
 from sentry_sdk import Hub, Client, add_breadcrumb, capture_message
+from sentry_sdk.transport import _parse_rate_limits
+from sentry_sdk.integrations.logging import LoggingIntegration
 
 
 @pytest.fixture(params=[True, False])
@@ -54,3 +56,132 @@
     assert httpserver.requests
 
     assert any("Sending event" in record.msg for record in caplog.records) == 
debug
+
+
+def test_transport_infinite_loop(httpserver, request):
+    httpserver.serve_content("ok", 200)
+
+    client = Client(
+        "http://foobar@{}/123".format(httpserver.url[len("http://";) :]),
+        debug=True,
+        # Make sure we cannot create events from our own logging
+        integrations=[LoggingIntegration(event_level=logging.DEBUG)],
+    )
+
+    with Hub(client):
+        capture_message("hi")
+        client.flush()
+
+    assert len(httpserver.requests) == 1
+
+
+NOW = datetime(2014, 6, 2)
+
+
[email protected](
+    "input,expected",
+    [
+        # Invalid rate limits
+        ("", {}),
+        ("invalid", {}),
+        (",,,", {}),
+        (
+            "42::organization, invalid, 
4711:foobar;transaction;security:project",
+            {
+                None: NOW + timedelta(seconds=42),
+                "transaction": NOW + timedelta(seconds=4711),
+                "security": NOW + timedelta(seconds=4711),
+                # Unknown data categories
+                "foobar": NOW + timedelta(seconds=4711),
+            },
+        ),
+        (
+            "4711:foobar;;transaction:organization",
+            {
+                "transaction": NOW + timedelta(seconds=4711),
+                # Unknown data categories
+                "foobar": NOW + timedelta(seconds=4711),
+                "": NOW + timedelta(seconds=4711),
+            },
+        ),
+    ],
+)
+def test_parse_rate_limits(input, expected):
+    assert dict(_parse_rate_limits(input, now=NOW)) == expected
+
+
+def test_simple_rate_limits(httpserver, capsys, caplog):
+    client = 
Client(dsn="http://foobar@{}/123".format(httpserver.url[len("http://";) :]))
+    httpserver.serve_content("no", 429, headers={"Retry-After": "4"})
+
+    client.capture_event({"type": "transaction"})
+    client.flush()
+
+    assert len(httpserver.requests) == 1
+    del httpserver.requests[:]
+
+    assert set(client.transport._disabled_until) == set([None])
+
+    client.capture_event({"type": "transaction"})
+    client.capture_event({"type": "event"})
+    client.flush()
+
+    assert not httpserver.requests
+
+
[email protected]("response_code", [200, 429])
+def test_data_category_limits(httpserver, capsys, caplog, response_code):
+    client = Client(
+        dict(dsn="http://foobar@{}/123".format(httpserver.url[len("http://";) 
:]))
+    )
+    httpserver.serve_content(
+        "hm",
+        response_code,
+        headers={"X-Sentry-Rate-Limits": "4711:transaction:organization"},
+    )
+
+    client.capture_event({"type": "transaction"})
+    client.flush()
+
+    assert len(httpserver.requests) == 1
+    del httpserver.requests[:]
+
+    assert set(client.transport._disabled_until) == set(["transaction"])
+
+    client.transport.capture_event({"type": "transaction"})
+    client.transport.capture_event({"type": "transaction"})
+    client.flush()
+
+    assert not httpserver.requests
+
+    client.capture_event({"type": "event"})
+    client.flush()
+
+    assert len(httpserver.requests) == 1
+
+
[email protected]("response_code", [200, 429])
+def test_complex_limits_without_data_category(
+    httpserver, capsys, caplog, response_code
+):
+    client = Client(
+        dict(dsn="http://foobar@{}/123".format(httpserver.url[len("http://";) 
:]))
+    )
+    httpserver.serve_content(
+        "hm", response_code, headers={"X-Sentry-Rate-Limits": 
"4711::organization"},
+    )
+
+    client.capture_event({"type": "transaction"})
+    client.flush()
+
+    assert len(httpserver.requests) == 1
+    del httpserver.requests[:]
+
+    assert set(client.transport._disabled_until) == set([None])
+
+    client.transport.capture_event({"type": "transaction"})
+    client.transport.capture_event({"type": "transaction"})
+    client.capture_event({"type": "event"})
+    client.flush()
+
+    assert len(httpserver.requests) == 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sentry-python-0.14.3/tox.ini 
new/sentry-python-0.14.4/tox.ini
--- old/sentry-python-0.14.3/tox.ini    2020-03-20 14:19:55.000000000 +0100
+++ new/sentry-python-0.14.4/tox.ini    2020-05-13 13:03:55.000000000 +0200
@@ -11,30 +11,35 @@
 
 
     # === Integrations ===
-    # Formatting: 1 blank line between different integrations.
+    # General format is {pythonversion}-{integrationname}-{frameworkversion}
+    # 1 blank line between different integrations
+    # Each framework version should only be mentioned once. I.e:
+    #   {py2.7,py3.7}-django-{1.11}
+    #   {py3.7}-django-{2.2}
+    # instead of:
+    #   {py2.7}-django-{1.11}
+    #   {py2.7,py3.7}-django-{1.11,2.2}
 
-    py{3.7,3.8}-django-{2.2,3.0,dev}
+    {pypy,py2.7}-django-{1.6,1.7}
+    {pypy,py2.7,py3.5}-django-{1.8,1.9,1.10,1.11}
     {py3.5,py3.6,py3.7}-django-{2.0,2.1}
-    {pypy,py2.7,py3.5}-django-1.11
-    {pypy,py2.7,py3.5}-django-{1.8,1.9,1.10}
-    {pypy,py2.7}-django-1.7
-    {pypy,py2.7}-django-1.6
+    {py3.7,py3.8}-django-{2.2,3.0,dev}
 
-    {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-flask-{1.1,1.0,0.11,0.12,dev}
+    {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-flask-{1.1,1.0,0.11,0.12}
+    {py3.6,py3.7,py3.8}-flask-{1.1,1.0,0.11,0.12,dev}
 
     {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-bottle-0.12
 
     {pypy,py2.7,py3.5,py3.6,py3.7}-falcon-1.4
     {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-falcon-2.0
 
-    py3.5-sanic-{0.8,18}
-    {py3.6,py3.7}-sanic-{0.8,18,19}
+    {py3.5,py3.6,py3.7}-sanic-{0.8,18}
+    {py3.6,py3.7}-sanic-19
 
     {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-celery-{4.1,4.2,4.3,4.4}
     {pypy,py2.7}-celery-3
 
-    py2.7-beam-{2.12,2.13}
-    py3.7-beam-{2.12,2.13}
+    {py2.7,py3.7}-beam-{2.12,2.13}
 
     # The aws_lambda tests deploy to the real AWS and have their own matrix of 
Python versions.
     py3.7-aws_lambda
@@ -45,13 +50,13 @@
     {pypy,py2.7,py3.5,py3.6,py3.7,py3.8}-rq-{0.12,0.13,1.0,1.1,1.2,1.3}
 
     py3.7-aiohttp-3.5
-    py{3.7,3.8}-aiohttp-3.6
+    {py3.7,py3.8}-aiohttp-3.6
 
     {py3.7,py3.8}-tornado-{5,6}
 
-    {py3.4}-trytond-{4.6,4.8,5.0}
-    {py3.5}-trytond-{4.6,4.8,5.0,5.2}
-    {py3.6,py3.7,py3.8}-trytond-{4.6,4.8,5.0,5.2,5.4}
+    {py3.4,py3.5,py3.6,py3.7,py3.8}-trytond-{4.6,4.8,5.0}
+    {py3.5,py3.6,py3.7,py3.8}-trytond-{5.2}
+    {py3.6,py3.7,py3.8}-trytond-{5.4}
 
     {py2.7,py3.8}-requests
 
@@ -69,7 +74,7 @@
 
     django-{1.11,2.0,2.1,2.2,3.0}: djangorestframework>=3.0.0,<4.0.0
     py3.7-django-{1.11,2.0,2.1,2.2,3.0}: channels>2
-    py3.7-django-{1.11,2.0,2.1,2.2,3.0}: pytest-asyncio
+    py3.7-django-{1.11,2.0,2.1,2.2,3.0}: pytest-asyncio==0.10.0
     {py2.7,py3.7}-django-{1.11,2.2,3.0}: psycopg2-binary
 
     django-{1.6,1.7,1.8}: pytest-django<3.0


Reply via email to