1
1
import logging
2
- from typing import Any , Dict , List , Optional
2
+ from typing import Any , Dict , List , Optional , cast
3
3
4
4
from . import schema
5
- from .exceptions import ConfigurationException
5
+ from .exceptions import ConfigurationError
6
6
from .schema_fetcher import SchemaFetcher
7
7
8
8
logger = logging .getLogger (__name__ )
@@ -12,8 +12,10 @@ class ConfigurationStore:
12
12
def __init__ (self , schema_fetcher : SchemaFetcher ):
13
13
"""constructor
14
14
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.
17
19
"""
18
20
self ._logger = logger
19
21
self ._schema_fetcher = schema_fetcher
@@ -39,25 +41,28 @@ def _match_by_action(self, action: str, condition_value: Any, context_value: Any
39
41
def _is_rule_matched (self , feature_name : str , rule : Dict [str , Any ], rules_context : Dict [str , Any ]) -> bool :
40
42
rule_name = rule .get (schema .RULE_NAME_KEY , "" )
41
43
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 ) )
43
45
44
46
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 ) ))
46
48
if not self ._match_by_action (
47
- condition .get (schema .CONDITION_ACTION ),
49
+ condition .get (schema .CONDITION_ACTION , "" ),
48
50
condition .get (schema .CONDITION_VALUE ),
49
51
context_value ,
50
52
):
51
53
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 )} "
53
56
)
54
57
# context doesn't match condition
55
58
return False
56
59
# if we got here, all conditions match
57
60
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 } "
59
63
)
60
64
return True
65
+ return False
61
66
62
67
def _handle_rules (
63
68
self ,
@@ -70,66 +75,77 @@ def _handle_rules(
70
75
for rule in rules :
71
76
rule_default_value = rule .get (schema .RULE_DEFAULT_VALUE )
72
77
if self ._is_rule_matched (feature_name , rule , rules_context ):
73
- return rule_default_value
78
+ return bool ( rule_default_value )
74
79
# no rule matched, return default value of feature
75
80
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 } "
77
83
)
78
84
return feature_default_value
85
+ return False
79
86
80
87
def get_configuration (self ) -> Dict [str , Any ]:
81
88
"""Get configuration string from AWs AppConfig and returned the parsed JSON dictionary
82
89
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
85
94
86
- Returns:
87
- Dict[str, Any]: parsed JSON dictionary
95
+ Returns
96
+ ------
97
+ Dict[str, Any]
98
+ parsed JSON dictionary
88
99
"""
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 ()
94
102
# validate schema
95
- self ._schema_validator .validate_json_schema (schema )
96
- return schema
103
+ self ._schema_validator .validate_json_schema (config )
104
+ return config
97
105
98
106
def get_feature_toggle (
99
107
self , * , feature_name : str , rules_context : Optional [Dict [str , Any ]] = None , value_if_missing : bool
100
108
) -> 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
119
134
"""
120
135
if rules_context is None :
121
136
rules_context = {}
122
137
123
138
try :
124
139
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" )
127
142
return value_if_missing
128
143
129
144
feature : Dict [str , Dict ] = toggles_dict .get (schema .FEATURES_KEY , {}).get (feature_name , None )
130
145
if feature is None :
131
146
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 } "
133
149
)
134
150
return value_if_missing
135
151
@@ -138,38 +154,46 @@ def get_feature_toggle(
138
154
if not rules_list :
139
155
# not rules but has a value
140
156
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 } "
142
159
)
143
- return feature_default_value
160
+ return bool ( feature_default_value )
144
161
# look for first rule match
145
162
logger .debug (
146
163
f"looking for rule match, feature_name={ feature_name } , feature_default_value={ feature_default_value } "
147
- ) # noqa: E501
164
+ )
148
165
return self ._handle_rules (
149
166
feature_name = feature_name ,
150
167
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 ) ,
153
170
)
154
171
155
172
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)
165
187
"""
166
188
if rules_context is None :
167
189
rules_context = {}
190
+
168
191
try :
169
192
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" )
172
195
return []
196
+
173
197
ret_list = []
174
198
features : Dict [str , Any ] = toggles_dict .get (schema .FEATURES_KEY , {})
175
199
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
188
212
):
189
213
self ._logger .debug (f"feature's calculated value is True, feature_name={ feature_name } " )
190
214
ret_list .append (feature_name )
215
+
191
216
return ret_list
0 commit comments