1
1
from __future__ import annotations
2
2
3
3
import logging
4
- from typing import Any , Callable , Dict , List , Optional , TypeVar , Union , cast
4
+ from typing import TYPE_CHECKING , Any , Callable , List , cast
5
5
6
- from typing_extensions import ParamSpec
7
-
8
- from aws_lambda_powertools .logging import Logger
9
6
from aws_lambda_powertools .utilities .feature_flags import schema
10
- from aws_lambda_powertools .utilities .feature_flags .base import StoreProvider
11
7
from aws_lambda_powertools .utilities .feature_flags .comparators import (
12
8
compare_all_in_list ,
13
9
compare_any_in_list ,
18
14
compare_time_range ,
19
15
)
20
16
from aws_lambda_powertools .utilities .feature_flags .exceptions import ConfigurationStoreError
21
- from aws_lambda_powertools .utilities .feature_flags .types import JSONType
17
+ from aws_lambda_powertools .utilities .feature_flags .types import P , T
18
+
19
+ if TYPE_CHECKING :
20
+ from aws_lambda_powertools .logging import Logger
21
+ from aws_lambda_powertools .utilities .feature_flags .base import StoreProvider
22
+ from aws_lambda_powertools .utilities .feature_flags .types import JSONType
22
23
23
- T = TypeVar ("T" )
24
- P = ParamSpec ("P" )
25
24
26
25
RULE_ACTION_MAPPING = {
27
26
schema .RuleAction .EQUALS .value : lambda a , b : a == b ,
49
48
50
49
51
50
class FeatureFlags :
52
- def __init__ (self , store : StoreProvider , logger : Optional [ Union [ logging .Logger , Logger ]] = None ):
51
+ def __init__ (self , store : StoreProvider , logger : logging .Logger | Logger | None = None ):
53
52
"""Evaluates whether feature flags should be enabled based on a given context.
54
53
55
54
It uses the provided store to fetch feature flag rules before evaluating them.
@@ -100,12 +99,12 @@ def _evaluate_conditions(
100
99
self ,
101
100
rule_name : str ,
102
101
feature_name : str ,
103
- rule : Dict [str , Any ],
104
- context : Dict [str , Any ],
102
+ rule : dict [str , Any ],
103
+ context : dict [str , Any ],
105
104
) -> bool :
106
105
"""Evaluates whether context matches conditions, return False otherwise"""
107
106
rule_match_value = rule .get (schema .RULE_MATCH_VALUE )
108
- conditions = cast (List [Dict ], rule .get (schema .CONDITIONS_KEY ))
107
+ conditions = cast (List [dict ], rule .get (schema .CONDITIONS_KEY ))
109
108
110
109
if not conditions :
111
110
self .logger .debug (
@@ -141,9 +140,9 @@ def _evaluate_rules(
141
140
self ,
142
141
* ,
143
142
feature_name : str ,
144
- context : Dict [str , Any ],
143
+ context : dict [str , Any ],
145
144
feat_default : Any ,
146
- rules : Dict [str , Any ],
145
+ rules : dict [str , Any ],
147
146
boolean_feature : bool ,
148
147
) -> bool :
149
148
"""Evaluates whether context matches rules and conditions, otherwise return feature default"""
@@ -164,7 +163,7 @@ def _evaluate_rules(
164
163
)
165
164
return feat_default
166
165
167
- def get_configuration (self ) -> Dict :
166
+ def get_configuration (self ) -> dict :
168
167
"""Get validated feature flag schema from configured store.
169
168
170
169
Largely used to aid testing, since it's called by `evaluate` and `get_enabled_features` methods.
@@ -178,7 +177,7 @@ def get_configuration(self) -> Dict:
178
177
179
178
Returns
180
179
------
181
- Dict [str, Dict ]
180
+ dict [str, dict ]
182
181
parsed JSON dictionary
183
182
184
183
**Example**
@@ -208,13 +207,13 @@ def get_configuration(self) -> Dict:
208
207
"""
209
208
# parse result conf as JSON, keep in cache for max age defined in store
210
209
self .logger .debug (f"Fetching schema from registered store, store={ self .store } " )
211
- config : Dict = self .store .get_configuration ()
210
+ config : dict = self .store .get_configuration ()
212
211
validator = schema .SchemaValidator (schema = config , logger = self .logger )
213
212
validator .validate ()
214
213
215
214
return config
216
215
217
- def evaluate (self , * , name : str , context : Optional [ Dict [ str , Any ]] = None , default : JSONType ) -> JSONType :
216
+ def evaluate (self , * , name : str , context : dict [ str , Any ] | None = None , default : JSONType ) -> JSONType :
218
217
"""Evaluate whether a feature flag should be enabled according to stored schema and input context
219
218
220
219
**Logic when evaluating a feature flag**
@@ -243,7 +242,7 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
243
242
----------
244
243
name: str
245
244
feature name to evaluate
246
- context: Optional[Dict[ str, Any]]
245
+ context: dict[ str, Any] | None
247
246
Attributes that should be evaluated against the stored schema.
248
247
249
248
for example: `{"tenant_id": "X", "username": "Y", "region": "Z"}`
@@ -306,7 +305,7 @@ def lambda_handler(event: dict, context: LambdaContext):
306
305
# Maintenance: Revisit before going GA. We might to simplify customers on-boarding by not requiring it
307
306
# for non-boolean flags. It'll need minor implementation changes, docs changes, and maybe refactor
308
307
# get_enabled_features. We can minimize breaking change, despite Beta label, by having a new
309
- # method `get_matching_features` returning Dict [feature_name, feature_value]
308
+ # method `get_matching_features` returning dict [feature_name, feature_value]
310
309
boolean_feature = feature .get (
311
310
schema .FEATURE_DEFAULT_VAL_TYPE_KEY ,
312
311
True ,
@@ -330,19 +329,19 @@ def lambda_handler(event: dict, context: LambdaContext):
330
329
boolean_feature = boolean_feature ,
331
330
)
332
331
333
- def get_enabled_features (self , * , context : Optional [ Dict [ str , Any ]] = None ) -> List [str ]:
332
+ def get_enabled_features (self , * , context : dict [ str , Any ] | None = None ) -> list [str ]:
334
333
"""Get all enabled feature flags while also taking into account context
335
334
(when a feature has defined rules)
336
335
337
336
Parameters
338
337
----------
339
- context: Optional[Dict[ str, Any]]
338
+ context: dict[ str, Any] | None
340
339
dict of attributes that you would like to match the rules
341
340
against, can be `{'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'}` etc.
342
341
343
342
Returns
344
343
----------
345
- List [str]
344
+ list [str]
346
345
list of all feature names that either matches context or have True as default
347
346
348
347
**Example**
@@ -359,10 +358,10 @@ def get_enabled_features(self, *, context: Optional[Dict[str, Any]] = None) -> L
359
358
if context is None :
360
359
context = {}
361
360
362
- features_enabled : List [str ] = []
361
+ features_enabled : list [str ] = []
363
362
364
363
try :
365
- features : Dict [str , Any ] = self .get_configuration ()
364
+ features : dict [str , Any ] = self .get_configuration ()
366
365
except ConfigurationStoreError as err :
367
366
self .logger .debug (f"Failed to fetch feature flags from store, returning empty list, reason={ err } " )
368
367
return features_enabled
0 commit comments