Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: Patched functions decorator for integrations #2454

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
190923c
Created async and sync decorators
szokeasaurusrex Oct 18, 2023
c8aeadb
Added use of each sentry decorator
szokeasaurusrex Oct 18, 2023
c18a902
Fix circular import
szokeasaurusrex Oct 18, 2023
0fbea43
Merge branch 'sentry-sdk-2.0' into szokeasaurusrex/sentry_patched_dec…
szokeasaurusrex Mar 15, 2024
9f5c279
Revert changes to starlette.py
szokeasaurusrex Mar 15, 2024
45b90ab
Rename method
szokeasaurusrex Mar 15, 2024
69ebafb
Use actual generics, move async implementation to utils
szokeasaurusrex Mar 15, 2024
46cd0e2
Refactor parameters
szokeasaurusrex Mar 15, 2024
7a8196a
Undo changes to _types.py
szokeasaurusrex Mar 15, 2024
d9016db
Use client instead of Hub
szokeasaurusrex Mar 15, 2024
75934d1
Add doc string
szokeasaurusrex Mar 15, 2024
66726d0
Move type comments
szokeasaurusrex Mar 15, 2024
4e48ce3
Merge branch 'sentry-sdk-2.0' into szokeasaurusrex/sentry_patched_dec…
szokeasaurusrex Mar 18, 2024
e8c921c
Fix mypy
szokeasaurusrex Mar 18, 2024
20688fd
Fix circular import
szokeasaurusrex Mar 18, 2024
d93ff92
Added unit tests for decorators
szokeasaurusrex Mar 18, 2024
4b90191
Merge branch 'sentry-sdk-2.0' into szokeasaurusrex/sentry_patched_dec…
szokeasaurusrex Mar 18, 2024
85c1a1f
Revert gql changes
szokeasaurusrex Mar 18, 2024
1bb2f11
Fix typo
szokeasaurusrex Mar 18, 2024
41a6bad
Try forking the flaky test
antonpirker Mar 18, 2024
f05e00c
no
antonpirker Mar 18, 2024
a9aa22d
debug output
antonpirker Mar 18, 2024
25fb0b9
debug output
antonpirker Mar 18, 2024
38d6249
Debug output
antonpirker Mar 19, 2024
099d747
debugging
antonpirker Mar 19, 2024
41b433e
fix?
antonpirker Mar 19, 2024
56e5eeb
Cleanup
antonpirker Mar 19, 2024
940ef7c
typo
antonpirker Mar 19, 2024
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
4 changes: 4 additions & 0 deletions sentry_sdk/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


if TYPE_CHECKING:
from collections.abc import Awaitable
from types import TracebackType
from typing import Any
from typing import Callable
Expand Down Expand Up @@ -116,3 +117,6 @@
FlushedMetricValue = Union[int, float]

BucketKey = Tuple[MetricType, str, MeasurementUnit, MetricTagsInternal]

GenericCallable = Callable[..., Any]
GenericAsyncCallable = Callable[..., Awaitable[Any]]
8 changes: 3 additions & 5 deletions sentry_sdk/integrations/gql.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sentry_sdk.utils import event_from_exception, parse_version
from sentry_sdk.utils import event_from_exception, integration_patched, parse_version

Check warning on line 1 in sentry_sdk/integrations/gql.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/integrations/gql.py#L1

Added line #L1 was not covered by tests
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.integrations import DidNotEnable, Integration

Expand Down Expand Up @@ -85,13 +85,11 @@
# type: () -> None
real_execute = gql.Client.execute

@integration_patched(original_function=real_execute, integration=GQLIntegration)

Check warning on line 88 in sentry_sdk/integrations/gql.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/integrations/gql.py#L88

Added line #L88 was not covered by tests
def sentry_patched_execute(self, document, *args, **kwargs):
# type: (gql.Client, DocumentNode, Any, Any) -> Any
hub = Hub.current
if hub.get_integration(GQLIntegration) is None:
return real_execute(self, document, *args, **kwargs)

with Hub.current.configure_scope() as scope:
with hub.configure_scope() as scope:
scope.add_event_processor(_make_gql_event_processor(self, document))

try:
Expand Down
98 changes: 49 additions & 49 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
parse_version,
transaction_from_function,
)
from sentry_sdk.utils_py3 import integration_patched_async

if TYPE_CHECKING:
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple
Expand Down Expand Up @@ -103,61 +104,60 @@ def _enable_span_for_middleware(middleware_class):
# type: (Any) -> type
old_call = middleware_class.__call__

@integration_patched_async(
original_function=old_call, integration=StarletteIntegration
)
async def _create_span_call(app, scope, receive, send, **kwargs):
# type: (Any, Dict[str, Any], Callable[[], Awaitable[Dict[str, Any]]], Callable[[Dict[str, Any]], Awaitable[None]], Any) -> None
hub = Hub.current
integration = hub.get_integration(StarletteIntegration)
if integration is not None:
middleware_name = app.__class__.__name__

# Update transaction name with middleware name
with hub.configure_scope() as sentry_scope:
name, source = _get_transaction_from_middleware(app, scope, integration)
if name is not None:
sentry_scope.set_transaction_name(
name,
source=source,
)
middleware_name = app.__class__.__name__

# Update transaction name with middleware name
with hub.configure_scope() as sentry_scope:
name, source = _get_transaction_from_middleware(app, scope, integration)
if name is not None:
sentry_scope.set_transaction_name(
name,
source=source,
)

with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE, description=middleware_name
) as middleware_span:
middleware_span.set_tag("starlette.middleware_name", middleware_name)

# Creating spans for the "receive" callback
async def _sentry_receive(*args, **kwargs):
# type: (*Any, **Any) -> Any
hub = Hub.current
with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE_RECEIVE,
description=getattr(receive, "__qualname__", str(receive)),
) as span:
span.set_tag("starlette.middleware_name", middleware_name)
return await receive(*args, **kwargs)

receive_name = getattr(receive, "__name__", str(receive))
receive_patched = receive_name == "_sentry_receive"
new_receive = _sentry_receive if not receive_patched else receive

# Creating spans for the "send" callback
async def _sentry_send(*args, **kwargs):
# type: (*Any, **Any) -> Any
hub = Hub.current
with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE_SEND,
description=getattr(send, "__qualname__", str(send)),
) as span:
span.set_tag("starlette.middleware_name", middleware_name)
return await send(*args, **kwargs)

send_name = getattr(send, "__name__", str(send))
send_patched = send_name == "_sentry_send"
new_send = _sentry_send if not send_patched else send

return await old_call(app, scope, new_receive, new_send, **kwargs)
with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE, description=middleware_name
) as middleware_span:
middleware_span.set_tag("starlette.middleware_name", middleware_name)

else:
return await old_call(app, scope, receive, send, **kwargs)
# Creating spans for the "receive" callback
async def _sentry_receive(*args, **kwargs):
# type: (*Any, **Any) -> Any
hub = Hub.current
with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE_RECEIVE,
description=getattr(receive, "__qualname__", str(receive)),
) as span:
span.set_tag("starlette.middleware_name", middleware_name)
return await receive(*args, **kwargs)

receive_name = getattr(receive, "__name__", str(receive))
receive_patched = receive_name == "_sentry_receive"
new_receive = _sentry_receive if not receive_patched else receive

# Creating spans for the "send" callback
async def _sentry_send(*args, **kwargs):
# type: (*Any, **Any) -> Any
hub = Hub.current
with hub.start_span(
op=OP.MIDDLEWARE_STARLETTE_SEND,
description=getattr(send, "__qualname__", str(send)),
) as span:
span.set_tag("starlette.middleware_name", middleware_name)
return await send(*args, **kwargs)

send_name = getattr(send, "__name__", str(send))
send_patched = send_name == "_sentry_send"
new_send = _sentry_send if not send_patched else send

return await old_call(app, scope, new_receive, new_send, **kwargs)

not_yet_patched = old_call.__name__ not in [
"_create_span_call",
Expand Down
25 changes: 24 additions & 1 deletion sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from collections import namedtuple
from copy import copy
from decimal import Decimal
from functools import wraps
from numbers import Real

try:
Expand Down Expand Up @@ -48,6 +49,7 @@
_PARTIALMETHOD_AVAILABLE = False

import sentry_sdk
import sentry_sdk.hub
from sentry_sdk._compat import PY2, PY33, PY37, implements_str, text_type, urlparse
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH
Expand All @@ -67,8 +69,9 @@
Type,
Union,
)
from sentry_sdk.integrations import Integration

Check warning on line 72 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L72

Added line #L72 was not covered by tests

from sentry_sdk._types import EndpointType, ExcInfo
from sentry_sdk._types import EndpointType, ExcInfo, GenericCallable

Check warning on line 74 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L74

Added line #L74 was not covered by tests


epoch = datetime(1970, 1, 1)
Expand Down Expand Up @@ -1563,6 +1566,26 @@
return release_tuple


def integration_patched(original_function, integration=None):
antonpirker marked this conversation as resolved.
Show resolved Hide resolved
# type: (GenericCallable, Optional[type[Integration]]) -> Callable[[GenericCallable], GenericCallable]
def patcher(sentry_patched_function):

Check warning on line 1571 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1571

Added line #L1571 was not covered by tests
# type: (GenericCallable) -> GenericCallable
@wraps(original_function)

Check warning on line 1573 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1573

Added line #L1573 was not covered by tests
def runner(*args, **kwargs):
# type: (*object, **object) -> object
if (
integration is not None
and sentry_sdk.hub.Hub.current.get_integration(integration) is None
antonpirker marked this conversation as resolved.
Show resolved Hide resolved
):
return original_function(*args, **kwargs)

Check warning on line 1580 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1580

Added line #L1580 was not covered by tests

return sentry_patched_function(*args, **kwargs)

Check warning on line 1582 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1582

Added line #L1582 was not covered by tests

return runner

Check warning on line 1584 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1584

Added line #L1584 was not covered by tests

return patcher

Check warning on line 1586 in sentry_sdk/utils.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils.py#L1586

Added line #L1586 was not covered by tests


if PY37:

def nanosecond_time():
Expand Down
31 changes: 31 additions & 0 deletions sentry_sdk/utils_py3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from sentry_sdk.hub import Hub
from functools import wraps

from sentry_sdk._types import TYPE_CHECKING

if TYPE_CHECKING:
from collections.abc import Callable
from typing import Optional
from sentry_sdk.integrations import Integration

Check warning on line 9 in sentry_sdk/utils_py3.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils_py3.py#L7-L9

Added lines #L7 - L9 were not covered by tests

from sentry_sdk._types import GenericAsyncCallable

Check warning on line 11 in sentry_sdk/utils_py3.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils_py3.py#L11

Added line #L11 was not covered by tests


def integration_patched_async(original_function, integration=None):
antonpirker marked this conversation as resolved.
Show resolved Hide resolved
# type: (GenericAsyncCallable, Optional[type[Integration]]) -> Callable[[GenericAsyncCallable], GenericAsyncCallable]
def patcher(sentry_patched_function):
# type: (GenericAsyncCallable) -> GenericAsyncCallable
@wraps(original_function)
async def runner(*args, **kwargs):
# type: (*object, **object) -> object
if (
integration is not None
and Hub.current.get_integration(integration) is None
):
return await original_function(*args, **kwargs)

Check warning on line 25 in sentry_sdk/utils_py3.py

View check run for this annotation

Codecov / codecov/patch

sentry_sdk/utils_py3.py#L25

Added line #L25 was not covered by tests

return await sentry_patched_function(*args, **kwargs)

return runner

return patcher
Loading