Skip to content

Commit

Permalink
update to remove async hooks
Browse files Browse the repository at this point in the history
Signed-off-by: leohoare <leo@insight.co>
  • Loading branch information
leohoare committed Nov 13, 2024
1 parent e94b06b commit 3b0c459
Show file tree
Hide file tree
Showing 6 changed files with 19 additions and 312 deletions.
24 changes: 7 additions & 17 deletions openfeature/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,9 @@
from openfeature.hook import Hook, HookContext
from openfeature.hook._hook_support import (
after_all_hooks,
after_all_hooks_async,
after_hooks,
after_hooks_async,
before_hooks,
before_hooks_async,
error_hooks,
error_hooks_async,
)
from openfeature.provider import FeatureProvider, ProviderStatus
from openfeature.provider._registry import provider_registry
Expand Down Expand Up @@ -673,7 +669,7 @@ async def evaluate_flag_details( # noqa: PLR0915

status = self.get_provider_status()
if status == ProviderStatus.NOT_READY:
await error_hooks_async(
error_hooks(
flag_type,
hook_context,
ProviderNotReadyError(),
Expand All @@ -687,7 +683,7 @@ async def evaluate_flag_details( # noqa: PLR0915
error_code=ErrorCode.PROVIDER_NOT_READY,
)
if status == ProviderStatus.FATAL:
await error_hooks_async(
error_hooks(
flag_type,
hook_context,
ProviderFatalError(),
Expand All @@ -706,7 +702,7 @@ async def evaluate_flag_details( # noqa: PLR0915
# Any resulting evaluation context from a before hook will overwrite
# duplicate fields defined globally, on the client, or in the invocation.
# Requirement 3.2.2, 4.3.4: API.context->client.context->invocation.context
invocation_context = await before_hooks_async(
invocation_context = before_hooks(
flag_type, hook_context, merged_hooks, hook_hints
)

Expand All @@ -726,7 +722,7 @@ async def evaluate_flag_details( # noqa: PLR0915
merged_context,
)

await after_hooks_async(
after_hooks(
flag_type,
hook_context,
flag_evaluation,
Expand All @@ -737,9 +733,7 @@ async def evaluate_flag_details( # noqa: PLR0915
return flag_evaluation

except OpenFeatureError as err:
await error_hooks_async(
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
)
error_hooks(flag_type, hook_context, err, reversed_merged_hooks, hook_hints)

return FlagEvaluationDetails(
flag_key=flag_key,
Expand All @@ -755,9 +749,7 @@ async def evaluate_flag_details( # noqa: PLR0915
"Unable to correctly evaluate flag with key: '%s'", flag_key
)

await error_hooks_async(
flag_type, hook_context, err, reversed_merged_hooks, hook_hints
)
error_hooks(flag_type, hook_context, err, reversed_merged_hooks, hook_hints)

error_message = getattr(err, "error_message", str(err))
return FlagEvaluationDetails(
Expand All @@ -769,9 +761,7 @@ async def evaluate_flag_details( # noqa: PLR0915
)

finally:
await after_all_hooks_async(
flag_type, hook_context, reversed_merged_hooks, hook_hints
)
after_all_hooks(flag_type, hook_context, reversed_merged_hooks, hook_hints)

async def _create_provider_evaluation(
self,
Expand Down
63 changes: 0 additions & 63 deletions openfeature/hook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,66 +128,3 @@ def supports_flag_value_type(self, flag_type: FlagType) -> bool:
or not (False)
"""
return True

class AsyncHook:
async def before(
self, hook_context: HookContext, hints: HookHints
) -> typing.Optional[EvaluationContext]:
"""
Runs before flag is resolved.
:param hook_context: Information about the particular flag evaluation
:param hints: An immutable mapping of data for users to
communicate to the hooks.
:return: An EvaluationContext. It will be merged with the
EvaluationContext instances from other hooks, the client and API.
"""
return None

async def after(
self,
hook_context: HookContext,
details: FlagEvaluationDetails[typing.Any],
hints: HookHints,
) -> None:
"""
Runs after a flag is resolved.
:param hook_context: Information about the particular flag evaluation
:param details: Information about how the flag was resolved,
including any resolved values.
:param hints: A mapping of data for users to communicate to the hooks.
"""
pass

async def error(
self, hook_context: HookContext, exception: Exception, hints: HookHints
) -> None:
"""
Run when evaluation encounters an error. Errors thrown will be swallowed.
:param hook_context: Information about the particular flag evaluation
:param exception: The exception that was thrown
:param hints: A mapping of data for users to communicate to the hooks.
"""
pass

async def finally_after(self, hook_context: HookContext, hints: HookHints) -> None:
"""
Run after flag evaluation, including any error processing.
This will always run. Errors will be swallowed.
:param hook_context: Information about the particular flag evaluation
:param hints: A mapping of data for users to communicate to the hooks.
"""
pass

def supports_flag_value_type(self, flag_type: FlagType) -> bool:
"""
Check to see if the hook supports the particular flag type.
:param flag_type: particular type of the flag
:return: a boolean containing whether the flag type is supported (True)
or not (False)
"""
return True
124 changes: 0 additions & 124 deletions openfeature/hook/_hook_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,6 @@ def error_hooks(
)


async def error_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
exception: Exception,
hooks: typing.List[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "exception": exception, "hints": hints}
await _execute_hooks_async(
flag_type=flag_type, hooks=hooks, hook_method=HookType.ERROR, **kwargs
)


def after_all_hooks(
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -47,18 +34,6 @@ def after_all_hooks(
)


async def after_all_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
hooks: typing.List[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "hints": hints}
await _execute_hooks_async(
flag_type=flag_type, hooks=hooks, hook_method=HookType.FINALLY_AFTER, **kwargs
)


def after_hooks(
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -72,19 +47,6 @@ def after_hooks(
)


async def after_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
details: FlagEvaluationDetails[typing.Any],
hooks: typing.List[Hook],
hints: typing.Optional[HookHints] = None,
) -> None:
kwargs = {"hook_context": hook_context, "details": details, "hints": hints}
await _execute_hooks_async_unchecked(
flag_type=flag_type, hooks=hooks, hook_method=HookType.AFTER, **kwargs
)


def before_hooks(
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -103,23 +65,6 @@ def before_hooks(
return EvaluationContext()


async def before_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
hooks: typing.List[Hook],
hints: typing.Optional[HookHints] = None,
) -> EvaluationContext:
kwargs = {"hook_context": hook_context, "hints": hints}
executed_hooks = await _execute_hooks_async(
flag_type=flag_type, hooks=hooks, hook_method=HookType.BEFORE, **kwargs
)
filtered_hooks = [result for result in executed_hooks if result is not None]
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],
Expand All @@ -143,29 +88,6 @@ def _execute_hooks(
]


async def _execute_hooks_async(
flag_type: FlagType,
hooks: typing.List[Hook],
hook_method: HookType,
**kwargs: typing.Any,
) -> 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
"""
return [
await _execute_hook_checked_async(hook, hook_method, **kwargs)
for hook in hooks
if hook.supports_flag_value_type(flag_type)
]


def _execute_hooks_unchecked(
flag_type: FlagType,
hooks: typing.List[Hook],
Expand All @@ -190,30 +112,6 @@ def _execute_hooks_unchecked(
]


async def _execute_hooks_async_unchecked(
flag_type: FlagType,
hooks: typing.List[Hook],
hook_method: HookType,
**kwargs: typing.Any,
) -> typing.List[typing.Optional[EvaluationContext]]:
"""
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
"""
return [
await getattr(hook, hook_method.value)(**kwargs)
for hook in hooks
if hook.supports_flag_value_type(flag_type)
]


def _execute_hook_checked(
hook: Hook, hook_method: HookType, **kwargs: typing.Any
) -> typing.Optional[EvaluationContext]:
Expand All @@ -234,25 +132,3 @@ def _execute_hook_checked(
except Exception: # pragma: no cover
logger.exception(f"Exception when running {hook_method.value} hooks")
return None


async def _execute_hook_checked_async(
hook: Hook, hook_method: HookType, **kwargs: typing.Any
) -> typing.Optional[EvaluationContext]:
"""
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 typing.cast(
"typing.Optional[EvaluationContext]",
await getattr(hook, hook_method.value)(**kwargs),
)
except Exception: # pragma: no cover
logger.exception(f"Exception when running {hook_method.value} hooks")
return None
12 changes: 0 additions & 12 deletions tests/hook/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import pytest

from openfeature.evaluation_context import EvaluationContext
from openfeature.hook import AsyncHook


@pytest.fixture()
Expand All @@ -15,14 +14,3 @@ def mock_hook():
mock_hook.error.return_value = None
mock_hook.finally_after.return_value = None
return mock_hook


@pytest.fixture()
def mock_hook_async():
mock_hook = AsyncHook()
mock_hook.supports_flag_value_type = mock.MagicMock(return_value=True)
mock_hook.before = mock.AsyncMock(return_value=None)
mock_hook.after = mock.AsyncMock(return_value=None)
mock_hook.error = mock.AsyncMock(return_value=None)
mock_hook.finally_after = mock.AsyncMock(return_value=None)
return mock_hook
Loading

0 comments on commit 3b0c459

Please sign in to comment.