From c74acb1bd8b4316fa8e264bff5c84f4d0bb8e731 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Wed, 13 May 2026 13:38:02 -0400 Subject: [PATCH 1/3] feat(flask): Add span streaming support and request body capture Add request body data to the streaming segment span when span streaming is enabled. Update existing tests to cover both streaming and non-streaming code paths, and add new tests for request body capture, size limits, and header scrubbing in streaming mode. Fixes PY-2323 Fixes #6021 --- sentry_sdk/integrations/flask.py | 63 +++- tests/integrations/flask/test_flask.py | 436 ++++++++++++++++++++----- 2 files changed, 418 insertions(+), 81 deletions(-) diff --git a/sentry_sdk/integrations/flask.py b/sentry_sdk/integrations/flask.py index 9adf8d51e8..44924fc7ab 100644 --- a/sentry_sdk/integrations/flask.py +++ b/sentry_sdk/integrations/flask.py @@ -1,27 +1,34 @@ +import json +from typing import TYPE_CHECKING + import sentry_sdk -from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration +from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version from sentry_sdk.integrations._wsgi_common import ( + _RAW_DATA_EXCEPTIONS, DEFAULT_HTTP_METHODS_TO_CAPTURE, RequestExtractor, + request_body_within_bounds, ) from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.traces import StreamedSpan, _get_current_streamed_span from sentry_sdk.tracing import SOURCE_FOR_STYLE +from sentry_sdk.tracing_utils import has_span_streaming_enabled from sentry_sdk.utils import ( + AnnotatedValue, capture_internal_exceptions, ensure_integration_enabled, event_from_exception, package_version, ) -from typing import TYPE_CHECKING - if TYPE_CHECKING: from typing import Any, Callable, Dict, Union + from werkzeug.datastructures import FileStorage, ImmutableMultiDict + from sentry_sdk._types import Event, EventProcessor from sentry_sdk.integrations.wsgi import _ScopedResponse - from werkzeug.datastructures import FileStorage, ImmutableMultiDict try: @@ -94,10 +101,9 @@ def setup_once() -> None: def sentry_patched_wsgi_app( self: "Any", environ: "Dict[str, str]", start_response: "Callable[..., Any]" ) -> "_ScopedResponse": - if sentry_sdk.get_client().get_integration(FlaskIntegration) is None: - return old_app(self, environ, start_response) - integration = sentry_sdk.get_client().get_integration(FlaskIntegration) + if integration is None: + return old_app(self, environ, start_response) middleware = SentryWsgiMiddleware( lambda *a, **kw: old_app(self, *a, **kw), @@ -158,6 +164,49 @@ def _request_started(app: "Flask", **kwargs: "Any") -> None: evt_processor = _make_request_event_processor(app, request, integration) scope.add_event_processor(evt_processor) + client = sentry_sdk.get_client() + if has_span_streaming_enabled(client.options): + _set_request_body_data_on_streaming_segment(request, client) + + +def _set_request_body_data_on_streaming_segment( + request: "Request", client: "sentry_sdk.client.BaseClient" +) -> None: + current_span = _get_current_streamed_span() + if type(current_span) is not StreamedSpan: + return + + with capture_internal_exceptions(): + content_length = int(request.content_length or 0) + extractor = FlaskRequestExtractor(request) + + if not request_body_within_bounds(client, content_length): + data = AnnotatedValue.substituted_because_over_size_limit() + else: + raw_data = None + try: + raw_data = extractor.raw_data() + except _RAW_DATA_EXCEPTIONS: + pass + + parsed_body = extractor.parsed_body() + if parsed_body is not None: + data = parsed_body + elif raw_data: + data = AnnotatedValue.substituted_because_raw_data() + else: + return + + def _default(value: "Any") -> "Any": + if isinstance(value, AnnotatedValue): + return value.value + return str(value) + + current_span._segment.set_attribute( + "http.request.body.data", + json.dumps(data, default=_default), + ) + class FlaskRequestExtractor(RequestExtractor): def env(self) -> "Dict[str, str]": diff --git a/tests/integrations/flask/test_flask.py b/tests/integrations/flask/test_flask.py index 246a0cb4b0..330d486757 100644 --- a/tests/integrations/flask/test_flask.py +++ b/tests/integrations/flask/test_flask.py @@ -1,16 +1,16 @@ import json -import re import logging +import re from io import BytesIO import pytest from flask import ( Flask, Response, - request, abort, - stream_with_context, render_template_string, + request, + stream_with_context, ) from flask.views import View from flask_login import LoginManager, login_user @@ -23,15 +23,19 @@ import sentry_sdk import sentry_sdk.integrations.flask as flask_sentry from sentry_sdk import ( - set_tag, - capture_message, capture_exception, + capture_message, + set_tag, +) +from sentry_sdk._types import ( + OVER_SIZE_LIMIT_SUBSTITUTE, + SENSITIVE_DATA_SUBSTITUTE, + UNPARSABLE_RAW_DATA_SUBSTITUTE, ) from sentry_sdk.consts import DEFAULT_MAX_VALUE_LENGTH from sentry_sdk.integrations.logging import LoggingIntegration from sentry_sdk.serializer import MAX_DATABAG_BREADTH - login_manager = LoginManager() @@ -93,29 +97,49 @@ def test_has_context(sentry_init, app, capture_events): ("/message/123456", "url", "/message/", "route"), ], ) +@pytest.mark.parametrize("span_streaming", [True, False]) def test_transaction_style( sentry_init, app, capture_events, + capture_items, url, transaction_style, expected_transaction, expected_source, + span_streaming, ): sentry_init( integrations=[ flask_sentry.FlaskIntegration(transaction_style=transaction_style) - ] + ], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - events = capture_events() + + if span_streaming: + items = capture_items("span") + else: + events = capture_events() client = app.test_client() response = client.get(url) assert response.status_code == 200 - (event,) = events - assert event["transaction"] == expected_transaction - assert event["transaction_info"] == {"source": expected_source} + if span_streaming: + sentry_sdk.flush() + + assert len(items) == 1 + + span = items[0].payload + + assert span["is_segment"] is True + assert span["name"] == expected_transaction + assert span["attributes"]["sentry.span.source"] == expected_source + else: + message_event, transaction_event = events + assert transaction_event["transaction"] == expected_transaction + assert transaction_event["transaction_info"] == {"source": expected_source} @pytest.mark.parametrize("debug", (True, False)) @@ -751,8 +775,15 @@ def zerodivision(e): assert not events -def test_tracing_success(sentry_init, capture_events, app): - sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_tracing_success( + sentry_init, capture_events, capture_items, app, span_streaming +): + sentry_init( + traces_sample_rate=1.0, + integrations=[flask_sentry.FlaskIntegration()], + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, + ) @app.before_request def _(): @@ -764,30 +795,61 @@ def hi_tx(): capture_message("hi") return "ok" - events = capture_events() + if span_streaming: + items = capture_items("event", "span") + else: + events = capture_events() with app.test_client() as client: response = client.get("/message_tx") assert response.status_code == 200 - message_event, transaction_event = events + if span_streaming: + sentry_sdk.flush() + + spans = [i for i in items if i.type == "span"] + message_events = [i for i in items if i.type == "event"] + + assert len(spans) == 1 + span = spans[0].payload + assert span["is_segment"] is True + assert span["name"] == "hi_tx" + assert span["status"] == "ok" + assert span["attributes"]["sentry.op"] == "http.server" + assert span["attributes"]["sentry.origin"] == "auto.http.flask" + + assert len(message_events) == 1 + assert message_events[0].payload["message"] == "hi" + assert message_events[0].payload["transaction"] == "hi_tx" + assert message_events[0].payload["tags"]["view"] == "yes" + assert message_events[0].payload["tags"]["before_request"] == "yes" + else: + message_event, transaction_event = events - assert transaction_event["type"] == "transaction" - assert transaction_event["transaction"] == "hi_tx" - assert transaction_event["contexts"]["trace"]["status"] == "ok" - assert transaction_event["tags"]["view"] == "yes" - assert transaction_event["tags"]["before_request"] == "yes" + assert transaction_event["type"] == "transaction" + assert transaction_event["transaction"] == "hi_tx" + assert transaction_event["contexts"]["trace"]["status"] == "ok" + assert transaction_event["tags"]["view"] == "yes" + assert transaction_event["tags"]["before_request"] == "yes" - assert message_event["message"] == "hi" - assert message_event["transaction"] == "hi_tx" - assert message_event["tags"]["view"] == "yes" - assert message_event["tags"]["before_request"] == "yes" + assert message_event["message"] == "hi" + assert message_event["transaction"] == "hi_tx" + assert message_event["tags"]["view"] == "yes" + assert message_event["tags"]["before_request"] == "yes" -def test_tracing_error(sentry_init, capture_events, app): - sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()]) +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_tracing_error(sentry_init, capture_events, capture_items, app, span_streaming): + sentry_init( + traces_sample_rate=1.0, + integrations=[flask_sentry.FlaskIntegration()], + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, + ) - events = capture_events() + if span_streaming: + items = capture_items("event", "span") + else: + events = capture_events() @app.route("/error") def error(): @@ -798,15 +860,33 @@ def error(): response = client.get("/error") assert response.status_code == 500 - error_event, transaction_event = events + if span_streaming: + sentry_sdk.flush() + + spans = [i for i in items if i.type == "span"] + error_events = [i for i in items if i.type == "event"] + + assert len(spans) == 1 + span = spans[0].payload + assert span["is_segment"] is True + assert span["name"] == "error" + assert span["status"] == "error" + assert span["attributes"]["sentry.op"] == "http.server" + assert span["attributes"]["sentry.origin"] == "auto.http.flask" + assert len(error_events) == 1 + assert error_events[0].payload["transaction"] == "error" + (exception,) = error_events[0].payload["exception"]["values"] + assert exception["type"] == "ZeroDivisionError" + else: + error_event, transaction_event = events - assert transaction_event["type"] == "transaction" - assert transaction_event["transaction"] == "error" - assert transaction_event["contexts"]["trace"]["status"] == "internal_error" + assert transaction_event["type"] == "transaction" + assert transaction_event["transaction"] == "error" + assert transaction_event["contexts"]["trace"]["status"] == "internal_error" - assert error_event["transaction"] == "error" - (exception,) = error_event["exception"]["values"] - assert exception["type"] == "ZeroDivisionError" + assert error_event["transaction"] == "error" + (exception,) = error_event["exception"]["values"] + assert exception["type"] == "ZeroDivisionError" def test_error_has_trace_context_if_tracing_disabled(sentry_init, capture_events, app): @@ -925,92 +1005,133 @@ def index(): assert event["request"]["headers"]["Proxy-Authorization"] == "[Filtered]" -def test_response_status_code_ok_in_transaction_context( - sentry_init, capture_envelopes, app +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_response_status_code_ok( + sentry_init, capture_envelopes, capture_items, app, span_streaming ): """ - Tests that the response status code is added to the transaction context. - This also works for when there is an Exception during the request, but somehow the test flask app doesn't seem to trigger that. + Tests that the response status code is added to the transaction/span. """ sentry_init( integrations=[flask_sentry.FlaskIntegration()], traces_sample_rate=1.0, release="demo-release", + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - envelopes = capture_envelopes() + if span_streaming: + items = capture_items("span") + else: + envelopes = capture_envelopes() client = app.test_client() client.get("/message") sentry_sdk.get_client().flush() - (_, transaction_envelope, _) = envelopes - transaction = transaction_envelope.get_transaction_event() + if span_streaming: + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.response.status_code"] == 200 + assert span["status"] == "ok" + else: + (_, transaction_envelope, _) = envelopes + transaction = transaction_envelope.get_transaction_event() - assert transaction["type"] == "transaction" - assert len(transaction["contexts"]) > 0 - assert "response" in transaction["contexts"].keys(), ( - "Response context not found in transaction" - ) - assert transaction["contexts"]["response"]["status_code"] == 200 + assert transaction["type"] == "transaction" + assert len(transaction["contexts"]) > 0 + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) + assert transaction["contexts"]["response"]["status_code"] == 200 -def test_response_status_code_not_found_in_transaction_context( - sentry_init, capture_envelopes, app +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_response_status_code_not_found( + sentry_init, capture_envelopes, capture_items, app, span_streaming ): sentry_init( integrations=[flask_sentry.FlaskIntegration()], traces_sample_rate=1.0, release="demo-release", + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - envelopes = capture_envelopes() + if span_streaming: + items = capture_items("span") + else: + envelopes = capture_envelopes() client = app.test_client() client.get("/not-existing-route") sentry_sdk.get_client().flush() - (transaction_envelope, _) = envelopes - transaction = transaction_envelope.get_transaction_event() + if span_streaming: + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.response.status_code"] == 404 + assert span["status"] == "error" + else: + (transaction_envelope, _) = envelopes + transaction = transaction_envelope.get_transaction_event() - assert transaction["type"] == "transaction" - assert len(transaction["contexts"]) > 0 - assert "response" in transaction["contexts"].keys(), ( - "Response context not found in transaction" - ) - assert transaction["contexts"]["response"]["status_code"] == 404 + assert transaction["type"] == "transaction" + assert len(transaction["contexts"]) > 0 + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) + assert transaction["contexts"]["response"]["status_code"] == 404 -def test_span_origin(sentry_init, app, capture_events): +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_span_origin(sentry_init, app, capture_events, capture_items, span_streaming): sentry_init( integrations=[flask_sentry.FlaskIntegration()], traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - events = capture_events() + + if span_streaming: + items = capture_items("span") + else: + events = capture_events() client = app.test_client() client.get("/message") - (_, event) = events + if span_streaming: + sentry_sdk.flush() - assert event["contexts"]["trace"]["origin"] == "auto.http.flask" + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["sentry.origin"] == "auto.http.flask" + else: + (_, event) = events + assert event["contexts"]["trace"]["origin"] == "auto.http.flask" -def test_transaction_http_method_default( +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_http_method_default( sentry_init, app, capture_events, + capture_items, + span_streaming, ): """ - By default OPTIONS and HEAD requests do not create a transaction. + By default OPTIONS and HEAD requests do not create a transaction/segment. """ sentry_init( traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()], + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - events = capture_events() + + if span_streaming: + items = capture_items("span") + else: + events = capture_events() client = app.test_client() response = client.get("/nomessage") @@ -1022,16 +1143,25 @@ def test_transaction_http_method_default( response = client.head("/nomessage") assert response.status_code == 200 - (event,) = events + if span_streaming: + sentry_sdk.flush() - assert len(events) == 1 - assert event["request"]["method"] == "GET" + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.request.method"] == "GET" + else: + assert len(events) == 1 + (event,) = events + assert event["request"]["method"] == "GET" -def test_transaction_http_method_custom( +@pytest.mark.parametrize("span_streaming", [True, False]) +def test_http_method_custom( sentry_init, app, capture_events, + capture_items, + span_streaming, ): """ Configure FlaskIntegration to ONLY capture OPTIONS and HEAD requests. @@ -1046,8 +1176,13 @@ def test_transaction_http_method_custom( ) # capitalization does not matter ) # case does not matter ], + _experiments={"trace_lifecycle": "stream"} if span_streaming else {}, ) - events = capture_events() + + if span_streaming: + items = capture_items("span") + else: + events = capture_events() client = app.test_client() response = client.get("/nomessage") @@ -1059,8 +1194,161 @@ def test_transaction_http_method_custom( response = client.head("/nomessage") assert response.status_code == 200 - assert len(events) == 2 + if span_streaming: + sentry_sdk.flush() + + assert len(items) == 2 + assert items[0].payload["attributes"]["http.request.method"] == "OPTIONS" + assert items[1].payload["attributes"]["http.request.method"] == "HEAD" + else: + assert len(events) == 2 + (event1, event2) = events + assert event1["request"]["method"] == "OPTIONS" + assert event2["request"]["method"] == "HEAD" + + +def test_request_body_captured_on_segment_span_streaming( + sentry_init, capture_items, app +): + sentry_init( + integrations=[flask_sentry.FlaskIntegration()], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"}, + ) + + body = {"key": "value"} + + @app.route("/body", methods=["POST"]) + def body_endpoint(): + request.get_json() + return "ok" + + items = capture_items("span") + + client = app.test_client() + response = client.post("/body", json=body) + assert response.status_code == 200 + + sentry_sdk.flush() + + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.request.body.data"] == json.dumps(body) + + +def test_request_body_not_read_span_streaming(sentry_init, capture_items, app): + sentry_init( + integrations=[flask_sentry.FlaskIntegration()], + traces_sample_rate=1.0, + max_request_body_size="never", + _experiments={"trace_lifecycle": "stream"}, + ) + + @app.route("/body", methods=["POST"]) + def body_endpoint(): + request.get_json() + return "ok" + + items = capture_items("span") + + client = app.test_client() + response = client.post("/body", json={"key": "value"}) + assert response.status_code == 200 + + sentry_sdk.flush() + + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.request.body.data"] == json.dumps( + OVER_SIZE_LIMIT_SUBSTITUTE + ) + + +def test_request_body_over_size_limit_span_streaming(sentry_init, capture_items, app): + sentry_init( + integrations=[flask_sentry.FlaskIntegration()], + traces_sample_rate=1.0, + max_request_body_size="small", + _experiments={"trace_lifecycle": "stream"}, + ) + + @app.route("/body", methods=["POST"]) + def body_endpoint(): + request.get_data() + return "ok" + + items = capture_items("span") + + client = app.test_client() + response = client.post("/body", data=b"x" * 2000) + assert response.status_code == 200 - (event1, event2) = events - assert event1["request"]["method"] == "OPTIONS" - assert event2["request"]["method"] == "HEAD" + sentry_sdk.flush() + + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.request.body.data"] == json.dumps( + OVER_SIZE_LIMIT_SUBSTITUTE + ) + + +def test_request_body_raw_data_substituted_span_streaming( + sentry_init, capture_items, app +): + sentry_init( + integrations=[flask_sentry.FlaskIntegration()], + traces_sample_rate=1.0, + max_request_body_size="always", + _experiments={"trace_lifecycle": "stream"}, + ) + + @app.route("/body", methods=["POST"]) + def body_endpoint(): + request.get_data() + return "ok" + + items = capture_items("span") + + client = app.test_client() + response = client.post( + "/body", data=b"some raw bytes", content_type="application/octet-stream" + ) + assert response.status_code == 200 + + sentry_sdk.flush() + + assert len(items) == 1 + span = items[0].payload + assert span["attributes"]["http.request.body.data"] == json.dumps( + UNPARSABLE_RAW_DATA_SUBSTITUTE + ) + + +def test_sensitive_header_scrubbing_span_streaming(sentry_init, capture_items, app): + sentry_init( + integrations=[flask_sentry.FlaskIntegration()], + traces_sample_rate=1.0, + _experiments={"trace_lifecycle": "stream"}, + ) + + items = capture_items("span") + + client = app.test_client() + response = client.get( + "/message", + headers={ + "Authorization": "Bearer secret-token", + "X-Custom-Header": "passthrough", + }, + ) + assert response.status_code == 200 + + sentry_sdk.flush() + + assert len(items) == 1 + span = items[0].payload + assert ( + span["attributes"]["http.request.header.authorization"] + == SENSITIVE_DATA_SUBSTITUTE + ) + assert span["attributes"]["http.request.header.x-custom-header"] == "passthrough" From c557b263be5558462423555eb03e84765b9aa34c Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Wed, 13 May 2026 14:49:45 -0400 Subject: [PATCH 2/3] move common function between flask and starlette to more central place --- sentry_sdk/integrations/_wsgi_common.py | 9 +++++++++ sentry_sdk/integrations/flask.py | 9 ++------- sentry_sdk/integrations/starlette.py | 12 +----------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index 433c437d1a..60bc3c73f7 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -214,6 +214,15 @@ def _is_json_content_type(ct: "Optional[str]") -> bool: ) +def _serialize_request_body_data(data: "Any") -> str: + def _default(value: "Any") -> "Any": + if isinstance(value, AnnotatedValue): + return value.value + return str(value) + + return json.dumps(data, default=_default) + + def _filter_headers( headers: "Mapping[str, str]", use_annotated_value: bool = True, diff --git a/sentry_sdk/integrations/flask.py b/sentry_sdk/integrations/flask.py index 44924fc7ab..eda854ade5 100644 --- a/sentry_sdk/integrations/flask.py +++ b/sentry_sdk/integrations/flask.py @@ -1,4 +1,3 @@ -import json from typing import TYPE_CHECKING import sentry_sdk @@ -7,6 +6,7 @@ _RAW_DATA_EXCEPTIONS, DEFAULT_HTTP_METHODS_TO_CAPTURE, RequestExtractor, + _serialize_request_body_data, request_body_within_bounds, ) from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware @@ -197,14 +197,9 @@ def _set_request_body_data_on_streaming_segment( else: return - def _default(value: "Any") -> "Any": - if isinstance(value, AnnotatedValue): - return value.value - return str(value) - current_span._segment.set_attribute( "http.request.body.data", - json.dumps(data, default=_default), + _serialize_request_body_data(data), ) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 4371ed4f58..66e174e35c 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -1,5 +1,4 @@ import functools -import json import sys import warnings from collections.abc import Set @@ -18,6 +17,7 @@ DEFAULT_HTTP_METHODS_TO_CAPTURE, HttpCodeRangeContainer, _is_json_content_type, + _serialize_request_body_data, request_body_within_bounds, ) from sentry_sdk.integrations.asgi import SentryAsgiMiddleware @@ -242,16 +242,6 @@ async def _sentry_send(*args: "Any", **kwargs: "Any") -> "Any": return middleware_class -def _serialize_request_body_data(data: "Any") -> str: - # data may be a JSON-serializable value, an AnnotatedValue, or a dict with AnnotatedValue values - def _default(value: "Any") -> "Any": - if isinstance(value, AnnotatedValue): - return value.value - return str(value) - - return json.dumps(data, default=_default) - - def _set_request_body_data_on_streaming_segment( info: "Optional[Dict[str, Any]]", ) -> None: From 2df2cd37202c8924000de32a82e07a108131d4c7 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Thu, 14 May 2026 10:25:34 -0400 Subject: [PATCH 3/3] add comment back in that was accidentally removed, small cleanups --- tests/integrations/flask/test_flask.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/integrations/flask/test_flask.py b/tests/integrations/flask/test_flask.py index 330d486757..c0455bad01 100644 --- a/tests/integrations/flask/test_flask.py +++ b/tests/integrations/flask/test_flask.py @@ -15,6 +15,8 @@ from flask.views import View from flask_login import LoginManager, login_user +from sentry_sdk.traces import SpanStatus + try: from werkzeug.wrappers.request import UnsupportedMediaType except ImportError: @@ -814,7 +816,7 @@ def hi_tx(): span = spans[0].payload assert span["is_segment"] is True assert span["name"] == "hi_tx" - assert span["status"] == "ok" + assert span["status"] == SpanStatus.OK assert span["attributes"]["sentry.op"] == "http.server" assert span["attributes"]["sentry.origin"] == "auto.http.flask" @@ -870,7 +872,7 @@ def error(): span = spans[0].payload assert span["is_segment"] is True assert span["name"] == "error" - assert span["status"] == "error" + assert span["status"] == SpanStatus.ERROR assert span["attributes"]["sentry.op"] == "http.server" assert span["attributes"]["sentry.origin"] == "auto.http.flask" assert len(error_events) == 1 @@ -1011,6 +1013,7 @@ def test_response_status_code_ok( ): """ Tests that the response status code is added to the transaction/span. + This also works for when there is an Exception during the request, but somehow the test flask app doesn't seem to trigger that. """ sentry_init( integrations=[flask_sentry.FlaskIntegration()], @@ -1033,7 +1036,7 @@ def test_response_status_code_ok( assert len(items) == 1 span = items[0].payload assert span["attributes"]["http.response.status_code"] == 200 - assert span["status"] == "ok" + assert span["status"] == SpanStatus.OK else: (_, transaction_envelope, _) = envelopes transaction = transaction_envelope.get_transaction_event() @@ -1071,7 +1074,7 @@ def test_response_status_code_not_found( assert len(items) == 1 span = items[0].payload assert span["attributes"]["http.response.status_code"] == 404 - assert span["status"] == "error" + assert span["status"] == SpanStatus.ERROR else: (transaction_envelope, _) = envelopes transaction = transaction_envelope.get_transaction_event()