Skip to content

Commit d40313b

Browse files
authored
ref: Introduce inline type check for whether a span is StreamedSpan (#6180)
The `isinstance(span, StreamedSpan) and not isinstance(span, NoOpStreamedSpan)` pattern was repeated in multiple places. Omitting the `NoOpStreamedSpan` guard is a frequent code review catch — it silently treats unsampled spans as sampled. Introduce a more concise type check for proper type narrowing, and replace all combined-check instances in `scope.py`, `starlette.py`, and `fastapi.py`. Locations where `_span` can also be a regular `Span` (not just `StreamedSpan`) were intentionally left unchanged, as the semantics differ there. Fixes PY-2397 Fixes #6182
1 parent 2187f29 commit d40313b

4 files changed

Lines changed: 59 additions & 68 deletions

File tree

sentry_sdk/integrations/fastapi.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import sys
22
from copy import deepcopy
33
from functools import wraps
4+
from typing import TYPE_CHECKING
45

56
import sentry_sdk
67
from sentry_sdk.integrations import DidNotEnable
78
from sentry_sdk.scope import should_send_default_pii
8-
from sentry_sdk.traces import NoOpStreamedSpan, StreamedSpan
9+
from sentry_sdk.traces import StreamedSpan
910
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
1011
from sentry_sdk.tracing_utils import has_span_streaming_enabled
1112
from sentry_sdk.utils import transaction_from_function
1213

13-
from typing import TYPE_CHECKING
14-
1514
if TYPE_CHECKING:
1615
from typing import Any, Callable, Dict
16+
1717
from sentry_sdk._types import Event
1818

1919
try:
@@ -95,9 +95,7 @@ def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any":
9595
if has_span_streaming_enabled(client.options):
9696
current_span = current_scope.streamed_span
9797

98-
if isinstance(current_span, StreamedSpan) and not isinstance(
99-
current_span, NoOpStreamedSpan
100-
):
98+
if type(current_span) is StreamedSpan:
10199
segment = current_span._segment
102100
segment._update_active_thread()
103101

sentry_sdk/integrations/starlette.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import functools
22
import json
3-
import warnings
43
import sys
4+
import warnings
55
from collections.abc import Set
66
from copy import deepcopy
77
from json import JSONDecodeError
8+
from typing import TYPE_CHECKING
89

910
import sentry_sdk
1011
from sentry_sdk.consts import OP
1112
from sentry_sdk.integrations import (
13+
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
1214
DidNotEnable,
1315
Integration,
14-
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
1516
)
1617
from sentry_sdk.integrations._wsgi_common import (
1718
DEFAULT_HTTP_METHODS_TO_CAPTURE,
@@ -21,7 +22,8 @@
2122
)
2223
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
2324
from sentry_sdk.scope import should_send_default_pii
24-
from sentry_sdk.traces import NoOpStreamedSpan, StreamedSpan, _get_current_streamed_span
25+
from sentry_sdk.traces import _get_current_streamed_span
26+
from sentry_sdk.traces import StreamedSpan
2527
from sentry_sdk.tracing import (
2628
SOURCE_FOR_STYLE,
2729
TransactionSource,
@@ -36,8 +38,6 @@
3638
transaction_from_function,
3739
)
3840

39-
from typing import TYPE_CHECKING
40-
4141
if TYPE_CHECKING:
4242
from typing import Any, Awaitable, Callable, Container, Dict, Optional, Tuple, Union
4343

@@ -54,7 +54,8 @@
5454
)
5555
from starlette.requests import Request # type: ignore
5656
from starlette.routing import Match # type: ignore
57-
from starlette.types import ASGIApp, Receive, Scope as StarletteScope, Send # type: ignore
57+
from starlette.types import ASGIApp, Receive, Send # type: ignore
58+
from starlette.types import Scope as StarletteScope
5859
except ImportError:
5960
raise DidNotEnable("Starlette is not installed")
6061

@@ -255,12 +256,7 @@ def _set_request_body_data_on_streaming_segment(
255256
info: "Optional[Dict[str, Any]]",
256257
) -> None:
257258
current_span = _get_current_streamed_span()
258-
if (
259-
info
260-
and "data" in info
261-
and isinstance(current_span, StreamedSpan)
262-
and not isinstance(current_span, NoOpStreamedSpan)
263-
):
259+
if info and "data" in info and type(current_span) is StreamedSpan:
264260
with capture_internal_exceptions():
265261
current_span._segment.set_attribute(
266262
"http.request.body.data",
@@ -557,9 +553,7 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
557553
if span_streaming:
558554
current_span = current_scope.streamed_span
559555

560-
if isinstance(current_span, StreamedSpan) and not isinstance(
561-
current_span, NoOpStreamedSpan
562-
):
556+
if type(current_span) is StreamedSpan:
563557
current_span._segment._update_active_thread()
564558
elif current_scope.transaction is not None:
565559
current_scope.transaction.update_active_thread()

sentry_sdk/scope.py

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import os
22
import sys
33
import warnings
4-
from copy import copy, deepcopy
54
from collections import deque
65
from contextlib import contextmanager
7-
from enum import Enum
6+
from copy import copy, deepcopy
87
from datetime import datetime, timezone
8+
from enum import Enum
99
from functools import wraps
1010
from itertools import chain
11+
from typing import TYPE_CHECKING, cast
1112

1213
import sentry_sdk
1314
from sentry_sdk._types import AnnotatedValue
@@ -18,64 +19,68 @@
1819
INSTRUMENTER,
1920
SPANDATA,
2021
)
21-
from sentry_sdk.feature_flags import FlagBuffer, DEFAULT_FLAG_CAPACITY
22+
from sentry_sdk.feature_flags import DEFAULT_FLAG_CAPACITY, FlagBuffer
2223
from sentry_sdk.profiler.continuous_profiler import (
2324
get_profiler_id,
2425
try_autostart_continuous_profiler,
2526
try_profile_lifecycle_trace_start,
2627
)
2728
from sentry_sdk.profiler.transaction_profiler import Profile
2829
from sentry_sdk.session import Session
29-
from sentry_sdk.tracing_utils import (
30-
Baggage,
31-
has_tracing_enabled,
32-
has_span_streaming_enabled,
33-
is_ignored_span,
34-
_make_sampling_decision,
35-
PropagationContext,
30+
from sentry_sdk.traces import (
31+
_DEFAULT_PARENT_SPAN,
32+
NoOpStreamedSpan,
33+
StreamedSpan,
3634
)
37-
from sentry_sdk.traces import _DEFAULT_PARENT_SPAN, StreamedSpan, NoOpStreamedSpan
3835
from sentry_sdk.tracing import (
3936
BAGGAGE_HEADER_NAME,
4037
SENTRY_TRACE_HEADER_NAME,
4138
NoOpSpan,
4239
Span,
4340
Transaction,
4441
)
42+
from sentry_sdk.tracing_utils import (
43+
Baggage,
44+
PropagationContext,
45+
_make_sampling_decision,
46+
has_span_streaming_enabled,
47+
has_tracing_enabled,
48+
is_ignored_span,
49+
)
4550
from sentry_sdk.utils import (
51+
ContextVar,
4652
capture_internal_exception,
4753
capture_internal_exceptions,
48-
ContextVar,
4954
datetime_from_isoformat,
5055
disable_capture_event,
5156
event_from_exception,
5257
exc_info_from_error,
5358
format_attribute,
54-
logger,
5559
has_logs_enabled,
5660
has_metrics_enabled,
61+
logger,
5762
)
5863

59-
from typing import TYPE_CHECKING, cast
60-
6164
if TYPE_CHECKING:
6265
from collections.abc import Mapping
63-
64-
from typing import Any
65-
from typing import Callable
66-
from typing import Deque
67-
from typing import Dict
68-
from typing import Generator
69-
from typing import Iterator
70-
from typing import List
71-
from typing import Optional
72-
from typing import ParamSpec
73-
from typing import Tuple
74-
from typing import TypeVar
75-
from typing import Union
66+
from typing import (
67+
Any,
68+
Callable,
69+
Deque,
70+
Dict,
71+
Generator,
72+
Iterator,
73+
List,
74+
Optional,
75+
ParamSpec,
76+
Tuple,
77+
TypeVar,
78+
Union,
79+
)
7680

7781
from typing_extensions import Unpack
7882

83+
import sentry_sdk
7984
from sentry_sdk._types import (
8085
Attributes,
8186
AttributeValue,
@@ -92,11 +97,8 @@
9297
SamplingContext,
9398
Type,
9499
)
95-
96100
from sentry_sdk.tracing import TransactionKwargs
97101

98-
import sentry_sdk
99-
100102
P = ParamSpec("P")
101103
R = TypeVar("R")
102104

@@ -585,11 +587,7 @@ def get_traceparent(self, *args: "Any", **kwargs: "Any") -> "Optional[str]":
585587

586588
span_streaming = has_span_streaming_enabled(client.options)
587589
# If we have an active span, return traceparent from there
588-
if (
589-
span_streaming
590-
and self.streamed_span is not None
591-
and not isinstance(self.streamed_span, NoOpStreamedSpan)
592-
):
590+
if span_streaming and type(self.streamed_span) is StreamedSpan:
593591
return self.streamed_span._to_traceparent()
594592
elif not span_streaming and self.span is not None:
595593
return self.span._to_traceparent()
@@ -609,11 +607,7 @@ def get_baggage(self, *args: "Any", **kwargs: "Any") -> "Optional[Baggage]":
609607

610608
span_streaming = has_span_streaming_enabled(client.options)
611609
# If we have an active span, return baggage from there
612-
if (
613-
span_streaming
614-
and self.streamed_span is not None
615-
and not isinstance(self.streamed_span, NoOpStreamedSpan)
616-
):
610+
if span_streaming and type(self.streamed_span) is StreamedSpan:
617611
return self.streamed_span._to_baggage()
618612
elif not span_streaming and self.span is not None:
619613
return self.span._to_baggage()
@@ -918,11 +912,7 @@ def streamed_span(self, span: "Optional[StreamedSpan]") -> None:
918912

919913
# Also set _transaction and _transaction_info in streaming mode as this
920914
# is used for populating events and linking them to segments
921-
if (
922-
isinstance(span, StreamedSpan)
923-
and not isinstance(span, NoOpStreamedSpan)
924-
and span._is_segment()
925-
):
915+
if type(span) is StreamedSpan and span._is_segment():
926916
self._transaction = span.name
927917
if span._attributes.get("sentry.span.source"):
928918
self._transaction_info["source"] = str(

sentry_sdk/traces.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,16 @@
3131
)
3232

3333
if TYPE_CHECKING:
34-
from typing import Any, Callable, Iterator, Optional, ParamSpec, TypeVar, Union
34+
from typing import (
35+
Any,
36+
Callable,
37+
Iterator,
38+
Optional,
39+
ParamSpec,
40+
TypeVar,
41+
Union,
42+
)
43+
3544
from sentry_sdk._types import Attributes, AttributeValue
3645
from sentry_sdk.profiler.continuous_profiler import ContinuousProfile
3746

0 commit comments

Comments
 (0)