22import typing
33from collections .abc import Awaitable , Sequence
44from dataclasses import dataclass
5+ from itertools import chain
56
67from openfeature import _event_support
78from openfeature .evaluation_context import EvaluationContext , get_evaluation_context
@@ -420,10 +421,10 @@ def _establish_hooks_and_provider(
420421 flag_evaluation_options : typing .Optional [FlagEvaluationOptions ],
421422 ) -> tuple [
422423 FeatureProvider ,
423- HookContext ,
424424 HookHints ,
425- list [Hook ],
426- list [Hook ],
425+ list [tuple [Hook , HookContext ]],
426+ list [tuple [Hook , HookContext ]],
427+ EvaluationContext ,
427428 ]:
428429 if evaluation_context is None :
429430 evaluation_context = EvaluationContext ()
@@ -444,25 +445,43 @@ def _establish_hooks_and_provider(
444445 .merge (evaluation_context )
445446 )
446447
447- hook_context = HookContext (
448- flag_key = flag_key ,
449- flag_type = flag_type ,
450- default_value = default_value ,
451- evaluation_context = merged_eval_context ,
452- client_metadata = self .get_metadata (),
453- provider_metadata = provider .get_metadata (),
454- )
448+ client_metadata = self .get_metadata ()
449+ provider_metadata = provider .get_metadata ()
450+
455451 # Hooks need to be handled in different orders at different stages
456452 # in the flag evaluation
457453 # before: API, Client, Invocation, Provider
458- merged_hooks = (
459- get_hooks () + self .hooks + evaluation_hooks + provider .get_provider_hooks ()
460- )
454+ merged_hooks_and_context = [
455+ (
456+ hook ,
457+ HookContext (
458+ flag_key = flag_key ,
459+ flag_type = flag_type ,
460+ default_value = default_value ,
461+ evaluation_context = merged_eval_context ,
462+ client_metadata = client_metadata ,
463+ provider_metadata = provider_metadata ,
464+ hook_data = {},
465+ ),
466+ )
467+ for hook in chain (
468+ get_hooks (),
469+ self .hooks ,
470+ evaluation_hooks ,
471+ provider .get_provider_hooks (),
472+ )
473+ ]
461474 # after, error, finally: Provider, Invocation, Client, API
462- reversed_merged_hooks = merged_hooks [:]
463- reversed_merged_hooks .reverse ()
464-
465- return provider , hook_context , hook_hints , merged_hooks , reversed_merged_hooks
475+ reversed_merged_hooks_and_context = merged_hooks_and_context [:]
476+ reversed_merged_hooks_and_context .reverse ()
477+
478+ return (
479+ provider ,
480+ hook_hints ,
481+ merged_hooks_and_context ,
482+ reversed_merged_hooks_and_context ,
483+ merged_eval_context ,
484+ )
466485
467486 def _assert_provider_status (
468487 self ,
@@ -477,24 +496,21 @@ def _assert_provider_status(
477496 def _run_before_hooks_and_update_context (
478497 self ,
479498 flag_type : FlagType ,
480- hook_context : HookContext ,
481- merged_hooks : list [Hook ],
499+ merged_hooks_and_context : list [tuple [Hook , HookContext ]],
482500 hook_hints : HookHints ,
483- evaluation_context : typing . Optional [ EvaluationContext ] ,
501+ evaluation_context : EvaluationContext ,
484502 ) -> EvaluationContext :
485503 # https://github.com/open-feature/spec/blob/main/specification/sections/03-evaluation-context.md
486504 # Any resulting evaluation context from a before hook will overwrite
487505 # duplicate fields defined globally, on the client, or in the invocation.
488506 # Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
489507 before_hooks_context = before_hooks (
490- flag_type , hook_context , merged_hooks , hook_hints
508+ flag_type , merged_hooks_and_context , hook_hints
491509 )
492510
493511 # The hook_context.evaluation_context already contains the merged context from
494512 # _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-
497- return merged_context
513+ return evaluation_context .merge (before_hooks_context )
498514
499515 @typing .overload
500516 async def evaluate_flag_details_async (
@@ -575,23 +591,26 @@ async def evaluate_flag_details_async(
575591 :return: a typing.Awaitable[FlagEvaluationDetails] object with the fully evaluated flag from a
576592 provider
577593 """
578- provider , hook_context , hook_hints , merged_hooks , reversed_merged_hooks = (
579- self ._establish_hooks_and_provider (
580- flag_type ,
581- flag_key ,
582- default_value ,
583- evaluation_context ,
584- flag_evaluation_options ,
585- )
594+ (
595+ provider ,
596+ hook_hints ,
597+ merged_hooks_and_context ,
598+ reversed_merged_hooks_and_context ,
599+ merged_eval_context ,
600+ ) = self ._establish_hooks_and_provider (
601+ flag_type ,
602+ flag_key ,
603+ default_value ,
604+ evaluation_context ,
605+ flag_evaluation_options ,
586606 )
587607
588608 try :
589609 if provider_err := self ._assert_provider_status ():
590610 error_hooks (
591611 flag_type ,
592- hook_context ,
593612 provider_err ,
594- reversed_merged_hooks ,
613+ reversed_merged_hooks_and_context ,
595614 hook_hints ,
596615 )
597616 flag_evaluation = FlagEvaluationDetails (
@@ -605,10 +624,9 @@ async def evaluate_flag_details_async(
605624
606625 merged_context = self ._run_before_hooks_and_update_context (
607626 flag_type ,
608- hook_context ,
609- merged_hooks ,
627+ merged_hooks_and_context ,
610628 hook_hints ,
611- evaluation_context ,
629+ merged_eval_context ,
612630 )
613631
614632 flag_evaluation = await self ._create_provider_evaluation_async (
@@ -620,22 +638,21 @@ async def evaluate_flag_details_async(
620638 )
621639 if err := flag_evaluation .get_exception ():
622640 error_hooks (
623- flag_type , hook_context , err , reversed_merged_hooks , hook_hints
641+ flag_type , err , reversed_merged_hooks_and_context , hook_hints
624642 )
625643 return flag_evaluation
626644
627645 after_hooks (
628646 flag_type ,
629- hook_context ,
630647 flag_evaluation ,
631- reversed_merged_hooks ,
648+ reversed_merged_hooks_and_context ,
632649 hook_hints ,
633650 )
634651
635652 return flag_evaluation
636653
637654 except OpenFeatureError as err :
638- error_hooks (flag_type , hook_context , err , reversed_merged_hooks , hook_hints )
655+ error_hooks (flag_type , err , reversed_merged_hooks_and_context , hook_hints )
639656 flag_evaluation = FlagEvaluationDetails (
640657 flag_key = flag_key ,
641658 value = default_value ,
@@ -651,7 +668,7 @@ async def evaluate_flag_details_async(
651668 "Unable to correctly evaluate flag with key: '%s'" , flag_key
652669 )
653670
654- error_hooks (flag_type , hook_context , err , reversed_merged_hooks , hook_hints )
671+ error_hooks (flag_type , err , reversed_merged_hooks_and_context , hook_hints )
655672
656673 error_message = getattr (err , "error_message" , str (err ))
657674 flag_evaluation = FlagEvaluationDetails (
@@ -666,9 +683,8 @@ async def evaluate_flag_details_async(
666683 finally :
667684 after_all_hooks (
668685 flag_type ,
669- hook_context ,
670686 flag_evaluation ,
671- reversed_merged_hooks ,
687+ reversed_merged_hooks_and_context ,
672688 hook_hints ,
673689 )
674690
@@ -751,23 +767,26 @@ def evaluate_flag_details(
751767 :return: a FlagEvaluationDetails object with the fully evaluated flag from a
752768 provider
753769 """
754- provider , hook_context , hook_hints , merged_hooks , reversed_merged_hooks = (
755- self ._establish_hooks_and_provider (
756- flag_type ,
757- flag_key ,
758- default_value ,
759- evaluation_context ,
760- flag_evaluation_options ,
761- )
770+ (
771+ provider ,
772+ hook_hints ,
773+ merged_hooks_and_context ,
774+ reversed_merged_hooks_and_context ,
775+ merged_eval_context ,
776+ ) = self ._establish_hooks_and_provider (
777+ flag_type ,
778+ flag_key ,
779+ default_value ,
780+ evaluation_context ,
781+ flag_evaluation_options ,
762782 )
763783
764784 try :
765785 if provider_err := self ._assert_provider_status ():
766786 error_hooks (
767787 flag_type ,
768- hook_context ,
769788 provider_err ,
770- reversed_merged_hooks ,
789+ reversed_merged_hooks_and_context ,
771790 hook_hints ,
772791 )
773792 flag_evaluation = FlagEvaluationDetails (
@@ -781,10 +800,9 @@ def evaluate_flag_details(
781800
782801 merged_context = self ._run_before_hooks_and_update_context (
783802 flag_type ,
784- hook_context ,
785- merged_hooks ,
803+ merged_hooks_and_context ,
786804 hook_hints ,
787- evaluation_context ,
805+ merged_eval_context ,
788806 )
789807
790808 flag_evaluation = self ._create_provider_evaluation (
@@ -796,23 +814,22 @@ def evaluate_flag_details(
796814 )
797815 if err := flag_evaluation .get_exception ():
798816 error_hooks (
799- flag_type , hook_context , err , reversed_merged_hooks , hook_hints
817+ flag_type , err , reversed_merged_hooks_and_context , hook_hints
800818 )
801819 flag_evaluation .value = default_value
802820 return flag_evaluation
803821
804822 after_hooks (
805823 flag_type ,
806- hook_context ,
807824 flag_evaluation ,
808- reversed_merged_hooks ,
825+ reversed_merged_hooks_and_context ,
809826 hook_hints ,
810827 )
811828
812829 return flag_evaluation
813830
814831 except OpenFeatureError as err :
815- error_hooks (flag_type , hook_context , err , reversed_merged_hooks , hook_hints )
832+ error_hooks (flag_type , err , reversed_merged_hooks_and_context , hook_hints )
816833
817834 flag_evaluation = FlagEvaluationDetails (
818835 flag_key = flag_key ,
@@ -829,7 +846,7 @@ def evaluate_flag_details(
829846 "Unable to correctly evaluate flag with key: '%s'" , flag_key
830847 )
831848
832- error_hooks (flag_type , hook_context , err , reversed_merged_hooks , hook_hints )
849+ error_hooks (flag_type , err , reversed_merged_hooks_and_context , hook_hints )
833850
834851 error_message = getattr (err , "error_message" , str (err ))
835852 flag_evaluation = FlagEvaluationDetails (
@@ -844,9 +861,8 @@ def evaluate_flag_details(
844861 finally :
845862 after_all_hooks (
846863 flag_type ,
847- hook_context ,
848864 flag_evaluation ,
849- reversed_merged_hooks ,
865+ reversed_merged_hooks_and_context ,
850866 hook_hints ,
851867 )
852868
0 commit comments