Skip to content

Commit 6984f83

Browse files
committed
feat: process flag evaluation options in client
- performs validation of options as dictionary before pulling hooks and hints - introduces immutable dictionary extension of dict following naming from 3.12 release - documented "upgrade" process for MappingProxyType Signed-off-by: Tom Carrio <[email protected]>
1 parent b39cced commit 6984f83

File tree

3 files changed

+49
-5
lines changed

3 files changed

+49
-5
lines changed

open_feature/immutable_dict/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
class MappingProxyType(dict):
2+
"""
3+
MappingProxyType is an immutable dictionary type, written to
4+
support Python 3.8 with easy transition to 3.12 upon removal
5+
of older versions.
6+
7+
See: https://stackoverflow.com/a/72474524
8+
9+
When upgrading to Python 3.12, you can update all references
10+
from:
11+
`from open_feature.immutable_dict.mapping_proxy_type import MappingProxyType`
12+
13+
to:
14+
`from types import MappingProxyType`
15+
"""
16+
def __hash__(self):
17+
return id(self)
18+
19+
def _immutable(self, *args, **kws):
20+
raise TypeError('immutable instance of dictionary')
21+
22+
__setitem__ = _immutable
23+
__delitem__ = _immutable
24+
clear = _immutable
25+
update = _immutable
26+
setdefault = _immutable
27+
pop = _immutable
28+
popitem = _immutable

open_feature/open_feature_client.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
before_hooks,
1717
error_hooks,
1818
)
19+
from open_feature.immutable_dict.mapping_proxy_type import MappingProxyType
1920
from open_feature.open_feature_evaluation_context import api_evaluation_context
2021
from open_feature.provider.no_op_provider import NoOpProvider
2122
from open_feature.provider.provider import AbstractProvider
@@ -182,6 +183,8 @@ def evaluate_flag_details(
182183
if evaluation_context is None:
183184
evaluation_context = EvaluationContext()
184185

186+
evaluation_hooks, hook_hints = self.__extract_evaluation_options(flag_evaluation_options)
187+
185188
hook_context = HookContext(
186189
flag_key=flag_key,
187190
flag_type=flag_type,
@@ -197,7 +200,7 @@ def evaluate_flag_details(
197200
# Any resulting evaluation context from a before hook will overwrite
198201
# duplicate fields defined globally, on the client, or in the invocation.
199202
invocation_context = before_hooks(
200-
flag_type, hook_context, merged_hooks, None
203+
flag_type, hook_context, merged_hooks, hook_hints
201204
)
202205
invocation_context.merge(ctx2=evaluation_context)
203206

@@ -213,12 +216,12 @@ def evaluate_flag_details(
213216
merged_context,
214217
)
215218

216-
after_hooks(type, hook_context, flag_evaluation, merged_hooks, None)
219+
after_hooks(type, hook_context, flag_evaluation, merged_hooks, hook_hints)
217220

218221
return flag_evaluation
219222

220223
except OpenFeatureError as e: # noqa
221-
error_hooks(flag_type, hook_context, e, merged_hooks, None)
224+
error_hooks(flag_type, hook_context, e, merged_hooks, hook_hints)
222225
return FlagEvaluationDetails(
223226
flag_key=flag_key,
224227
value=default_value,
@@ -229,7 +232,7 @@ def evaluate_flag_details(
229232
# Catch any type of exception here since the user can provide any exception
230233
# in the error hooks
231234
except Exception as e: # noqa
232-
error_hooks(flag_type, hook_context, e, merged_hooks, None)
235+
error_hooks(flag_type, hook_context, e, merged_hooks, hook_hints)
233236
error_message = getattr(e, "error_message", str(e))
234237
return FlagEvaluationDetails(
235238
flag_key=flag_key,
@@ -240,7 +243,7 @@ def evaluate_flag_details(
240243
)
241244

242245
finally:
243-
after_all_hooks(flag_type, hook_context, merged_hooks, None)
246+
after_all_hooks(flag_type, hook_context, merged_hooks, hook_hints)
244247

245248
def create_provider_evaluation(
246249
self,
@@ -280,3 +283,16 @@ def create_provider_evaluation(
280283
raise GeneralError(error_message="Unknown flag type")
281284

282285
return get_details_callable(*args)
286+
287+
def __extract_evaluation_options(self, flag_evaluation_options: typing.Any) -> typing.Tuple(typing.List[Hook], MappingProxyType):
288+
evaluation_hooks: typing.List[Hook] = []
289+
hook_hints: dict = {}
290+
291+
if flag_evaluation_options is dict:
292+
if 'hook_hints' in flag_evaluation_options and flag_evaluation_options['hook_hints'] is dict:
293+
hook_hints = dict(flag_evaluation_options['hook_hints'])
294+
295+
if 'hooks' in flag_evaluation_options and flag_evaluation_options['hooks'] is list:
296+
evaluation_hooks = flag_evaluation_options['hooks']
297+
298+
return (evaluation_hooks, MappingProxyType(hook_hints))

0 commit comments

Comments
 (0)