From a5c12d8a5286d2c2877e4c61c87102252438d0bb Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Wed, 5 Aug 2026 10:56:19 +0200 Subject: [PATCH] ref(launchdarkly): Remove __init__ from integration --- MIGRATION_GUIDE.md | 1 + sentry_sdk/integrations/launchdarkly.py | 18 +-- .../launchdarkly/test_launchdarkly.py | 105 ++++++------------ 3 files changed, 42 insertions(+), 82 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 4b7c32fce2..cc26d6abaa 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -33,6 +33,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - Dropped support for Falcon versions below 3.0. - Dropped support for Flask below 2.0. - Dropped support for aiohttp below 3.7. +- Removed the possibility to supply a specific client to the LaunchDarklyIntegration. - The `enable_tracing` option was removed. Use `traces_sample_rate=1.0` instead. - The deprecated `push_scope` and `configure_scope` APIs have been removed. Use `with new_scope():` to push a new scope and `scope = get_current_scope()` to retrieve the current scope instead. - Transaction profiling and related code was removed. diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index b053462491..a16d2bf6f8 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -12,7 +12,6 @@ if TYPE_CHECKING: from typing import Any - from ldclient import LDClient from ldclient.evaluation import EvaluationDetail from ldclient.hook import EvaluationSeriesContext except ImportError: @@ -22,13 +21,13 @@ class LaunchDarklyIntegration(Integration): identifier = "launchdarkly" - def __init__(self, ld_client: "LDClient | None" = None) -> None: - """ - :param client: An initialized LDClient instance. If a client is not provided, this - integration will attempt to use the shared global instance. - """ + @staticmethod + def setup_once() -> None: + version = parse_version(LDCLIENT_VERSION) + _check_minimum_version(LaunchDarklyIntegration, version) + try: - client = ld_client or ldclient.get() + client = ldclient.get() except Exception as exc: raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) @@ -38,11 +37,6 @@ def __init__(self, ld_client: "LDClient | None" = None) -> None: # Register the flag collection hook with the LD client. client.add_hook(LaunchDarklyHook()) - @staticmethod - def setup_once() -> None: - version = parse_version(LDCLIENT_VERSION) - _check_minimum_version(LaunchDarklyIntegration, version) - class LaunchDarklyHook(Hook): @property diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index 41a8a718bd..6f7c11b6de 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -2,7 +2,6 @@ import ldclient import pytest -from ldclient import LDClient from ldclient.config import Config from ldclient.context import Context from ldclient.integrations.test_data import TestData @@ -14,29 +13,18 @@ from tests.conftest import ApproxDict -@pytest.mark.parametrize( - "use_global_client", - (False, True), -) -def test_launchdarkly_integration( - sentry_init, use_global_client, capture_events, uninstall_integration -): +def test_launchdarkly_integration(sentry_init, capture_events, uninstall_integration): td = TestData.data_source() td.update(td.flag("hello").variation_for_all(True)) td.update(td.flag("world").variation_for_all(True)) - # Disable background requests as we aren't using a server. config = Config( "sdk-key", update_processor_class=td, diagnostic_opt_out=True, send_events=False ) uninstall_integration(LaunchDarklyIntegration.identifier) - if use_global_client: - ldclient.set_config(config) - sentry_init(integrations=[LaunchDarklyIntegration()]) - client = ldclient.get() - else: - client = LDClient(config=config) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + ldclient.set_config(config) + sentry_init(integrations=[LaunchDarklyIntegration()]) + client = ldclient.get() # Evaluate client.variation("hello", Context.create("my-org", "organization"), False) @@ -62,18 +50,18 @@ def test_launchdarkly_integration_threaded( td = TestData.data_source() td.update(td.flag("hello").variation_for_all(True)) td.update(td.flag("world").variation_for_all(True)) - client = LDClient( - config=Config( - "sdk-key", - update_processor_class=td, - diagnostic_opt_out=True, # Disable background requests as we aren't using a server. - send_events=False, - ) + config = Config( + "sdk-key", + update_processor_class=td, + diagnostic_opt_out=True, # Disable background requests as we aren't using a server. + send_events=False, ) + ldclient.set_config(config) + client = ldclient.get() context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration()]) events = capture_events() def task(flag_key): @@ -122,23 +110,24 @@ def test_launchdarkly_integration_asyncio( ): """Assert concurrently evaluated flags do not pollute one another.""" + uninstall_integration(LaunchDarklyIntegration.identifier) + asyncio = pytest.importorskip("asyncio") td = TestData.data_source() td.update(td.flag("hello").variation_for_all(True)) td.update(td.flag("world").variation_for_all(True)) - client = LDClient( - config=Config( - "sdk-key", - update_processor_class=td, - diagnostic_opt_out=True, # Disable background requests as we aren't using a server. - send_events=False, - ) + config = Config( + "sdk-key", + update_processor_class=td, + diagnostic_opt_out=True, # Disable background requests as we aren't using a server. + send_events=False, ) + ldclient.set_config(config) + client = ldclient.get() context = Context.create("user1") - uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration()]) events = capture_events() async def task(flag_key): @@ -182,10 +171,8 @@ async def runner(): } -def test_launchdarkly_integration_did_not_enable(monkeypatch): - # Client is not passed in and set_config wasn't called. - # TODO: Bad practice to access internals like this. We can skip this test, or remove this - # case entirely (force user to pass in a client instance). +def test_launchdarkly_integration_did_not_enable(uninstall_integration): + # set_config wasn't called. ldclient._reset_client() try: ldclient.__lock.lock() @@ -193,33 +180,20 @@ def test_launchdarkly_integration_did_not_enable(monkeypatch): finally: ldclient.__lock.unlock() - with pytest.raises(DidNotEnable): - LaunchDarklyIntegration() + uninstall_integration(LaunchDarklyIntegration.identifier) - td = TestData.data_source() - # Disable background requests as we aren't using a server. - # Required because we corrupt the internal state above. - config = Config( - "sdk-key", update_processor_class=td, diagnostic_opt_out=True, send_events=False - ) - # Client not initialized. - client = LDClient(config=config) - monkeypatch.setattr(client, "is_initialized", lambda: False) with pytest.raises(DidNotEnable): - LaunchDarklyIntegration(ld_client=client) + sentry_sdk.init( + integrations=[LaunchDarklyIntegration()], + ) -@pytest.mark.parametrize( - "use_global_client", - (False, True), -) @pytest.mark.parametrize( "span_streaming", [True, False], ) def test_launchdarkly_span_integration( sentry_init, - use_global_client, capture_events, capture_items, uninstall_integration, @@ -227,27 +201,18 @@ def test_launchdarkly_span_integration( ): td = TestData.data_source() td.update(td.flag("hello").variation_for_all(True)) - # Disable background requests as we aren't using a server. config = Config( "sdk-key", update_processor_class=td, diagnostic_opt_out=True, send_events=False ) uninstall_integration(LaunchDarklyIntegration.identifier) - if use_global_client: - ldclient.set_config(config) - sentry_init( - traces_sample_rate=1.0, - integrations=[LaunchDarklyIntegration()], - trace_lifecycle="stream" if span_streaming else "static", - ) - client = ldclient.get() - else: - client = LDClient(config=config) - sentry_init( - traces_sample_rate=1.0, - integrations=[LaunchDarklyIntegration(ld_client=client)], - trace_lifecycle="stream" if span_streaming else "static", - ) + ldclient.set_config(config) + sentry_init( + traces_sample_rate=1.0, + integrations=[LaunchDarklyIntegration()], + trace_lifecycle="stream" if span_streaming else "static", + ) + client = ldclient.get() if span_streaming: items = capture_items("span")