From 0c314ab77cd60d3347aea7f733d324a6228e8871 Mon Sep 17 00:00:00 2001 From: Michael Beemer Date: Wed, 1 Nov 2023 14:56:48 -0400 Subject: [PATCH] fix!: raise error if the flag wasn't found using the in-memory provider (#228) Signed-off-by: Michael Beemer --- openfeature/provider/in_memory_provider.py | 21 ++++++---------- tests/features/data.py | 7 ------ tests/provider/test_in_memory_provider.py | 28 +++++++--------------- tests/test_client.py | 1 - 4 files changed, 16 insertions(+), 41 deletions(-) diff --git a/openfeature/provider/in_memory_provider.py b/openfeature/provider/in_memory_provider.py index b8cbc94a..1f526bca 100644 --- a/openfeature/provider/in_memory_provider.py +++ b/openfeature/provider/in_memory_provider.py @@ -3,7 +3,7 @@ from openfeature._backports.strenum import StrEnum from openfeature.evaluation_context import EvaluationContext -from openfeature.exception import ErrorCode +from openfeature.exception import FlagNotFoundError from openfeature.flag_evaluation import FlagMetadata, FlagResolutionDetails, Reason from openfeature.hook import Hook from openfeature.provider.metadata import Metadata @@ -26,7 +26,6 @@ class State(StrEnum): ENABLED = "ENABLED" DISABLED = "DISABLED" - flag_key: str default_variant: str variants: typing.Dict[str, T] flag_metadata: FlagMetadata = field(default_factory=dict) @@ -74,7 +73,7 @@ def resolve_boolean_details( default_value: bool, evaluation_context: typing.Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[bool]: - return self._resolve(flag_key, default_value, evaluation_context) + return self._resolve(flag_key, evaluation_context) def resolve_string_details( self, @@ -82,7 +81,7 @@ def resolve_string_details( default_value: str, evaluation_context: typing.Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[str]: - return self._resolve(flag_key, default_value, evaluation_context) + return self._resolve(flag_key, evaluation_context) def resolve_integer_details( self, @@ -90,7 +89,7 @@ def resolve_integer_details( default_value: int, evaluation_context: typing.Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[int]: - return self._resolve(flag_key, default_value, evaluation_context) + return self._resolve(flag_key, evaluation_context) def resolve_float_details( self, @@ -98,7 +97,7 @@ def resolve_float_details( default_value: float, evaluation_context: typing.Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[float]: - return self._resolve(flag_key, default_value, evaluation_context) + return self._resolve(flag_key, evaluation_context) def resolve_object_details( self, @@ -106,20 +105,14 @@ def resolve_object_details( default_value: typing.Union[dict, list], evaluation_context: typing.Optional[EvaluationContext] = None, ) -> FlagResolutionDetails[typing.Union[dict, list]]: - return self._resolve(flag_key, default_value, evaluation_context) + return self._resolve(flag_key, evaluation_context) def _resolve( self, flag_key: str, - default_value: V, evaluation_context: typing.Optional[EvaluationContext], ) -> FlagResolutionDetails[V]: flag = self._flags.get(flag_key) if flag is None: - return FlagResolutionDetails( - value=default_value, - reason=Reason.ERROR, - error_code=ErrorCode.FLAG_NOT_FOUND, - error_message=f"Flag '{flag_key}' not found", - ) + raise FlagNotFoundError(f"Flag '{flag_key}' not found") return flag.resolve(evaluation_context) diff --git a/tests/features/data.py b/tests/features/data.py index 8172d259..3b519894 100644 --- a/tests/features/data.py +++ b/tests/features/data.py @@ -22,35 +22,30 @@ def context_func(flag: InMemoryFlag, evaluation_context: EvaluationContext): IN_MEMORY_FLAGS = { "boolean-flag": InMemoryFlag( - flag_key="boolean-flag", state=InMemoryFlag.State.ENABLED, default_variant="on", variants={"on": True, "off": False}, context_evaluator=None, ), "string-flag": InMemoryFlag( - flag_key="string-flag", state=InMemoryFlag.State.ENABLED, default_variant="greeting", variants={"greeting": "hi", "parting": "bye"}, context_evaluator=None, ), "integer-flag": InMemoryFlag( - flag_key="integer-flag", state=InMemoryFlag.State.ENABLED, default_variant="ten", variants={"one": 1, "ten": 10}, context_evaluator=None, ), "float-flag": InMemoryFlag( - flag_key="float-flag", state=InMemoryFlag.State.ENABLED, default_variant="half", variants={"tenth": 0.1, "half": 0.5}, context_evaluator=None, ), "object-flag": InMemoryFlag( - flag_key="object-flag", state=InMemoryFlag.State.ENABLED, default_variant="template", variants={ @@ -64,14 +59,12 @@ def context_func(flag: InMemoryFlag, evaluation_context: EvaluationContext): context_evaluator=None, ), "context-aware": InMemoryFlag( - flag_key="context-aware", state=InMemoryFlag.State.ENABLED, variants={"internal": "INTERNAL", "external": "EXTERNAL"}, default_variant="external", context_evaluator=context_func, ), "wrong-flag": InMemoryFlag( - flag_key="wrong-flag", state="ENABLED", variants={"one": "uno", "two": "dos"}, default_variant="one", diff --git a/tests/provider/test_in_memory_provider.py b/tests/provider/test_in_memory_provider.py index ec6066e1..505f5ec6 100644 --- a/tests/provider/test_in_memory_provider.py +++ b/tests/provider/test_in_memory_provider.py @@ -1,6 +1,7 @@ +import pytest from numbers import Number -from openfeature.exception import ErrorCode +from openfeature.exception import FlagNotFoundError from openfeature.flag_evaluation import FlagResolutionDetails, Reason from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider @@ -19,14 +20,9 @@ def test_should_handle_unknown_flags_correctly(): # Given provider = InMemoryProvider({}) # When - flag = provider.resolve_boolean_details(flag_key="Key", default_value=True) + with pytest.raises(FlagNotFoundError): + provider.resolve_boolean_details(flag_key="Key", default_value=True) # Then - assert flag is not None - assert flag.value is True - assert isinstance(flag.value, bool) - assert flag.reason == Reason.ERROR - assert flag.error_code == ErrorCode.FLAG_NOT_FOUND - assert flag.error_message == "Flag 'Key' not found" def test_calls_context_evaluator_if_present(): @@ -40,7 +36,6 @@ def context_evaluator(flag: InMemoryFlag, evaluation_context: dict): provider = InMemoryProvider( { "Key": InMemoryFlag( - "Key", "true", {"true": True, "false": False}, context_evaluator=context_evaluator, @@ -59,7 +54,7 @@ def context_evaluator(flag: InMemoryFlag, evaluation_context: dict): def test_should_resolve_boolean_flag_from_in_memory(): # Given provider = InMemoryProvider( - {"Key": InMemoryFlag("Key", "true", {"true": True, "false": False})} + {"Key": InMemoryFlag("true", {"true": True, "false": False})} ) # When flag = provider.resolve_boolean_details(flag_key="Key", default_value=False) @@ -73,7 +68,7 @@ def test_should_resolve_boolean_flag_from_in_memory(): def test_should_resolve_integer_flag_from_in_memory(): # Given provider = InMemoryProvider( - {"Key": InMemoryFlag("Key", "hundred", {"zero": 0, "hundred": 100})} + {"Key": InMemoryFlag("hundred", {"zero": 0, "hundred": 100})} ) # When flag = provider.resolve_integer_details(flag_key="Key", default_value=0) @@ -87,7 +82,7 @@ def test_should_resolve_integer_flag_from_in_memory(): def test_should_resolve_float_flag_from_in_memory(): # Given provider = InMemoryProvider( - {"Key": InMemoryFlag("Key", "ten", {"zero": 0.0, "ten": 10.23})} + {"Key": InMemoryFlag("ten", {"zero": 0.0, "ten": 10.23})} ) # When flag = provider.resolve_float_details(flag_key="Key", default_value=0.0) @@ -103,7 +98,6 @@ def test_should_resolve_string_flag_from_in_memory(): provider = InMemoryProvider( { "Key": InMemoryFlag( - "Key", "stringVariant", {"defaultVariant": "Default", "stringVariant": "String"}, ) @@ -121,11 +115,7 @@ def test_should_resolve_string_flag_from_in_memory(): def test_should_resolve_list_flag_from_in_memory(): # Given provider = InMemoryProvider( - { - "Key": InMemoryFlag( - "Key", "twoItems", {"empty": [], "twoItems": ["item1", "item2"]} - ) - } + {"Key": InMemoryFlag("twoItems", {"empty": [], "twoItems": ["item1", "item2"]})} ) # When flag = provider.resolve_object_details(flag_key="Key", default_value=[]) @@ -144,7 +134,7 @@ def test_should_resolve_object_flag_from_in_memory(): "Boolean": True, } provider = InMemoryProvider( - {"Key": InMemoryFlag("Key", "obj", {"obj": return_value, "empty": {}})} + {"Key": InMemoryFlag("obj", {"obj": return_value, "empty": {}})} ) # When flag = provider.resolve_object_details(flag_key="Key", default_value={}) diff --git a/tests/test_client.py b/tests/test_client.py index ad6def9b..1faed781 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -103,7 +103,6 @@ def test_should_pass_flag_metadata_from_resolution_to_evaluation_details(): provider = InMemoryProvider( { "Key": InMemoryFlag( - "Key", "true", {"true": True, "false": False}, flag_metadata={"foo": "bar"},