Skip to content

Commit a1da4db

Browse files
committed
feat: add support for domains
Signed-off-by: Federico Bond <[email protected]>
1 parent 8329e89 commit a1da4db

File tree

5 files changed

+103
-27
lines changed

5 files changed

+103
-27
lines changed

Diff for: openfeature/api.py

+30-9
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,52 @@
1414

1515
_hooks: typing.List[Hook] = []
1616

17+
_providers: typing.Dict[str, FeatureProvider] = {}
18+
1719

1820
def get_client(
19-
name: typing.Optional[str] = None, version: typing.Optional[str] = None
21+
domain: typing.Optional[str] = None, version: typing.Optional[str] = None
2022
) -> OpenFeatureClient:
21-
return OpenFeatureClient(name=name, version=version, provider=_provider)
23+
return OpenFeatureClient(domain=domain, version=version)
2224

2325

24-
def set_provider(provider: FeatureProvider) -> None:
25-
global _provider
26+
def set_provider(
27+
provider: FeatureProvider, domain: typing.Optional[str] = None
28+
) -> None:
2629
if provider is None:
2730
raise GeneralError(error_message="No provider")
31+
32+
if domain:
33+
_set_domain_provider(domain, provider)
34+
return
35+
36+
global _provider
2837
if _provider:
2938
_provider.shutdown()
3039
_provider = provider
3140
provider.initialize(_evaluation_context)
3241

3342

34-
def get_provider() -> FeatureProvider:
35-
global _provider
36-
return _provider
43+
def _set_domain_provider(domain: str, provider: FeatureProvider) -> None:
44+
if domain in _providers:
45+
old_provider = _providers[domain]
46+
del _providers[domain]
47+
if old_provider not in _providers.values():
48+
old_provider.shutdown()
49+
if provider not in _providers.values():
50+
provider.initialize(_evaluation_context)
51+
_providers[domain] = provider
3752

3853

39-
def get_provider_metadata() -> Metadata:
54+
def get_provider(domain: typing.Optional[str] = None) -> FeatureProvider:
4055
global _provider
41-
return _provider.get_metadata()
56+
if domain is None:
57+
return _provider
58+
return _providers.get(domain, _provider)
59+
60+
61+
def get_provider_metadata(domain: typing.Optional[str] = None) -> Metadata:
62+
return get_provider(domain).get_metadata()
4263

4364

4465
def get_evaluation_context() -> EvaluationContext:

Diff for: openfeature/client.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -60,26 +60,29 @@
6060

6161
@dataclass
6262
class ClientMetadata:
63-
name: typing.Optional[str]
63+
name: typing.Optional[str] = None
64+
domain: typing.Optional[str] = None
6465

6566

6667
class OpenFeatureClient:
6768
def __init__(
6869
self,
69-
name: typing.Optional[str],
70+
domain: typing.Optional[str],
7071
version: typing.Optional[str],
71-
provider: FeatureProvider,
7272
context: typing.Optional[EvaluationContext] = None,
7373
hooks: typing.Optional[typing.List[Hook]] = None,
7474
) -> None:
75-
self.name = name
75+
self.domain = domain
7676
self.version = version
7777
self.context = context or EvaluationContext()
7878
self.hooks = hooks or []
79-
self.provider = provider
79+
80+
@property
81+
def provider(self) -> FeatureProvider:
82+
return api.get_provider(domain=self.domain)
8083

8184
def get_metadata(self) -> ClientMetadata:
82-
return ClientMetadata(name=self.name)
85+
return ClientMetadata(domain=self.domain)
8386

8487
def add_hooks(self, hooks: typing.List[Hook]) -> None:
8588
self.hooks = self.hooks + hooks

Diff for: tests/features/steps/steps.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ def step_impl(context, flag_type, key, expected_reason):
2727
@given("a provider is registered with cache disabled")
2828
def step_impl(context):
2929
set_provider(InMemoryProvider(IN_MEMORY_FLAGS))
30-
context.client = get_client(name="Default Provider", version="1.0")
30+
context.client = get_client()
3131

3232

3333
@when(
3434
'a {flag_type} flag with key "{key}" is evaluated with details and default value '
3535
'"{default_value}"'
3636
)
3737
def step_impl(context, flag_type, key, default_value):
38-
context.client = get_client(name="Default Provider", version="1.0")
38+
context.client = get_client()
3939
if flag_type == "boolean":
4040
context.boolean_flag_details = context.client.get_boolean_details(
4141
key, default_value

Diff for: tests/test_api.py

+56-6
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ def test_should_not_raise_exception_with_noop_client():
2626
# Given
2727
# No provider has been set
2828
# When
29-
client = get_client(name="Default Provider", version="1.0")
29+
client = get_client()
3030

3131
# Then
32-
assert client.name == "Default Provider"
33-
assert client.version == "1.0"
3432
assert isinstance(client.provider, NoOpProvider)
3533

3634

@@ -39,11 +37,9 @@ def test_should_return_open_feature_client_when_configured_correctly():
3937
set_provider(NoOpProvider())
4038

4139
# When
42-
client = get_client(name="No-op Provider", version="1.0")
40+
client = get_client()
4341

4442
# Then
45-
assert client.name == "No-op Provider"
46-
assert client.version == "1.0"
4743
assert isinstance(client.provider, NoOpProvider)
4844

4945

@@ -156,3 +152,57 @@ def test_should_call_provider_shutdown_on_api_shutdown():
156152

157153
# Then
158154
assert provider.shutdown.called
155+
156+
157+
def test_should_provide_a_function_to_bind_provider_through_domain():
158+
# Given
159+
provider = MagicMock(spec=FeatureProvider)
160+
test_client = get_client("test")
161+
default_client = get_client()
162+
163+
# When
164+
set_provider(provider, domain="test")
165+
166+
# Then
167+
assert default_client.provider != provider
168+
assert default_client.domain is None
169+
170+
assert test_client.provider == provider
171+
assert test_client.domain == "test"
172+
173+
174+
def test_should_not_initialize_provider_already_bound_to_another_domain():
175+
# Given
176+
provider = MagicMock(spec=FeatureProvider)
177+
set_provider(provider, "foo")
178+
179+
# When
180+
set_provider(provider, "bar")
181+
182+
# Then
183+
provider.initialize.assert_called_once()
184+
185+
186+
def test_should_shutdown_unbound_provider():
187+
# Given
188+
provider = MagicMock(spec=FeatureProvider)
189+
set_provider(provider, "foo")
190+
191+
# When
192+
other_provider = MagicMock(spec=FeatureProvider)
193+
set_provider(other_provider, "foo")
194+
195+
provider.shutdown.assert_called_once()
196+
197+
198+
def test_should_not_shutdown_provider_bound_to_another_domain():
199+
# Given
200+
provider = MagicMock(spec=FeatureProvider)
201+
set_provider(provider, "foo")
202+
set_provider(provider, "bar")
203+
204+
# When
205+
other_provider = MagicMock(spec=FeatureProvider)
206+
set_provider(other_provider, "foo")
207+
208+
provider.shutdown.assert_not_called()

Diff for: tests/test_client.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from openfeature.api import add_hooks, clear_hooks
5+
from openfeature.api import add_hooks, clear_hooks, set_provider
66
from openfeature.client import OpenFeatureClient
77
from openfeature.exception import ErrorCode, OpenFeatureError
88
from openfeature.flag_evaluation import Reason
@@ -109,7 +109,9 @@ def test_should_pass_flag_metadata_from_resolution_to_evaluation_details():
109109
)
110110
}
111111
)
112-
client = OpenFeatureClient("my-client", None, provider)
112+
set_provider(provider, "my-client")
113+
114+
client = OpenFeatureClient("my-client", None)
113115

114116
# When
115117
details = client.get_boolean_details(flag_key="Key", default_value=False)
@@ -158,14 +160,14 @@ def test_should_handle_an_open_feature_exception_thrown_by_a_provider(
158160
assert flag_details.error_message == "error_message"
159161

160162

161-
def test_should_return_client_metadata_with_name():
163+
def test_should_return_client_metadata_with_domain():
162164
# Given
163165
client = OpenFeatureClient("my-client", None, NoOpProvider())
164166
# When
165167
metadata = client.get_metadata()
166168
# Then
167169
assert metadata is not None
168-
assert metadata.name == "my-client"
170+
assert metadata.domain == "my-client"
169171

170172

171173
def test_should_call_api_level_hooks(no_op_provider_client):

0 commit comments

Comments
 (0)