22from typing import Any , Dict , List , Optional , Union , cast
33
44from ... import Logger
5+ from ...shared .types import JSONType
56from . import schema
67from .base import StoreProvider
78from .exceptions import ConfigurationStoreError
@@ -97,21 +98,30 @@ def _evaluate_conditions(
9798 return True
9899
99100 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 ,
101108 ) -> bool :
102109 """Evaluates whether context matches rules and conditions, otherwise return feature default"""
103110 for rule_name , rule in rules .items ():
104111 rule_match_value = rule .get (schema .RULE_MATCH_VALUE )
105112
106113 # Context might contain PII data; do not log its value
107114 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
109116 )
110117 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
112120
113121 # 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+ )
115125 return feat_default
116126
117127 def get_configuration (self ) -> Dict :
@@ -164,7 +174,7 @@ def get_configuration(self) -> Dict:
164174
165175 return config
166176
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 :
168178 """Evaluate whether a feature flag should be enabled according to stored schema and input context
169179
170180 **Logic when evaluating a feature flag**
@@ -181,14 +191,15 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
181191 Attributes that should be evaluated against the stored schema.
182192
183193 for example: `{"tenant_id": "X", "username": "Y", "region": "Z"}`
184- default: bool
194+ default: JSONType
185195 default value if feature flag doesn't exist in the schema,
186196 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.
187198
188199 Returns
189200 ------
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
192203
193204 Raises
194205 ------
@@ -211,12 +222,27 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
211222
212223 rules = feature .get (schema .RULES_KEY )
213224 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
214232 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
217239
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+ )
220246
221247 def get_enabled_features (self , * , context : Optional [Dict [str , Any ]] = None ) -> List [str ]:
222248 """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
259285 for name , feature in features .items ():
260286 rules = feature .get (schema .RULES_KEY , {})
261287 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+
262292 if feature_default_value and not rules :
263293 self .logger .debug (f"feature is enabled by default and has no defined rules, name={ name } " )
264294 features_enabled .append (name )
265295 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 ,
267301 ):
268302 self .logger .debug (f"feature's calculated value is True, name={ name } " )
269303 features_enabled .append (name )
0 commit comments