Skip to content

Commit cda5e95

Browse files
committed
feat: implement api-level hooks
Signed-off-by: Federico Bond <[email protected]>
1 parent 052e149 commit cda5e95

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

open_feature/hooks/__init__.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import typing
2+
3+
from open_feature.hooks.hook import Hook
4+
5+
6+
_hooks: typing.List[Hook] = []
7+
8+
9+
def add_api_hooks(hooks: typing.List[Hook]):
10+
global _hooks
11+
_hooks = _hooks + hooks
12+
13+
14+
def clear_api_hooks():
15+
global _hooks
16+
_hooks = []
17+
18+
19+
def api_hooks() -> typing.List[Hook]:
20+
global _hooks
21+
return _hooks

open_feature/open_feature_client.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from open_feature.flag_evaluation.flag_type import FlagType
1414
from open_feature.flag_evaluation.reason import Reason
1515
from open_feature.flag_evaluation.resolution_details import FlagResolutionDetails
16+
from open_feature.hooks import api_hooks
1617
from open_feature.hooks.hook import Hook
1718
from open_feature.hooks.hook_context import HookContext
1819
from open_feature.hooks.hook_support import (
@@ -248,13 +249,14 @@ def evaluate_flag_details(
248249
client_metadata=None,
249250
provider_metadata=None,
250251
)
251-
# Todo add api level hooks
252-
# https://github.com/open-feature/spec/blob/main/specification/sections/04-hooks.md#requirement-442
253252
# Hooks need to be handled in different orders at different stages
254253
# in the flag evaluation
255254
# before: API, Client, Invocation, Provider
256255
merged_hooks = (
257-
self.hooks + evaluation_hooks + self.provider.get_provider_hooks()
256+
api_hooks()
257+
+ self.hooks
258+
+ evaluation_hooks
259+
+ self.provider.get_provider_hooks()
258260
)
259261
# after, error, finally: Provider, Invocation, Client, API
260262
reversed_merged_hooks = merged_hooks[:]

tests/hooks/test_init.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from unittest.mock import MagicMock
2+
3+
from open_feature.hooks.hook import Hook
4+
from open_feature.hooks import add_api_hooks, clear_api_hooks, api_hooks
5+
6+
7+
def test_should_add_hooks_to_api_hooks():
8+
# Given
9+
hook_1 = MagicMock(spec=Hook)
10+
hook_2 = MagicMock(spec=Hook)
11+
clear_api_hooks()
12+
13+
# When
14+
add_api_hooks([hook_1])
15+
add_api_hooks([hook_2])
16+
17+
# Then
18+
assert api_hooks() == [hook_1, hook_2]

tests/test_open_feature_client.py

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from open_feature.exception.error_code import ErrorCode
66
from open_feature.exception.exceptions import OpenFeatureError
77
from open_feature.flag_evaluation.reason import Reason
8+
from open_feature.hooks import clear_api_hooks, add_api_hooks
89
from open_feature.hooks.hook import Hook
910

1011

@@ -132,3 +133,19 @@ def test_should_handle_an_open_feature_exception_thrown_by_a_provider(
132133
assert isinstance(flag_details.value, bool)
133134
assert flag_details.reason == Reason.ERROR.value
134135
assert flag_details.error_message == "error_message"
136+
137+
138+
def test_should_call_api_level_hooks(no_op_provider_client):
139+
# Given
140+
clear_api_hooks()
141+
api_hook = MagicMock(spec=Hook)
142+
add_api_hooks([api_hook])
143+
144+
# When
145+
no_op_provider_client.get_boolean_details(
146+
flag_key="Key", default_value=True
147+
)
148+
149+
# Then
150+
api_hook.before.assert_called_once()
151+
api_hook.after.assert_called_once()

0 commit comments

Comments
 (0)