Skip to content

Commit a5b3aa9

Browse files
authored
fix: merge transaction context into hook context evaluation context (#521) (#523)
Signed-off-by: Konstantinos Koukopoulos <koukopoulos@gmail.com>
1 parent 5652c0c commit a5b3aa9

File tree

2 files changed

+69
-14
lines changed

2 files changed

+69
-14
lines changed

openfeature/client.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -435,11 +435,20 @@ def _establish_hooks_and_provider(
435435
evaluation_hooks = flag_evaluation_options.hooks
436436
hook_hints = flag_evaluation_options.hook_hints
437437

438+
# Merge transaction context into evaluation context before creating hook_context
439+
# This ensures hooks have access to the complete context including transaction context
440+
merged_eval_context = (
441+
get_evaluation_context()
442+
.merge(get_transaction_context())
443+
.merge(self.context)
444+
.merge(evaluation_context)
445+
)
446+
438447
hook_context = HookContext(
439448
flag_key=flag_key,
440449
flag_type=flag_type,
441450
default_value=default_value,
442-
evaluation_context=evaluation_context,
451+
evaluation_context=merged_eval_context,
443452
client_metadata=self.get_metadata(),
444453
provider_metadata=provider.get_metadata(),
445454
)
@@ -465,7 +474,7 @@ def _assert_provider_status(
465474
return ProviderFatalError()
466475
return None
467476

468-
def _before_hooks_and_merge_context(
477+
def _run_before_hooks_and_update_context(
469478
self,
470479
flag_type: FlagType,
471480
hook_context: HookContext,
@@ -477,19 +486,14 @@ def _before_hooks_and_merge_context(
477486
# Any resulting evaluation context from a before hook will overwrite
478487
# duplicate fields defined globally, on the client, or in the invocation.
479488
# Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
480-
invocation_context = before_hooks(
489+
before_hooks_context = before_hooks(
481490
flag_type, hook_context, merged_hooks, hook_hints
482491
)
483-
if evaluation_context:
484-
invocation_context = invocation_context.merge(ctx2=evaluation_context)
485492

486-
# Requirement 3.2.2 merge: API.context->transaction.context->client.context->invocation.context
487-
merged_context = (
488-
get_evaluation_context()
489-
.merge(get_transaction_context())
490-
.merge(self.context)
491-
.merge(invocation_context)
492-
)
493+
# The hook_context.evaluation_context already contains the merged context from
494+
# _establish_hooks_and_provider, so we just need to merge with the before hooks result
495+
merged_context = hook_context.evaluation_context.merge(before_hooks_context)
496+
493497
return merged_context
494498

495499
@typing.overload
@@ -599,7 +603,7 @@ async def evaluate_flag_details_async(
599603
)
600604
return flag_evaluation
601605

602-
merged_context = self._before_hooks_and_merge_context(
606+
merged_context = self._run_before_hooks_and_update_context(
603607
flag_type,
604608
hook_context,
605609
merged_hooks,
@@ -775,7 +779,7 @@ def evaluate_flag_details(
775779
)
776780
return flag_evaluation
777781

778-
merged_context = self._before_hooks_and_merge_context(
782+
merged_context = self._run_before_hooks_and_update_context(
779783
flag_type,
780784
hook_context,
781785
merged_hooks,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from openfeature.api import (
2+
set_provider,
3+
set_transaction_context,
4+
set_transaction_context_propagator,
5+
)
6+
from openfeature.client import OpenFeatureClient
7+
from openfeature.evaluation_context import EvaluationContext
8+
from openfeature.hook import Hook
9+
from openfeature.provider.no_op_provider import NoOpProvider
10+
from openfeature.transaction_context import ContextVarsTransactionContextPropagator
11+
12+
13+
class TransactionContextHook(Hook):
14+
def __init__(self):
15+
self.before_called = False
16+
self.transaction_attr_value = None
17+
18+
def before(self, hook_context, hints):
19+
self.before_called = True
20+
# Check if the transaction context attribute is in the hook context
21+
if "transaction_attr" in hook_context.evaluation_context.attributes:
22+
self.transaction_attr_value = hook_context.evaluation_context.attributes[
23+
"transaction_attr"
24+
]
25+
return None
26+
27+
28+
def test_transaction_context_merged_into_hook_context():
29+
"""Test that transaction context is merged into the hook context's evaluation context."""
30+
set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
31+
32+
provider = NoOpProvider()
33+
set_provider(provider)
34+
35+
client = OpenFeatureClient(domain=None, version=None)
36+
37+
hook = TransactionContextHook()
38+
client.add_hooks([hook])
39+
40+
transaction_context = EvaluationContext(
41+
targeting_key="transaction",
42+
attributes={"transaction_attr": "transaction_value"},
43+
)
44+
set_transaction_context(transaction_context)
45+
46+
client.get_boolean_value(flag_key="test-flag", default_value=False)
47+
48+
assert hook.before_called, "Hook's before method was not called"
49+
assert hook.transaction_attr_value == "transaction_value", (
50+
"Transaction context attribute was not found in hook context"
51+
)

0 commit comments

Comments
 (0)