feat: implement api-level hooks (#139)
Signed-off-by: Federico Bond <federicobond@gmail.com>
This commit is contained in:
parent
571f5eb3bf
commit
7fe511ffe0
|
|
@ -0,0 +1,21 @@
|
|||
import typing
|
||||
|
||||
from open_feature.hooks.hook import Hook
|
||||
|
||||
|
||||
_hooks: typing.List[Hook] = []
|
||||
|
||||
|
||||
def add_api_hooks(hooks: typing.List[Hook]):
|
||||
global _hooks
|
||||
_hooks = _hooks + hooks
|
||||
|
||||
|
||||
def clear_api_hooks():
|
||||
global _hooks
|
||||
_hooks = []
|
||||
|
||||
|
||||
def api_hooks() -> typing.List[Hook]:
|
||||
global _hooks
|
||||
return _hooks
|
||||
|
|
@ -14,6 +14,7 @@ from open_feature.flag_evaluation.flag_evaluation_options import FlagEvaluationO
|
|||
from open_feature.flag_evaluation.flag_type import FlagType
|
||||
from open_feature.flag_evaluation.reason import Reason
|
||||
from open_feature.flag_evaluation.resolution_details import FlagResolutionDetails
|
||||
from open_feature.hooks import api_hooks
|
||||
from open_feature.hooks.hook import Hook
|
||||
from open_feature.hooks.hook_context import HookContext
|
||||
from open_feature.hooks.hook_support import (
|
||||
|
|
@ -257,13 +258,14 @@ class OpenFeatureClient:
|
|||
client_metadata=None,
|
||||
provider_metadata=None,
|
||||
)
|
||||
# Todo add api level hooks
|
||||
# https://github.com/open-feature/spec/blob/main/specification/sections/04-hooks.md#requirement-442
|
||||
# Hooks need to be handled in different orders at different stages
|
||||
# in the flag evaluation
|
||||
# before: API, Client, Invocation, Provider
|
||||
merged_hooks = (
|
||||
self.hooks + evaluation_hooks + self.provider.get_provider_hooks()
|
||||
api_hooks()
|
||||
+ self.hooks
|
||||
+ evaluation_hooks
|
||||
+ self.provider.get_provider_hooks()
|
||||
)
|
||||
# after, error, finally: Provider, Invocation, Client, API
|
||||
reversed_merged_hooks = merged_hooks[:]
|
||||
|
|
|
|||
19
readme.md
19
readme.md
|
|
@ -137,7 +137,24 @@ See [here](https://openfeature.dev/ecosystem) for a catalog of available provide
|
|||
|
||||
### Hooks:
|
||||
|
||||
TBD (See Issue [#72](https://github.com/open-feature/python-sdk/issues/72))
|
||||
A hook is a mechanism that allows for adding arbitrary behavior at well-defined points of the flag evaluation life-cycle. Use cases include validating the resolved flag value, modifying or adding data to the evaluation context, logging, telemetry, and tracking.
|
||||
|
||||
```python
|
||||
from open_feature.hooks.hook import Hook
|
||||
|
||||
class MyHook(Hook):
|
||||
def after(self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict):
|
||||
print("This runs after the flag has been evaluated")
|
||||
|
||||
|
||||
# set global hooks at the API-level
|
||||
from open_feature.hooks import add_api_hooks
|
||||
add_api_hooks([MyHook()])
|
||||
|
||||
# or configure them in the client
|
||||
client = OpenFeatureClient()
|
||||
client.add_hooks([MyHook()])
|
||||
```
|
||||
|
||||
See [here](https://openfeature.dev/ecosystem) for a catalog of available hooks.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
from open_feature.hooks.hook import Hook
|
||||
from open_feature.hooks import add_api_hooks, clear_api_hooks, api_hooks
|
||||
|
||||
|
||||
def test_should_add_hooks_to_api_hooks():
|
||||
# Given
|
||||
hook_1 = MagicMock(spec=Hook)
|
||||
hook_2 = MagicMock(spec=Hook)
|
||||
clear_api_hooks()
|
||||
|
||||
# When
|
||||
add_api_hooks([hook_1])
|
||||
add_api_hooks([hook_2])
|
||||
|
||||
# Then
|
||||
assert api_hooks() == [hook_1, hook_2]
|
||||
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
from open_feature.exception.error_code import ErrorCode
|
||||
from open_feature.exception.exceptions import OpenFeatureError
|
||||
from open_feature.flag_evaluation.reason import Reason
|
||||
from open_feature.hooks import clear_api_hooks, add_api_hooks
|
||||
from open_feature.hooks.hook import Hook
|
||||
from open_feature.open_feature_client import OpenFeatureClient
|
||||
from open_feature.provider.no_op_provider import NoOpProvider
|
||||
|
|
@ -144,3 +145,17 @@ def test_should_return_client_metadata_with_name():
|
|||
# Then
|
||||
assert metadata is not None
|
||||
assert metadata.name == "my-client"
|
||||
|
||||
|
||||
def test_should_call_api_level_hooks(no_op_provider_client):
|
||||
# Given
|
||||
clear_api_hooks()
|
||||
api_hook = MagicMock(spec=Hook)
|
||||
add_api_hooks([api_hook])
|
||||
|
||||
# When
|
||||
no_op_provider_client.get_boolean_details(flag_key="Key", default_value=True)
|
||||
|
||||
# Then
|
||||
api_hook.before.assert_called_once()
|
||||
api_hook.after.assert_called_once()
|
||||
|
|
|
|||
Loading…
Reference in New Issue