From 8b6e5adfc6a55c40b7e64b099ae51c2cdb244031 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:30:30 +0200 Subject: [PATCH 1/9] feat(sessions): Add top-level start- and end session methods (#4474) Closes #4473. Co-authored-by: Cursor Agent --- sentry_sdk/__init__.py | 2 ++ sentry_sdk/api.py | 16 ++++++++++++++ tests/test_sessions.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index 9fd7253fc2..e03f3b4484 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -47,6 +47,8 @@ "trace", "monitor", "logger", + "start_session", + "end_session", ] # Initialize the debug support after everything is loaded diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index e56109cbd0..698a2085ab 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -82,6 +82,8 @@ def overload(x): "start_transaction", "trace", "monitor", + "start_session", + "end_session", ] @@ -450,3 +452,17 @@ def continue_trace( return get_isolation_scope().continue_trace( environ_or_headers, op, name, source, origin ) + + +@scopemethod +def start_session( + session_mode="application", # type: str +): + # type: (...) -> None + return get_isolation_scope().start_session(session_mode=session_mode) + + +@scopemethod +def end_session(): + # type: () -> None + return get_isolation_scope().end_session() diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 9cad0b7252..731b188727 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -246,3 +246,52 @@ def test_no_thread_on_shutdown_no_errors_deprecated( sentry_sdk.flush() # If we reach this point without error, the test is successful. + + +def test_top_level_start_session_basic(sentry_init, capture_envelopes): + """Test that top-level start_session starts a session on the isolation scope.""" + sentry_init(release="test-release", environment="test-env") + envelopes = capture_envelopes() + + # Start a session using the top-level API + sentry_sdk.start_session() + + # End the session + sentry_sdk.end_session() + sentry_sdk.flush() + + # Check that we got a session envelope + assert len(envelopes) == 1 + sess = envelopes[0] + assert len(sess.items) == 1 + sess_event = sess.items[0].payload.json + + assert sess_event["attrs"] == { + "release": "test-release", + "environment": "test-env", + } + assert sess_event["status"] == "exited" + + +def test_top_level_start_session_with_mode(sentry_init, capture_envelopes): + """Test that top-level start_session accepts session_mode parameter.""" + sentry_init(release="test-release", environment="test-env") + envelopes = capture_envelopes() + + # Start a session with request mode + sentry_sdk.start_session(session_mode="request") + sentry_sdk.end_session() + sentry_sdk.flush() + + # Request mode sessions are aggregated + assert len(envelopes) == 1 + sess = envelopes[0] + assert len(sess.items) == 1 + sess_event = sess.items[0].payload.json + + assert sess_event["attrs"] == { + "release": "test-release", + "environment": "test-env", + } + # Request sessions show up as aggregates + assert "aggregates" in sess_event From dae02180dfb095cdbd8ed7e81544ef048482d70b Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Wed, 25 Jun 2025 11:34:58 +0300 Subject: [PATCH 2/9] fix(Litestar): Apply `failed_request_status_codes` to exceptions raised in middleware (#4074) This is a fix for #4021: exceptions raised in middleware were sent without taking into account `failed_request_status_codes` value. See the test case for an example. --------- Co-authored-by: Anton Pirker Co-authored-by: Daniel Szoke --- sentry_sdk/integrations/asgi.py | 22 ++++++- sentry_sdk/integrations/litestar.py | 9 +++ tests/integrations/litestar/test_litestar.py | 66 +++++++++++++++++++- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index fc8ee29b1a..1b020ebbc0 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -145,6 +145,22 @@ def __init__( else: self.__call__ = self._run_asgi2 + def _capture_lifespan_exception(self, exc): + # type: (Exception) -> None + """Capture exceptions raise in application lifespan handlers. + + The separate function is needed to support overriding in derived integrations that use different catching mechanisms. + """ + return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) + + def _capture_request_exception(self, exc): + # type: (Exception) -> None + """Capture exceptions raised in incoming request handlers. + + The separate function is needed to support overriding in derived integrations that use different catching mechanisms. + """ + return _capture_exception(exc=exc, mechanism_type=self.mechanism_type) + def _run_asgi2(self, scope): # type: (Any) -> Any async def inner(receive, send): @@ -158,7 +174,7 @@ async def _run_asgi3(self, scope, receive, send): return await self._run_app(scope, receive, send, asgi_version=3) async def _run_app(self, scope, receive, send, asgi_version): - # type: (Any, Any, Any, Any, int) -> Any + # type: (Any, Any, Any, int) -> Any is_recursive_asgi_middleware = _asgi_middleware_applied.get(False) is_lifespan = scope["type"] == "lifespan" if is_recursive_asgi_middleware or is_lifespan: @@ -169,7 +185,7 @@ async def _run_app(self, scope, receive, send, asgi_version): return await self.app(scope, receive, send) except Exception as exc: - _capture_exception(exc, mechanism_type=self.mechanism_type) + self._capture_lifespan_exception(exc) raise exc from None _asgi_middleware_applied.set(True) @@ -256,7 +272,7 @@ async def _sentry_wrapped_send(event): scope, receive, _sentry_wrapped_send ) except Exception as exc: - _capture_exception(exc, mechanism_type=self.mechanism_type) + self._capture_request_exception(exc) raise exc from None finally: _asgi_middleware_applied.set(False) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 5f0b32b04e..4e15081cba 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -87,6 +87,15 @@ def __init__(self, app, span_origin=LitestarIntegration.origin): span_origin=span_origin, ) + def _capture_request_exception(self, exc): + # type: (Exception) -> None + """Avoid catching exceptions from request handlers. + + Those exceptions are already handled in Litestar.after_exception handler. + We still catch exceptions from application lifespan handlers. + """ + pass + def patch_app_init(): # type: () -> None diff --git a/tests/integrations/litestar/test_litestar.py b/tests/integrations/litestar/test_litestar.py index 4f642479e4..b064c17112 100644 --- a/tests/integrations/litestar/test_litestar.py +++ b/tests/integrations/litestar/test_litestar.py @@ -402,7 +402,7 @@ async def __call__(self, scope, receive, send): @parametrize_test_configurable_status_codes -def test_configurable_status_codes( +def test_configurable_status_codes_handler( sentry_init, capture_events, failed_request_status_codes, @@ -427,3 +427,67 @@ async def error() -> None: client.get("/error") assert len(events) == int(expected_error) + + +@parametrize_test_configurable_status_codes +def test_configurable_status_codes_middleware( + sentry_init, + capture_events, + failed_request_status_codes, + status_code, + expected_error, +): + integration_kwargs = ( + {"failed_request_status_codes": failed_request_status_codes} + if failed_request_status_codes is not None + else {} + ) + sentry_init(integrations=[LitestarIntegration(**integration_kwargs)]) + + events = capture_events() + + def create_raising_middleware(app): + async def raising_middleware(scope, receive, send): + raise HTTPException(status_code=status_code) + + return raising_middleware + + @get("/error") + async def error() -> None: ... + + app = Litestar([error], middleware=[create_raising_middleware]) + client = TestClient(app) + client.get("/error") + + assert len(events) == int(expected_error) + + +def test_catch_non_http_exceptions_in_middleware( + sentry_init, + capture_events, +): + sentry_init(integrations=[LitestarIntegration()]) + + events = capture_events() + + def create_raising_middleware(app): + async def raising_middleware(scope, receive, send): + raise RuntimeError("Too Hot") + + return raising_middleware + + @get("/error") + async def error() -> None: ... + + app = Litestar([error], middleware=[create_raising_middleware]) + client = TestClient(app) + + try: + client.get("/error") + except RuntimeError: + pass + + assert len(events) == 1 + event_exception = events[0]["exception"]["values"][0] + assert event_exception["type"] == "RuntimeError" + assert event_exception["value"] == "Too Hot" From 0a2d8585f18f1d135d1f04624b702ef46fd119bb Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:37:39 +0200 Subject: [PATCH 3/9] fix(langchain): Ensure no duplicate `SentryLangchainCallback` (#4485) Ensure that `SentryLangchainCallback` does not get added twice by also checking the `inheritable_callbacks` Fixes https://github.com/getsentry/sentry-python/issues/4443 --- sentry_sdk/integrations/langchain.py | 10 ++- .../integrations/langchain/test_langchain.py | 78 ++++++++++++++++++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 1064f29ffd..5f82401389 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -1,3 +1,4 @@ +import itertools from collections import OrderedDict from functools import wraps @@ -451,7 +452,14 @@ def new_configure( **kwargs, ) - if not any(isinstance(cb, SentryLangchainCallback) for cb in callbacks_list): + inheritable_callbacks_list = ( + inheritable_callbacks if isinstance(inheritable_callbacks, list) else [] + ) + + if not any( + isinstance(cb, SentryLangchainCallback) + for cb in itertools.chain(callbacks_list, inheritable_callbacks_list) + ): # Avoid mutating the existing callbacks list callbacks_list = [ *callbacks_list, diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 3f1b3b1da5..863e6daf4c 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -14,10 +14,15 @@ from langchain_core.callbacks import CallbackManagerForLLMRun from langchain_core.messages import BaseMessage, AIMessageChunk -from langchain_core.outputs import ChatGenerationChunk +from langchain_core.outputs import ChatGenerationChunk, ChatResult +from langchain_core.runnables import RunnableConfig +from langchain_core.language_models.chat_models import BaseChatModel from sentry_sdk import start_transaction -from sentry_sdk.integrations.langchain import LangchainIntegration +from sentry_sdk.integrations.langchain import ( + LangchainIntegration, + SentryLangchainCallback, +) from langchain.agents import tool, AgentExecutor, create_openai_tools_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder @@ -342,3 +347,72 @@ def test_span_origin(sentry_init, capture_events): assert event["contexts"]["trace"]["origin"] == "manual" for span in event["spans"]: assert span["origin"] == "auto.ai.langchain" + + +def test_manual_callback_no_duplication(sentry_init): + """ + Test that when a user manually provides a SentryLangchainCallback, + the integration doesn't create a duplicate callback. + """ + + # Track callback instances + tracked_callback_instances = set() + + class CallbackTrackingModel(BaseChatModel): + """Mock model that tracks callback instances for testing.""" + + def _generate( + self, + messages, + stop=None, + run_manager=None, + **kwargs, + ): + # Track all SentryLangchainCallback instances + if run_manager: + for handler in run_manager.handlers: + if isinstance(handler, SentryLangchainCallback): + tracked_callback_instances.add(id(handler)) + + for handler in run_manager.inheritable_handlers: + if isinstance(handler, SentryLangchainCallback): + tracked_callback_instances.add(id(handler)) + + return ChatResult( + generations=[ + ChatGenerationChunk(message=AIMessageChunk(content="Hello!")) + ], + llm_output={}, + ) + + @property + def _llm_type(self): + return "test_model" + + @property + def _identifying_params(self): + return {} + + sentry_init(integrations=[LangchainIntegration()]) + + # Create a manual SentryLangchainCallback + manual_callback = SentryLangchainCallback( + max_span_map_size=100, include_prompts=False + ) + + # Create RunnableConfig with the manual callback + config = RunnableConfig(callbacks=[manual_callback]) + + # Invoke the model with the config + llm = CallbackTrackingModel() + llm.invoke("Hello", config) + + # Verify that only ONE SentryLangchainCallback instance was used + assert len(tracked_callback_instances) == 1, ( + f"Expected exactly 1 SentryLangchainCallback instance, " + f"but found {len(tracked_callback_instances)}. " + f"This indicates callback duplication occurred." + ) + + # Verify the callback ID matches our manual callback + assert id(manual_callback) in tracked_callback_instances From 7804260fbf3ed8f797af95d2c0bdfcfeb85b0605 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:47:41 +0200 Subject: [PATCH 4/9] fix(langchain): Make `span_map` an instance variable (#4476) `span_map` should be an instance variable; otherwise, separate instances of the `SentryLangchainCallback` share the same `span_map` object, which is clearly not intended here. Also, remove the `max_span_map_size` class variable, it is always set on the instance, and so not needed. Ref #4443 Co-authored-by: Cursor Agent --- sentry_sdk/integrations/langchain.py | 5 +---- tests/integrations/langchain/test_langchain.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 5f82401389..0b8bbd8049 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -89,12 +89,9 @@ def __init__(self, span): class SentryLangchainCallback(BaseCallbackHandler): # type: ignore[misc] """Base callback handler that can be used to handle callbacks from langchain.""" - span_map = OrderedDict() # type: OrderedDict[UUID, WatchedSpan] - - max_span_map_size = 0 - def __init__(self, max_span_map_size, include_prompts, tiktoken_encoding_name=None): # type: (int, bool, Optional[str]) -> None + self.span_map = OrderedDict() # type: OrderedDict[UUID, WatchedSpan] self.max_span_map_size = max_span_map_size self.include_prompts = include_prompts diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index 863e6daf4c..8ace6d4821 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -416,3 +416,15 @@ def _identifying_params(self): # Verify the callback ID matches our manual callback assert id(manual_callback) in tracked_callback_instances + + +def test_span_map_is_instance_variable(): + """Test that each SentryLangchainCallback instance has its own span_map.""" + # Create two separate callback instances + callback1 = SentryLangchainCallback(max_span_map_size=100, include_prompts=True) + callback2 = SentryLangchainCallback(max_span_map_size=100, include_prompts=True) + + # Verify they have different span_map instances + assert ( + callback1.span_map is not callback2.span_map + ), "span_map should be an instance variable, not shared between instances" From ab2e3f08b600b22a95c3313eddd66f733e2d133c Mon Sep 17 00:00:00 2001 From: svartalf Date: Wed, 25 Jun 2025 11:54:42 +0200 Subject: [PATCH 5/9] fix(integrations/ray): Correctly pass keyword arguments to ray.remote function (#4430) Monkey-patched implementation was passing the provided keyword arguments incorrectly due to a typo - "*kwargs" was used instead of "**kwargs" twice. Fixed integration started hitting an assert in the Ray codebase that requires for users to use "@ray.remote" decorator either with no arguments and no parentheses, or with some of the arguments provided. An additional wrapper function was added to support both scenarios. --------- Co-authored-by: Ivana Kellyer --- sentry_sdk/integrations/ray.py | 132 +++++++++++++++-------------- tests/integrations/ray/test_ray.py | 14 ++- 2 files changed, 81 insertions(+), 65 deletions(-) diff --git a/sentry_sdk/integrations/ray.py b/sentry_sdk/integrations/ray.py index 0842b92265..8d6cdc1201 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -42,73 +42,81 @@ def _patch_ray_remote(): old_remote = ray.remote @functools.wraps(old_remote) - def new_remote(f, *args, **kwargs): - # type: (Callable[..., Any], *Any, **Any) -> Callable[..., Any] + def new_remote(f=None, *args, **kwargs): + # type: (Optional[Callable[..., Any]], *Any, **Any) -> Callable[..., Any] + if inspect.isclass(f): # Ray Actors # (https://docs.ray.io/en/latest/ray-core/actors.html) # are not supported # (Only Ray Tasks are supported) - return old_remote(f, *args, *kwargs) - - def _f(*f_args, _tracing=None, **f_kwargs): - # type: (Any, Optional[dict[str, Any]], Any) -> Any - """ - Ray Worker - """ - _check_sentry_initialized() - - transaction = sentry_sdk.continue_trace( - _tracing or {}, - op=OP.QUEUE_TASK_RAY, - name=qualname_from_function(f), - origin=RayIntegration.origin, - source=TransactionSource.TASK, - ) - - with sentry_sdk.start_transaction(transaction) as transaction: - try: - result = f(*f_args, **f_kwargs) - transaction.set_status(SPANSTATUS.OK) - except Exception: - transaction.set_status(SPANSTATUS.INTERNAL_ERROR) - exc_info = sys.exc_info() - _capture_exception(exc_info) - reraise(*exc_info) - - return result - - rv = old_remote(_f, *args, *kwargs) - old_remote_method = rv.remote - - def _remote_method_with_header_propagation(*args, **kwargs): - # type: (*Any, **Any) -> Any - """ - Ray Client - """ - with sentry_sdk.start_span( - op=OP.QUEUE_SUBMIT_RAY, - name=qualname_from_function(f), - origin=RayIntegration.origin, - ) as span: - tracing = { - k: v - for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() - } - try: - result = old_remote_method(*args, **kwargs, _tracing=tracing) - span.set_status(SPANSTATUS.OK) - except Exception: - span.set_status(SPANSTATUS.INTERNAL_ERROR) - exc_info = sys.exc_info() - _capture_exception(exc_info) - reraise(*exc_info) - - return result - - rv.remote = _remote_method_with_header_propagation - - return rv + return old_remote(f, *args, **kwargs) + + def wrapper(user_f): + # type: (Callable[..., Any]) -> Any + def new_func(*f_args, _tracing=None, **f_kwargs): + # type: (Any, Optional[dict[str, Any]], Any) -> Any + _check_sentry_initialized() + + transaction = sentry_sdk.continue_trace( + _tracing or {}, + op=OP.QUEUE_TASK_RAY, + name=qualname_from_function(user_f), + origin=RayIntegration.origin, + source=TransactionSource.TASK, + ) + + with sentry_sdk.start_transaction(transaction) as transaction: + try: + result = user_f(*f_args, **f_kwargs) + transaction.set_status(SPANSTATUS.OK) + except Exception: + transaction.set_status(SPANSTATUS.INTERNAL_ERROR) + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + if f: + rv = old_remote(new_func) + else: + rv = old_remote(*args, **kwargs)(new_func) + old_remote_method = rv.remote + + def _remote_method_with_header_propagation(*args, **kwargs): + # type: (*Any, **Any) -> Any + """ + Ray Client + """ + with sentry_sdk.start_span( + op=OP.QUEUE_SUBMIT_RAY, + name=qualname_from_function(user_f), + origin=RayIntegration.origin, + ) as span: + tracing = { + k: v + for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers() + } + try: + result = old_remote_method(*args, **kwargs, _tracing=tracing) + span.set_status(SPANSTATUS.OK) + except Exception: + span.set_status(SPANSTATUS.INTERNAL_ERROR) + exc_info = sys.exc_info() + _capture_exception(exc_info) + reraise(*exc_info) + + return result + + rv.remote = _remote_method_with_header_propagation + + return rv + + if f is not None: + return wrapper(f) + else: + return wrapper ray.remote = new_remote diff --git a/tests/integrations/ray/test_ray.py b/tests/integrations/ray/test_ray.py index 95ab4ad0fa..b5bdd473c4 100644 --- a/tests/integrations/ray/test_ray.py +++ b/tests/integrations/ray/test_ray.py @@ -59,7 +59,10 @@ def read_error_from_log(job_id): @pytest.mark.forked -def test_tracing_in_ray_tasks(): +@pytest.mark.parametrize( + "task_options", [{}, {"num_cpus": 0, "memory": 1024 * 1024 * 10}] +) +def test_tracing_in_ray_tasks(task_options): setup_sentry() ray.init( @@ -69,14 +72,19 @@ def test_tracing_in_ray_tasks(): } ) - # Setup ray task - @ray.remote def example_task(): with sentry_sdk.start_span(op="task", name="example task step"): ... return sentry_sdk.get_client().transport.envelopes + # Setup ray task, calling decorator directly instead of @, + # to accommodate for test parametrization + if task_options: + example_task = ray.remote(**task_options)(example_task) + else: + example_task = ray.remote(example_task) + with sentry_sdk.start_transaction(op="task", name="ray test transaction"): worker_envelopes = ray.get(example_task.remote()) From 546ce1f71023b651860d6b576024b9d93b4c9ab8 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 26 Jun 2025 14:21:09 +0200 Subject: [PATCH 6/9] Set tool span to failed if an error is raised in the tool (#4527) Co-authored-by: Ivana Kellyer --- .../integrations/openai_agents/spans/execute_tool.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py index e6e880b64c..5f9e4cb340 100644 --- a/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +++ b/sentry_sdk/integrations/openai_agents/spans/execute_tool.py @@ -1,5 +1,5 @@ import sentry_sdk -from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS from sentry_sdk.scope import should_send_default_pii from ..consts import SPAN_ORIGIN @@ -39,5 +39,10 @@ def update_execute_tool_span(span, agent, tool, result): # type: (sentry_sdk.tracing.Span, agents.Agent, agents.Tool, Any) -> None _set_agent_data(span, agent) + if isinstance(result, str) and result.startswith( + "An error occurred while running the tool" + ): + span.set_status(SPANSTATUS.INTERNAL_ERROR) + if should_send_default_pii(): span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, result) From bca8816ac1f84fe4304682bd6de173fbf0c005a3 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 26 Jun 2025 12:27:44 +0000 Subject: [PATCH 7/9] release: 2.32.0 --- CHANGELOG.md | 11 +++++++++++ docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bcd8ddc73..63cb761830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 2.32.0 + +### Various fixes & improvements + +- Set tool span to failed if an error is raised in the tool (#4527) by @antonpirker +- fix(integrations/ray): Correctly pass keyword arguments to ray.remote function (#4430) by @svartalf +- fix(langchain): Make `span_map` an instance variable (#4476) by @szokeasaurusrex +- fix(langchain): Ensure no duplicate `SentryLangchainCallback` (#4485) by @szokeasaurusrex +- fix(Litestar): Apply `failed_request_status_codes` to exceptions raised in middleware (#4074) by @vrslev +- feat(sessions): Add top-level start- and end session methods (#4474) by @szokeasaurusrex + ## 2.31.0 ### Various fixes & improvements diff --git a/docs/conf.py b/docs/conf.py index 01b40ae828..ea5995ee36 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,7 @@ copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year) author = "Sentry Team and Contributors" -release = "2.31.0" +release = "2.32.0" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 7102eea0e7..01f72e2887 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -1181,4 +1181,4 @@ def _get_default_options(): del _get_default_options -VERSION = "2.31.0" +VERSION = "2.32.0" diff --git a/setup.py b/setup.py index 0662be384e..ae86cab158 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="2.31.0", + version="2.32.0", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python", From c815a3245d10e45bebee5b47292deec438a4d4d2 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 26 Jun 2025 14:28:54 +0200 Subject: [PATCH 8/9] updated changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cb761830..fd4a98e717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,12 @@ ### Various fixes & improvements -- Set tool span to failed if an error is raised in the tool (#4527) by @antonpirker +- feat(sessions): Add top-level start- and end session methods (#4474) by @szokeasaurusrex +- feat(openai-agents): Set tool span to failed if an error is raised in the tool (#4527) by @antonpirker - fix(integrations/ray): Correctly pass keyword arguments to ray.remote function (#4430) by @svartalf - fix(langchain): Make `span_map` an instance variable (#4476) by @szokeasaurusrex - fix(langchain): Ensure no duplicate `SentryLangchainCallback` (#4485) by @szokeasaurusrex - fix(Litestar): Apply `failed_request_status_codes` to exceptions raised in middleware (#4074) by @vrslev -- feat(sessions): Add top-level start- and end session methods (#4474) by @szokeasaurusrex ## 2.31.0 From 2634a523b3416748cf952bc517641594b9b40bac Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 27 Jun 2025 08:35:14 +0200 Subject: [PATCH 9/9] Pin zope.event (#4531) zope.event [released](https://pypi.org/project/zope.event/#history) a new version recently that broke our gevent ci --- scripts/populate_tox/tox.jinja | 1 + tox.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index ac14bdb02a..c67f4127d5 100644 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -173,6 +173,7 @@ deps = {py3.6,py3.7}-gevent: pytest<7.0.0 {py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest gevent: pytest-asyncio + {py3.10,py3.11}-gevent: zope.event<5.0.0 # === Integrations === diff --git a/tox.ini b/tox.ini index 5c993718d7..881fb44574 100644 --- a/tox.ini +++ b/tox.ini @@ -336,6 +336,7 @@ deps = {py3.6,py3.7}-gevent: pytest<7.0.0 {py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest gevent: pytest-asyncio + {py3.10,py3.11}-gevent: zope.event<5.0.0 # === Integrations ===