From 0ea4a5fb72b65f77152a1577e06dfe54de57f542 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 08:56:53 +0100 Subject: [PATCH 01/16] feat(api): Add enable_integration --- sentry_sdk/__init__.py | 1 + sentry_sdk/api.py | 27 +++++++++++++++++++++++++++ sentry_sdk/integrations/__init__.py | 22 ++++++++++++++++++++++ tests/test_basics.py | 14 ++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index e149418c38..04df103bb8 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -24,6 +24,7 @@ "capture_message", "configure_scope", "continue_trace", + "enable_integration", "flush", "get_baggage", "get_client", diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index c4e2229938..d0aa06ea2a 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -4,6 +4,7 @@ from sentry_sdk import tracing_utils, Client from sentry_sdk._init_implementation import init +from sentry_sdk.integrations import Integration, _enable_integration from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope from sentry_sdk.tracing import NoOpSpan, Transaction, trace @@ -57,6 +58,7 @@ def overload(x: "T") -> "T": "capture_message", "configure_scope", "continue_trace", + "enable_integration", "flush", "get_baggage", "get_client", @@ -523,3 +525,28 @@ def update_current_span( if attributes is not None: current_span.update_data(attributes) + + +def enable_integration(integration: Integration) -> None: + """ + Enable an additional integration after sentry_sdk.init() has been called. + + This should be used sparingly, only in situations where, for whatever reason, + it's not feasible to enable an integration during init(). + + Most integrations rely on being enabled before any actual user code is + executed, and this function doesn't change that. enable_integration should + still be called as early as possible. + + One usecase for enable_integration is setting up the AsyncioIntegration only + once the event loop is running, but without having to set up the whole SDK + anew. + + :param integration: The integration instance or class to enable. + :type integration: sentry_sdk.integrations.Integration + """ + client = get_client() + integration = _enable_integration(integration) + if integration is None: + return + client.integrations[integration.identifier] = integration diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 5ab181df25..e99f74f630 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -2,6 +2,7 @@ from threading import Lock from typing import TYPE_CHECKING +import sentry_sdk from sentry_sdk.utils import logger if TYPE_CHECKING: @@ -279,6 +280,27 @@ def setup_integrations( return integrations +def _enable_integration(integration: "Integration") -> None: + if integration.identifier in _installed_integrations: + return + + client = sentry_sdk.get_client() + + with _installer_lock: + logger.debug( + "Setting up previously not enabled integration %s", integration.identifier + ) + _processed_integrations.add(integration.identifier) + try: + type(integration).setup_once() + integration.setup_once_with_options(client.options) + except DidNotEnable as e: + logger.debug("Did not enable integration %s: %s", integration.identifier, e) + else: + _installed_integrations.add(integration.identifier) + return integration + + def _check_minimum_version( integration: "type[Integration]", version: "Optional[tuple[int, ...]]", diff --git a/tests/test_basics.py b/tests/test_basics.py index da836462d8..59997782f9 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -985,6 +985,20 @@ def test_multiple_setup_integrations_calls(): assert second_call_return == {NoOpIntegration.identifier: NoOpIntegration()} +def test_enable_integration(sentry_init): + sentry_init() + + client = get_client() + assert "asyncio" not in client.integrations + + from sentry_sdk.integrations import AsyncioIntegration + + sentry_sdk.enable_integration(AsyncioIntegration()) + + client = get_client() + assert "asyncio" in client.integrations + + class TracingTestClass: @staticmethod def static(arg): From 5a18190576b93ef4c46b7092df6015bb89cfa721 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 11:35:46 +0100 Subject: [PATCH 02/16] fix --- tests/test_basics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index 59997782f9..9ae2b78d13 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -991,7 +991,7 @@ def test_enable_integration(sentry_init): client = get_client() assert "asyncio" not in client.integrations - from sentry_sdk.integrations import AsyncioIntegration + from sentry_sdk.integrations.asyncio import AsyncioIntegration sentry_sdk.enable_integration(AsyncioIntegration()) From 29a3b6b45962ec6798142966a7c7eae04fa89ebe Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 11:51:40 +0100 Subject: [PATCH 03/16] . --- tests/test_basics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index 9ae2b78d13..dc26e1f040 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -985,7 +985,7 @@ def test_multiple_setup_integrations_calls(): assert second_call_return == {NoOpIntegration.identifier: NoOpIntegration()} -def test_enable_integration(sentry_init): +def test_enable_integration(sentry_init, reset_integrations): sentry_init() client = get_client() From 15c5016487896974d73058dd8016d768736685bf Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 11:58:35 +0100 Subject: [PATCH 04/16] naja --- tests/test_basics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index dc26e1f040..e735c48047 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -985,7 +985,8 @@ def test_multiple_setup_integrations_calls(): assert second_call_return == {NoOpIntegration.identifier: NoOpIntegration()} -def test_enable_integration(sentry_init, reset_integrations): +@pytest.mark.forked +def test_enable_integration(sentry_init): sentry_init() client = get_client() From 49bda60db0c4ef03b54f09df97db50a4e3b51356 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:04:04 +0100 Subject: [PATCH 05/16] . --- tests/test_basics.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index e735c48047..dc26e1f040 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -985,8 +985,7 @@ def test_multiple_setup_integrations_calls(): assert second_call_return == {NoOpIntegration.identifier: NoOpIntegration()} -@pytest.mark.forked -def test_enable_integration(sentry_init): +def test_enable_integration(sentry_init, reset_integrations): sentry_init() client = get_client() From 3ae2c6a0bd59fa6307eb3ed14a684b3c72625304 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:26:03 +0100 Subject: [PATCH 06/16] . --- sentry_sdk/integrations/__init__.py | 4 ++-- tests/test_basics.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index e99f74f630..e9734ec3c4 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -280,9 +280,9 @@ def setup_integrations( return integrations -def _enable_integration(integration: "Integration") -> None: +def _enable_integration(integration: "Integration") -> "Optional[Integration]": if integration.identifier in _installed_integrations: - return + return integration client = sentry_sdk.get_client() diff --git a/tests/test_basics.py b/tests/test_basics.py index dc26e1f040..9ae2b78d13 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -985,7 +985,7 @@ def test_multiple_setup_integrations_calls(): assert second_call_return == {NoOpIntegration.identifier: NoOpIntegration()} -def test_enable_integration(sentry_init, reset_integrations): +def test_enable_integration(sentry_init): sentry_init() client = get_client() From 0110fc6ec9eaa4b5e5b7f8ed3a04c991d1c12799 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:39:31 +0100 Subject: [PATCH 07/16] explicit None --- sentry_sdk/integrations/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index e9734ec3c4..1733a06a27 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -296,6 +296,7 @@ def _enable_integration(integration: "Integration") -> "Optional[Integration]": integration.setup_once_with_options(client.options) except DidNotEnable as e: logger.debug("Did not enable integration %s: %s", integration.identifier, e) + return None else: _installed_integrations.add(integration.identifier) return integration From cdfcd78f76bb0a3e57de7a82cf68cfef1816ff52 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:41:07 +0100 Subject: [PATCH 08/16] . --- tests/test_basics.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index 9ae2b78d13..634a343387 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -989,14 +989,17 @@ def test_enable_integration(sentry_init): sentry_init() client = get_client() - assert "asyncio" not in client.integrations + assert "gnu_backtrace" not in client.integrations - from sentry_sdk.integrations.asyncio import AsyncioIntegration + # Using gnu backtrace here because to test this properly it should be a + # non-auto-enabling integration that's also unlikely to become auto-enabling + # in the future + from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration - sentry_sdk.enable_integration(AsyncioIntegration()) + sentry_sdk.enable_integration(GnuBacktraceIntegration()) client = get_client() - assert "asyncio" in client.integrations + assert "gnu_backtrace" in client.integrations class TracingTestClass: From 2afd472f2845e4b86f82b6518eb1c8a6c6552a68 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:41:35 +0100 Subject: [PATCH 09/16] . --- tests/test_basics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_basics.py b/tests/test_basics.py index 634a343387..f6d83a42c6 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -993,7 +993,7 @@ def test_enable_integration(sentry_init): # Using gnu backtrace here because to test this properly it should be a # non-auto-enabling integration that's also unlikely to become auto-enabling - # in the future + # in the future. AsyncioIntegration would be nicer but it doesn't work in 3.6 from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration sentry_sdk.enable_integration(GnuBacktraceIntegration()) From cd091d59e09853496ef417e8fa8161e9722b03a8 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 13:54:22 +0100 Subject: [PATCH 10/16] mypy --- sentry_sdk/api.py | 4 ++++ sentry_sdk/client.py | 1 + 2 files changed, 5 insertions(+) diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index d0aa06ea2a..fa1e8a1a83 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -546,7 +546,11 @@ def enable_integration(integration: Integration) -> None: :type integration: sentry_sdk.integrations.Integration """ client = get_client() + if not client.is_active(): + return + integration = _enable_integration(integration) if integration is None: return + client.integrations[integration.identifier] = integration diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 259196d1c6..e3821f48ca 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -188,6 +188,7 @@ def __init__(self, options: "Optional[Dict[str, Any]]" = None) -> None: self.monitor: "Optional[Monitor]" = None self.log_batcher: "Optional[LogBatcher]" = None self.metrics_batcher: "Optional[MetricsBatcher]" = None + self.integrations: "dict[str, Integration]" = {} def __getstate__(self, *args: "Any", **kwargs: "Any") -> "Any": return {"options": {}} From be603459ab0da06a469df09fba92e8ee6f55c9fa Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 14:04:55 +0100 Subject: [PATCH 11/16] bigger lock --- sentry_sdk/integrations/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 1733a06a27..7848ae65d1 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -281,12 +281,12 @@ def setup_integrations( def _enable_integration(integration: "Integration") -> "Optional[Integration]": - if integration.identifier in _installed_integrations: - return integration + with _installer_lock: + if integration.identifier in _installed_integrations: + return integration - client = sentry_sdk.get_client() + client = sentry_sdk.get_client() - with _installer_lock: logger.debug( "Setting up previously not enabled integration %s", integration.identifier ) From 44dd1e5b314d4ee0e1ba9cdb286fc804e4e9e481 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 14:07:11 +0100 Subject: [PATCH 12/16] . --- sentry_sdk/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index fa1e8a1a83..d3804ee598 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -542,7 +542,7 @@ def enable_integration(integration: Integration) -> None: once the event loop is running, but without having to set up the whole SDK anew. - :param integration: The integration instance or class to enable. + :param integration: The integration to enable. :type integration: sentry_sdk.integrations.Integration """ client = get_client() From 1ba998acc0c25404bd8b0c38a29f14f5102a3754 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 14:25:11 +0100 Subject: [PATCH 13/16] tests --- tests/test_basics.py | 83 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/test_basics.py b/tests/test_basics.py index f6d83a42c6..4b7924ba3e 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1002,6 +1002,89 @@ def test_enable_integration(sentry_init): assert "gnu_backtrace" in client.integrations +def test_enable_enabled_integration(sentry_init): + from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration + + sentry_init(integrations=[GnuBacktraceIntegration()]) + + assert "gnu_backtrace" in get_client().integrations + + # Second call should not raise or cause issues + sentry_sdk.enable_integration(GnuBacktraceIntegration()) + assert "gnu_backtrace" in get_client().integrations + + +def test_enable_integration_twice(sentry_init): + sentry_init() + + from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration + + assert "gnu_backtrace" not in get_client().integrations + + sentry_sdk.enable_integration(GnuBacktraceIntegration()) + assert "gnu_backtrace" in get_client().integrations + + # Second call should not raise or cause issues + sentry_sdk.enable_integration(GnuBacktraceIntegration()) + assert "gnu_backtrace" in get_client().integrations + + +def test_enable_integration_did_not_enable(sentry_init): + sentry_init() + + class FailingIntegration(Integration): + identifier = "failing_test_integration" + + @staticmethod + def setup_once(): + raise DidNotEnable("This integration cannot be enabled") + + sentry_sdk.enable_integration(FailingIntegration()) + + assert "failing_test_integration" not in get_client().integrations + + +def test_enable_integration_setup_once_called(sentry_init): + sentry_init() + + setup_called = [] + + class TrackingIntegration(Integration): + identifier = "tracking_test_integration" + + @staticmethod + def setup_once(): + setup_called.append(True) + + def setup_once_with_options(self, options): + setup_called.append(True) + + assert len(setup_called) == 0 + sentry_sdk.enable_integration(TrackingIntegration()) + assert len(setup_called) == 2 + assert "tracking_test_integration" in get_client().integrations + + +def test_enable_integration_setup_once_not_called_twice(sentry_init): + sentry_init() + + setup_count = [] + + class CountingIntegration(Integration): + identifier = "counting_test_integration" + + @staticmethod + def setup_once(): + setup_count.append(1) + + sentry_sdk.enable_integration(CountingIntegration()) + assert len(setup_count) == 1 + + # Enable again - setup_once should NOT be called + sentry_sdk.enable_integration(CountingIntegration()) + assert len(setup_count) == 1 + + class TracingTestClass: @staticmethod def static(arg): From b2358eb468aca99876610b31ec86aea747014250 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 14:49:05 +0100 Subject: [PATCH 14/16] fix re-enabling enabled integration --- sentry_sdk/integrations/__init__.py | 9 ++++++--- tests/test_basics.py | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index 7848ae65d1..c159df9a5c 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -282,11 +282,14 @@ def setup_integrations( def _enable_integration(integration: "Integration") -> "Optional[Integration]": with _installer_lock: - if integration.identifier in _installed_integrations: - return integration - client = sentry_sdk.get_client() + if ( + integration.identifier in _installed_integrations + and integration.identifier in client.integrations + ): + return client.integrations[integration.identifier] + logger.debug( "Setting up previously not enabled integration %s", integration.identifier ) diff --git a/tests/test_basics.py b/tests/test_basics.py index 4b7924ba3e..64f453be5f 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1005,13 +1005,15 @@ def test_enable_integration(sentry_init): def test_enable_enabled_integration(sentry_init): from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration - sentry_init(integrations=[GnuBacktraceIntegration()]) + integration = GnuBacktraceIntegration() + sentry_init(integrations=[integration]) assert "gnu_backtrace" in get_client().integrations # Second call should not raise or cause issues sentry_sdk.enable_integration(GnuBacktraceIntegration()) assert "gnu_backtrace" in get_client().integrations + assert get_client().integrations["gnu_backtrace"] == integration def test_enable_integration_twice(sentry_init): @@ -1021,12 +1023,14 @@ def test_enable_integration_twice(sentry_init): assert "gnu_backtrace" not in get_client().integrations - sentry_sdk.enable_integration(GnuBacktraceIntegration()) + integration = GnuBacktraceIntegration() + sentry_sdk.enable_integration(integration) assert "gnu_backtrace" in get_client().integrations # Second call should not raise or cause issues sentry_sdk.enable_integration(GnuBacktraceIntegration()) assert "gnu_backtrace" in get_client().integrations + assert get_client().integrations["gnu_backtrace"] == integration def test_enable_integration_did_not_enable(sentry_init): From 8fc34fbc5ef98ba278feca0e1fe8c78a940db3fd Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 14:52:41 +0100 Subject: [PATCH 15/16] docstring --- sentry_sdk/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index d3804ee598..34c053aa9e 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -542,6 +542,9 @@ def enable_integration(integration: Integration) -> None: once the event loop is running, but without having to set up the whole SDK anew. + Running this function with an integration that is already enabled will not + have any effect. + :param integration: The integration to enable. :type integration: sentry_sdk.integrations.Integration """ From 51045da158ab9f0f0cee90158508eeaa39ce399d Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 8 Jan 2026 15:12:20 +0100 Subject: [PATCH 16/16] disabled integration test --- tests/test_basics.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_basics.py b/tests/test_basics.py index 64f453be5f..73a85b4088 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1089,6 +1089,18 @@ def setup_once(): assert len(setup_count) == 1 +def test_enable_integration_after_disabled(sentry_init): + from sentry_sdk.integrations.gnu_backtrace import GnuBacktraceIntegration + + sentry_init(disabled_integrations=[GnuBacktraceIntegration]) + + assert "gnu_backtrace" not in get_client().integrations + + sentry_sdk.enable_integration(GnuBacktraceIntegration()) + + assert "gnu_backtrace" in get_client().integrations + + class TracingTestClass: @staticmethod def static(arg):