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