Skip to content

Next Python SDK major - #5005

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

sentrivana wants to merge 372 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
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
Comment thread sentry_sdk/integrations/__init__.py
Comment thread sentry_sdk/integrations/threading.py
Comment thread tests/integrations/bottle/test_bottle.py
Comment thread tests/integrations/httpx/test_httpx.py Outdated
sentrivana added a commit that referenced this pull request Aug 26, 2026
Originally raised by a bot
[here](#5005 (comment)):
the `parse_version` function parses version strings as is (e.g. 3.1
becomes `(3, 1)`). We use these parsed version tuples in integrations to
compare the installed version against the minimum (defined in
`integrations/__init__.py`). The minimum versions are often three-part,
e.g. `(3, 1, 0)`.

This means that we can mistakenly consider a valid version to be below
the minimum, because in pure tuple terms, `(3, 1) < (3, 1, 0)` is true.

This can also happen in reverse (package version has three parts, while
our min version boundary has two).

In this PR, we make the internal version comparison work as expected
regardless of mismatches in the length of the version strings/tuples.
Comment thread sentry_sdk/integrations/typer.py
Comment thread sentry_sdk/_init_implementation.py
Comment thread sentry_sdk/integrations/arq.py Outdated
Comment thread sentry_sdk/integrations/asgi.py Outdated
Comment thread sentry_sdk/integrations/strawberry.py Outdated
Comment thread sentry_sdk/integrations/redis/redis.py
Comment thread sentry_sdk/integrations/stdlib.py Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread sentry_sdk/consts.py
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

sentrivana and others added 21 commits October 5, 2026 09:11
### Description
Remove `send_default_pii` support from Flask.


#### Issues
Resolves #7590
…nt spans (#7856)

Remove the request model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
### Description
Drop `send_default_pii` support from WSGI.

#### Issues
Resolves #7616
…ans (#7859)

Remove the request model from Invoke Agent spans because it is ambiguous for an agent that can call different models in the course of its execution.
…pans (#7855)

Remove model request parameters from Invoke Agent spans because the attributes are ambiguous for an agent that can make multiple model calls in the course of its execution.
Each model call may have different request parameters.
…7858)

Remove model request parameters from Invoke Agent spans because the attributes are ambiguous for an agent that can make multiple model calls in the course of its execution.
Each model call may have different request parameters.
…t_pii` fallback (#7821)

`record_sql_queries` now reads only
`data_collection.database_query_data` to
decide whether to record query params, and the
`_experiments["record_sql_params"]`
option is removed.

Tests that relied on the experiment now opt in with
`data_collection={"database_query_data": True}`.

Also removes `record_params` options in the aiomysql and asyncpg
integrations.

Refs PY-2798
Refs #7566
…#7823)

The gql integration now relies solely on `data_collection.graphql` to
decide
whether to attach the query document, variables and error response to
events.
`send_default_pii` no longer has any effect here.

Also fix a typo in a test assertion message (`exception(type)` ->
`exception["type"]`) and remove the legacy PII tests.

Fixes PY-2818
Fixes #7589
Redis command arguments are now gated solely on
`data_collection["database_query_data"]`. The `send_default_pii`
fallback in `_get_safe_command` is removed.

Tests that exercised `send_default_pii` (including the precedence test)
are removed, and the remaining ones use `data_collection` instead.

Fixes PY-2837
Fixes #7608
…#7822)

Always route the query string and request body through `data_collection`
instead of falling back to `send_default_pii` when `data_collection` is
not
configured.

Update tests to drive behaviour through `data_collection` only.

Fixes PY-2820
Fixes #7591
#7533)

Replace DedupeIntegration's weakref/ContextVar-based "last seen"
tracking with a `_handled_by_sentry` flag set directly on the
exception. This avoids the contextvar leaking across async contexts
and removes the need for `reset_last_seen()`, which the client used
to call after `before_send` dropped an exception.

Add a regression test for the case where the same exception instance
is re-raised (e.g. Django re-raising in middleware after a view
already handled it) to confirm it's still deduped correctly.

Fixes PY-2381
Fixes #6094

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.

5 participants