1- from __future__ import annotations
2-
3- import logging
4- import typing
5- from collections .abc import Mapping , MutableMapping , Sequence
6- from datetime import datetime
7- from enum import Enum
8-
9- from openfeature .evaluation_context import EvaluationContext
10- from openfeature .exception import ErrorCode , OpenFeatureError
11- from openfeature .flag_evaluation import FlagEvaluationDetails , FlagType , FlagValueType
12-
13- if typing .TYPE_CHECKING :
14- from openfeature .client import ClientMetadata
15- from openfeature .provider .metadata import Metadata
1+ from openfeature .hook .hook import Hook , HookContext , HookData , HookHints , HookType
2+ from openfeature .hook .logging_hook import LoggingHook
163
174__all__ = [
185 "Hook" ,
2916_hooks : list [Hook ] = []
3017
3118
32- # https://openfeature.dev/specification/sections/hooks/#requirement-461
33- HookData = MutableMapping [str , typing .Any ]
34-
35-
36- class HookType (Enum ):
37- BEFORE = "before"
38- AFTER = "after"
39- FINALLY_AFTER = "finally_after"
40- ERROR = "error"
41-
42-
43- class HookContext :
44- def __init__ ( # noqa: PLR0913
45- self ,
46- flag_key : str ,
47- flag_type : FlagType ,
48- default_value : FlagValueType ,
49- evaluation_context : EvaluationContext ,
50- client_metadata : ClientMetadata | None = None ,
51- provider_metadata : Metadata | None = None ,
52- hook_data : HookData | None = None ,
53- ):
54- self .flag_key = flag_key
55- self .flag_type = flag_type
56- self .default_value = default_value
57- self .evaluation_context = evaluation_context
58- self .client_metadata = client_metadata
59- self .provider_metadata = provider_metadata
60- self .hook_data = hook_data or {}
61-
62- def __setattr__ (self , key : str , value : typing .Any ) -> None :
63- if hasattr (self , key ) and key in (
64- "flag_key" ,
65- "flag_type" ,
66- "default_value" ,
67- "client_metadata" ,
68- "provider_metadata" ,
69- ):
70- raise AttributeError (f"Attribute { key !r} is immutable" )
71- super ().__setattr__ (key , value )
72-
73-
74- # https://openfeature.dev/specification/sections/hooks/#requirement-421
75- HookHintValue : typing .TypeAlias = (
76- bool
77- | int
78- | float
79- | str
80- | datetime
81- | Sequence ["HookHintValue" ]
82- | Mapping [str , "HookHintValue" ]
83- )
84-
85- HookHints = Mapping [str , HookHintValue ]
86-
87-
88- class Hook :
89- def before (
90- self , hook_context : HookContext , hints : HookHints
91- ) -> EvaluationContext | None :
92- """
93- Runs before flag is resolved.
94-
95- :param hook_context: Information about the particular flag evaluation
96- :param hints: An immutable mapping of data for users to
97- communicate to the hooks.
98- :return: An EvaluationContext. It will be merged with the
99- EvaluationContext instances from other hooks, the client and API.
100- """
101- return None
102-
103- def after (
104- self ,
105- hook_context : HookContext ,
106- details : FlagEvaluationDetails [FlagValueType ],
107- hints : HookHints ,
108- ) -> None :
109- """
110- Runs after a flag is resolved.
111-
112- :param hook_context: Information about the particular flag evaluation
113- :param details: Information about how the flag was resolved,
114- including any resolved values.
115- :param hints: A mapping of data for users to communicate to the hooks.
116- """
117- pass
118-
119- def error (
120- self , hook_context : HookContext , exception : Exception , hints : HookHints
121- ) -> None :
122- """
123- Run when evaluation encounters an error. Errors thrown will be swallowed.
124-
125- :param hook_context: Information about the particular flag evaluation
126- :param exception: The exception that was thrown
127- :param hints: A mapping of data for users to communicate to the hooks.
128- """
129- pass
130-
131- def finally_after (
132- self ,
133- hook_context : HookContext ,
134- details : FlagEvaluationDetails [FlagValueType ],
135- hints : HookHints ,
136- ) -> None :
137- """
138- Run after flag evaluation, including any error processing.
139- This will always run. Errors will be swallowed.
140-
141- :param hook_context: Information about the particular flag evaluation
142- :param hints: A mapping of data for users to communicate to the hooks.
143- """
144- pass
145-
146- def supports_flag_value_type (self , flag_type : FlagType ) -> bool :
147- """
148- Check to see if the hook supports the particular flag type.
149-
150- :param flag_type: particular type of the flag
151- :return: a boolean containing whether the flag type is supported (True)
152- or not (False)
153- """
154- return True
155-
156-
15719def add_hooks (hooks : list [Hook ]) -> None :
15820 global _hooks
15921 _hooks = _hooks + hooks
@@ -166,65 +28,3 @@ def clear_hooks() -> None:
16628
16729def get_hooks () -> list [Hook ]:
16830 return _hooks
169-
170-
171- class LoggingHook (Hook ):
172- def __init__ (
173- self ,
174- include_evaluation_context : bool = False ,
175- logger : logging .Logger | None = None ,
176- ):
177- self .logger = logger or logging .getLogger ("openfeature" )
178- self .include_evaluation_context = include_evaluation_context
179-
180- def _build_args (self , hook_context : HookContext ) -> dict :
181- args = {
182- "domain" : hook_context .client_metadata .domain
183- if hook_context .client_metadata
184- else None ,
185- "provider_name" : hook_context .provider_metadata .name
186- if hook_context .provider_metadata
187- else None ,
188- "flag_key" : hook_context .flag_key ,
189- "default_value" : hook_context .default_value ,
190- }
191- if self .include_evaluation_context :
192- args ["evaluation_context" ] = {
193- "targeting_key" : hook_context .evaluation_context .targeting_key ,
194- "attributes" : hook_context .evaluation_context .attributes ,
195- }
196- return args
197-
198- def before (
199- self , hook_context : HookContext , hints : HookHints
200- ) -> EvaluationContext | None :
201- args = self ._build_args (hook_context )
202- args ["stage" ] = "before"
203- self .logger .debug ("Before stage %s" , args )
204- return None
205-
206- def after (
207- self ,
208- hook_context : HookContext ,
209- details : FlagEvaluationDetails [FlagValueType ],
210- hints : HookHints ,
211- ) -> None :
212- args = self ._build_args (hook_context )
213- extra_args = {
214- "stage" : "after" ,
215- "reason" : details .reason ,
216- "variant" : details .variant ,
217- "value" : details .value ,
218- }
219- self .logger .debug ("After stage %s" , {** args , ** extra_args })
220-
221- def error (
222- self , hook_context : HookContext , exception : Exception , hints : HookHints
223- ) -> None :
224- args = self ._build_args (hook_context )
225- extra_args = {
226- "stage" : "error" ,
227- "error_code" : exception .error_code if isinstance (exception , OpenFeatureError ) else ErrorCode .GENERAL ,
228- "error_message" : str (exception ),
229- }
230- self .logger .error ("Error stage %s" , {** args , ** extra_args })
0 commit comments