Skip to content

Commit b51d798

Browse files
committed
ref(flags): change LaunchDarkly and OpenFeature integrations to track a single client instance
1 parent bb85c26 commit b51d798

File tree

4 files changed

+51
-62
lines changed

4 files changed

+51
-62
lines changed

sentry_sdk/integrations/launchdarkly.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,37 @@
44
from sentry_sdk.integrations import DidNotEnable, Integration
55
from sentry_sdk.flag_utils import flag_error_processor
66

7+
if TYPE_CHECKING:
8+
from typing import Any, Optional
9+
710
try:
8-
import ldclient
911
from ldclient.hook import Hook, Metadata
1012

1113
if TYPE_CHECKING:
1214
from ldclient import LDClient
1315
from ldclient.hook import EvaluationSeriesContext
1416
from ldclient.evaluation import EvaluationDetail
15-
16-
from typing import Any
1717
except ImportError:
1818
raise DidNotEnable("LaunchDarkly is not installed")
1919

2020

2121
class LaunchDarklyIntegration(Integration):
2222
identifier = "launchdarkly"
23-
_ld_client = None # type: LDClient | None
23+
_client = None # type: Optional[LDClient]
2424

25-
def __init__(self, ld_client=None):
26-
# type: (LDClient | None) -> None
25+
def __init__(self, client):
26+
# type: (LDClient) -> None
2727
"""
28-
:param client: An initialized LDClient instance. If a client is not provided, this
29-
integration will attempt to use the shared global instance.
28+
:param client: An initialized LDClient instance.
3029
"""
31-
self.__class__._ld_client = ld_client
30+
self.__class__._client = client
3231

3332
@staticmethod
3433
def setup_once():
3534
# type: () -> None
36-
try:
37-
client = LaunchDarklyIntegration._ld_client or ldclient.get()
38-
except Exception as exc:
39-
raise DidNotEnable("Error getting LaunchDarkly client. " + repr(exc))
35+
client = LaunchDarklyIntegration._client
36+
if not client:
37+
raise DidNotEnable("Error getting LDClient instance")
4038

4139
# Register the flag collection hook with the LD client.
4240
client.add_hook(LaunchDarklyHook())

sentry_sdk/integrations/openfeature.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,42 @@
44
from sentry_sdk.integrations import DidNotEnable, Integration
55
from sentry_sdk.flag_utils import flag_error_processor
66

7+
if TYPE_CHECKING:
8+
from typing import Optional
9+
710
try:
8-
from openfeature import api
911
from openfeature.hook import Hook
1012

1113
if TYPE_CHECKING:
1214
from openfeature.flag_evaluation import FlagEvaluationDetails
1315
from openfeature.hook import HookContext, HookHints
16+
from openfeature.client import OpenFeatureClient
1417
except ImportError:
1518
raise DidNotEnable("OpenFeature is not installed")
1619

1720

1821
class OpenFeatureIntegration(Integration):
1922
identifier = "openfeature"
23+
_client = None # type: Optional[OpenFeatureClient]
24+
25+
def __init__(self, client):
26+
# type: (OpenFeatureClient) -> None
27+
self.__class__._client = client
2028

2129
@staticmethod
2230
def setup_once():
2331
# type: () -> None
32+
33+
client = OpenFeatureIntegration._client
34+
if not client:
35+
raise DidNotEnable("Error getting OpenFeatureClient instance")
36+
37+
# Register the hook within the openfeature client.
38+
client.add_hooks(hooks=[OpenFeatureHook()])
39+
2440
scope = sentry_sdk.get_current_scope()
2541
scope.add_error_processor(flag_error_processor)
2642

27-
# Register the hook within the global openfeature hooks list.
28-
api.add_hooks(hooks=[OpenFeatureHook()])
29-
3043

3144
class OpenFeatureHook(Hook):
3245

tests/integrations/launchdarkly/test_launchdarkly.py

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from ldclient.integrations.test_data import TestData
1111

1212
import sentry_sdk
13-
from sentry_sdk.integrations import DidNotEnable
1413
from sentry_sdk.integrations.launchdarkly import LaunchDarklyIntegration
1514

1615

@@ -27,11 +26,10 @@ def test_launchdarkly_integration(
2726
uninstall_integration(LaunchDarklyIntegration.identifier)
2827
if use_global_client:
2928
ldclient.set_config(config)
30-
sentry_init(integrations=[LaunchDarklyIntegration()])
3129
client = ldclient.get()
3230
else:
3331
client = LDClient(config=config)
34-
sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)])
32+
sentry_init(integrations=[LaunchDarklyIntegration(client)])
3533

3634
# Set test values
3735
td.update(td.flag("hello").variation_for_all(True))
@@ -63,7 +61,7 @@ def test_launchdarkly_integration_threaded(
6361
context = Context.create("user1")
6462

6563
uninstall_integration(LaunchDarklyIntegration.identifier)
66-
sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)])
64+
sentry_init(integrations=[LaunchDarklyIntegration(client)])
6765
events = capture_events()
6866

6967
def task(flag_key):
@@ -122,7 +120,7 @@ def test_launchdarkly_integration_asyncio(
122120
context = Context.create("user1")
123121

124122
uninstall_integration(LaunchDarklyIntegration.identifier)
125-
sentry_init(integrations=[LaunchDarklyIntegration(ld_client=client)])
123+
sentry_init(integrations=[LaunchDarklyIntegration(client)])
126124
events = capture_events()
127125

128126
async def task(flag_key):
@@ -166,23 +164,3 @@ async def runner():
166164
{"flag": "world", "result": False},
167165
]
168166
}
169-
170-
171-
def test_launchdarkly_integration_did_not_enable(sentry_init, uninstall_integration):
172-
"""
173-
Setup should fail when using global client and ldclient.set_config wasn't called.
174-
175-
We're accessing ldclient internals to set up this test, so it might break if launchdarkly's
176-
implementation changes.
177-
"""
178-
179-
ldclient._reset_client()
180-
try:
181-
ldclient.__lock.lock()
182-
ldclient.__config = None
183-
finally:
184-
ldclient.__lock.unlock()
185-
186-
uninstall_integration(LaunchDarklyIntegration.identifier)
187-
with pytest.raises(DidNotEnable):
188-
sentry_init(integrations=[LaunchDarklyIntegration()])

tests/integrations/openfeature/test_openfeature.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111

1212

1313
def test_openfeature_integration(sentry_init, capture_events, uninstall_integration):
14-
uninstall_integration(OpenFeatureIntegration.identifier)
15-
sentry_init(integrations=[OpenFeatureIntegration()])
16-
1714
flags = {
1815
"hello": InMemoryFlag("on", {"on": True, "off": False}),
1916
"world": InMemoryFlag("off", {"on": True, "off": False}),
2017
}
2118
api.set_provider(InMemoryProvider(flags))
22-
2319
client = api.get_client()
20+
21+
uninstall_integration(OpenFeatureIntegration.identifier)
22+
sentry_init(integrations=[OpenFeatureIntegration(client)])
23+
2424
client.get_boolean_value("hello", default_value=False)
2525
client.get_boolean_value("world", default_value=False)
2626
client.get_boolean_value("other", default_value=True)
@@ -41,18 +41,18 @@ def test_openfeature_integration(sentry_init, capture_events, uninstall_integrat
4141
def test_openfeature_integration_threaded(
4242
sentry_init, capture_events, uninstall_integration
4343
):
44-
uninstall_integration(OpenFeatureIntegration.identifier)
45-
sentry_init(integrations=[OpenFeatureIntegration()])
46-
events = capture_events()
47-
4844
flags = {
4945
"hello": InMemoryFlag("on", {"on": True, "off": False}),
5046
"world": InMemoryFlag("off", {"on": True, "off": False}),
5147
}
5248
api.set_provider(InMemoryProvider(flags))
49+
client = api.get_client()
50+
51+
uninstall_integration(OpenFeatureIntegration.identifier)
52+
sentry_init(integrations=[OpenFeatureIntegration(client)])
53+
events = capture_events()
5354

5455
# Capture an eval before we split isolation scopes.
55-
client = api.get_client()
5656
client.get_boolean_value("hello", default_value=False)
5757

5858
def task(flag):
@@ -101,10 +101,20 @@ def test_openfeature_integration_asyncio(
101101

102102
asyncio = pytest.importorskip("asyncio")
103103

104+
flags = {
105+
"hello": InMemoryFlag("on", {"on": True, "off": False}),
106+
"world": InMemoryFlag("off", {"on": True, "off": False}),
107+
}
108+
api.set_provider(InMemoryProvider(flags))
109+
client = api.get_client()
110+
104111
uninstall_integration(OpenFeatureIntegration.identifier)
105-
sentry_init(integrations=[OpenFeatureIntegration()])
112+
sentry_init(integrations=[OpenFeatureIntegration(client)])
106113
events = capture_events()
107114

115+
# Capture an eval before we split isolation scopes.
116+
client.get_boolean_value("hello", default_value=False)
117+
108118
async def task(flag):
109119
with sentry_sdk.isolation_scope():
110120
client.get_boolean_value(flag, default_value=False)
@@ -115,16 +125,6 @@ async def task(flag):
115125
async def runner():
116126
return asyncio.gather(task("world"), task("other"))
117127

118-
flags = {
119-
"hello": InMemoryFlag("on", {"on": True, "off": False}),
120-
"world": InMemoryFlag("off", {"on": True, "off": False}),
121-
}
122-
api.set_provider(InMemoryProvider(flags))
123-
124-
# Capture an eval before we split isolation scopes.
125-
client = api.get_client()
126-
client.get_boolean_value("hello", default_value=False)
127-
128128
asyncio.run(runner())
129129

130130
# Capture error in original scope

0 commit comments

Comments
 (0)