Skip to content

ref: Introduce _is_sampled_streamed_span() helper to replace repeated check#6180

Open
ericapisani wants to merge 1 commit intomasterfrom
create-sampled-span-helper-0doa2
Open

ref: Introduce _is_sampled_streamed_span() helper to replace repeated check#6180
ericapisani wants to merge 1 commit intomasterfrom
create-sampled-span-helper-0doa2

Conversation

@ericapisani
Copy link
Copy Markdown
Member

@ericapisani ericapisani commented May 1, 2026

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 _is_sampled_streamed_span() in traces.py with a TypeGuard[StreamedSpan] return type 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

…ed check

The `isinstance(span, StreamedSpan) and not isinstance(span, NoOpStreamedSpan)`
pattern was scattered across the codebase and easy to get wrong — forgetting the
`NoOpStreamedSpan` guard is a recurring code review catch.

Extract it into a single `_is_sampled_streamed_span()` helper in `traces.py` with
a `TypeGuard[StreamedSpan]` return type for proper type narrowing, and replace all
instances of the combined check in `scope.py`, `starlette.py`, and `fastapi.py`.
@ericapisani
Copy link
Copy Markdown
Member Author

bugbot run

@ericapisani
Copy link
Copy Markdown
Member Author

@sentry review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Codecov Results 📊

13 passed | Total: 13 | Pass Rate: 100% | Execution Time: 12.39s

All tests are passing successfully.

❌ Patch coverage is 72.73%. Project has 14939 uncovered lines.

Files with missing lines (4)
File Patch % Lines
starlette.py 5.61% ⚠️ 370 Missing
scope.py 71.73% ⚠️ 253 Missing and 84 partials
traces.py 69.48% ⚠️ 94 Missing and 21 partials
fastapi.py 15.96% ⚠️ 79 Missing

Generated by Codecov Action

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 10e96aa. Configure here.

@ericapisani ericapisani marked this pull request as ready for review May 1, 2026 16:16
@ericapisani ericapisani requested a review from a team as a code owner May 1, 2026 16:16
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

@alexander-alderman-webb alexander-alderman-webb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like in blocks gated by not isinstance(current_span, NoOpStreamedSpan) we rely on private attributes and functions of StreamedSpan such as _segment and _is_segment().

If we're relying on private details that we expect to only exist on StreamedSpan (and not subclasses) it would be more appropriate to replace logic like

isinstance(span, StreamedSpan) and not isinstance(span, NoOpStreamedSpan)

with

type(span) is StreamedSpan

Copy link
Copy Markdown
Contributor

@sentrivana sentrivana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it'd be better to replace

isinstance(span, StreamedSpan) and not isinstance(span, NoOpStreamedSpan)

with

isinstance(span, StreamedSpan) and span.sampled

But I assume that might cause mypy to complain about nonexistent methods on NoOpStreamedSpans since it won't be able to realize the condition effectively means it can't be a NoOpStreamedSpan?

Edit: Only saw Alex's comment above now. That also sounds good.

@ericapisani
Copy link
Copy Markdown
Member Author

Re: using the type approach rather than isinstance (i.e.: type(span) is StreamedSpan)

I think it depends on how strict we want to be with ensuring that the only spans that pass this are StreamedSpan classes specifically.

I wrote this check with isinstance in case there are additional children introduced in the future of StreamedSpan that we would want to handle similar to their parent, and that we ultimately didn't want to filter out with this guard - using type would unfortunately cause that to happen.

Re: using the sampled property:

But I assume that might cause mypy to complain about nonexistent methods on NoOpStreamedSpans since it won't be able to realize the condition effectively means it can't be a NoOpStreamedSpan?

We would achieve the same outcome using the sampled property as is written here because it exists on both the StreamedSpan and the NoOpStreamedSpan, but I think it doesn't make the conditional as explicitly clear that it's more about removing any instances of NoOpStreamedSpan due to the reason that you mentioned in that they lack the methods that StreamedSpan has.

@alexander-alderman-webb
Copy link
Copy Markdown
Contributor

alexander-alderman-webb commented May 4, 2026

I think it depends on how strict we want to be with ensuring that the only spans that pass this are StreamedSpan classes specifically.

The spans touched by the filtering logic are created by the SDK, so we know that they are of type StreamedSpan and not a subclass other than NoOpStreamedSpan. If we introduce other subclasses in the future, I think we have full flexibility to change 😄.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create helper method to replace frequent "is streamed span but not a noop streamed span" check

3 participants