Skip to content

Commit 3d4305b

Browse files
committed
feat: add get_raw_configuration property in store; expose store
1 parent dc14b5e commit 3d4305b

File tree

4 files changed

+56
-27
lines changed

4 files changed

+56
-27
lines changed

aws_lambda_powertools/utilities/feature_flags/appconfig.py

+28-20
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,31 @@ def __init__(
5555
self.jmespath_options = jmespath_options
5656
self._conf_store = AppConfigProvider(environment=environment, application=application, config=sdk_config)
5757

58+
@property
59+
def get_raw_configuration(self) -> Dict[str, Any]:
60+
"""Fetch feature schema configuration from AWS AppConfig"""
61+
try:
62+
# parse result conf as JSON, keep in cache for self.max_age seconds
63+
return cast(
64+
dict,
65+
self._conf_store.get(
66+
name=self.name,
67+
transform=TRANSFORM_TYPE,
68+
max_age=self.cache_seconds,
69+
),
70+
)
71+
except (GetParameterError, TransformParameterError) as exc:
72+
err_msg = traceback.format_exc()
73+
if "AccessDenied" in err_msg:
74+
raise StoreClientError(err_msg) from exc
75+
raise ConfigurationStoreError("Unable to get AWS AppConfig configuration file") from exc
76+
5877
def get_configuration(self) -> Dict[str, Any]:
5978
"""Fetch feature schema configuration from AWS AppConfig
6079
80+
If envelope is set, it'll extract and return feature flags from configuration,
81+
otherwise it'll return the entire configuration fetched from AWS AppConfig.
82+
6183
Raises
6284
------
6385
ConfigurationStoreError
@@ -68,25 +90,11 @@ def get_configuration(self) -> Dict[str, Any]:
6890
Dict[str, Any]
6991
parsed JSON dictionary
7092
"""
71-
try:
72-
# parse result conf as JSON, keep in cache for self.max_age seconds
73-
config = cast(
74-
dict,
75-
self._conf_store.get(
76-
name=self.name,
77-
transform=TRANSFORM_TYPE,
78-
max_age=self.cache_seconds,
79-
),
80-
)
93+
config = self.get_raw_configuration
8194

82-
if self.envelope:
83-
config = jmespath_utils.extract_data_from_envelope(
84-
data=config, envelope=self.envelope, jmespath_options=self.jmespath_options
85-
)
95+
if self.envelope:
96+
config = jmespath_utils.extract_data_from_envelope(
97+
data=config, envelope=self.envelope, jmespath_options=self.jmespath_options
98+
)
8699

87-
return config
88-
except (GetParameterError, TransformParameterError) as exc:
89-
err_msg = traceback.format_exc()
90-
if "AccessDenied" in err_msg:
91-
raise StoreClientError(err_msg) from exc
92-
raise ConfigurationStoreError("Unable to get AWS AppConfig configuration file") from exc
100+
return config

aws_lambda_powertools/utilities/feature_flags/base.py

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33

44

55
class StoreProvider(ABC):
6+
@property
7+
@abstractmethod
8+
def get_raw_configuration(self) -> Dict[str, Any]:
9+
"""Get configuration from any store and return the parsed JSON dictionary"""
10+
raise NotImplementedError() # pragma: no cover
11+
612
@abstractmethod
713
def get_configuration(self) -> Dict[str, Any]:
814
"""Get configuration from any store and return the parsed JSON dictionary
915
16+
If envelope is set, it'll extract and return feature flags from configuration,
17+
otherwise it'll return the entire configuration fetched from the store.
18+
1019
Raises
1120
------
1221
ConfigurationStoreError
@@ -42,10 +51,10 @@ def get_configuration(self) -> Dict[str, Any]:
4251
}
4352
```
4453
"""
45-
return NotImplemented # pragma: no cover
54+
raise NotImplementedError() # pragma: no cover
4655

4756

4857
class BaseValidator(ABC):
4958
@abstractmethod
5059
def validate(self):
51-
return NotImplemented # pragma: no cover
60+
raise NotImplementedError() # pragma: no cover

aws_lambda_powertools/utilities/feature_flags/feature_flags.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import Any, Dict, List, Optional, Union, cast
2+
from typing import Any, Dict, List, Optional, cast
33

44
from . import schema
55
from .base import StoreProvider
@@ -36,7 +36,7 @@ def __init__(self, store: StoreProvider):
3636
store: StoreProvider
3737
Store to use to fetch feature flag schema configuration.
3838
"""
39-
self._store = store
39+
self.store = store
4040

4141
@staticmethod
4242
def _match_by_action(action: str, condition_value: Any, context_value: Any) -> bool:
@@ -103,7 +103,7 @@ def _evaluate_rules(
103103
return feat_default
104104
return False
105105

106-
def get_configuration(self) -> Union[Dict[str, Dict], Dict]:
106+
def get_configuration(self) -> Dict:
107107
"""Get validated feature flag schema from configured store.
108108
109109
Largely used to aid testing, since it's called by `evaluate` and `get_enabled_features` methods.
@@ -146,8 +146,8 @@ def get_configuration(self) -> Union[Dict[str, Dict], Dict]:
146146
```
147147
"""
148148
# parse result conf as JSON, keep in cache for max age defined in store
149-
logger.debug(f"Fetching schema from registered store, store={self._store}")
150-
config = self._store.get_configuration()
149+
logger.debug(f"Fetching schema from registered store, store={self.store}")
150+
config: Dict = self.store.get_configuration()
151151
validator = schema.SchemaValidator(schema=config)
152152
validator.validate()
153153

tests/functional/feature_flags/test_feature_flags.py

+12
Original file line numberDiff line numberDiff line change
@@ -587,3 +587,15 @@ def test_get_feature_toggle_propagates_access_denied_error(mocker, config):
587587
# THEN raise StoreClientError error
588588
with pytest.raises(StoreClientError, match="AccessDeniedException") as err:
589589
feature_flags.evaluate(name="Foo", default=False)
590+
591+
592+
def test_get_configuration_with_envelope_and_raw(mocker, config):
593+
expected_value = True
594+
mocked_app_config_schema = {"log_level": "INFO", "features": {"my_feature": {"default": expected_value}}}
595+
feature_flags = init_feature_flags(mocker, mocked_app_config_schema, config, envelope="features")
596+
597+
features_config = feature_flags.get_configuration()
598+
config = feature_flags.store.get_raw_configuration
599+
600+
assert "log_level" in config
601+
assert "log_level" not in features_config

0 commit comments

Comments
 (0)