Next Python SDK major - #5005
sentrivana wants to merge 372 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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
|
Codecov Results 📊✅ 57303 passed | ❌ 1 failed | ⏭️ 2727 skipped | Total: 60031 | Pass Rate: 95.46% | Execution Time: 163m 35s 📊 Comparison with Base Branch
➕ New Tests (1)View new tests
➖ Removed Tests (1)View removed tests
❌ Failed Tests
|
Semver Impact of This PR⚪ None (no version bump detected) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛Anthropic
Documentation 📚
Internal Changes 🔧
Other
🤖 This preview updates automatically when you update the PR. |
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".
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.
| if "incoming_request" in data_collection["http_bodies"]: | ||
| if "body" in aws_event: | ||
| request["data"] = aws_event.get("body", "") |
There was a problem hiding this comment.
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_piidocuments bodies as bounded bymax_request_body_size.aiohttp.get_aiohttp_request_data()and_wsgi_common.RequestExtractorboth callrequest_body_within_bounds()before attaching bodies.- This path is now the default for all clients because
data_collectionis always resolved.
Identified by Warden · code-review, find-bugs · AD4-33Z
| 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" |
There was a problem hiding this comment.
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_handlecalls Bottle's original handler, then readsbottle_request.route.rule; that access catchesRuntimeErroronly and runs when a server span exists.- Regardless of tracing,
_patched_handlethen calls_get_transaction_name, which also dereferencesrequest.route.ruleand catches onlyRuntimeError. - Bottle's route property may be
Nonewhen no route matched, so either dereference raisesAttributeErrorinstead of allowing the normal 404 response to proceed.
Identified by Warden · find-bugs · 68K-JMP
Also: consolidated some tests. Closes https://linear.app/getsentry/issue/PY-2861/remove-send-default-pii-from-httpx
Basically the same as httpx Closes https://linear.app/getsentry/issue/PY-2862/remove-send-default-pii-from-httpx2
…#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(...)`.
| def set_conversation_id(conversation_id: str) -> None: | ||
| """ | ||
| Set the conversation_id in the scope. |
There was a problem hiding this comment.
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_partcopiessource["data"]directly into blobcontent; the OpenAI and generic transformers also return inline content verbatim.- Anthropic, LiteLLM, and LangChain pass transformed messages to
set_data_normalizedwhen GenAI input collection is enabled. set_data_normalizedJSON-serializes messages and callsspan.set_attributewithout 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
| 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) |
There was a problem hiding this comment.
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 onlyincomingand returnsNone; its docstring says it does not start spans.sentry_sdk.traces.continue_tracesets propagation context, whilestart_spanis a separate API for creating spans.- The 3.x
MIGRATION_GUIDE.mdlists other removed APIs but does not describe thiscontinue_tracesignature and behavior change.
Identified by Warden · code-review · UAA-4PR
| 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) |
There was a problem hiding this comment.
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
AnthropicIntegrationno longer defines__init__;AnthropicIntegration(include_prompts=...)will raiseTypeError.- Prompt/response capture now uses
data_collection["gen_ai"]["inputs"|"outputs"](e.g. around_set_common_input_data/_set_output_data). - When
data_collectionis unset, those flags map fromsend_default_pii(default False), so the old defaultinclude_prompts=Trueis no longer preserved. MIGRATION_GUIDE.mdhas no entry forinclude_promptsor this AI integration option change.
Identified by Warden · code-review · Y5C-REM
| collect_response = ( | ||
| "outgoing_response" in client_options["data_collection"]["http_bodies"] | ||
| ) |
There was a problem hiding this comment.
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_piisetshttp_bodiesto all body types regardless ofsend_default_pii;has_data_collection_enableddistinguishes this default mapping from user-provided configuration.- Ariadne’s
_make_response_event_processorchecks only foroutgoing_responseandresponse.get("errors"), then stores the entire response undercontexts.response.data. - The Ariadne error handlers attach this processor to events for GraphQL errors, so a response containing both errors and partial
datais 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
### Description Remove `send_default_pii` support from Flask. #### Issues Resolves #7590
…nt spans (#7854) The attribute was removed from OTel conventions in open-telemetry/semantic-conventions-genai@126e72f.
…ans (#7691) The attribute was removed from OTel conventions in open-telemetry/semantic-conventions-genai@126e72f.
…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
….tool.call.result` (#7864)
…#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
#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
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
SDK3.0 label on GitHub so that you can follow along.Notable changes
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-basebranch, 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.