Skip to content

Next Python SDK major - #5005

Draft
sentrivana wants to merge 351 commits into
masterfrom
major/3.0
Draft

sentrivana wants to merge 351 commits into
masterfrom
major/3.0

Conversation

@sentrivana

@sentrivana sentrivana commented Oct 24, 2025 •

Copy link
Copy Markdown
Contributor

We're preparing our next major on this branch.

The project is tracked in Linear. If you don't have access, we'll try to tag issues belonging to the project with the SDK 3.0 label on GitHub so that you can follow along.

Notable changes

  • Transaction-based tracing will be removed. Span streaming will be the default tracing model.
  • Python 3.6 support will be removed.

Context

You might have read this announcement about us discontinuing work on a 3.0. This is referring to the work done on the potel-base branch, which included two types of changes: a huge refactor of our tracing code on the one hand, and various unrelated changes, improvements and fixes on the other. We're dropping the huge refactor part, and only porting the rest, to a new branch and eventually a new 3.0 release.

@codecov

codecov Bot commented Oct 24, 2025 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.76%. Comparing base (14aff96) to head (d864ba0).
⚠️ Report is 4 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #5005       +/-   ##
===========================================
+ Coverage   70.55%   83.76%   +13.21%     
===========================================
  Files         180      180               
  Lines       18077    18080        +3     
  Branches     3008     3009        +1     
===========================================
+ Hits        12754    15145     +2391     
+ Misses       4432     1943     -2489     
- Partials      891      992      +101     
Files with missing lines Coverage Δ
sentry_sdk/integrations/__init__.py 88.42% <100.00%> (+0.37%) ⬆️

... and 61 files with indirect coverage changes

@github-actions

github-actions Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ 57303 passed | ❌ 1 failed | ⏭️ 2727 skipped | Total: 60031 | Pass Rate: 95.46% | Execution Time: 163m 35s

📊 Comparison with Base Branch

Metric Change
Total Tests 📉 -79288
Passed Tests 📉 -74795
Failed Tests —
Skipped Tests 📉 -4493

➕ New Tests (1)

View new tests
  • test_cache_spans_templatetag
    • File: tests.integrations.django.test_cache_module
    • Status: ❌ Failing

➖ Removed Tests (1)

View removed tests
  • test_cache_spans_item_size[True]
    • File: tests.integrations.django.test_cache_module

❌ Failed Tests

test_cache_spans_templatetag

File: tests.integrations.django.test_cache_module
Suite: py3.12-django-v6.1.1
Error: tests/integrations/django/test_cache_module.py:362: in test_cache_spans_templatetag assert not spans[0]["attributes"]["cache.hit"] E assert not True

Stack Trace
tests/integrations/django/test_cache_module.py:362: in test_cache_spans_templatetag
    assert not spans[0]["attributes"]["cache.hit"]
E   assert not True

✅ Patch coverage is 90.06%. Project has 2073 uncovered lines.
❌ Project coverage is 90.06%. Comparing base (8afefe8) to head (456c77c).

Coverage diff
@@            Coverage Diff             @@
##        master       #PR       +/-##
==========================================
- Coverage    90.34%    90.06%    -0.28%
==========================================
  Files          202       185       -17
  Lines        26551     20860     -5691
  Branches      9856      7244     -2612
==========================================
+ Hits         23988     18787     -5201
- Misses        2563      2073      -490
- Partials      1487      1207      -280

Generated by Codecov Action

@github-actions

github-actions Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

⚪ None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (logging) Separate ignore lists for events/breadcrumbs and sentry logs by sl0thentr0py in #5698

Bug Fixes 🐛

Anthropic

  • Set exception info on streaming span when applicable by alexander-alderman-webb in #5683
  • Patch AsyncStream.close() and AsyncMessageStream.close() to finish spans by alexander-alderman-webb in #5675
  • Patch Stream.close() and MessageStream.close() to finish spans by alexander-alderman-webb in #5674

Documentation 📚

  • Add note on AI PRs to CONTRIBUTING.md by sentrivana in #5696

Internal Changes 🔧

  • Add -latest alias for each integration test suite by sentrivana in #5706
  • Use date-based branch names for toxgen PRs by sentrivana in #5704
  • 🤖 Update test matrix with new releases (03/19) by github-actions in #5703
  • Add client report tests for span streaming by sentrivana in #5677

Other

  • Next Python SDK major by sentrivana in #5005
  • Update CHANGELOG.md by sentrivana in #5685

🤖 This preview updates automatically when you update the PR.

Comment thread sentry_sdk/consts.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread tests/integrations/django/asgi/test_asgi.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread tests/integrations/threading/test_threading.py Outdated
Comment thread sentry_sdk/integrations/launchdarkly.py Outdated
Comment thread sentry_sdk/scope.py Outdated
Comment thread sentry_sdk/integrations/trytond.py
Comment thread sentry_sdk/integrations/chalice.py
Comment thread sentry_sdk/integrations/openai_agents/__init__.py
Comment thread sentry_sdk/integrations/pydantic_ai/__init__.py
Comment thread sentry_sdk/integrations/spark/spark_driver.py
Comment thread tests/integrations/launchdarkly/test_launchdarkly.py
Comment thread sentry_sdk/integrations/__init__.py Outdated
Comment thread sentry_sdk/integrations/openai_agents/__init__.py
Comment thread sentry_sdk/integrations/starlette.py
sentrivana added a commit that referenced this pull request Aug 6, 2026
Fixes for things that the bots
[surfaced](#5005) on the
major branch:
- some version checks were too late (after patching)
- fix TrytondWSGI integration name/`_MIN_VERSIONS` entry mismatch

Also, changed the warning of the `DidNotEnable` message from "X not
installed" to "X not installed or incompatible".
Comment thread sentry_sdk/integrations/redis/modules/queries.py
Comment thread sentry_sdk/integrations/langchain.py
Comment thread sentry_sdk/spotlight.py
Comment thread sentry_sdk/spotlight.py
mjq and others added 5 commits August 25, 2026 11:48
…#7201)

The sync request/response handler passed the _isolation_ scope to
`_set_transaction_name_and_source`, but the transaction/segment span
lives on the _current_ scope. As a result the route-resolved name never
reached the span for sync endpoints, which were instead named by the raw
URL from the ASGI middleware (`transaction_info.source` of `url` rather
than `route`). Async handlers already used the current scope and were
unaffected.

Pass the current scope (already computed above) so sync and async
handlers behave identically:
- streaming: the segment name / `sentry.segment.name.source` are
route-based
- static: the transaction event name / source are route-based

For parametrized routes this also removes high-cardinality URL
transaction names for sync endpoints.

Found while working on
#7183.
Comment thread sentry_sdk/integrations/otlp.py
Comment thread sentry_sdk/integrations/aiomysql.py
Comment thread sentry_sdk/integrations/aiomysql.py Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread sentry_sdk/integrations/otlp.py
Comment thread sentry_sdk/integrations/pyramid.py Outdated
Comment thread sentry_sdk/integrations/strawberry.py Outdated
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread sentry_sdk/integrations/stdlib.py Outdated
Comment thread sentry_sdk/integrations/stdlib.py Outdated
alexander-alderman-webb and others added 20 commits October 1, 2026 13:18
Inline the branches that set the route template as the segment name (i.e., `transaction_style="route_pattern"`).

Closes #7761
Inline the branches that set the route template as the segment name (i.e., `transaction_style="uri_template"`).

Closes #7757
Inline the branches that set the route template as the segment name (i.e., `transaction_style="url"`).

Closes #7758
Inline the branches that set the route template as the segment name (i.e., `transaction_style="url"`).

Closes #7763
- inline things to avoid mental context switches
- use global API
Inline the branches that set the route template as the segment name (i.e., `transaction_style="method_and_path_pattern"`).
Stop including the HTTP method in the name.

Closes #7801
Inline the branches that set the route template as the segment name (i.e., `transaction_style="url"`).

Closes #7811
Always filter the query string through the `data_collection`
`url_query_params` setting. The legacy `send_default_pii` path is
resolved into an equivalent `data_collection` default, so the raw
query string is no longer passed through.

Update the Django ASGI and FastAPI tests to configure
`data_collection` instead of `send_default_pii`.

Refs PY-2798
Refs #7566
…s` (#7769)

Remove the legacy `send_default_pii` fallback branches and always
gate `http.query`, `url.path`, `url.full` and `client.address` on
`data_collection`.

Refs PY-2798
Refs #7566
…7772)

`data_collection` is now the default way to control what data the SDK
collects, so the always-on `EventScrubber` is no longer needed. The
expectation is that, if people need to filter out request bodies, etc.,
that they use the `before_send` callback.

Removes `sentry_sdk.scrubber` (`EventScrubber`, `DEFAULT_DENYLIST`,
`DEFAULT_PII_DENYLIST`), the scrubbing step in `_prepare_event`, and the
`event_scrubber` init option. Passing `event_scrubber=` to `init` now
raises a `TypeError`.

Refs PY-2798
Refs #7566
### Description
Align span names with OTel conventions:
> Span name MUST be of the format Service.Operation as per the AWS HTTP
API, e.g., DynamoDB.GetItem, S3.ListBuckets. This is equivalent to
concatenating rpc.service and rpc.method with . and consistent with the
naming guidelines for RPC client spans.

So we use modeled `ServiceID` and operation for span names:
`aws.<hyphenized-service-id>.<Operation>` to `<ServiceID>.<Operation>`;
e.g. `aws.s3.GetObject` => `S3.GetObject`.

#### Issues
Resolves #7498
…ess (#7818)

Always gate the `user.ip_address` span attribute on
`data_collection["user_info"]` instead of falling back to
`send_default_pii` when `data_collection` isn't enabled.

Update the FastAPI form-data test to use `data_collection` rather than
`send_default_pii`.

Refs PY-2798
Refs #7566
Comment thread sentry_sdk/client.py
Comment on lines 555 to 558
self.options["project_root"],
)

if event is not None:
event_scrubber = self.options["event_scrubber"]
if event_scrubber:
event_scrubber.scrub_event(event)

if scope is not None and scope._gen_ai_original_message_count:
spans: "List[Dict[str, Any]] | AnnotatedValue" = event.get("spans", [])
if isinstance(spans, list):
for span in spans:
span_id = span.get("span_id", None)
span_data = span.get("data", {})
if (
span_id
and span_id in scope._gen_ai_original_message_count
and SPANDATA.GEN_AI_REQUEST_MESSAGES in span_data
):
span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES] = AnnotatedValue(
span_data[SPANDATA.GEN_AI_REQUEST_MESSAGES],
{"len": scope._gen_ai_original_message_count[span_id]},
)
if previous_total_spans is not None:
event["spans"] = AnnotatedValue(
event.get("spans", []), {"len": previous_total_spans}
)
if previous_total_breadcrumbs is not None:

@sentry-warden sentry-warden Bot Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

EventScrubber removal leaves user extras and contexts unredacted by default

With send_default_pii=False (the default), arbitrary values added to event extra or contexts are no longer passed through EventScrubber’s denylist before sending. data_collection filters specific collection categories but does not cover arbitrary custom fields. The removed event_scrubber option now raises during initialization, and the migration guide does not explain the change. Please document the removal and recommend before_send for equivalent scrubbing of custom event data.

Evidence
  • Scope._apply_extras_to_event() and _apply_contexts_to_event() copy custom values into the event without filtering them.
  • Client._prepare_event() proceeds from handle_in_app() to serialization and before_send; there is no EventScrubber pass in that path.
  • _map_from_send_default_pii() configures filtering for specific categories, not arbitrary extra or contexts fields.
  • event_scrubber is no longer an accepted option, and MIGRATION_GUIDE.md does not document its removal or a replacement.
Also found at 2 additional locations
  • sentry_sdk/client.py:131-133
  • sentry_sdk/consts.py:1373-1374

Identified by Warden · code-review, find-bugs · 5ZW-LDV

Comment on lines +224 to +228

ip = _get_ip(scope)
if ip:
client_options = sentry_sdk.get_client().options
if client_options["data_collection"]["user_info"]:

@sentry-warden sentry-warden Bot Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ASGI middleware dereferences unresolved data_collection for a non-recording client

When the SDK has not been initialized, get_client() can return a NonRecordingClient whose options retain data_collection=None. For requests with a client IP, the direct access to client_options["data_collection"]["user_info"] raises TypeError before the ASGI app is called. Guard this access with has_data_collection_enabled() and the legacy PII fallback, or handle None, as the WSGI middleware does.

Evidence
  • Scope.get_client() falls back to NonRecordingClient when no active client is available; its options come from DEFAULT_OPTIONS, where data_collection defaults to None.
  • _run_app() gets an IP from _get_ip(scope) and directly indexes client_options["data_collection"]["user_info"] at line 228 when one is present.
  • That access raises TypeError before the downstream ASGI app is called; the WSGI middleware instead checks has_data_collection_enabled() and has a legacy PII fallback.

Identified by Warden · code-review, find-bugs · RBX-L42

Comment thread sentry_sdk/integrations/aws_lambda.py
Comment thread sentry_sdk/integrations/celery/__init__.py
Comment thread sentry_sdk/integrations/flask.py
Comment on lines +1183 to +1190
span = sentry_sdk.start_span(
name=f"invoke_agent {run_name}" if run_name else "invoke_agent",
attributes={
"sentry.op": OP.GEN_AI_INVOKE_AGENT,
"sentry.origin": LangchainIntegration.origin,
SPANDATA.GEN_AI_OPERATION_NAME: "invoke_agent",
},
)

@sentry-warden sentry-warden Bot Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stream agent spans drop gen_ai.response.streaming attribute

Include SPANDATA.GEN_AI_RESPONSE_STREAMING: True in the start_span attributes so stream invoke_agent spans stay distinguishable from invoke.

Evidence
  • Prior code set SPANDATA.GEN_AI_RESPONSE_STREAMING: True on both the span-streaming and legacy branches.
  • The consolidated sentry_sdk.start_span(... attributes=...) block omits that attribute entirely.
  • Other streaming AI integrations (e.g. openai_agents agent_run, google_genai, openai) still set GEN_AI_RESPONSE_STREAMING for stream paths.

Identified by Warden · code-review, find-bugs · NDG-GCQ

Comment thread sentry_sdk/integrations/asyncio.py
…#7820)

Gate user info on `data_collection["user_info"]` only, removing the
`send_default_pii` fallback from the WSGI and ASGI request event
processors and the post-response user lookup.

Refs PY-2798
Refs #7566
Comment on lines +410 to 412
if "incoming_request" in data_collection["http_bodies"]:
if "body" in aws_event:
request["data"] = aws_event.get("body", "")

@sentry-warden sentry-warden Bot Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Request body attached without max_request_body_size check

When attaching aws_event body data, call request_body_within_bounds() like aiohttp/WSGI so max_request_body_size still limits payload size.

Evidence
  • The new body path sets request["data"] = aws_event.get("body", "") with no size guard.
  • data_collection._map_from_send_default_pii documents bodies as bounded by max_request_body_size.
  • aiohttp.get_aiohttp_request_data() and _wsgi_common.RequestExtractor both call request_body_within_bounds() before attaching bodies.
  • This path is now the default for all clients because data_collection is always resolved.

Identified by Warden · code-review, find-bugs · AD4-33Z

Comment on lines +185 to +189
def _get_transaction_name(request: "Any") -> str:
try:
if transaction_style == "url":
name = bottle_request.route.rule or "bottle request"
else:
name = (
bottle_request.route.name
or transaction_from_function(bottle_request.route.callback)
or "bottle request"
)

sentry_sdk.get_current_scope().set_transaction_name(
name,
source=SEGMENT_SOURCE_FOR_STYLE[transaction_style],
)
return request.route.rule or "bottle request"
except RuntimeError:
pass


def _set_transaction_name_and_source(
event: "Event", transaction_style: str, request: "Any"
) -> None:
name = ""

if transaction_style == "url":
try:
name = request.route.rule or ""
except RuntimeError:
pass

elif transaction_style == "endpoint":
try:
name = (
request.route.name
or transaction_from_function(request.route.callback)
or ""
)
except RuntimeError:
pass

event["transaction"] = name
event["transaction_info"] = {
"source": TRANSACTION_SOURCE_FOR_STYLE[transaction_style]
}
return "bottle request"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unmatched Bottle requests can fail while resolving the transaction route

When Bottle handles an unmatched path, request.route can be None. _patched_handle then dereferences .rule without guarding against None—in its HTTP-route block when tracing is enabled, and in _get_transaction_name otherwise. The resulting AttributeError can prevent Bottle's normal 404 response from being returned. Check that the route exists before reading its rule at both access sites.

Evidence
  • _patched_handle calls Bottle's original handler, then reads bottle_request.route.rule; that access catches RuntimeError only and runs when a server span exists.
  • Regardless of tracing, _patched_handle then calls _get_transaction_name, which also dereferences request.route.rule and catches only RuntimeError.
  • Bottle's route property may be None when no route matched, so either dereference raises AttributeError instead of allowing the normal 404 response to proceed.

Identified by Warden · find-bugs · 68K-JMP

Comment thread sentry_sdk/integrations/google_genai/streaming.py
sentrivana and others added 4 commits October 2, 2026 12:19
…#7831)

### Description
- drop legacy test cases from `tests/integrations/utils.py`
(`DATA_COLLECTION_USER_INFO_CASES_LEGACY`;
`DATA_COLLECTION_REMOTE_ADDR_CASES_LEGACY`;
`DATA_COLLECTION_QUEUES_CASES_LEGACY`).
- removes `**init_kwargs` from `sentry_init(...)`.
Comment thread sentry_sdk/ai/utils.py
Comment on lines 489 to 491
def set_conversation_id(conversation_id: str) -> None:
"""
Set the conversation_id in the scope.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI input spans include raw, unbounded blob content

When data_collection.gen_ai.inputs is enabled, Anthropic base64 content and corresponding OpenAI/LangChain formats are copied into span message attributes verbatim. The message serialization path has no local size cap, so large image or document payloads can also inflate spans or cause them to exceed payload limits. Replace blob contents with a substitute and bound or truncate serialized messages before attaching them.

Evidence
  • transform_anthropic_content_part copies source["data"] directly into blob content; the OpenAI and generic transformers also return inline content verbatim.
  • Anthropic, LiteLLM, and LangChain pass transformed messages to set_data_normalized when GenAI input collection is enabled.
  • set_data_normalized JSON-serializes messages and calls span.set_attribute without a message-size limit.
  • Google GenAI and PydanticAI explicitly replace blob contents with BLOB_DATA_SUBSTITUTE, but these shared transforms do not.

Identified by Warden · code-review · 4JF-TJE

Comment thread sentry_sdk/api.py
Comment on lines +299 to +310
def continue_trace(incoming: "Dict[str, Any]") -> None:
"""
Sets the propagation context from environment or headers and returns a transaction.
Continue a trace from headers or environment variables.

This function sets the propagation context on the scope. Any span started
in the updated scope will belong under the trace extracted from the
provided propagation headers or environment variables.

continue_trace() doesn't start any spans on its own. Use the start_span()
API for that.
"""
return get_isolation_scope().continue_trace(
environ_or_headers, op, name, source, origin
)
return traces.continue_trace(incoming)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Document the continue_trace migration

The 3.x migration guide does not explain that continue_trace no longer accepts op, name, source, or origin, or returns a Transaction. Add the migration pattern: call continue_trace(headers) to set propagation context, then use start_span(...) to create a span.

Evidence
  • sentry_sdk.api.continue_trace(incoming) now accepts only incoming and returns None; its docstring says it does not start spans.
  • sentry_sdk.traces.continue_trace sets propagation context, while start_span is a separate API for creating spans.
  • The 3.x MIGRATION_GUIDE.md lists other removed APIs but does not describe this continue_trace signature and behavior change.

Identified by Warden · code-review · UAA-4PR

Comment on lines 214 to +219
identifier = "anthropic"
origin = f"auto.ai.{identifier}"

def __init__(self: "AnthropicIntegration", include_prompts: bool = True) -> None:
self.include_prompts = include_prompts

@staticmethod
def setup_once() -> None:
version = package_version("anthropic")
version = parse_version(ANTHROPIC_VERSION)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

include_prompts removed without migration path

Removing AnthropicIntegration(include_prompts=...) is a breaking API/behavior change—document the move to data_collection.gen_ai (and that the old default True is now False unless send_default_pii/data_collection enables it) in MIGRATION_GUIDE.md.

Evidence
  • AnthropicIntegration no longer defines __init__; AnthropicIntegration(include_prompts=...) will raise TypeError.
  • Prompt/response capture now uses data_collection["gen_ai"]["inputs"|"outputs"] (e.g. around _set_common_input_data / _set_output_data).
  • When data_collection is unset, those flags map from send_default_pii (default False), so the old default include_prompts=True is no longer preserved.
  • MIGRATION_GUIDE.md has no entry for include_prompts or this AI integration option change.

Identified by Warden · code-review · Y5C-REM

Comment on lines +168 to +170
collect_response = (
"outgoing_response" in client_options["data_collection"]["http_bodies"]
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ariadne responses bypass the legacy PII gate

When data_collection was not explicitly provided, Ariadne previously gated response capture on send_default_pii. The new processor instead relies only on http_bodies; the default mapping includes outgoing_response even when send_default_pii is false, so error events can now include the full GraphQL response payload, including partial data. Preserve the legacy gate for this configuration, while continuing to honor explicit data_collection settings.

Evidence
  • _map_from_send_default_pii sets http_bodies to all body types regardless of send_default_pii; has_data_collection_enabled distinguishes this default mapping from user-provided configuration.
  • Ariadne’s _make_response_event_processor checks only for outgoing_response and response.get("errors"), then stores the entire response under contexts.response.data.
  • The Ariadne error handlers attach this processor to events for GraphQL errors, so a response containing both errors and partial data is included.
  • Strawberry and gql retain a should_send_default_pii() fallback when data collection was not user-provided, unlike Ariadne.

Identified by Warden · code-review · L64-KV5

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants