Skip to content

Commit 496c72f

Browse files
committed
refactor(feature_flags): add from __future__ import annotations
and update code according to ruff rules TCH, UP006, UP007, UP037 and FA100.
1 parent 2d59b7a commit 496c72f

File tree

6 files changed

+80
-68
lines changed

6 files changed

+80
-68
lines changed

aws_lambda_powertools/utilities/feature_flags/appconfig.py

+18-14
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
from __future__ import annotations
2+
13
import logging
24
import traceback
3-
from typing import Any, Dict, Optional, Union, cast
4-
5-
from botocore.config import Config
5+
from typing import TYPE_CHECKING, Any, cast
66

7-
from aws_lambda_powertools.logging import Logger
87
from aws_lambda_powertools.utilities import jmespath_utils
98
from aws_lambda_powertools.utilities.feature_flags.base import StoreProvider
109
from aws_lambda_powertools.utilities.feature_flags.exceptions import ConfigurationStoreError, StoreClientError
@@ -14,6 +13,11 @@
1413
TransformParameterError,
1514
)
1615

16+
if TYPE_CHECKING:
17+
from botocore.config import Config
18+
19+
from aws_lambda_powertools.logging import Logger
20+
1721

1822
class AppConfigStore(StoreProvider):
1923
def __init__(
@@ -22,10 +26,10 @@ def __init__(
2226
application: str,
2327
name: str,
2428
max_age: int = 5,
25-
sdk_config: Optional[Config] = None,
26-
envelope: Optional[str] = "",
27-
jmespath_options: Optional[Dict] = None,
28-
logger: Optional[Union[logging.Logger, Logger]] = None,
29+
sdk_config: Config | None = None,
30+
envelope: str | None = "",
31+
jmespath_options: dict | None = None,
32+
logger: logging.Logger | Logger | None = None,
2933
):
3034
"""This class fetches JSON schemas from AWS AppConfig
3135
@@ -39,11 +43,11 @@ def __init__(
3943
AppConfig configuration name e.g. `my_conf`
4044
max_age: int
4145
cache expiration time in seconds, or how often to call AppConfig to fetch latest configuration
42-
sdk_config: Optional[Config]
46+
sdk_config: Config | None
4347
Botocore Config object to pass during client initialization
44-
envelope : Optional[str]
48+
envelope : str | None
4549
JMESPath expression to pluck feature flags data from config
46-
jmespath_options : Optional[Dict]
50+
jmespath_options : dict | None
4751
Alternative JMESPath options to be included when filtering expr
4852
logger: A logging object
4953
Used to log messages. If None is supplied, one will be created.
@@ -60,7 +64,7 @@ def __init__(
6064
self._conf_store = AppConfigProvider(environment=environment, application=application, boto_config=sdk_config)
6165

6266
@property
63-
def get_raw_configuration(self) -> Dict[str, Any]:
67+
def get_raw_configuration(self) -> dict[str, Any]:
6468
"""Fetch feature schema configuration from AWS AppConfig"""
6569
try:
6670
# parse result conf as JSON, keep in cache for self.max_age seconds
@@ -82,7 +86,7 @@ def get_raw_configuration(self) -> Dict[str, Any]:
8286
raise StoreClientError(err_msg) from exc
8387
raise ConfigurationStoreError("Unable to get AWS AppConfig configuration file") from exc
8488

85-
def get_configuration(self) -> Dict[str, Any]:
89+
def get_configuration(self) -> dict[str, Any]:
8690
"""Fetch feature schema configuration from AWS AppConfig
8791
8892
If envelope is set, it'll extract and return feature flags from configuration,
@@ -95,7 +99,7 @@ def get_configuration(self) -> Dict[str, Any]:
9599
96100
Returns
97101
-------
98-
Dict[str, Any]
102+
dict[str, Any]
99103
parsed JSON dictionary
100104
"""
101105
config = self.get_raw_configuration

aws_lambda_powertools/utilities/feature_flags/base.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
from __future__ import annotations
2+
13
from abc import ABC, abstractmethod
2-
from typing import Any, Dict
4+
from typing import Any
35

46

57
class StoreProvider(ABC):
68
@property
79
@abstractmethod
8-
def get_raw_configuration(self) -> Dict[str, Any]:
10+
def get_raw_configuration(self) -> dict[str, Any]:
911
"""Get configuration from any store and return the parsed JSON dictionary"""
1012
raise NotImplementedError() # pragma: no cover
1113

1214
@abstractmethod
13-
def get_configuration(self) -> Dict[str, Any]:
15+
def get_configuration(self) -> dict[str, Any]:
1416
"""Get configuration from any store and return the parsed JSON dictionary
1517
1618
If envelope is set, it'll extract and return feature flags from configuration,
@@ -23,7 +25,7 @@ def get_configuration(self) -> Dict[str, Any]:
2325
2426
Returns
2527
-------
26-
Dict[str, Any]
28+
dict[str, Any]
2729
parsed JSON dictionary
2830
2931
**Example**

aws_lambda_powertools/utilities/feature_flags/comparators.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from __future__ import annotations
22

33
from datetime import datetime, tzinfo
4-
from typing import Any, Dict, Optional
4+
from typing import Any
55

66
from dateutil.tz import gettz
77

88
from aws_lambda_powertools.utilities.feature_flags.schema import HOUR_MIN_SEPARATOR, ModuloRangeValues, TimeValues
99

1010

11-
def _get_now_from_timezone(timezone: Optional[tzinfo]) -> datetime:
11+
def _get_now_from_timezone(timezone: tzinfo | None) -> datetime:
1212
"""
1313
Returns now in the specified timezone. Defaults to UTC if not present.
1414
At this stage, we already validated that the passed timezone string is valid, so we assume that
@@ -18,7 +18,7 @@ def _get_now_from_timezone(timezone: Optional[tzinfo]) -> datetime:
1818
return datetime.now(timezone)
1919

2020

21-
def compare_days_of_week(context_value: Any, condition_value: Dict) -> bool:
21+
def compare_days_of_week(context_value: Any, condition_value: dict) -> bool:
2222
timezone_name = condition_value.get(TimeValues.TIMEZONE.value, "UTC")
2323

2424
# %A = Weekday as locale’s full name.
@@ -28,7 +28,7 @@ def compare_days_of_week(context_value: Any, condition_value: Dict) -> bool:
2828
return current_day in days
2929

3030

31-
def compare_datetime_range(context_value: Any, condition_value: Dict) -> bool:
31+
def compare_datetime_range(context_value: Any, condition_value: dict) -> bool:
3232
timezone_name = condition_value.get(TimeValues.TIMEZONE.value, "UTC")
3333
timezone = gettz(timezone_name)
3434
current_time: datetime = _get_now_from_timezone(timezone)
@@ -44,7 +44,7 @@ def compare_datetime_range(context_value: Any, condition_value: Dict) -> bool:
4444
return start_date <= current_time <= end_date
4545

4646

47-
def compare_time_range(context_value: Any, condition_value: Dict) -> bool:
47+
def compare_time_range(context_value: Any, condition_value: dict) -> bool:
4848
timezone_name = condition_value.get(TimeValues.TIMEZONE.value, "UTC")
4949
current_time: datetime = _get_now_from_timezone(gettz(timezone_name))
5050

@@ -75,7 +75,7 @@ def compare_time_range(context_value: Any, condition_value: Dict) -> bool:
7575
return start_time <= current_time <= end_time
7676

7777

78-
def compare_modulo_range(context_value: int, condition_value: Dict) -> bool:
78+
def compare_modulo_range(context_value: int, condition_value: dict) -> bool:
7979
"""
8080
Returns for a given context 'a' and modulo condition 'b' -> b.start <= a % b.base <= b.end
8181
"""

aws_lambda_powertools/utilities/feature_flags/feature_flags.py

+23-21
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
from __future__ import annotations
22

33
import logging
4-
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union, cast
4+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
55

66
from typing_extensions import ParamSpec
77

8-
from aws_lambda_powertools.logging import Logger
98
from aws_lambda_powertools.utilities.feature_flags import schema
10-
from aws_lambda_powertools.utilities.feature_flags.base import StoreProvider
119
from aws_lambda_powertools.utilities.feature_flags.comparators import (
1210
compare_all_in_list,
1311
compare_any_in_list,
@@ -18,7 +16,11 @@
1816
compare_time_range,
1917
)
2018
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
2224

2325
T = TypeVar("T")
2426
P = ParamSpec("P")
@@ -49,7 +51,7 @@
4951

5052

5153
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):
5355
"""Evaluates whether feature flags should be enabled based on a given context.
5456
5557
It uses the provided store to fetch feature flag rules before evaluating them.
@@ -100,12 +102,12 @@ def _evaluate_conditions(
100102
self,
101103
rule_name: str,
102104
feature_name: str,
103-
rule: Dict[str, Any],
104-
context: Dict[str, Any],
105+
rule: dict[str, Any],
106+
context: dict[str, Any],
105107
) -> bool:
106108
"""Evaluates whether context matches conditions, return False otherwise"""
107109
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))
109111

110112
if not conditions:
111113
self.logger.debug(
@@ -141,9 +143,9 @@ def _evaluate_rules(
141143
self,
142144
*,
143145
feature_name: str,
144-
context: Dict[str, Any],
146+
context: dict[str, Any],
145147
feat_default: Any,
146-
rules: Dict[str, Any],
148+
rules: dict[str, Any],
147149
boolean_feature: bool,
148150
) -> bool:
149151
"""Evaluates whether context matches rules and conditions, otherwise return feature default"""
@@ -164,7 +166,7 @@ def _evaluate_rules(
164166
)
165167
return feat_default
166168

167-
def get_configuration(self) -> Dict:
169+
def get_configuration(self) -> dict:
168170
"""Get validated feature flag schema from configured store.
169171
170172
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:
178180
179181
Returns
180182
------
181-
Dict[str, Dict]
183+
dict[str, dict]
182184
parsed JSON dictionary
183185
184186
**Example**
@@ -208,13 +210,13 @@ def get_configuration(self) -> Dict:
208210
"""
209211
# parse result conf as JSON, keep in cache for max age defined in store
210212
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()
212214
validator = schema.SchemaValidator(schema=config, logger=self.logger)
213215
validator.validate()
214216

215217
return config
216218

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:
218220
"""Evaluate whether a feature flag should be enabled according to stored schema and input context
219221
220222
**Logic when evaluating a feature flag**
@@ -243,7 +245,7 @@ def evaluate(self, *, name: str, context: Optional[Dict[str, Any]] = None, defau
243245
----------
244246
name: str
245247
feature name to evaluate
246-
context: Optional[Dict[str, Any]]
248+
context: dict[str, Any] | None
247249
Attributes that should be evaluated against the stored schema.
248250
249251
for example: `{"tenant_id": "X", "username": "Y", "region": "Z"}`
@@ -306,7 +308,7 @@ def lambda_handler(event: dict, context: LambdaContext):
306308
# Maintenance: Revisit before going GA. We might to simplify customers on-boarding by not requiring it
307309
# for non-boolean flags. It'll need minor implementation changes, docs changes, and maybe refactor
308310
# 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]
310312
boolean_feature = feature.get(
311313
schema.FEATURE_DEFAULT_VAL_TYPE_KEY,
312314
True,
@@ -330,19 +332,19 @@ def lambda_handler(event: dict, context: LambdaContext):
330332
boolean_feature=boolean_feature,
331333
)
332334

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]:
334336
"""Get all enabled feature flags while also taking into account context
335337
(when a feature has defined rules)
336338
337339
Parameters
338340
----------
339-
context: Optional[Dict[str, Any]]
341+
context: dict[str, Any] | None
340342
dict of attributes that you would like to match the rules
341343
against, can be `{'tenant_id: 'X', 'username':' 'Y', 'region': 'Z'}` etc.
342344
343345
Returns
344346
----------
345-
List[str]
347+
list[str]
346348
list of all feature names that either matches context or have True as default
347349
348350
**Example**
@@ -359,10 +361,10 @@ def get_enabled_features(self, *, context: Optional[Dict[str, Any]] = None) -> L
359361
if context is None:
360362
context = {}
361363

362-
features_enabled: List[str] = []
364+
features_enabled: list[str] = []
363365

364366
try:
365-
features: Dict[str, Any] = self.get_configuration()
367+
features: dict[str, Any] = self.get_configuration()
366368
except ConfigurationStoreError as err:
367369
self.logger.debug(f"Failed to fetch feature flags from store, returning empty list, reason={err}")
368370
return features_enabled

0 commit comments

Comments
 (0)