11import logging
2- from typing import Any , Dict , List , Optional
2+ from typing import Any , Dict , List , Optional , cast
33
44from . import schema
5- from .exceptions import ConfigurationException
5+ from .exceptions import ConfigurationError
66from .schema_fetcher import SchemaFetcher
77
88logger = logging .getLogger (__name__ )
@@ -12,8 +12,10 @@ class ConfigurationStore:
1212 def __init__ (self , schema_fetcher : SchemaFetcher ):
1313 """constructor
1414
15- Args:
16- schema_fetcher (SchemaFetcher): A schema JSON fetcher, can be AWS AppConfig, Hashicorp Consul etc.
15+ Parameters
16+ ----------
17+ schema_fetcher: SchemaFetcher
18+ A schema JSON fetcher, can be AWS AppConfig, Hashicorp Consul etc.
1719 """
1820 self ._logger = logger
1921 self ._schema_fetcher = schema_fetcher
@@ -39,25 +41,28 @@ def _match_by_action(self, action: str, condition_value: Any, context_value: Any
3941 def _is_rule_matched (self , feature_name : str , rule : Dict [str , Any ], rules_context : Dict [str , Any ]) -> bool :
4042 rule_name = rule .get (schema .RULE_NAME_KEY , "" )
4143 rule_default_value = rule .get (schema .RULE_DEFAULT_VALUE )
42- conditions : Dict [ str , str ] = rule .get (schema .CONDITIONS_KEY )
44+ conditions = cast ( List [ Dict ], rule .get (schema .CONDITIONS_KEY ) )
4345
4446 for condition in conditions :
45- context_value = rules_context .get (condition .get (schema .CONDITION_KEY ))
47+ context_value = rules_context .get (str ( condition .get (schema .CONDITION_KEY ) ))
4648 if not self ._match_by_action (
47- condition .get (schema .CONDITION_ACTION ),
49+ condition .get (schema .CONDITION_ACTION , "" ),
4850 condition .get (schema .CONDITION_VALUE ),
4951 context_value ,
5052 ):
5153 logger .debug (
52- f"rule did not match action, rule_name={ rule_name } , rule_default_value={ rule_default_value } , feature_name={ feature_name } , context_value={ str (context_value )} " # noqa: E501
54+ f"rule did not match action, rule_name={ rule_name } , rule_default_value={ rule_default_value } , "
55+ f"feature_name={ feature_name } , context_value={ str (context_value )} "
5356 )
5457 # context doesn't match condition
5558 return False
5659 # if we got here, all conditions match
5760 logger .debug (
58- f"rule matched, rule_name={ rule_name } , rule_default_value={ rule_default_value } , feature_name={ feature_name } " # noqa: E501
61+ f"rule matched, rule_name={ rule_name } , rule_default_value={ rule_default_value } , "
62+ f"feature_name={ feature_name } "
5963 )
6064 return True
65+ return False
6166
6267 def _handle_rules (
6368 self ,
@@ -70,66 +75,77 @@ def _handle_rules(
7075 for rule in rules :
7176 rule_default_value = rule .get (schema .RULE_DEFAULT_VALUE )
7277 if self ._is_rule_matched (feature_name , rule , rules_context ):
73- return rule_default_value
78+ return bool ( rule_default_value )
7479 # no rule matched, return default value of feature
7580 logger .debug (
76- f"no rule matched, returning default value of feature, feature_default_value={ feature_default_value } , feature_name={ feature_name } " # noqa: E501
81+ f"no rule matched, returning default value of feature, feature_default_value={ feature_default_value } , "
82+ f"feature_name={ feature_name } "
7783 )
7884 return feature_default_value
85+ return False
7986
8087 def get_configuration (self ) -> Dict [str , Any ]:
8188 """Get configuration string from AWs AppConfig and returned the parsed JSON dictionary
8289
83- Raises:
84- ConfigurationException: Any validation error or appconfig error that can occur
90+ Raises
91+ ------
92+ ConfigurationError
93+ Any validation error or appconfig error that can occur
8594
86- Returns:
87- Dict[str, Any]: parsed JSON dictionary
95+ Returns
96+ ------
97+ Dict[str, Any]
98+ parsed JSON dictionary
8899 """
89- schema : Dict [
90- str , Any
91- ] = (
92- self ._schema_fetcher .get_json_configuration ()
93- ) # parse result conf as JSON, keep in cache for self.max_age seconds
100+ # parse result conf as JSON, keep in cache for self.max_age seconds
101+ config = self ._schema_fetcher .get_json_configuration ()
94102 # validate schema
95- self ._schema_validator .validate_json_schema (schema )
96- return schema
103+ self ._schema_validator .validate_json_schema (config )
104+ return config
97105
98106 def get_feature_toggle (
99107 self , * , feature_name : str , rules_context : Optional [Dict [str , Any ]] = None , value_if_missing : bool
100108 ) -> bool :
101- """get a feature toggle boolean value. Value is calculated according to a set of rules and conditions.
102- see below for explanation.
103-
104- Args:
105- feature_name (str): feature name that you wish to fetch
106- rules_context (Optional[Dict[str, Any]]): dict of attributes that you would like to match the rules
107- against, can be {'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'} etc.
108- value_if_missing (bool): this will be the returned value in case the feature toggle doesn't exist in
109- the schema or there has been an error while fetching the
110- configuration from appconfig
111-
112- Returns:
113- bool: calculated feature toggle value. several possibilities:
114- 1. if the feature doesn't appear in the schema or there has been an error fetching the
115- configuration -> error/warning log would appear and value_if_missing is returned
116- 2. feature exists and has no rules or no rules have matched -> return feature_default_value of
117- the defined feature
118- 3. feature exists and a rule matches -> rule_default_value of rule is returned
109+ """Get a feature toggle boolean value. Value is calculated according to a set of rules and conditions.
110+
111+ See below for explanation.
112+
113+ Parameters
114+ ----------
115+ feature_name: str
116+ feature name that you wish to fetch
117+ rules_context: Optional[Dict[str, Any]]
118+ dict of attributes that you would like to match the rules
119+ against, can be {'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'} etc.
120+ value_if_missing: bool
121+ this will be the returned value in case the feature toggle doesn't exist in
122+ the schema or there has been an error while fetching the
123+ configuration from appconfig
124+
125+ Returns
126+ ------
127+ bool
128+ calculated feature toggle value. several possibilities:
129+ 1. if the feature doesn't appear in the schema or there has been an error fetching the
130+ configuration -> error/warning log would appear and value_if_missing is returned
131+ 2. feature exists and has no rules or no rules have matched -> return feature_default_value of
132+ the defined feature
133+ 3. feature exists and a rule matches -> rule_default_value of rule is returned
119134 """
120135 if rules_context is None :
121136 rules_context = {}
122137
123138 try :
124139 toggles_dict : Dict [str , Any ] = self .get_configuration ()
125- except ConfigurationException :
126- logger .error ("unable to get feature toggles JSON, returning provided value_if_missing value" ) # noqa: E501
140+ except ConfigurationError :
141+ logger .error ("unable to get feature toggles JSON, returning provided value_if_missing value" )
127142 return value_if_missing
128143
129144 feature : Dict [str , Dict ] = toggles_dict .get (schema .FEATURES_KEY , {}).get (feature_name , None )
130145 if feature is None :
131146 logger .warning (
132- f"feature does not appear in configuration, using provided value_if_missing, feature_name={ feature_name } , value_if_missing={ value_if_missing } " # noqa: E501
147+ f"feature does not appear in configuration, using provided value_if_missing, "
148+ f"feature_name={ feature_name } , value_if_missing={ value_if_missing } "
133149 )
134150 return value_if_missing
135151
@@ -138,38 +154,46 @@ def get_feature_toggle(
138154 if not rules_list :
139155 # not rules but has a value
140156 logger .debug (
141- f"no rules found, returning feature default value, feature_name={ feature_name } , default_value={ feature_default_value } " # noqa: E501
157+ f"no rules found, returning feature default value, feature_name={ feature_name } , "
158+ f"default_value={ feature_default_value } "
142159 )
143- return feature_default_value
160+ return bool ( feature_default_value )
144161 # look for first rule match
145162 logger .debug (
146163 f"looking for rule match, feature_name={ feature_name } , feature_default_value={ feature_default_value } "
147- ) # noqa: E501
164+ )
148165 return self ._handle_rules (
149166 feature_name = feature_name ,
150167 rules_context = rules_context ,
151- feature_default_value = feature_default_value ,
152- rules = rules_list ,
168+ feature_default_value = bool ( feature_default_value ) ,
169+ rules = cast ( List , rules_list ) ,
153170 )
154171
155172 def get_all_enabled_feature_toggles (self , * , rules_context : Optional [Dict [str , Any ]] = None ) -> List [str ]:
156- """Get all enabled feature toggles while also taking into account rule_context (when a feature has defined rules)
157-
158- Args:
159- rules_context (Optional[Dict[str, Any]]): dict of attributes that you would like to match the rules
160- against, can be {'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'} etc.
161-
162- Returns:
163- List[str]: a list of all features name that are enabled by also taking into account
164- rule_context (when a feature has defined rules)
173+ """Get all enabled feature toggles while also taking into account rule_context
174+ (when a feature has defined rules)
175+
176+ Parameters
177+ ----------
178+ rules_context: Optional[Dict[str, Any]]
179+ dict of attributes that you would like to match the rules
180+ against, can be `{'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'}` etc.
181+
182+ Returns
183+ ----------
184+ List[str]
185+ a list of all features name that are enabled by also taking into account
186+ rule_context (when a feature has defined rules)
165187 """
166188 if rules_context is None :
167189 rules_context = {}
190+
168191 try :
169192 toggles_dict : Dict [str , Any ] = self .get_configuration ()
170- except ConfigurationException :
171- logger .error ("unable to get feature toggles JSON" ) # noqa: E501
193+ except ConfigurationError :
194+ logger .error ("unable to get feature toggles JSON" )
172195 return []
196+
173197 ret_list = []
174198 features : Dict [str , Any ] = toggles_dict .get (schema .FEATURES_KEY , {})
175199 for feature_name , feature_dict_def in features .items ():
@@ -188,4 +212,5 @@ def get_all_enabled_feature_toggles(self, *, rules_context: Optional[Dict[str, A
188212 ):
189213 self ._logger .debug (f"feature's calculated value is True, feature_name={ feature_name } " )
190214 ret_list .append (feature_name )
215+
191216 return ret_list
0 commit comments