Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions openfeature/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,11 +435,20 @@ def _establish_hooks_and_provider(
evaluation_hooks = flag_evaluation_options.hooks
hook_hints = flag_evaluation_options.hook_hints

# Merge transaction context into evaluation context before creating hook_context
# This ensures hooks have access to the complete context including transaction context
merged_eval_context = (
get_evaluation_context()
.merge(get_transaction_context())
.merge(self.context)
.merge(evaluation_context)
)

hook_context = HookContext(
flag_key=flag_key,
flag_type=flag_type,
default_value=default_value,
evaluation_context=evaluation_context,
evaluation_context=merged_eval_context,
client_metadata=self.get_metadata(),
provider_metadata=provider.get_metadata(),
)
Expand All @@ -465,7 +474,7 @@ def _assert_provider_status(
return ProviderFatalError()
return None

def _before_hooks_and_merge_context(
def _run_before_hooks_and_update_context(
self,
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -477,19 +486,14 @@ def _before_hooks_and_merge_context(
# Any resulting evaluation context from a before hook will overwrite
# duplicate fields defined globally, on the client, or in the invocation.
# Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
invocation_context = before_hooks(
before_hooks_context = before_hooks(
flag_type, hook_context, merged_hooks, hook_hints
)
if evaluation_context:
invocation_context = invocation_context.merge(ctx2=evaluation_context)

# Requirement 3.2.2 merge: API.context->transaction.context->client.context->invocation.context
merged_context = (
get_evaluation_context()
.merge(get_transaction_context())
.merge(self.context)
.merge(invocation_context)
)
# The hook_context.evaluation_context already contains the merged context from
# _establish_hooks_and_provider, so we just need to merge with the before hooks result
merged_context = hook_context.evaluation_context.merge(before_hooks_context)

return merged_context

@typing.overload
Expand Down Expand Up @@ -599,7 +603,7 @@ async def evaluate_flag_details_async(
)
return flag_evaluation

merged_context = self._before_hooks_and_merge_context(
merged_context = self._run_before_hooks_and_update_context(
flag_type,
hook_context,
merged_hooks,
Expand Down Expand Up @@ -775,7 +779,7 @@ def evaluate_flag_details(
)
return flag_evaluation

merged_context = self._before_hooks_and_merge_context(
merged_context = self._run_before_hooks_and_update_context(
flag_type,
hook_context,
merged_hooks,
Expand Down
51 changes: 51 additions & 0 deletions tests/test_transaction_context_in_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from openfeature.api import (
set_provider,
set_transaction_context,
set_transaction_context_propagator,
)
from openfeature.client import OpenFeatureClient
from openfeature.evaluation_context import EvaluationContext
from openfeature.hook import Hook
from openfeature.provider.no_op_provider import NoOpProvider
from openfeature.transaction_context import ContextVarsTransactionContextPropagator


class TransactionContextHook(Hook):
def __init__(self):
self.before_called = False
self.transaction_attr_value = None

def before(self, hook_context, hints):
self.before_called = True
# Check if the transaction context attribute is in the hook context
if "transaction_attr" in hook_context.evaluation_context.attributes:
self.transaction_attr_value = hook_context.evaluation_context.attributes[
"transaction_attr"
]
return None


def test_transaction_context_merged_into_hook_context():
"""Test that transaction context is merged into the hook context's evaluation context."""
set_transaction_context_propagator(ContextVarsTransactionContextPropagator())

provider = NoOpProvider()
set_provider(provider)

client = OpenFeatureClient(domain=None, version=None)

hook = TransactionContextHook()
client.add_hooks([hook])

transaction_context = EvaluationContext(
targeting_key="transaction",
attributes={"transaction_attr": "transaction_value"},
)
set_transaction_context(transaction_context)

client.get_boolean_value(flag_key="test-flag", default_value=False)

assert hook.before_called, "Hook's before method was not called"
assert hook.transaction_attr_value == "transaction_value", (
"Transaction context attribute was not found in hook context"
)