Skip to content

Commit c661ab2

Browse files
fix: Hook methods should have default non-abstract implementations (#216)
* fix: Hook methods should have default non-abstract implementations Signed-off-by: Federico Bond <federicobond@gmail.com> * fix: use correct return type for Hook.before method Signed-off-by: Federico Bond <federicobond@gmail.com> * feat: make EvaluationContext a dataclass Signed-off-by: Federico Bond <federicobond@gmail.com> * test: add unit test for evaluation context merging in before_hooks Signed-off-by: Federico Bond <federicobond@gmail.com> --------- Signed-off-by: Federico Bond <federicobond@gmail.com> Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com>
1 parent 84af1ae commit c661ab2

File tree

3 files changed

+28
-17
lines changed

3 files changed

+28
-17
lines changed

openfeature/evaluation_context.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import typing
2+
from dataclasses import dataclass, field
23

34

5+
@dataclass
46
class EvaluationContext:
5-
def __init__(
6-
self,
7-
targeting_key: typing.Optional[str] = None,
8-
attributes: typing.Optional[dict] = None,
9-
):
10-
self.targeting_key = targeting_key
11-
self.attributes = attributes or {}
7+
targeting_key: typing.Optional[str] = None
8+
attributes: dict = field(default_factory=dict)
129

1310
def merge(self, ctx2: "EvaluationContext") -> "EvaluationContext":
1411
if not (self and ctx2):

openfeature/hook/__init__.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import typing
4-
from abc import abstractmethod
54
from dataclasses import dataclass
65
from enum import Enum
76

@@ -27,8 +26,9 @@ class HookContext:
2726

2827

2928
class Hook:
30-
@abstractmethod
31-
def before(self, hook_context: HookContext, hints: dict) -> EvaluationContext:
29+
def before(
30+
self, hook_context: HookContext, hints: dict
31+
) -> typing.Optional[EvaluationContext]:
3232
"""
3333
Runs before flag is resolved.
3434
@@ -38,9 +38,8 @@ def before(self, hook_context: HookContext, hints: dict) -> EvaluationContext:
3838
:return: An EvaluationContext. It will be merged with the
3939
EvaluationContext instances from other hooks, the client and API.
4040
"""
41-
pass
41+
return None
4242

43-
@abstractmethod
4443
def after(
4544
self, hook_context: HookContext, details: FlagEvaluationDetails, hints: dict
4645
):
@@ -54,7 +53,6 @@ def after(
5453
"""
5554
pass
5655

57-
@abstractmethod
5856
def error(self, hook_context: HookContext, exception: Exception, hints: dict):
5957
"""
6058
Run when evaluation encounters an error. Errors thrown will be swallowed.
@@ -65,7 +63,6 @@ def error(self, hook_context: HookContext, exception: Exception, hints: dict):
6563
"""
6664
pass
6765

68-
@abstractmethod
6966
def finally_after(self, hook_context: HookContext, hints: dict):
7067
"""
7168
Run after flag evaluation, including any error processing.
@@ -76,7 +73,6 @@ def finally_after(self, hook_context: HookContext, hints: dict):
7673
"""
7774
pass
7875

79-
@abstractmethod
8076
def supports_flag_value_type(self, flag_type: FlagType) -> bool:
8177
"""
8278
Check to see if the hook supports the particular flag type.

tests/hook/test_hook_support.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from unittest.mock import ANY
1+
from unittest.mock import ANY, MagicMock
22

3+
from openfeature.evaluation_context import EvaluationContext
34
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType
4-
from openfeature.hook import HookContext
5+
from openfeature.hook import Hook, HookContext
56
from openfeature.hook.hook_support import (
67
after_all_hooks,
78
after_hooks,
@@ -37,6 +38,23 @@ def test_before_hooks_run_before_method(mock_hook):
3738
mock_hook.before.assert_called_with(hook_context=hook_context, hints=hook_hints)
3839

3940

41+
def test_before_hooks_merges_evaluation_contexts():
42+
# Given
43+
hook_context = HookContext("flag_key", FlagType.BOOLEAN, True, "")
44+
hook_1 = MagicMock(spec=Hook)
45+
hook_1.before.return_value = EvaluationContext("foo", {"key_1": "val_1"})
46+
hook_2 = MagicMock(spec=Hook)
47+
hook_2.before.return_value = EvaluationContext("bar", {"key_2": "val_2"})
48+
hook_3 = MagicMock(spec=Hook)
49+
hook_3.before.return_value = None
50+
51+
# When
52+
context = before_hooks(FlagType.BOOLEAN, hook_context, [hook_1, hook_2, hook_3])
53+
54+
# Then
55+
assert context == EvaluationContext("bar", {"key_1": "val_1", "key_2": "val_2"})
56+
57+
4058
def test_after_hooks_run_after_method(mock_hook):
4159
# Given
4260
hook_context = HookContext("flag_key", FlagType.BOOLEAN, True, "")

0 commit comments

Comments
 (0)