Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions sentry_sdk/_functools.py
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 2 additions & 4 deletions sentry_sdk/integrations/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,9 +92,7 @@ async def _run_app(self, scope, callback):
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"):
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/beam.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions sentry_sdk/integrations/celery.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import absolute_import

import functools
import sys

from sentry_sdk.hub import Hub
Expand All @@ -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
Expand Down Expand Up @@ -87,7 +87,7 @@ def sentry_build_tracer(name, task, *args, **kwargs):

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
Expand Down Expand Up @@ -118,7 +118,7 @@ def _wrap_tracer(task, f):
# 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
Expand Down Expand Up @@ -157,7 +157,7 @@ def _wrap_task_call(task, f):
# 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:
Expand Down
6 changes: 2 additions & 4 deletions sentry_sdk/integrations/django/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/serverless.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -42,7 +42,7 @@ def serverless_function(f=None, flush=True): # noqa
# 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:
Expand Down
6 changes: 2 additions & 4 deletions sentry_sdk/integrations/wsgi.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -121,9 +121,7 @@ def __call__(self, environ, start_response):
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))
Expand Down
4 changes: 2 additions & 2 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pytest-localserver==0.5.0
pytest-cov==2.8.1
gevent
eventlet
newrelic
27 changes: 27 additions & 0 deletions tests/integrations/celery/test_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,30 @@ def dummy_task(self):

# if this is nonempty, the worker never really forked
assert not runs


@pytest.mark.forked
@pytest.mark.parametrize("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