Skip to content

Commit

Permalink
added abstract schema fetcher class
Browse files Browse the repository at this point in the history
  • Loading branch information
Ran Isenberg committed Jul 8, 2021
1 parent b0c9daa commit 2679cb9
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 33 deletions.
4 changes: 4 additions & 0 deletions aws_lambda_powertools/utilities/feature_toggles/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Advanced feature toggles utility
"""
from .appconfig_fetcher import AppConfigFetcher
from .configuration_store import ConfigurationStore
from .exceptions import ConfigurationException
from .schema import ACTION, SchemaValidator
from .schema_fetcher import SchemaFetcher

__all__ = [
"ConfigurationException",
"ConfigurationStore",
"ACTION",
"SchemaValidator",
"AppConfigFetcher",
"SchemaFetcher",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import logging
from typing import Any, Dict, Optional

from botocore.config import Config

from aws_lambda_powertools.utilities.parameters import AppConfigProvider, GetParameterError, TransformParameterError

from .exceptions import ConfigurationException
from .schema_fetcher import SchemaFetcher

logger = logging.getLogger(__name__)


TRANSFORM_TYPE = "json"


class AppConfigFetcher(SchemaFetcher):
def __init__(
self,
environment: str,
service: str,
configuration_name: str,
cache_seconds: int,
config: Optional[Config] = None,
):
"""This class fetches JSON schemas from AWS AppConfig
Args:
environment (str): what appconfig environment to use 'dev/test' etc.
service (str): what service name to use from the supplied environment
configuration_name (str): what configuration to take from the environment & service combination
cache_seconds (int): cache expiration time, how often to call AppConfig to fetch latest configuration
config (Optional[Config]): boto3 client configuration
"""
super().__init__(configuration_name, cache_seconds)
self._logger = logger
self._conf_store = AppConfigProvider(environment=environment, application=service, config=config)

def get_json_configuration(self) -> Dict[str, Any]:
"""Get configuration string from AWs AppConfig and return the parsed JSON dictionary
Raises:
ConfigurationException: Any validation error or appconfig error that can occur
Returns:
Dict[str, Any]: parsed JSON dictionary
"""
try:
return self._conf_store.get(
name=self.configuration_name,
transform=TRANSFORM_TYPE,
max_age=self._cache_seconds,
) # parse result conf as JSON, keep in cache for self.max_age seconds
except (GetParameterError, TransformParameterError) as exc:
error_str = f"unable to get AWS AppConfig configuration file, exception={str(exc)}"
self._logger.error(error_str)
raise ConfigurationException(error_str)
Original file line number Diff line number Diff line change
@@ -1,36 +1,23 @@
# pylint: disable=no-name-in-module,line-too-long
import logging
from typing import Any, Dict, List, Optional

from botocore.config import Config

from aws_lambda_powertools.utilities.parameters import AppConfigProvider, GetParameterError, TransformParameterError

from . import schema
from .exceptions import ConfigurationException

TRANSFORM_TYPE = "json"
from .schema_fetcher import SchemaFetcher

logger = logging.getLogger(__name__)


class ConfigurationStore:
def __init__(
self, environment: str, service: str, conf_name: str, cache_seconds: int, config: Optional[Config] = None
):
def __init__(self, schema_fetcher: SchemaFetcher):
"""constructor
Args:
environment (str): what appconfig environment to use 'dev/test' etc.
service (str): what service name to use from the supplied environment
conf_name (str): what configuration to take from the environment & service combination
cache_seconds (int): cache expiration time, how often to call AppConfig to fetch latest configuration
schema_fetcher (SchemaFetcher): A schema JSON fetcher, can be AWS AppConfig, Hashicorp Consul etc.
"""
self._cache_seconds = cache_seconds
self._logger = logger
self._conf_name = conf_name
self._schema_fetcher = schema_fetcher
self._schema_validator = schema.SchemaValidator(self._logger)
self._conf_store = AppConfigProvider(environment=environment, application=service, config=config)

def _match_by_action(self, action: str, condition_value: Any, context_value: Any) -> bool:
if not context_value:
Expand Down Expand Up @@ -99,17 +86,11 @@ def get_configuration(self) -> Dict[str, Any]:
Returns:
Dict[str, Any]: parsed JSON dictionary
"""
try:
schema = self._conf_store.get(
name=self._conf_name,
transform=TRANSFORM_TYPE,
max_age=self._cache_seconds,
) # parse result conf as JSON, keep in cache for self.max_age seconds
except (GetParameterError, TransformParameterError) as exc:
error_str = f"unable to get AWS AppConfig configuration file, exception={str(exc)}"
self._logger.error(error_str)
raise ConfigurationException(error_str)

schema: Dict[
str, Any
] = (
self._schema_fetcher.get_json_configuration()
) # parse result conf as JSON, keep in cache for self.max_age seconds
# validate schema
self._schema_validator.validate_json_schema(schema)
return schema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
class ConfigurationException(Exception):
"""When a a configuration store raises an exception on schema retrieval or parsing"""
"""When a a configuration store raises an exception on config retrieval or parsing"""
20 changes: 20 additions & 0 deletions aws_lambda_powertools/utilities/feature_toggles/schema_fetcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from abc import ABC, abstractclassmethod
from typing import Any, Dict


class SchemaFetcher(ABC):
def __init__(self, configuration_name: str, cache_seconds: int):
self.configuration_name = configuration_name
self._cache_seconds = cache_seconds

@abstractclassmethod
def get_json_configuration(self) -> Dict[str, Any]:
"""Get configuration string from any configuration storing service and return the parsed JSON dictionary
Raises:
ConfigurationException: Any error that can occur during schema fetch or JSON parse
Returns:
Dict[str, Any]: parsed JSON dictionary
"""
return None
7 changes: 5 additions & 2 deletions tests/functional/feature_toggles/test_feature_toggles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest # noqa: F401
from botocore.config import Config

from aws_lambda_powertools.utilities.feature_toggles.appconfig_fetcher import AppConfigFetcher
from aws_lambda_powertools.utilities.feature_toggles.configuration_store import ConfigurationStore
from aws_lambda_powertools.utilities.feature_toggles.schema import ACTION

Expand All @@ -15,13 +16,15 @@ def config():
def init_configuration_store(mocker, mock_schema: Dict, config: Config) -> ConfigurationStore:
mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get")
mocked_get_conf.return_value = mock_schema
conf_store: ConfigurationStore = ConfigurationStore(

app_conf_fetcher = AppConfigFetcher(
environment="test_env",
service="test_app",
conf_name="test_conf_name",
configuration_name="test_conf_name",
cache_seconds=600,
config=config,
)
conf_store: ConfigurationStore = ConfigurationStore(schema_fetcher=app_conf_fetcher)
return conf_store


Expand Down
4 changes: 2 additions & 2 deletions tests/functional/feature_toggles/test_schema_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from aws_lambda_powertools.utilities.feature_toggles.exceptions import ConfigurationException
from aws_lambda_powertools.utilities.feature_toggles.schema import (
ACTION,
FEATURE_DEFAULT_VAL_KEY,
FEATURES_KEY,
CONDITION_ACTION,
CONDITION_KEY,
CONDITION_VALUE,
CONDITIONS_KEY,
FEATURE_DEFAULT_VAL_KEY,
FEATURES_KEY,
RULE_DEFAULT_VALUE,
RULE_NAME_KEY,
RULES_KEY,
Expand Down

0 comments on commit 2679cb9

Please sign in to comment.