Skip to content

Commit 427f917

Browse files
committed
fix: merge transaction context into hook context evaluation context (#521)
1 parent 288bd6b commit 427f917

File tree

2 files changed

+71
-13
lines changed

2 files changed

+71
-13
lines changed

openfeature/client.py

Lines changed: 17 additions & 13 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,
@@ -480,16 +489,11 @@ def _before_hooks_and_merge_context(
480489
invocation_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(invocation_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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import pytest
2+
from unittest.mock import MagicMock
3+
4+
from openfeature.api import set_provider, set_transaction_context, set_transaction_context_propagator
5+
from openfeature.client import OpenFeatureClient
6+
from openfeature.evaluation_context import EvaluationContext
7+
from openfeature.hook import Hook
8+
from openfeature.provider.no_op_provider import NoOpProvider
9+
from openfeature.transaction_context import ContextVarsTransactionContextPropagator
10+
11+
12+
class TransactionContextHook(Hook):
13+
def __init__(self):
14+
self.before_called = False
15+
self.transaction_attr_value = None
16+
17+
def before(self, hook_context, hints):
18+
self.before_called = True
19+
# Check if the transaction context attribute is in the hook context
20+
if "transaction_attr" in hook_context.evaluation_context.attributes:
21+
self.transaction_attr_value = hook_context.evaluation_context.attributes["transaction_attr"]
22+
return None
23+
24+
25+
def test_transaction_context_merged_into_hook_context():
26+
"""Test that transaction context is merged into the hook context's evaluation context."""
27+
# Set up transaction context propagator
28+
set_transaction_context_propagator(ContextVarsTransactionContextPropagator())
29+
30+
# Set up a provider
31+
provider = NoOpProvider()
32+
set_provider(provider)
33+
34+
# Create a client
35+
client = OpenFeatureClient(domain=None, version=None)
36+
37+
# Create and add a hook that will check for transaction context
38+
hook = TransactionContextHook()
39+
client.add_hooks([hook])
40+
41+
# Set transaction context with a specific attribute
42+
transaction_context = EvaluationContext(
43+
targeting_key="transaction",
44+
attributes={"transaction_attr": "transaction_value"},
45+
)
46+
set_transaction_context(transaction_context)
47+
48+
# Evaluate a flag
49+
client.get_boolean_value(flag_key="test-flag", default_value=False)
50+
51+
# Verify that the hook was called and saw the transaction context
52+
assert hook.before_called, "Hook's before method was not called"
53+
assert hook.transaction_attr_value == "transaction_value", \
54+
"Transaction context attribute was not found in hook context"

0 commit comments

Comments
 (0)