Skip to content

Commit 6aeeaaa

Browse files
stasspumer
authored andcommitted
fix: handling no sentry-sdk installed corner case
1 parent 6e85b5e commit 6aeeaaa

File tree

4 files changed

+91
-68
lines changed

4 files changed

+91
-68
lines changed

fastapi_jsonrpc/__init__.py

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from fastapi.dependencies.utils import _should_embed_body_fields # noqa
1515
from fastapi.openapi.constants import REF_PREFIX
1616

17-
1817
from fastapi._compat import ModelField, Undefined # noqa
1918
from fastapi.dependencies.models import Dependant
2019
from fastapi.encoders import jsonable_encoder
@@ -36,41 +35,55 @@
3635

3736
logger = logging.getLogger(__name__)
3837

39-
40-
try:
41-
import sentry_sdk
42-
import sentry_sdk.tracing
43-
from sentry_sdk.utils import transaction_from_function as sentry_transaction_from_function
44-
except ImportError:
45-
sentry_sdk = None
46-
sentry_transaction_from_function = None
47-
4838
try:
4939
from fastapi._compat import _normalize_errors # noqa
5040
except ImportError:
5141
def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
5242
return errors
43+
try:
44+
import sentry_sdk
45+
import sentry_sdk.tracing
46+
from sentry_sdk.utils import transaction_from_function as sentry_transaction_from_function
5347

54-
if hasattr(sentry_sdk, 'new_scope'):
55-
# sentry_sdk 2.x
56-
sentry_new_scope = sentry_sdk.new_scope
57-
58-
def get_sentry_integration():
59-
return sentry_sdk.get_client().get_integration(
60-
"FastApiJsonRPCIntegration"
61-
)
62-
else:
63-
# sentry_sdk 1.x
64-
@contextmanager
65-
def sentry_new_scope():
66-
hub = sentry_sdk.Hub.current
67-
with sentry_sdk.Hub(hub) as hub:
68-
with hub.configure_scope() as scope:
48+
if hasattr(sentry_sdk, 'new_scope'):
49+
# sentry_sdk 2.x
50+
@contextmanager
51+
def _handle_disabled_sentry_integration(self: "JsonRpcContext"):
52+
with sentry_sdk.new_scope() as scope:
53+
scope.clear_breadcrumbs()
54+
scope.add_event_processor(self._make_sentry_event_processor())
6955
yield scope
7056

71-
get_sentry_integration = lambda : None
57+
58+
def get_sentry_integration():
59+
return sentry_sdk.get_client().get_integration(
60+
"FastApiJsonRPCIntegration"
61+
)
62+
else:
63+
# sentry_sdk 1.x
64+
@contextmanager
65+
def _handle_disabled_sentry_integration(self: "JsonRpcContext"):
66+
hub = sentry_sdk.Hub.current
67+
with sentry_sdk.Hub(hub) as hub:
68+
with hub.configure_scope() as scope:
69+
scope.clear_breadcrumbs()
70+
scope.add_event_processor(self._make_sentry_event_processor())
71+
yield scope
72+
73+
74+
get_sentry_integration = lambda: None
75+
76+
except ImportError:
77+
# no sentry installed
78+
sentry_sdk = None
79+
sentry_transaction_from_function = None
80+
get_sentry_integration = lambda: None
7281

7382

83+
@asynccontextmanager
84+
def _handle_disabled_sentry_integration(self: "JsonRpcContext"):
85+
yield None
86+
7487
class Params(fastapi.params.Body):
7588
def __init__(
7689
self,
@@ -650,25 +663,19 @@ async def _handle_exception(self, reraise=True):
650663

651664
@contextmanager
652665
def _enter_sentry_scope(self):
653-
if get_sentry_integration() is not None:
666+
integration_is_active = get_sentry_integration() is not None
667+
# we do not need to use sentry fallback if `sentry-sdk`is not installed or integration is enabled explicitly
668+
if integration_is_active or sentry_sdk is None:
654669
yield
655670
return
656671

672+
657673
warnings.warn(
658674
"Implicit Sentry integration is deprecated and may be removed in a future major release. "
659675
"To ensure compatibility, use sentry-sdk 2.* and explicit integration:"
660676
"`from fastapi_jsonrpc.contrib.sentry import FastApiJsonRPCIntegration`. "
661677
)
662-
with sentry_new_scope() as scope:
663-
# Actually we can use set_transaction_name
664-
# scope.set_transaction_name(
665-
# sentry_transaction_from_function(method_route.func),
666-
# source=sentry_sdk.tracing.TRANSACTION_SOURCE_CUSTOM,
667-
# )
668-
# and we need `method_route` instance for that,
669-
# but method_route is optional and is harder to track it than adding event processor
670-
scope.clear_breadcrumbs()
671-
scope.add_event_processor(self._make_sentry_event_processor())
678+
with _handle_disabled_sentry_integration(self) as scope:
672679
yield scope
673680

674681
def _make_sentry_event_processor(self):

tests/sentry/conftest.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,6 @@ def capture_event_scope(self, event, hint=None, scope=None):
8080
@pytest.fixture
8181
def sentry_init(request):
8282
def inner(*a, **kw):
83-
kw.setdefault("transport", TestTransport())
84-
kw.setdefault("integrations", [FastApiJsonRPCIntegration()])
85-
kw.setdefault("traces_sample_rate", 1.0)
86-
kw.setdefault("disabled_integrations", [StarletteIntegration, FastApiIntegration])
87-
8883
client = sentry_sdk.Client(*a, **kw)
8984
sentry_sdk.get_global_scope().set_client(client)
9085

@@ -102,6 +97,22 @@ def inner(*a, **kw):
10297
sentry_sdk.get_global_scope().set_client(old_client)
10398

10499

100+
@pytest.fixture
101+
def sentry_with_integration(sentry_init):
102+
kw = {
103+
"transport": TestTransport(),
104+
"integrations": [FastApiJsonRPCIntegration()],
105+
"traces_sample_rate": 1.0,
106+
"disabled_integrations": [StarletteIntegration, FastApiIntegration],
107+
}
108+
sentry_init(**kw)
109+
110+
111+
@pytest.fixture
112+
def sentry_no_integration(sentry_init):
113+
sentry_init()
114+
115+
105116
class TestTransport(Transport):
106117
def capture_envelope(self, _: Envelope) -> None:
107118
"""

tests/sentry/test_sentry_sdk_1x.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,24 @@ def test_transaction_is_jsonrpc_method(
4141
exceptions = capture_exceptions()
4242
events = capture_events()
4343

44-
# Test in batch to ensure we correctly handle multiple requests
45-
json_request(
46-
[
47-
{
48-
"id": 1,
49-
"jsonrpc": "2.0",
50-
"method": "probe",
51-
"params": {},
52-
},
53-
{
54-
"id": 2,
55-
"jsonrpc": "2.0",
56-
"method": "probe2",
57-
"params": {},
58-
},
59-
]
60-
)
44+
with pytest.warns(UserWarning, match="Implicit Sentry integration is deprecated"):
45+
# Test in batch to ensure we correctly handle multiple requests
46+
json_request(
47+
[
48+
{
49+
"id": 1,
50+
"jsonrpc": "2.0",
51+
"method": "probe",
52+
"params": {},
53+
},
54+
{
55+
"id": 2,
56+
"jsonrpc": "2.0",
57+
"method": "probe2",
58+
"params": {},
59+
},
60+
]
61+
)
6162

6263
assert {type(e) for e in exceptions} == {RuntimeError, ZeroDivisionError}
6364

tests/sentry/test_sentry_sdk_2x.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ async def successful_method():
4545

4646

4747
def test_exception_logger_event_creation(
48-
json_request, capture_exceptions, capture_events, capture_envelopes, failing_router, sentry_init,
49-
assert_log_errors,
48+
json_request, capture_exceptions, capture_events, capture_envelopes, failing_router, sentry_with_integration,
49+
assert_log_errors,
5050
):
51-
sentry_init()
5251
exceptions = capture_exceptions()
5352
envelops = capture_envelopes()
5453
json_request(
@@ -67,10 +66,9 @@ def test_exception_logger_event_creation(
6766

6867

6968
def test_unhandled_exception_capturing(
70-
json_request, capture_exceptions, capture_events, capture_envelopes, failing_router, assert_log_errors,
71-
sentry_init
69+
json_request, capture_exceptions, capture_events, capture_envelopes, failing_router, assert_log_errors,
70+
sentry_with_integration
7271
):
73-
sentry_init()
7472
exceptions = capture_exceptions()
7573
envelops = capture_envelopes()
7674
json_request({"jsonrpc": "2.0", "method": "unhandled_error_method", "params": {}, "id": 1})
@@ -109,10 +107,9 @@ def test_unhandled_exception_capturing(
109107
],
110108
)
111109
def test_trace_id_propagation(
112-
request_payload, json_request, capture_exceptions, capture_events, capture_envelopes, failing_router,
113-
assert_log_errors, sentry_init
110+
request_payload, json_request, capture_exceptions, capture_events, capture_envelopes, failing_router,
111+
assert_log_errors, sentry_with_integration
114112
):
115-
sentry_init()
116113
envelops = capture_envelopes()
117114
expected_trace_id = uuid.uuid4().hex
118115
incoming_transaction = Transaction(trace_id=expected_trace_id)
@@ -129,3 +126,10 @@ def test_trace_id_propagation(
129126
'First route exc', pytest.raises(ValueError),
130127
'Third route exc', pytest.raises(RuntimeError),
131128
)
129+
130+
131+
def test_no_integration_warning_message(json_request, sentry_no_integration):
132+
with pytest.warns(UserWarning, match="Implicit Sentry integration is deprecated"):
133+
json_request(
134+
{"jsonrpc": "2.0", "method": "successful_method", "params": {}, "id": 1}
135+
)

0 commit comments

Comments
 (0)