2
2
from typing import Any , Dict , List , Optional , Union , cast
3
3
4
4
from ... import Logger
5
+ from ...shared .types import JSONType
5
6
from . import schema
6
7
from .base import StoreProvider
7
8
from .exceptions import ConfigurationStoreError
@@ -97,21 +98,30 @@ def _evaluate_conditions(
97
98
return True
98
99
99
100
def _evaluate_rules (
100
- self , * , feature_name : str , context : Dict [str , Any ], feat_default : bool , rules : Dict [str , Any ]
101
+ self ,
102
+ * ,
103
+ feature_name : str ,
104
+ context : Dict [str , Any ],
105
+ feat_default : Any ,
106
+ rules : Dict [str , Any ],
107
+ boolean_feature : bool ,
101
108
) -> bool :
102
109
"""Evaluates whether context matches rules and conditions, otherwise return feature default"""
103
110
for rule_name , rule in rules .items ():
104
111
rule_match_value = rule .get (schema .RULE_MATCH_VALUE )
105
112
106
113
# Context might contain PII data; do not log its value
107
114
self .logger .debug (
108
- f"Evaluating rule matching, rule={ rule_name } , feature={ feature_name } , default={ feat_default } "
115
+ f"Evaluating rule matching, rule={ rule_name } , feature={ feature_name } , default={ str ( feat_default ) } , boolean_feature= { boolean_feature } " # noqa: E501
109
116
)
110
117
if self ._evaluate_conditions (rule_name = rule_name , feature_name = feature_name , rule = rule , context = context ):
111
- return bool (rule_match_value )
118
+ # Maintenance: Revisit before going GA.
119
+ return bool (rule_match_value ) if boolean_feature else rule_match_value
112
120
113
121
# no rule matched, return default value of feature
114
- self .logger .debug (f"no rule matched, returning feature default, default={ feat_default } , name={ feature_name } " )
122
+ self .logger .debug (
123
+ f"no rule matched, returning feature default, default={ str (feat_default )} , name={ feature_name } , boolean_feature={ boolean_feature } " # noqa: E501
124
+ )
115
125
return feat_default
116
126
117
127
def get_configuration (self ) -> Dict :
@@ -164,7 +174,7 @@ def get_configuration(self) -> Dict:
164
174
165
175
return config
166
176
167
- def evaluate (self , * , name : str , context : Optional [Dict [str , Any ]] = None , default : bool ) -> bool :
177
+ def evaluate (self , * , name : str , context : Optional [Dict [str , Any ]] = None , default : JSONType ) -> JSONType :
168
178
"""Evaluate whether a feature flag should be enabled according to stored schema and input context
169
179
170
180
**Logic when evaluating a feature flag**
@@ -181,14 +191,15 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
181
191
Attributes that should be evaluated against the stored schema.
182
192
183
193
for example: `{"tenant_id": "X", "username": "Y", "region": "Z"}`
184
- default: bool
194
+ default: JSONType
185
195
default value if feature flag doesn't exist in the schema,
186
196
or there has been an error when fetching the configuration from the store
197
+ Can be boolean or any JSON values for non-boolean features.
187
198
188
199
Returns
189
200
------
190
- bool
191
- whether feature should be enabled or not
201
+ JSONType
202
+ whether feature should be enabled (bool flags) or JSON value when non-bool feature matches
192
203
193
204
Raises
194
205
------
@@ -211,12 +222,27 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
211
222
212
223
rules = feature .get (schema .RULES_KEY )
213
224
feat_default = feature .get (schema .FEATURE_DEFAULT_VAL_KEY )
225
+ # Maintenance: Revisit before going GA. We might to simplify customers on-boarding by not requiring it
226
+ # for non-boolean flags. It'll need minor implementation changes, docs changes, and maybe refactor
227
+ # get_enabled_features. We can minimize breaking change, despite Beta label, by having a new
228
+ # method `get_matching_features` returning Dict[feature_name, feature_value]
229
+ boolean_feature = feature .get (
230
+ schema .FEATURE_DEFAULT_VAL_TYPE_KEY , True
231
+ ) # backwards compatability ,assume feature flag
214
232
if not rules :
215
- self .logger .debug (f"no rules found, returning feature default, name={ name } , default={ feat_default } " )
216
- return bool (feat_default )
233
+ self .logger .debug (
234
+ f"no rules found, returning feature default, name={ name } , default={ str (feat_default )} , boolean_feature={ boolean_feature } " # noqa: E501
235
+ )
236
+ # Maintenance: Revisit before going GA. We might to simplify customers on-boarding by not requiring it
237
+ # for non-boolean flags.
238
+ return bool (feat_default ) if boolean_feature else feat_default
217
239
218
- self .logger .debug (f"looking for rule match, name={ name } , default={ feat_default } " )
219
- return self ._evaluate_rules (feature_name = name , context = context , feat_default = bool (feat_default ), rules = rules )
240
+ self .logger .debug (
241
+ f"looking for rule match, name={ name } , default={ str (feat_default )} , boolean_feature={ boolean_feature } " # noqa: E501
242
+ )
243
+ return self ._evaluate_rules (
244
+ feature_name = name , context = context , feat_default = feat_default , rules = rules , boolean_feature = boolean_feature
245
+ )
220
246
221
247
def get_enabled_features (self , * , context : Optional [Dict [str , Any ]] = None ) -> List [str ]:
222
248
"""Get all enabled feature flags while also taking into account context
@@ -259,11 +285,19 @@ def get_enabled_features(self, *, context: Optional[Dict[str, Any]] = None) -> L
259
285
for name , feature in features .items ():
260
286
rules = feature .get (schema .RULES_KEY , {})
261
287
feature_default_value = feature .get (schema .FEATURE_DEFAULT_VAL_KEY )
288
+ boolean_feature = feature .get (
289
+ schema .FEATURE_DEFAULT_VAL_TYPE_KEY , True
290
+ ) # backwards compatability ,assume feature flag
291
+
262
292
if feature_default_value and not rules :
263
293
self .logger .debug (f"feature is enabled by default and has no defined rules, name={ name } " )
264
294
features_enabled .append (name )
265
295
elif self ._evaluate_rules (
266
- feature_name = name , context = context , feat_default = feature_default_value , rules = rules
296
+ feature_name = name ,
297
+ context = context ,
298
+ feat_default = feature_default_value ,
299
+ rules = rules ,
300
+ boolean_feature = boolean_feature ,
267
301
):
268
302
self .logger .debug (f"feature's calculated value is True, name={ name } " )
269
303
features_enabled .append (name )
0 commit comments