Skip to content

Commit 3338f29

Browse files
committed
add readme, async to in memory provider and a few more tests
Signed-off-by: leohoare <[email protected]>
1 parent 5380ba6 commit 3338f29

File tree

3 files changed

+182
-43
lines changed

3 files changed

+182
-43
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,57 @@ class MyProvider(AbstractProvider):
390390
...
391391
```
392392

393+
Providers can also be extended to support async functionality.
394+
To support add asynchronous calls to a provider:
395+
* Implement the `AbstractProvider` as shown above.
396+
* Define asynchronous calls for each data type.
397+
398+
```python
399+
class MyProvider(AbstractProvider):
400+
...
401+
async def resolve_boolean_details_async(
402+
self,
403+
flag_key: str,
404+
default_value: bool,
405+
evaluation_context: Optional[EvaluationContext] = None,
406+
) -> FlagResolutionDetails[bool]:
407+
...
408+
409+
async def resolve_string_details_async(
410+
self,
411+
flag_key: str,
412+
default_value: str,
413+
evaluation_context: Optional[EvaluationContext] = None,
414+
) -> FlagResolutionDetails[str]:
415+
...
416+
417+
async def resolve_integer_details_async(
418+
self,
419+
flag_key: str,
420+
default_value: int,
421+
evaluation_context: Optional[EvaluationContext] = None,
422+
) -> FlagResolutionDetails[int]:
423+
...
424+
425+
async def resolve_float_details_async(
426+
self,
427+
flag_key: str,
428+
default_value: float,
429+
evaluation_context: Optional[EvaluationContext] = None,
430+
) -> FlagResolutionDetails[float]:
431+
...
432+
433+
async def resolve_object_details_async(
434+
self,
435+
flag_key: str,
436+
default_value: Union[dict, list],
437+
evaluation_context: Optional[EvaluationContext] = None,
438+
) -> FlagResolutionDetails[Union[dict, list]]:
439+
...
440+
441+
```
442+
443+
393444
> 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!
394445
395446
### Develop a hook

openfeature/provider/in_memory_provider.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ def resolve_boolean_details(
7676
) -> FlagResolutionDetails[bool]:
7777
return self._resolve(flag_key, evaluation_context)
7878

79+
async def resolve_boolean_details_async(
80+
self,
81+
flag_key: str,
82+
default_value: bool,
83+
evaluation_context: typing.Optional[EvaluationContext] = None,
84+
) -> FlagResolutionDetails[bool]:
85+
return await self._resolve_async(flag_key, evaluation_context)
86+
7987
def resolve_string_details(
8088
self,
8189
flag_key: str,
@@ -84,6 +92,14 @@ def resolve_string_details(
8492
) -> FlagResolutionDetails[str]:
8593
return self._resolve(flag_key, evaluation_context)
8694

95+
async def resolve_string_details_async(
96+
self,
97+
flag_key: str,
98+
default_value: str,
99+
evaluation_context: typing.Optional[EvaluationContext] = None,
100+
) -> FlagResolutionDetails[str]:
101+
return await self._resolve_async(flag_key, evaluation_context)
102+
87103
def resolve_integer_details(
88104
self,
89105
flag_key: str,
@@ -92,6 +108,14 @@ def resolve_integer_details(
92108
) -> FlagResolutionDetails[int]:
93109
return self._resolve(flag_key, evaluation_context)
94110

111+
async def resolve_integer_details_async(
112+
self,
113+
flag_key: str,
114+
default_value: int,
115+
evaluation_context: typing.Optional[EvaluationContext] = None,
116+
) -> FlagResolutionDetails[int]:
117+
return await self._resolve_async(flag_key, evaluation_context)
118+
95119
def resolve_float_details(
96120
self,
97121
flag_key: str,
@@ -100,6 +124,14 @@ def resolve_float_details(
100124
) -> FlagResolutionDetails[float]:
101125
return self._resolve(flag_key, evaluation_context)
102126

127+
async def resolve_float_details_async(
128+
self,
129+
flag_key: str,
130+
default_value: float,
131+
evaluation_context: typing.Optional[EvaluationContext] = None,
132+
) -> FlagResolutionDetails[float]:
133+
return await self._resolve_async(flag_key, evaluation_context)
134+
103135
def resolve_object_details(
104136
self,
105137
flag_key: str,
@@ -108,6 +140,14 @@ def resolve_object_details(
108140
) -> FlagResolutionDetails[typing.Union[dict, list]]:
109141
return self._resolve(flag_key, evaluation_context)
110142

143+
async def resolve_object_details_async(
144+
self,
145+
flag_key: str,
146+
default_value: typing.Union[dict, list],
147+
evaluation_context: typing.Optional[EvaluationContext] = None,
148+
) -> FlagResolutionDetails[typing.Union[dict, list]]:
149+
return await self._resolve_async(flag_key, evaluation_context)
150+
111151
def _resolve(
112152
self,
113153
flag_key: str,
@@ -117,3 +157,10 @@ def _resolve(
117157
if flag is None:
118158
raise FlagNotFoundError(f"Flag '{flag_key}' not found")
119159
return flag.resolve(evaluation_context)
160+
161+
async def _resolve_async(
162+
self,
163+
flag_key: str,
164+
evaluation_context: typing.Optional[EvaluationContext],
165+
) -> FlagResolutionDetails[V]:
166+
return self._resolve(flag_key, evaluation_context)

tests/provider/test_in_memory_provider.py

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@ def test_should_return_in_memory_provider_metadata():
1717
assert metadata.name == "In-Memory Provider"
1818

1919

20-
def test_should_handle_unknown_flags_correctly():
20+
@pytest.mark.asyncio
21+
async def test_should_handle_unknown_flags_correctly():
2122
# Given
2223
provider = InMemoryProvider({})
2324
# When
2425
with pytest.raises(FlagNotFoundError):
2526
provider.resolve_boolean_details(flag_key="Key", default_value=True)
27+
with pytest.raises(FlagNotFoundError):
28+
await provider.resolve_integer_details_async(flag_key="Key", default_value=1)
2629
# Then
2730

2831

29-
def test_calls_context_evaluator_if_present():
32+
@pytest.mark.asyncio
33+
async def test_calls_context_evaluator_if_present():
3034
# Given
3135
def context_evaluator(flag: InMemoryFlag, evaluation_context: dict):
3236
return FlagResolutionDetails(
@@ -44,57 +48,81 @@ def context_evaluator(flag: InMemoryFlag, evaluation_context: dict):
4448
}
4549
)
4650
# When
47-
flag = provider.resolve_boolean_details(flag_key="Key", default_value=False)
51+
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=False)
52+
flag_async = await provider.resolve_boolean_details_async(
53+
flag_key="Key", default_value=False
54+
)
4855
# Then
49-
assert flag is not None
50-
assert flag.value is False
51-
assert isinstance(flag.value, bool)
52-
assert flag.reason == Reason.TARGETING_MATCH
56+
assert flag_sync == flag_async
57+
for flag in [flag_sync, flag_async]:
58+
assert flag is not None
59+
assert flag.value is False
60+
assert isinstance(flag.value, bool)
61+
assert flag.reason == Reason.TARGETING_MATCH
5362

5463

55-
def test_should_resolve_boolean_flag_from_in_memory():
64+
@pytest.mark.asyncio
65+
async def test_should_resolve_boolean_flag_from_in_memory():
5666
# Given
5767
provider = InMemoryProvider(
5868
{"Key": InMemoryFlag("true", {"true": True, "false": False})}
5969
)
6070
# When
61-
flag = provider.resolve_boolean_details(flag_key="Key", default_value=False)
71+
flag_sync = provider.resolve_boolean_details(flag_key="Key", default_value=False)
72+
flag_async = await provider.resolve_boolean_details_async(
73+
flag_key="Key", default_value=False
74+
)
6275
# Then
63-
assert flag is not None
64-
assert flag.value is True
65-
assert isinstance(flag.value, bool)
66-
assert flag.variant == "true"
76+
assert flag_sync == flag_async
77+
for flag in [flag_sync, flag_async]:
78+
assert flag is not None
79+
assert flag.value is True
80+
assert isinstance(flag.value, bool)
81+
assert flag.variant == "true"
6782

6883

69-
def test_should_resolve_integer_flag_from_in_memory():
84+
@pytest.mark.asyncio
85+
async def test_should_resolve_integer_flag_from_in_memory():
7086
# Given
7187
provider = InMemoryProvider(
7288
{"Key": InMemoryFlag("hundred", {"zero": 0, "hundred": 100})}
7389
)
7490
# When
75-
flag = provider.resolve_integer_details(flag_key="Key", default_value=0)
91+
flag_sync = provider.resolve_integer_details(flag_key="Key", default_value=0)
92+
flag_async = await provider.resolve_integer_details_async(
93+
flag_key="Key", default_value=0
94+
)
7695
# Then
77-
assert flag is not None
78-
assert flag.value == 100
79-
assert isinstance(flag.value, Number)
80-
assert flag.variant == "hundred"
96+
assert flag_sync == flag_async
97+
for flag in [flag_sync, flag_async]:
98+
assert flag is not None
99+
assert flag.value == 100
100+
assert isinstance(flag.value, Number)
101+
assert flag.variant == "hundred"
81102

82103

83-
def test_should_resolve_float_flag_from_in_memory():
104+
@pytest.mark.asyncio
105+
async def test_should_resolve_float_flag_from_in_memory():
84106
# Given
85107
provider = InMemoryProvider(
86108
{"Key": InMemoryFlag("ten", {"zero": 0.0, "ten": 10.23})}
87109
)
88110
# When
89-
flag = provider.resolve_float_details(flag_key="Key", default_value=0.0)
111+
flag_sync = provider.resolve_float_details(flag_key="Key", default_value=0.0)
112+
flag_async = await provider.resolve_float_details_async(
113+
flag_key="Key", default_value=0.0
114+
)
90115
# Then
91-
assert flag is not None
92-
assert flag.value == 10.23
93-
assert isinstance(flag.value, Number)
94-
assert flag.variant == "ten"
116+
assert flag_sync == flag_async
117+
for flag in [flag_sync, flag_async]:
118+
assert flag is not None
119+
assert flag.value == 10.23
120+
assert isinstance(flag.value, Number)
121+
assert flag.variant == "ten"
95122

96123

97-
def test_should_resolve_string_flag_from_in_memory():
124+
@pytest.mark.asyncio
125+
async def test_should_resolve_string_flag_from_in_memory():
98126
# Given
99127
provider = InMemoryProvider(
100128
{
@@ -105,29 +133,39 @@ def test_should_resolve_string_flag_from_in_memory():
105133
}
106134
)
107135
# When
108-
flag = provider.resolve_string_details(flag_key="Key", default_value="Default")
136+
flag_sync = provider.resolve_string_details(flag_key="Key", default_value="Default")
137+
flag_async = await provider.resolve_string_details_async(
138+
flag_key="Key", default_value="Default"
139+
)
109140
# Then
110-
assert flag is not None
111-
assert flag.value == "String"
112-
assert isinstance(flag.value, str)
113-
assert flag.variant == "stringVariant"
141+
assert flag_sync == flag_async
142+
for flag in [flag_sync, flag_async]:
143+
assert flag is not None
144+
assert flag.value == "String"
145+
assert isinstance(flag.value, str)
146+
assert flag.variant == "stringVariant"
114147

115148

116-
def test_should_resolve_list_flag_from_in_memory():
149+
@pytest.mark.asyncio
150+
async def test_should_resolve_list_flag_from_in_memory():
117151
# Given
118152
provider = InMemoryProvider(
119153
{"Key": InMemoryFlag("twoItems", {"empty": [], "twoItems": ["item1", "item2"]})}
120154
)
121155
# When
122-
flag = provider.resolve_object_details(flag_key="Key", default_value=[])
156+
flag_sync = provider.resolve_object_details(flag_key="Key", default_value=[])
157+
flag_async = provider.resolve_object_details(flag_key="Key", default_value=[])
123158
# Then
124-
assert flag is not None
125-
assert flag.value == ["item1", "item2"]
126-
assert isinstance(flag.value, list)
127-
assert flag.variant == "twoItems"
159+
assert flag_sync == flag_async
160+
for flag in [flag_sync, flag_async]:
161+
assert flag is not None
162+
assert flag.value == ["item1", "item2"]
163+
assert isinstance(flag.value, list)
164+
assert flag.variant == "twoItems"
128165

129166

130-
def test_should_resolve_object_flag_from_in_memory():
167+
@pytest.mark.asyncio
168+
async def test_should_resolve_object_flag_from_in_memory():
131169
# Given
132170
return_value = {
133171
"String": "string",
@@ -138,9 +176,12 @@ def test_should_resolve_object_flag_from_in_memory():
138176
{"Key": InMemoryFlag("obj", {"obj": return_value, "empty": {}})}
139177
)
140178
# When
141-
flag = provider.resolve_object_details(flag_key="Key", default_value={})
179+
flag_sync = provider.resolve_object_details(flag_key="Key", default_value={})
180+
flag_async = provider.resolve_object_details(flag_key="Key", default_value={})
142181
# Then
143-
assert flag is not None
144-
assert flag.value == return_value
145-
assert isinstance(flag.value, dict)
146-
assert flag.variant == "obj"
182+
assert flag_sync == flag_async
183+
for flag in [flag_sync, flag_async]:
184+
assert flag is not None
185+
assert flag.value == return_value
186+
assert isinstance(flag.value, dict)
187+
assert flag.variant == "obj"

0 commit comments

Comments
 (0)