Skip to content

Commit 518a185

Browse files
committed
feat: add set_evaluation_context method to OpenFeatureClient
Implements client-level evaluation context setter as requested in #500. The OpenFeatureClient class now provides a set_evaluation_context() method that allows users to set a client-wide evaluation context. This context is properly merged during flag evaluation with the following precedence (lowest to highest): 1. API-level context (global) 2. Transaction context 3. Client context (set via this new method) 4. Invocation context (per-evaluation) Changes: - Added set_evaluation_context(context) method to OpenFeatureClient class - Added comprehensive tests verifying: * The setter correctly updates the client context * Client context is properly merged during flag evaluation * Context precedence is maintained Closes #500 Signed-off-by: vikasrao23 <vikasrao23@users.noreply.github.com>
1 parent 05382aa commit 518a185

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

openfeature/client.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,18 @@ def get_metadata(self) -> ClientMetadata:
9999
def add_hooks(self, hooks: list[Hook]) -> None:
100100
self.hooks = self.hooks + hooks
101101

102+
def set_evaluation_context(self, context: EvaluationContext) -> None:
103+
"""
104+
Set the evaluation context for this client.
105+
106+
The client-level context will be merged with API, transaction, and invocation contexts
107+
during flag evaluation, with the following precedence (lowest to highest):
108+
API context -> Transaction context -> Client context -> Invocation context
109+
110+
:param context: The evaluation context to set for this client
111+
"""
112+
self.context = context
113+
102114
def get_boolean_value(
103115
self,
104116
flag_key: str,

tests/test_client.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,3 +623,87 @@ def test_client_should_merge_contexts():
623623
assert context.attributes["transaction_attr"] == "transaction_value"
624624
assert context.attributes["client_attr"] == "client_value"
625625
assert context.attributes["invocation_attr"] == "invocation_value"
626+
627+
628+
def test_client_set_evaluation_context():
629+
# Given
630+
api.clear_hooks()
631+
api.set_provider(NoOpProvider())
632+
client = api.get_client()
633+
634+
initial_context = EvaluationContext(
635+
targeting_key="initial", attributes={"initial_attr": "initial_value"}
636+
)
637+
638+
# When
639+
client.set_evaluation_context(initial_context)
640+
641+
# Then
642+
assert client.context == initial_context
643+
assert client.context.targeting_key == "initial"
644+
assert client.context.attributes["initial_attr"] == "initial_value"
645+
646+
647+
def test_client_set_evaluation_context_updates_existing():
648+
# Given
649+
api.clear_hooks()
650+
api.set_provider(NoOpProvider())
651+
initial_context = EvaluationContext(
652+
targeting_key="initial", attributes={"initial_attr": "initial_value"}
653+
)
654+
client = OpenFeatureClient(domain=None, version=None, context=initial_context)
655+
656+
new_context = EvaluationContext(
657+
targeting_key="updated", attributes={"updated_attr": "updated_value"}
658+
)
659+
660+
# When
661+
client.set_evaluation_context(new_context)
662+
663+
# Then
664+
assert client.context == new_context
665+
assert client.context.targeting_key == "updated"
666+
assert client.context.attributes["updated_attr"] == "updated_value"
667+
668+
669+
def test_client_set_evaluation_context_is_merged_during_evaluation():
670+
# Given
671+
api.clear_hooks()
672+
api.set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
673+
674+
provider = NoOpProvider()
675+
provider.resolve_boolean_details = MagicMock(wraps=provider.resolve_boolean_details)
676+
api.set_provider(provider)
677+
678+
# API-level context
679+
api_context = EvaluationContext(
680+
targeting_key="api", attributes={"api_attr": "api_value"}
681+
)
682+
api.set_evaluation_context(api_context)
683+
684+
# Create client with initial context
685+
client = api.get_client()
686+
687+
# Update client context via setter
688+
client_context = EvaluationContext(
689+
targeting_key="client", attributes={"client_attr": "client_value"}
690+
)
691+
client.set_evaluation_context(client_context)
692+
693+
# Invocation context
694+
invocation_context = EvaluationContext(
695+
targeting_key="invocation", attributes={"invocation_attr": "invocation_value"}
696+
)
697+
698+
# When
699+
client.get_boolean_details("flag", False, invocation_context)
700+
701+
# Then
702+
_, kwargs = provider.resolve_boolean_details.call_args
703+
context = kwargs["evaluation_context"]
704+
705+
# Verify all contexts are merged with correct precedence
706+
assert context.targeting_key == "invocation" # Highest precedence
707+
assert context.attributes["api_attr"] == "api_value"
708+
assert context.attributes["client_attr"] == "client_value"
709+
assert context.attributes["invocation_attr"] == "invocation_value"

0 commit comments

Comments
 (0)