Skip to content

Commit a5e9a1f

Browse files
authored
FFM-11776 Add decorator context (#104)
1 parent c31caf6 commit a5e9a1f

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import functools
2+
from contextlib import contextmanager
3+
from featureflags.client import CfClient, FeatureFlagType
4+
from featureflags.evaluations.auth_target import Target
5+
6+
# Global context to store the ff_client and eval_target
7+
context = {}
8+
9+
10+
@contextmanager
11+
def feature_flag_context(client: CfClient, target: Target):
12+
context['ff_client'] = client
13+
context['eval_target'] = target
14+
try:
15+
yield
16+
finally:
17+
context.clear()
18+
19+
20+
def is_flag_enabled(flag_identifier: str, default_value, expected_value,
21+
fallback_func=None):
22+
def decorator_check_flag(func):
23+
@functools.wraps(func)
24+
def wrapper_check_flag(*args, **kwargs):
25+
ff_client = context.get('ff_client')
26+
eval_target = context.get('eval_target')
27+
28+
if ff_client is None or eval_target is None:
29+
raise ValueError(
30+
"ff_client and eval_target must be set in context")
31+
32+
flag_type = ff_client.get_flag_type(flag_identifier)
33+
34+
variation_methods = {
35+
FeatureFlagType.BOOLEAN: lambda: ff_client.bool_variation(
36+
flag_identifier, eval_target, default_value),
37+
38+
FeatureFlagType.STRING: lambda: ff_client.string_variation(
39+
flag_identifier, eval_target, default_value),
40+
41+
FeatureFlagType.INT_OR_FLOAT: lambda:
42+
ff_client.int_or_float_variation(
43+
flag_identifier, eval_target, default_value),
44+
45+
FeatureFlagType.JSON: lambda: ff_client.json_variation(
46+
flag_identifier, eval_target, default_value),
47+
48+
FeatureFlagType.FLAG_NOT_FOUND: lambda: default_value
49+
}
50+
51+
is_enabled = variation_methods[flag_type]()
52+
if is_enabled == expected_value:
53+
return func(*args, **kwargs)
54+
else:
55+
if fallback_func:
56+
return fallback_func(*args, **kwargs)
57+
return None # or some default behavior when the flag is not
58+
# enabled
59+
60+
return wrapper_check_flag
61+
62+
return decorator_check_flag
63+
64+
65+
def my_fallback_function(eval_target: Target):
66+
print(
67+
f"Flag is disabled for target with identifier '"
68+
f"{eval_target.identifier}'")
69+
70+
71+
# This example is using a string flag. But you can use the annotation
72+
# with any flag type and the decorator will use the correct variation method.
73+
# Ensure the expected and default values match the flag
74+
# type you are evaluating.
75+
@is_flag_enabled(flag_identifier="stringflag", default_value="default",
76+
expected_value="var12", fallback_func=my_fallback_function)
77+
def my_string_feature_function(eval_target: Target):
78+
print(
79+
f"stringflag is enabled for target with identifier '"
80+
f"{eval_target.identifier}'")
81+
82+
83+
@is_flag_enabled(flag_identifier="boolflag", default_value=False,
84+
expected_value=True, fallback_func=my_fallback_function)
85+
def my_bool_feature_function(eval_target: Target):
86+
print(
87+
f"boolflag is enabled for target with identifier '"
88+
f"{eval_target.identifier}'")
89+
90+
91+
def main():
92+
target = Target(identifier='harness')
93+
client = CfClient("")
94+
client.wait_for_initialization()
95+
96+
with feature_flag_context(client, target):
97+
my_bool_feature_function(target)
98+
my_string_feature_function(target)
99+
100+
101+
if __name__ == "__main__":
102+
main()

0 commit comments

Comments
 (0)