Skip to content

Commit 4b0f7ee

Browse files
committed
refactor: split logginghook into separate module
Signed-off-by: Danju Visvanathan <danju.visvanathan@gmail.com>
1 parent 876bf4b commit 4b0f7ee

4 files changed

Lines changed: 236 additions & 210 deletions

File tree

openfeature/hook/__init__.py

Lines changed: 2 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
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",
@@ -29,131 +16,6 @@
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-
15719
def add_hooks(hooks: list[Hook]) -> None:
15820
global _hooks
15921
_hooks = _hooks + hooks
@@ -166,65 +28,3 @@ def clear_hooks() -> None:
16628

16729
def 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})

openfeature/hook/hook.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from __future__ import annotations
2+
3+
import typing
4+
from collections.abc import Mapping, MutableMapping, Sequence
5+
from datetime import datetime
6+
from enum import Enum
7+
8+
from openfeature.evaluation_context import EvaluationContext
9+
from openfeature.flag_evaluation import FlagEvaluationDetails, FlagType, FlagValueType
10+
11+
if typing.TYPE_CHECKING:
12+
from openfeature.client import ClientMetadata
13+
from openfeature.provider.metadata import Metadata
14+
15+
# https://openfeature.dev/specification/sections/hooks/#requirement-461
16+
HookData = MutableMapping[str, typing.Any]
17+
18+
19+
class HookType(Enum):
20+
BEFORE = "before"
21+
AFTER = "after"
22+
FINALLY_AFTER = "finally_after"
23+
ERROR = "error"
24+
25+
26+
class HookContext:
27+
def __init__( # noqa: PLR0913
28+
self,
29+
flag_key: str,
30+
flag_type: FlagType,
31+
default_value: FlagValueType,
32+
evaluation_context: EvaluationContext,
33+
client_metadata: ClientMetadata | None = None,
34+
provider_metadata: Metadata | None = None,
35+
hook_data: HookData | None = None,
36+
):
37+
self.flag_key = flag_key
38+
self.flag_type = flag_type
39+
self.default_value = default_value
40+
self.evaluation_context = evaluation_context
41+
self.client_metadata = client_metadata
42+
self.provider_metadata = provider_metadata
43+
self.hook_data = hook_data or {}
44+
45+
def __setattr__(self, key: str, value: typing.Any) -> None:
46+
if hasattr(self, key) and key in (
47+
"flag_key",
48+
"flag_type",
49+
"default_value",
50+
"client_metadata",
51+
"provider_metadata",
52+
):
53+
raise AttributeError(f"Attribute {key!r} is immutable")
54+
super().__setattr__(key, value)
55+
56+
57+
# https://openfeature.dev/specification/sections/hooks/#requirement-421
58+
HookHintValue: typing.TypeAlias = (
59+
bool
60+
| int
61+
| float
62+
| str
63+
| datetime
64+
| Sequence["HookHintValue"]
65+
| Mapping[str, "HookHintValue"]
66+
)
67+
68+
HookHints = Mapping[str, HookHintValue]
69+
70+
71+
class Hook:
72+
def before(
73+
self, hook_context: HookContext, hints: HookHints
74+
) -> EvaluationContext | None:
75+
"""
76+
Runs before flag is resolved.
77+
78+
:param hook_context: Information about the particular flag evaluation
79+
:param hints: An immutable mapping of data for users to
80+
communicate to the hooks.
81+
:return: An EvaluationContext. It will be merged with the
82+
EvaluationContext instances from other hooks, the client and API.
83+
"""
84+
return None
85+
86+
def after(
87+
self,
88+
hook_context: HookContext,
89+
details: FlagEvaluationDetails[FlagValueType],
90+
hints: HookHints,
91+
) -> None:
92+
"""
93+
Runs after a flag is resolved.
94+
95+
:param hook_context: Information about the particular flag evaluation
96+
:param details: Information about how the flag was resolved,
97+
including any resolved values.
98+
:param hints: A mapping of data for users to communicate to the hooks.
99+
"""
100+
pass
101+
102+
def error(
103+
self, hook_context: HookContext, exception: Exception, hints: HookHints
104+
) -> None:
105+
"""
106+
Run when evaluation encounters an error. Errors thrown will be swallowed.
107+
108+
:param hook_context: Information about the particular flag evaluation
109+
:param exception: The exception that was thrown
110+
:param hints: A mapping of data for users to communicate to the hooks.
111+
"""
112+
pass
113+
114+
def finally_after(
115+
self,
116+
hook_context: HookContext,
117+
details: FlagEvaluationDetails[FlagValueType],
118+
hints: HookHints,
119+
) -> None:
120+
"""
121+
Run after flag evaluation, including any error processing.
122+
This will always run. Errors will be swallowed.
123+
124+
:param hook_context: Information about the particular flag evaluation
125+
:param hints: A mapping of data for users to communicate to the hooks.
126+
"""
127+
pass
128+
129+
def supports_flag_value_type(self, flag_type: FlagType) -> bool:
130+
"""
131+
Check to see if the hook supports the particular flag type.
132+
133+
:param flag_type: particular type of the flag
134+
:return: a boolean containing whether the flag type is supported (True)
135+
or not (False)
136+
"""
137+
return True

0 commit comments

Comments
 (0)