Skip to content

Commit

Permalink
add readme, async to in memory provider and a few more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
leohoare committed Jan 13, 2025
1 parent 80f54f1 commit edc6908
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 43 deletions.
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,57 @@ class MyProvider(AbstractProvider):
...
```

Providers can also be extended to support async functionality.
To support add asynchronous calls to a provider:
* Implement the `AbstractProvider` as shown above.
* Define asynchronous calls for each data type.

```python
class MyProvider(AbstractProvider):
...
async def resolve_boolean_details_async(
self,
flag_key: str,
default_value: bool,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[bool]:
...

async def resolve_string_details_async(
self,
flag_key: str,
default_value: str,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[str]:
...

async def resolve_integer_details_async(
self,
flag_key: str,
default_value: int,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[int]:
...

async def resolve_float_details_async(
self,
flag_key: str,
default_value: float,
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[float]:
...

async def resolve_object_details_async(
self,
flag_key: str,
default_value: Union[dict, list],
evaluation_context: Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[Union[dict, list]]:
...

```


> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs!
### Develop a hook
Expand Down
47 changes: 47 additions & 0 deletions openfeature/provider/in_memory_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ def resolve_boolean_details(
) -> FlagResolutionDetails[bool]:
return self._resolve(flag_key, evaluation_context)

async def resolve_boolean_details_async(
self,
flag_key: str,
default_value: bool,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[bool]:
return await self._resolve_async(flag_key, evaluation_context)

def resolve_string_details(
self,
flag_key: str,
Expand All @@ -84,6 +92,14 @@ def resolve_string_details(
) -> FlagResolutionDetails[str]:
return self._resolve(flag_key, evaluation_context)

async def resolve_string_details_async(
self,
flag_key: str,
default_value: str,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[str]:
return await self._resolve_async(flag_key, evaluation_context)

def resolve_integer_details(
self,
flag_key: str,
Expand All @@ -92,6 +108,14 @@ def resolve_integer_details(
) -> FlagResolutionDetails[int]:
return self._resolve(flag_key, evaluation_context)

async def resolve_integer_details_async(
self,
flag_key: str,
default_value: int,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[int]:
return await self._resolve_async(flag_key, evaluation_context)

def resolve_float_details(
self,
flag_key: str,
Expand All @@ -100,6 +124,14 @@ def resolve_float_details(
) -> FlagResolutionDetails[float]:
return self._resolve(flag_key, evaluation_context)

async def resolve_float_details_async(
self,
flag_key: str,
default_value: float,
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[float]:
return await self._resolve_async(flag_key, evaluation_context)

def resolve_object_details(
self,
flag_key: str,
Expand All @@ -108,6 +140,14 @@ def resolve_object_details(
) -> FlagResolutionDetails[typing.Union[dict, list]]:
return self._resolve(flag_key, evaluation_context)

async def resolve_object_details_async(
self,
flag_key: str,
default_value: typing.Union[dict, list],
evaluation_context: typing.Optional[EvaluationContext] = None,
) -> FlagResolutionDetails[typing.Union[dict, list]]:
return await self._resolve_async(flag_key, evaluation_context)

Check warning on line 149 in openfeature/provider/in_memory_provider.py

View check run for this annotation

Codecov / codecov/patch

openfeature/provider/in_memory_provider.py#L149

Added line #L149 was not covered by tests

def _resolve(
self,
flag_key: str,
Expand All @@ -117,3 +157,10 @@ def _resolve(
if flag is None:
raise FlagNotFoundError(f"Flag '{flag_key}' not found")
return flag.resolve(evaluation_context)

async def _resolve_async(
self,
flag_key: str,
evaluation_context: typing.Optional[EvaluationContext],
) -> FlagResolutionDetails[V]:
return self._resolve(flag_key, evaluation_context)
127 changes: 84 additions & 43 deletions tests/provider/test_in_memory_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ def test_should_return_in_memory_provider_metadata():
assert metadata.name == "In-Memory Provider"


def test_should_handle_unknown_flags_correctly():
@pytest.mark.asyncio
async def test_should_handle_unknown_flags_correctly():
# Given
provider = InMemoryProvider({})
# When
with pytest.raises(FlagNotFoundError):
provider.resolve_boolean_details(flag_key="Key", default_value=True)
with pytest.raises(FlagNotFoundError):
await provider.resolve_integer_details_async(flag_key="Key", default_value=1)
# Then


def test_calls_context_evaluator_if_present():
@pytest.mark.asyncio
async def test_calls_context_evaluator_if_present():
# Given
def context_evaluator(flag: InMemoryFlag, evaluation_context: dict):
return FlagResolutionDetails(
Expand All @@ -44,57 +48,81 @@ def context_evaluator(flag: InMemoryFlag, evaluation_context: dict):
}
)
# When
flag = provider.resolve_boolean_details(flag_key="Key", default_value=False)
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=False)
flag_async = await provider.resolve_boolean_details_async(
flag_key="Key", default_value=False
)
# Then
assert flag is not None
assert flag.value is False
assert isinstance(flag.value, bool)
assert flag.reason == Reason.TARGETING_MATCH
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value is False
assert isinstance(flag.value, bool)
assert flag.reason == Reason.TARGETING_MATCH


def test_should_resolve_boolean_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_boolean_flag_from_in_memory():
# Given
provider = InMemoryProvider(
{"Key": InMemoryFlag("true", {"true": True, "false": False})}
)
# When
flag = provider.resolve_boolean_details(flag_key="Key", default_value=False)
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=False)
flag_async = await provider.resolve_boolean_details_async(
flag_key="Key", default_value=False
)
# Then
assert flag is not None
assert flag.value is True
assert isinstance(flag.value, bool)
assert flag.variant == "true"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value is True
assert isinstance(flag.value, bool)
assert flag.variant == "true"


def test_should_resolve_integer_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_integer_flag_from_in_memory():
# Given
provider = InMemoryProvider(
{"Key": InMemoryFlag("hundred", {"zero": 0, "hundred": 100})}
)
# When
flag = provider.resolve_integer_details(flag_key="Key", default_value=0)
flag_sync = provider.resolve_integer_details(flag_key="Key", default_value=0)
flag_async = await provider.resolve_integer_details_async(
flag_key="Key", default_value=0
)
# Then
assert flag is not None
assert flag.value == 100
assert isinstance(flag.value, Number)
assert flag.variant == "hundred"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value == 100
assert isinstance(flag.value, Number)
assert flag.variant == "hundred"


def test_should_resolve_float_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_float_flag_from_in_memory():
# Given
provider = InMemoryProvider(
{"Key": InMemoryFlag("ten", {"zero": 0.0, "ten": 10.23})}
)
# When
flag = provider.resolve_float_details(flag_key="Key", default_value=0.0)
flag_sync = provider.resolve_float_details(flag_key="Key", default_value=0.0)
flag_async = await provider.resolve_float_details_async(
flag_key="Key", default_value=0.0
)
# Then
assert flag is not None
assert flag.value == 10.23
assert isinstance(flag.value, Number)
assert flag.variant == "ten"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value == 10.23
assert isinstance(flag.value, Number)
assert flag.variant == "ten"


def test_should_resolve_string_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_string_flag_from_in_memory():
# Given
provider = InMemoryProvider(
{
Expand All @@ -105,29 +133,39 @@ def test_should_resolve_string_flag_from_in_memory():
}
)
# When
flag = provider.resolve_string_details(flag_key="Key", default_value="Default")
flag_sync = provider.resolve_string_details(flag_key="Key", default_value="Default")
flag_async = await provider.resolve_string_details_async(
flag_key="Key", default_value="Default"
)
# Then
assert flag is not None
assert flag.value == "String"
assert isinstance(flag.value, str)
assert flag.variant == "stringVariant"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value == "String"
assert isinstance(flag.value, str)
assert flag.variant == "stringVariant"


def test_should_resolve_list_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_list_flag_from_in_memory():
# Given
provider = InMemoryProvider(
{"Key": InMemoryFlag("twoItems", {"empty": [], "twoItems": ["item1", "item2"]})}
)
# When
flag = provider.resolve_object_details(flag_key="Key", default_value=[])
flag_sync = provider.resolve_object_details(flag_key="Key", default_value=[])
flag_async = provider.resolve_object_details(flag_key="Key", default_value=[])
# Then
assert flag is not None
assert flag.value == ["item1", "item2"]
assert isinstance(flag.value, list)
assert flag.variant == "twoItems"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value == ["item1", "item2"]
assert isinstance(flag.value, list)
assert flag.variant == "twoItems"


def test_should_resolve_object_flag_from_in_memory():
@pytest.mark.asyncio
async def test_should_resolve_object_flag_from_in_memory():
# Given
return_value = {
"String": "string",
Expand All @@ -138,9 +176,12 @@ def test_should_resolve_object_flag_from_in_memory():
{"Key": InMemoryFlag("obj", {"obj": return_value, "empty": {}})}
)
# When
flag = provider.resolve_object_details(flag_key="Key", default_value={})
flag_sync = provider.resolve_object_details(flag_key="Key", default_value={})
flag_async = provider.resolve_object_details(flag_key="Key", default_value={})
# Then
assert flag is not None
assert flag.value == return_value
assert isinstance(flag.value, dict)
assert flag.variant == "obj"
assert flag_sync == flag_async
for flag in [flag_sync, flag_async]:
assert flag is not None
assert flag.value == return_value
assert isinstance(flag.value, dict)
assert flag.variant == "obj"

0 comments on commit edc6908

Please sign in to comment.