Skip to content

Commit

Permalink
fix unit tests, increased coverage, simplified implementation
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 df25571 commit e94b06b
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 168 deletions.
56 changes: 15 additions & 41 deletions openfeature/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,45 +470,21 @@ def _typecheck_flag_value(value: typing.Any, flag_type: FlagType) -> None:
raise TypeMismatchError(f"Expected type {_type} but got {type(value)}")


class AsyncOpenFeatureClient:
def __init__(
self,
domain: typing.Optional[str],
version: typing.Optional[str],
context: typing.Optional[EvaluationContext] = None,
hooks: typing.Optional[typing.List[Hook]] = None,
) -> None:
self.domain = domain
self.version = version
self.context = context or EvaluationContext()
self.hooks = hooks or []

@property
def provider(self) -> FeatureProvider:
return provider_registry.get_provider(self.domain)

def get_provider_status(self) -> ProviderStatus:
return provider_registry.get_provider_status(self.provider)

def get_metadata(self) -> ClientMetadata:
return ClientMetadata(domain=self.domain)

def add_hooks(self, hooks: typing.List[Hook]) -> None:
self.hooks = self.hooks + hooks

class AsyncOpenFeatureClient(OpenFeatureClient):
async def get_boolean_value(
self,
flag_key: str,
default_value: bool,
evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> bool:
return await self.get_boolean_value(
details = await self.get_boolean_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
)
return details.value

async def get_boolean_details(
self,
Expand All @@ -532,12 +508,13 @@ async def get_string_value(
evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> str:
return await self.get_string_details(
details = await self.get_string_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value
)
return details.value

async def get_string_details(
self,
Expand All @@ -561,12 +538,13 @@ async def get_integer_value(
evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> int:
return await self.get_integer_details(
details = await self.get_integer_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value
)
return details.value

async def get_integer_details(
self,
Expand All @@ -590,12 +568,13 @@ async def get_float_value(
evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> float:
return await self.get_float_details(
details = await self.get_float_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value
)
return details.value

async def get_float_details(
self,
Expand All @@ -619,12 +598,13 @@ async def get_object_value(
evaluation_context: typing.Optional[EvaluationContext] = None,
flag_evaluation_options: typing.Optional[FlagEvaluationOptions] = None,
) -> typing.Union[dict, list]:
return await self.get_object_details(
details = await self.get_object_details(
flag_key,
default_value,
evaluation_context,
flag_evaluation_options,
).value
)
return details.value

async def get_object_details(
self,
Expand Down Expand Up @@ -844,9 +824,3 @@ async def _create_provider_evaluation(
error_code=resolution.error_code,
error_message=resolution.error_message,
)

def add_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
_event_support.add_client_handler(self, event, handler)

def remove_handler(self, event: ProviderEvent, handler: EventHandler) -> None:
_event_support.remove_client_handler(self, event, handler)
8 changes: 8 additions & 0 deletions openfeature/hook/_hook_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def error_hooks(
flag_type=flag_type, hooks=hooks, hook_method=HookType.ERROR, **kwargs
)


async def error_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -33,6 +34,7 @@ async def error_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 @@ -44,6 +46,7 @@ def after_all_hooks(
flag_type=flag_type, hooks=hooks, hook_method=HookType.FINALLY_AFTER, **kwargs
)


async def after_all_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
Expand All @@ -68,6 +71,7 @@ def after_hooks(
flag_type=flag_type, hooks=hooks, hook_method=HookType.AFTER, **kwargs
)


async def after_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
Expand Down Expand Up @@ -98,6 +102,7 @@ def before_hooks(

return EvaluationContext()


async def before_hooks_async(
flag_type: FlagType,
hook_context: HookContext,
Expand Down Expand Up @@ -137,6 +142,7 @@ def _execute_hooks(
if hook.supports_flag_value_type(flag_type)
]


async def _execute_hooks_async(
flag_type: FlagType,
hooks: typing.List[Hook],
Expand Down Expand Up @@ -183,6 +189,7 @@ def _execute_hooks_unchecked(
if hook.supports_flag_value_type(flag_type)
]


async def _execute_hooks_async_unchecked(
flag_type: FlagType,
hooks: typing.List[Hook],
Expand Down Expand Up @@ -228,6 +235,7 @@ def _execute_hook_checked(
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]:
Expand Down
45 changes: 1 addition & 44 deletions openfeature/provider/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,32 +166,7 @@ def emit(self, event: ProviderEvent, details: ProviderEventDetails) -> None:
self._on_emit(self, event, details)


class AsyncFeatureProvider(FeatureProvider):
async def attach(
self,
on_emit: typing.Callable[
[FeatureProvider, ProviderEvent, ProviderEventDetails], None
],
) -> None:
self._on_emit = on_emit

async def detach(self) -> None:
if hasattr(self, "_on_emit"):
del self._on_emit

async def initialize(self, evaluation_context: EvaluationContext) -> None:
pass

async def shutdown(self) -> None:
pass

@abstractmethod
async def get_metadata(self) -> Metadata:
pass

async def get_provider_hooks(self) -> typing.List[Hook]:
return []

class AsyncAbstractProvider(AbstractProvider):
@abstractmethod
async def resolve_boolean_details(
self,
Expand Down Expand Up @@ -236,21 +211,3 @@ def resolve_object_details(
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[typing.Union[dict, list]]:
pass

async def emit_provider_ready(self, details: ProviderEventDetails) -> None:
self.emit(ProviderEvent.PROVIDER_READY, details)

async def emit_provider_configuration_changed(
self, details: ProviderEventDetails
) -> None:
self.emit(ProviderEvent.PROVIDER_CONFIGURATION_CHANGED, details)

async def emit_provider_error(self, details: ProviderEventDetails) -> None:
self.emit(ProviderEvent.PROVIDER_ERROR, details)

async def emit_provider_stale(self, details: ProviderEventDetails) -> None:
self.emit(ProviderEvent.PROVIDER_STALE, details)

async def emit(self, event: ProviderEvent, details: ProviderEventDetails) -> None:
if hasattr(self, "_on_emit"):
self._on_emit(self, event, details)
62 changes: 62 additions & 0 deletions openfeature/provider/no_op_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,65 @@ def resolve_object_details(
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)


class AsyncNoOpProvider(NoOpProvider):
async def resolve_boolean_details(
self,
flag_key: str,
default_value: bool,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[bool]:
return FlagResolutionDetails(
value=default_value,
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)

async def resolve_string_details(
self,
flag_key: str,
default_value: str,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[str]:
return FlagResolutionDetails(
value=default_value,
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)

async def resolve_integer_details(
self,
flag_key: str,
default_value: int,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[int]:
return FlagResolutionDetails(
value=default_value,
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)

async def resolve_float_details(
self,
flag_key: str,
default_value: float,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[float]:
return FlagResolutionDetails(
value=default_value,
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)

async def resolve_object_details(
self,
flag_key: str,
default_value: typing.Union[dict, list],
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[typing.Union[dict, list]]:
return FlagResolutionDetails(
value=default_value,
reason=Reason.DEFAULT,
variant=PASSED_IN_DEFAULT,
)
17 changes: 16 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from openfeature import api
from openfeature.provider.no_op_provider import NoOpProvider
from openfeature.provider.no_op_provider import AsyncNoOpProvider, NoOpProvider


@pytest.fixture(autouse=True)
Expand All @@ -13,7 +13,22 @@ def clear_providers():
api.clear_providers()


@pytest.fixture(autouse=True)
def clear_hooks_fixture():
"""
For tests that use add_hooks(), we need to clear the hooks to avoid issues
in other tests.
"""
api.clear_hooks()


@pytest.fixture()
def no_op_provider_client():
api.set_provider(NoOpProvider())
return api.get_client()


@pytest.fixture()
def no_op_provider_client_async():
api.set_provider(AsyncNoOpProvider())
return api.get_client_async("my-async-client")
12 changes: 12 additions & 0 deletions tests/hook/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

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


@pytest.fixture()
Expand All @@ -14,3 +15,14 @@ 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 e94b06b

Please sign in to comment.