diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index 066464cc22..6b9d654cd1 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -4,39 +4,37 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor +if TYPE_CHECKING: + from typing import Any, Optional + try: - import ldclient from ldclient.hook import Hook, Metadata if TYPE_CHECKING: from ldclient import LDClient from ldclient.hook import EvaluationSeriesContext from ldclient.evaluation import EvaluationDetail - - from typing import Any except ImportError: raise DidNotEnable("LaunchDarkly is not installed") class LaunchDarklyIntegration(Integration): identifier = "launchdarkly" - _ld_client = None # type: LDClient | None + _client = None # type: Optional[LDClient] - def __init__(self, ld_client=None): - # type: (LDClient | None) -> None + def __init__(self, client): + # type: (LDClient) -> None """ - :param client: An initialized LDClient instance. If a client is not provided, this - integration will attempt to use the shared global instance. + :param client: An initialized LDClient instance. """ - self.__class__._ld_client = ld_client + self.__class__._client = client @staticmethod def setup_once(): # type: () -> None - try: - client = LaunchDarklyIntegration._ld_client or ldclient.get() - except Exception as exc: - raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc)) + client = LaunchDarklyIntegration._client + if not client: + raise DidNotEnable("Error getting LDClient instance") # Register the flag collection hook with the LD client. client.add_hook(LaunchDarklyHook()) diff --git a/sentry_sdk/integrations/openfeature.py b/sentry_sdk/integrations/openfeature.py index 18f968a703..02ebb3673c 100644 --- a/sentry_sdk/integrations/openfeature.py +++ b/sentry_sdk/integrations/openfeature.py @@ -4,29 +4,42 @@ from sentry_sdk.integrations import DidNotEnable, Integration from sentry_sdk.flag_utils import flag_error_processor +if TYPE_CHECKING: + from typing import Optional + try: - from openfeature import api from openfeature.hook import Hook if TYPE_CHECKING: from openfeature.flag_evaluation import FlagEvaluationDetails from openfeature.hook import HookContext, HookHints + from openfeature.client import OpenFeatureClient except ImportError: raise DidNotEnable("OpenFeature is not installed") class OpenFeatureIntegration(Integration): identifier = "openfeature" + _client = None # type: Optional[OpenFeatureClient] + + def __init__(self, client): + # type: (OpenFeatureClient) -> None + self.__class__._client = client @staticmethod def setup_once(): # type: () -> None + + client = OpenFeatureIntegration._client + if not client: + raise DidNotEnable("Error getting OpenFeatureClient instance") + + # Register the hook within the openfeature client. + client.add_hooks(hooks=[OpenFeatureHook()]) + scope = sentry_sdk.get_current_scope() scope.add_error_processor(flag_error_processor) - # Register the hook within the global openfeature hooks list. - api.add_hooks(hooks=[OpenFeatureHook()]) - class OpenFeatureHook(Hook): diff --git a/tests/integrations/launchdarkly/test_launchdarkly.py b/tests/integrations/launchdarkly/test_launchdarkly.py index e7576bb469..73f4f71886 100644 --- a/tests/integrations/launchdarkly/test_launchdarkly.py +++ b/tests/integrations/launchdarkly/test_launchdarkly.py @@ -10,7 +10,6 @@ from ldclient.integrations.test_data import TestData import sentry_sdk -from sentry_sdk.integrations import DidNotEnable from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration @@ -27,11 +26,10 @@ def test_launchdarkly_integration( 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)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) # Set test values td.update(td.flag("hello").variation_for_all(True)) @@ -63,7 +61,7 @@ def test_launchdarkly_integration_threaded( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) events = capture_events() def task(flag_key): @@ -122,7 +120,7 @@ def test_launchdarkly_integration_asyncio( context = Context.create("user1") uninstall_integration(LaunchDarklyIntegration.identifier) - sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)]) + sentry_init(integrations=[LaunchDarklyIntegration(client)]) events = capture_events() async def task(flag_key): @@ -166,23 +164,3 @@ async def runner(): {"flag": "world", "result": False}, ] } - - -def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration): - """ - Setup should fail when using global client and ldclient.set_config wasn't called. - - We're accessing ldclient internals to set up this test, so it might break if launchdarkly's - implementation changes. - """ - - ldclient._reset_client() - try: - ldclient.__lock.lock() - ldclient.__config = None - finally: - ldclient.__lock.unlock() - - uninstall_integration(LaunchDarklyIntegration.identifier) - with pytest.raises(DidNotEnable): - sentry_init(integrations=[LaunchDarklyIntegration()]) diff --git a/tests/integrations/openfeature/test_openfeature.py b/tests/integrations/openfeature/test_openfeature.py index c180211c3f..cd2a0d47b0 100644 --- a/tests/integrations/openfeature/test_openfeature.py +++ b/tests/integrations/openfeature/test_openfeature.py @@ -11,16 +11,16 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integration): - uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) - flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), "world": InMemoryFlag("off", {"on": True, "off": False}), } api.set_provider(InMemoryProvider(flags)) - client = api.get_client() + + uninstall_integration(OpenFeatureIntegration.identifier) + sentry_init(integrations=[OpenFeatureIntegration(client)]) + client.get_boolean_value("hello", default_value=False) client.get_boolean_value("world", default_value=False) client.get_boolean_value("other", default_value=True) @@ -41,18 +41,18 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integrat def test_openfeature_integration_threaded( sentry_init, capture_events, uninstall_integration ): - uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) - events = capture_events() - flags = { "hello": InMemoryFlag("on", {"on": True, "off": False}), "world": InMemoryFlag("off", {"on": True, "off": False}), } api.set_provider(InMemoryProvider(flags)) + client = api.get_client() + + uninstall_integration(OpenFeatureIntegration.identifier) + sentry_init(integrations=[OpenFeatureIntegration(client)]) + events = capture_events() # Capture an eval before we split isolation scopes. - client = api.get_client() client.get_boolean_value("hello", default_value=False) def task(flag): @@ -101,10 +101,20 @@ def test_openfeature_integration_asyncio( asyncio = pytest.importorskip("asyncio") + flags = { + "hello": InMemoryFlag("on", {"on": True, "off": False}), + "world": InMemoryFlag("off", {"on": True, "off": False}), + } + api.set_provider(InMemoryProvider(flags)) + client = api.get_client() + uninstall_integration(OpenFeatureIntegration.identifier) - sentry_init(integrations=[OpenFeatureIntegration()]) + sentry_init(integrations=[OpenFeatureIntegration(client)]) events = capture_events() + # Capture an eval before we split isolation scopes. + client.get_boolean_value("hello", default_value=False) + async def task(flag): with sentry_sdk.isolation_scope(): client.get_boolean_value(flag, default_value=False) @@ -115,16 +125,6 @@ async def task(flag): async def runner(): return asyncio.gather(task("world"), task("other")) - flags = { - "hello": InMemoryFlag("on", {"on": True, "off": False}), - "world": InMemoryFlag("off", {"on": True, "off": False}), - } - api.set_provider(InMemoryProvider(flags)) - - # Capture an eval before we split isolation scopes. - client = api.get_client() - client.get_boolean_value("hello", default_value=False) - asyncio.run(runner()) # Capture error in original scope