Skip to content

Commit d8b805c

Browse files
committed
feat(api): add OpenFeatureAPI class with encapsulated state
1 parent cf0d6eb commit d8b805c

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

openfeature/_api.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
from __future__ import annotations
2+
3+
import typing
4+
5+
from openfeature._event_support import EventSupport
6+
from openfeature.evaluation_context import EvaluationContext
7+
from openfeature.event import EventHandler, ProviderEvent
8+
from openfeature.exception import GeneralError
9+
from openfeature.hook import Hook
10+
from openfeature.provider import FeatureProvider
11+
from openfeature.provider._registry import ProviderRegistry
12+
from openfeature.provider.metadata import Metadata
13+
from openfeature.transaction_context import (
14+
NoOpTransactionContextPropagator,
15+
TransactionContextPropagator,
16+
)
17+
18+
if typing.TYPE_CHECKING:
19+
from openfeature.client import OpenFeatureClient
20+
21+
22+
class OpenFeatureAPI:
23+
"""An independent OpenFeature API instance with its own isolated state.
24+
25+
Each instance maintains its own providers, evaluation context, hooks,
26+
event handlers, and transaction context propagator — fully separate from
27+
the global singleton and from other instances.
28+
"""
29+
30+
def __init__(self) -> None:
31+
self._hooks: list[Hook] = []
32+
self._evaluation_context = EvaluationContext()
33+
self._transaction_context_propagator: TransactionContextPropagator = (
34+
NoOpTransactionContextPropagator()
35+
)
36+
self._event_support = EventSupport()
37+
self._provider_registry = ProviderRegistry(
38+
event_support=self._event_support,
39+
evaluation_context_getter=self.get_evaluation_context,
40+
)
41+
42+
# --- Client creation ---
43+
44+
def get_client(
45+
self, domain: str | None = None, version: str | None = None
46+
) -> OpenFeatureClient:
47+
from openfeature.client import OpenFeatureClient # noqa: PLC0415
48+
49+
return OpenFeatureClient(domain=domain, version=version, api=self)
50+
51+
# --- Provider management ---
52+
53+
def set_provider(
54+
self, provider: FeatureProvider, domain: str | None = None
55+
) -> None:
56+
if domain is None:
57+
self._provider_registry.set_default_provider(provider)
58+
else:
59+
self._provider_registry.set_provider(domain, provider)
60+
61+
def get_provider_metadata(self, domain: str | None = None) -> Metadata:
62+
return self._provider_registry.get_provider(domain).get_metadata()
63+
64+
def clear_providers(self) -> None:
65+
self._provider_registry.clear_providers()
66+
self._event_support.clear()
67+
68+
def shutdown(self) -> None:
69+
self._provider_registry.shutdown()
70+
71+
# --- Hooks ---
72+
73+
def add_hooks(self, hooks: list[Hook]) -> None:
74+
self._hooks = self._hooks + hooks
75+
76+
def clear_hooks(self) -> None:
77+
self._hooks = []
78+
79+
def get_hooks(self) -> list[Hook]:
80+
return self._hooks
81+
82+
# --- Evaluation context ---
83+
84+
def get_evaluation_context(self) -> EvaluationContext:
85+
return self._evaluation_context
86+
87+
def set_evaluation_context(self, evaluation_context: EvaluationContext) -> None:
88+
if evaluation_context is None:
89+
raise GeneralError(error_message="No api level evaluation context")
90+
self._evaluation_context = evaluation_context
91+
92+
# --- Transaction context ---
93+
94+
def set_transaction_context_propagator(
95+
self, transaction_context_propagator: TransactionContextPropagator
96+
) -> None:
97+
self._transaction_context_propagator = transaction_context_propagator
98+
99+
def get_transaction_context(self) -> EvaluationContext:
100+
return self._transaction_context_propagator.get_transaction_context()
101+
102+
def set_transaction_context(
103+
self, evaluation_context: EvaluationContext
104+
) -> None:
105+
self._transaction_context_propagator.set_transaction_context(
106+
evaluation_context
107+
)
108+
109+
# --- Event handlers ---
110+
111+
def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
112+
self._event_support.add_global_handler(event, handler, self.get_client)
113+
114+
def remove_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
115+
self._event_support.remove_global_handler(event, handler)
116+
117+
118+
def _create_default_api() -> OpenFeatureAPI:
119+
"""Create the default global API instance, wired to legacy module-level singletons.
120+
121+
The default API reuses the module-level ``_default_event_support`` and
122+
``provider_registry`` so that backward-compatible module-level functions
123+
continue to work against the same state.
124+
"""
125+
from openfeature._event_support import _default_event_support # noqa: PLC0415
126+
from openfeature.provider._registry import provider_registry # noqa: PLC0415
127+
128+
api = OpenFeatureAPI.__new__(OpenFeatureAPI)
129+
api._hooks = []
130+
api._evaluation_context = EvaluationContext()
131+
api._transaction_context_propagator = NoOpTransactionContextPropagator()
132+
api._event_support = _default_event_support
133+
api._provider_registry = provider_registry
134+
135+
# Wire the registry to this API's event support and context getter
136+
provider_registry._event_support = _default_event_support
137+
provider_registry._evaluation_context_getter = api.get_evaluation_context
138+
139+
return api
140+
141+
142+
_default_api = _create_default_api()

0 commit comments

Comments
 (0)