-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor!: simplify namespaces to make public API more pythonic (#172)
* refactor!: simplify namespaces to make public API more pythonic Signed-off-by: Federico Bond <federicobond@gmail.com> Co-authored-by: Michael Beemer <beeme1mr@users.noreply.github.com> Co-authored-by: Todd Baert <todd.baert@dynatrace.com>
- Loading branch information
1 parent
6f7cdb8
commit 793ced1
Showing
35 changed files
with
286 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
11 changes: 10 additions & 1 deletion
11
open_feature/exception/exceptions.py → open_feature/exception.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from __future__ import annotations | ||
import typing | ||
from dataclasses import dataclass, field | ||
|
||
from open_feature._backports.strenum import StrEnum | ||
from open_feature.exception import ErrorCode | ||
|
||
if typing.TYPE_CHECKING: # resolves a circular dependency in type annotations | ||
from open_feature.hook import Hook | ||
|
||
|
||
class FlagType(StrEnum): | ||
BOOLEAN = "BOOLEAN" | ||
STRING = "STRING" | ||
OBJECT = "OBJECT" | ||
FLOAT = "FLOAT" | ||
INTEGER = "INTEGER" | ||
|
||
|
||
class Reason(StrEnum): | ||
CACHED = "CACHED" | ||
DEFAULT = "DEFAULT" | ||
DISABLED = "DISABLED" | ||
ERROR = "ERROR" | ||
STATIC = "STATIC" | ||
SPLIT = "SPLIT" | ||
TARGETING_MATCH = "TARGETING_MATCH" | ||
UNKNOWN = "UNKNOWN" | ||
|
||
|
||
T = typing.TypeVar("T", covariant=True) | ||
|
||
|
||
@dataclass | ||
class FlagEvaluationDetails(typing.Generic[T]): | ||
flag_key: str | ||
value: T | ||
variant: typing.Optional[str] = None | ||
reason: typing.Optional[Reason] = None | ||
error_code: typing.Optional[ErrorCode] = None | ||
error_message: typing.Optional[str] = None | ||
|
||
|
||
@dataclass | ||
class FlagEvaluationOptions: | ||
hooks: typing.List[Hook] = field(default_factory=list) | ||
hook_hints: dict = field(default_factory=dict) | ||
|
||
|
||
U = typing.TypeVar("U", covariant=True) | ||
|
||
|
||
@dataclass | ||
class FlagResolutionDetails(typing.Generic[U]): | ||
value: U | ||
error_code: typing.Optional[ErrorCode] = None | ||
error_message: typing.Optional[str] = None | ||
reason: typing.Optional[Reason] = None | ||
variant: typing.Optional[str] = None | ||
flag_metadata: typing.Optional[str] = None |
Empty file.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
27 changes: 23 additions & 4 deletions
27
open_feature/hooks/hook.py → open_feature/hook/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import logging | ||
import typing | ||
from functools import reduce | ||
|
||
from open_feature.evaluation_context import EvaluationContext | ||
from open_feature.flag_evaluation import FlagEvaluationDetails, FlagType | ||
from open_feature.hook import Hook, HookContext, HookType | ||
|
||
|
||
def error_hooks( | ||
flag_type: FlagType, | ||
hook_context: HookContext, | ||
exception: Exception, | ||
hooks: typing.List[Hook], | ||
hints: typing.Optional[typing.Mapping] = None, | ||
): | ||
kwargs = {"hook_context": hook_context, "exception": exception, "hints": hints} | ||
_execute_hooks( | ||
flag_type=flag_type, hooks=hooks, hook_method=HookType.ERROR, **kwargs | ||
) | ||
|
||
|
||
def after_all_hooks( | ||
flag_type: FlagType, | ||
hook_context: HookContext, | ||
hooks: typing.List[Hook], | ||
hints: typing.Optional[typing.Mapping] = None, | ||
): | ||
kwargs = {"hook_context": hook_context, "hints": hints} | ||
_execute_hooks( | ||
flag_type=flag_type, hooks=hooks, hook_method=HookType.FINALLY_AFTER, **kwargs | ||
) | ||
|
||
|
||
def after_hooks( | ||
flag_type: FlagType, | ||
hook_context: HookContext, | ||
details: FlagEvaluationDetails, | ||
hooks: typing.List[Hook], | ||
hints: typing.Optional[typing.Mapping] = None, | ||
): | ||
kwargs = {"hook_context": hook_context, "details": details, "hints": hints} | ||
_execute_hooks_unchecked( | ||
flag_type=flag_type, hooks=hooks, hook_method=HookType.AFTER, **kwargs | ||
) | ||
|
||
|
||
def before_hooks( | ||
flag_type: FlagType, | ||
hook_context: HookContext, | ||
hooks: typing.List[Hook], | ||
hints: typing.Optional[typing.Mapping] = None, | ||
) -> EvaluationContext: | ||
kwargs = {"hook_context": hook_context, "hints": hints} | ||
executed_hooks = _execute_hooks_unchecked( | ||
flag_type=flag_type, hooks=hooks, hook_method=HookType.BEFORE, **kwargs | ||
) | ||
filtered_hooks = list(filter(lambda hook: hook is not None, executed_hooks)) | ||
|
||
if filtered_hooks: | ||
return reduce(lambda a, b: a.merge(b), filtered_hooks) | ||
|
||
return EvaluationContext() | ||
|
||
|
||
def _execute_hooks( | ||
flag_type: FlagType, hooks: typing.List[Hook], hook_method: HookType, **kwargs | ||
) -> list: | ||
""" | ||
Run multiple hooks of any hook type. All of these hooks will be run through an | ||
exception check. | ||
:param flag_type: particular type of flag | ||
:param hooks: a list of hooks | ||
:param hook_method: the type of hook that is being run | ||
:param kwargs: arguments that need to be provided to the hook method | ||
:return: a list of results from the applied hook methods | ||
""" | ||
if hooks: | ||
filtered_hooks = list( | ||
filter( | ||
lambda hook: hook.supports_flag_value_type(flag_type=flag_type), hooks | ||
) | ||
) | ||
return [ | ||
_execute_hook_checked(hook, hook_method, **kwargs) | ||
for hook in filtered_hooks | ||
] | ||
return [] | ||
|
||
|
||
def _execute_hooks_unchecked( | ||
flag_type: FlagType, hooks, hook_method: HookType, **kwargs | ||
) -> list: | ||
""" | ||
Execute a single hook without checking whether an exception is thrown. This is | ||
used in the before and after hooks since any exception will be caught in the | ||
client. | ||
:param flag_type: particular type of flag | ||
:param hooks: a list of hooks | ||
:param hook_method: the type of hook that is being run | ||
:param kwargs: arguments that need to be provided to the hook method | ||
:return: a list of results from the applied hook methods | ||
""" | ||
if hooks: | ||
filtered_hooks = list( | ||
filter( | ||
lambda hook: hook.supports_flag_value_type(flag_type=flag_type), hooks | ||
) | ||
) | ||
return [getattr(hook, hook_method.value)(**kwargs) for hook in filtered_hooks] | ||
|
||
return [] | ||
|
||
|
||
def _execute_hook_checked(hook: Hook, hook_method: HookType, **kwargs): | ||
""" | ||
Try and run a single hook and catch any exception thrown. This is used in the | ||
after all and error hooks since any error thrown at this point needs to be caught. | ||
:param hook: a list of hooks | ||
:param hook_method: the type of hook that is being run | ||
:param kwargs: arguments that need to be provided to the hook method | ||
:return: the result of the hook method | ||
""" | ||
try: | ||
return getattr(hook, hook_method.value)(**kwargs) | ||
except Exception: # noqa | ||
logging.error(f"Exception when running {hook_method.value} hooks") |
Empty file.
Oops, something went wrong.