|  | 
| 8 | 8 | from devcycle_python_sdk.api.local_bucketing import LocalBucketing | 
| 9 | 9 | from devcycle_python_sdk.exceptions import VariableTypeMismatchError | 
| 10 | 10 | from devcycle_python_sdk.managers.config_manager import EnvironmentConfigManager | 
|  | 11 | +from devcycle_python_sdk.managers.eval_hooks_manager import ( | 
|  | 12 | +    EvalHooksManager, | 
|  | 13 | +    BeforeHookError, | 
|  | 14 | +    AfterHookError, | 
|  | 15 | +) | 
| 11 | 16 | from devcycle_python_sdk.managers.event_queue_manager import EventQueueManager | 
| 12 | 17 | from devcycle_python_sdk.models.bucketed_config import BucketedConfig | 
|  | 18 | +from devcycle_python_sdk.models.eval_hook import EvalHook | 
|  | 19 | +from devcycle_python_sdk.models.eval_hook_context import HookContext | 
| 13 | 20 | from devcycle_python_sdk.models.event import DevCycleEvent, EventType | 
| 14 | 21 | from devcycle_python_sdk.models.feature import Feature | 
| 15 | 22 | from devcycle_python_sdk.models.platform_data import default_platform_data | 
| @@ -51,6 +58,7 @@ def __init__(self, sdk_key: str, options: DevCycleLocalOptions): | 
| 51 | 58 |         ) | 
| 52 | 59 | 
 | 
| 53 | 60 |         self._openfeature_provider: Optional[DevCycleProvider] = None | 
|  | 61 | +        self.eval_hooks_manager = EvalHooksManager(self.options.eval_hooks) | 
| 54 | 62 | 
 | 
| 55 | 63 |     def get_sdk_platform(self) -> str: | 
| 56 | 64 |         return "Local" | 
| @@ -133,18 +141,44 @@ def variable(self, user: DevCycleUser, key: str, default_value: Any) -> Variable | 
| 133 | 141 |                 ) | 
| 134 | 142 |             return Variable.create_default_variable(key, default_value) | 
| 135 | 143 | 
 | 
|  | 144 | +        context = HookContext(key, user, default_value) | 
|  | 145 | +        variable = Variable.create_default_variable( | 
|  | 146 | +            key=key, default_value=default_value | 
|  | 147 | +        ) | 
|  | 148 | + | 
| 136 | 149 |         try: | 
|  | 150 | +            before_hook_error = None | 
|  | 151 | +            try: | 
|  | 152 | +                context = self.eval_hooks_manager.run_before(context) | 
|  | 153 | +            except BeforeHookError as e: | 
|  | 154 | +                before_hook_error = e | 
| 137 | 155 |             variable = self.local_bucketing.get_variable_for_user_protobuf( | 
| 138 | 156 |                 user, key, default_value | 
| 139 | 157 |             ) | 
| 140 |  | -            if variable: | 
| 141 |  | -                return variable | 
|  | 158 | +            if variable is None: | 
|  | 159 | +                variable = Variable.create_default_variable( | 
|  | 160 | +                    key=key, default_value=default_value | 
|  | 161 | +                ) | 
|  | 162 | + | 
|  | 163 | +            if before_hook_error is None: | 
|  | 164 | +                self.eval_hooks_manager.run_after(context, variable) | 
|  | 165 | +            else: | 
|  | 166 | +                raise before_hook_error | 
| 142 | 167 |         except VariableTypeMismatchError: | 
| 143 | 168 |             logger.debug("DevCycle: Variable type mismatch, returning default value") | 
|  | 169 | +            return variable | 
|  | 170 | +        except BeforeHookError as e: | 
|  | 171 | +            self.eval_hooks_manager.run_error(context, e) | 
|  | 172 | +            return variable | 
|  | 173 | +        except AfterHookError as e: | 
|  | 174 | +            self.eval_hooks_manager.run_error(context, e) | 
|  | 175 | +            return variable | 
| 144 | 176 |         except Exception as e: | 
| 145 | 177 |             logger.warning(f"DevCycle: Error retrieving variable for user: {e}") | 
| 146 |  | - | 
| 147 |  | -        return Variable.create_default_variable(key, default_value) | 
|  | 178 | +            return variable | 
|  | 179 | +        finally: | 
|  | 180 | +            self.eval_hooks_manager.run_finally(context, variable) | 
|  | 181 | +        return variable | 
| 148 | 182 | 
 | 
| 149 | 183 |     def _generate_bucketed_config(self, user: DevCycleUser) -> BucketedConfig: | 
| 150 | 184 |         """ | 
| @@ -234,6 +268,12 @@ def close(self) -> None: | 
| 234 | 268 |         self.config_manager.close() | 
| 235 | 269 |         self.event_queue_manager.close() | 
| 236 | 270 | 
 | 
|  | 271 | +    def add_hook(self, eval_hook: EvalHook) -> None: | 
|  | 272 | +        self.eval_hooks_manager.add_hook(eval_hook) | 
|  | 273 | + | 
|  | 274 | +    def clear_hooks(self) -> None: | 
|  | 275 | +        self.eval_hooks_manager.clear_hooks() | 
|  | 276 | + | 
| 237 | 277 | 
 | 
| 238 | 278 | def _validate_sdk_key(sdk_key: str) -> None: | 
| 239 | 279 |     if sdk_key is None or len(sdk_key) == 0: | 
|  | 
0 commit comments