From 6936dbfbc62709e32233a06649eecf2c74f1823b Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Sun, 14 Mar 2021 03:07:50 -0700 Subject: [PATCH 1/5] feat(parameters): Add force_update option Changes: - Add new force_update to always load even if there is a cached value --- .../utilities/parameters/base.py | 16 +++++-- .../utilities/parameters/ssm.py | 5 ++- tests/functional/test_utilities_parameters.py | 45 +++++++++++++++++++ 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index 7ce0c9e4d2e..acd3ba0ac40 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -38,7 +38,12 @@ def _has_not_expired(self, key: Tuple[str, Optional[str]]) -> bool: return key in self.store and self.store[key].ttl >= datetime.now() def get( - self, name: str, max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, **sdk_options + self, + name: str, + max_age: int = DEFAULT_MAX_AGE_SECS, + transform: Optional[str] = None, + force_update: bool = False, + **sdk_options, ) -> Union[str, list, dict, bytes]: """ Retrieve a parameter value or return the cached value @@ -53,6 +58,8 @@ def get( Optional transformation of the parameter value. Supported values are "json" for JSON strings and "binary" for base 64 encoded values. + force_update: bool, optional + Force update even before a cached item has expired sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -76,7 +83,7 @@ def get( # an acceptable tradeoff. key = (name, transform) - if self._has_not_expired(key): + if not force_update and self._has_not_expired(key): return self.store[key].value try: @@ -105,6 +112,7 @@ def get_multiple( max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, raise_on_transform_error: bool = False, + force_update: bool = False, **sdk_options, ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: """ @@ -123,6 +131,8 @@ def get_multiple( raise_on_transform_error: bool, optional Raises an exception if any transform fails, otherwise this will return a None value for each transform that failed + force_update: bool, optional + Force update even before a cached item has expired sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -137,7 +147,7 @@ def get_multiple( key = (path, transform) - if self._has_not_expired(key): + if not force_update and self._has_not_expired(key): return self.store[key].value try: diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index 0f39bfac9c0..c9b2feeea4e 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -92,6 +92,7 @@ def get( max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, decrypt: bool = False, + force_update: bool = False, **sdk_options ) -> Union[str, list, dict, bytes]: """ @@ -109,6 +110,8 @@ def get( values. decrypt: bool, optional If the parameter value should be decrypted + force_update: bool, optional + Force update even before a cached item has expired sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -124,7 +127,7 @@ def get( # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt - return super().get(name, max_age, transform, **sdk_options) + return super().get(name, max_age, transform, force_update, **sdk_options) def _get(self, name: str, decrypt: bool = False, **sdk_options) -> str: """ diff --git a/tests/functional/test_utilities_parameters.py b/tests/functional/test_utilities_parameters.py index 5a915f574ae..334174452bc 100644 --- a/tests/functional/test_utilities_parameters.py +++ b/tests/functional/test_utilities_parameters.py @@ -1663,3 +1663,48 @@ def test_get_transform_method_preserve_auto_unhandled(key): transform = parameters.base.get_transform_method(key, "auto") assert transform is None + + +def test_base_provider_get_multiple_force_update(mock_name, mock_value): + """ + Test BaseProvider.get_multiple() with cached values and force_update is True + """ + + class TestProvider(BaseProvider): + def _get(self, name: str, **kwargs) -> str: + raise NotImplementedError() + + def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: + assert path == mock_name + return {"A": mock_value} + + provider = TestProvider() + + provider.store[(mock_name, None)] = ExpirableValue({"B": mock_value}, datetime.now() + timedelta(seconds=60)) + + value = provider.get_multiple(mock_name, force_update=True) + + assert isinstance(value, dict) + assert value["A"] == mock_value + + +def test_base_provider_get_force_update(mock_name, mock_value): + """ + Test BaseProvider.get() with cached values and force_update is True + """ + + class TestProvider(BaseProvider): + def _get(self, name: str, **kwargs) -> str: + return mock_value + + def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: + raise NotImplementedError() + + provider = TestProvider() + + provider.store[(mock_name, None)] = ExpirableValue("not-value", datetime.now() + timedelta(seconds=60)) + + value = provider.get(mock_name, force_update=True) + + assert isinstance(value, str) + assert value == mock_value From 02d3957f97344156c1e3c3091c925ce95e3a537c Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Sun, 14 Mar 2021 11:10:26 -0700 Subject: [PATCH 2/5] feat(parameters): Add force_update as explicit param --- aws_lambda_powertools/utilities/parameters/ssm.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index c9b2feeea4e..5ead96e6427 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -188,7 +188,7 @@ def _get_multiple(self, path: str, decrypt: bool = False, recursive: bool = Fals def get_parameter( - name: str, transform: Optional[str] = None, decrypt: bool = False, **sdk_options + name: str, transform: Optional[str] = None, decrypt: bool = False, force_update: bool = False, **sdk_options ) -> Union[str, list, dict, bytes]: """ Retrieve a parameter value from AWS Systems Manager (SSM) Parameter Store @@ -201,6 +201,8 @@ def get_parameter( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted + force_update: bool, optional + Force update even before a cached item has expired sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -240,7 +242,7 @@ def get_parameter( # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt - return DEFAULT_PROVIDERS["ssm"].get(name, transform=transform, **sdk_options) + return DEFAULT_PROVIDERS["ssm"].get(name, transform=transform, force_update=force_update, **sdk_options) def get_parameters( From 3eeb47cfe16daa18d073c7823a28de4ef11ebd97 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Sun, 14 Mar 2021 17:55:56 -0700 Subject: [PATCH 3/5] feat(parameters): Use force_update consitently throughout --- .../utilities/parameters/appconfig.py | 11 +++++++++-- .../utilities/parameters/base.py | 4 ++-- .../utilities/parameters/secrets.py | 8 ++++++-- aws_lambda_powertools/utilities/parameters/ssm.py | 15 +++++++++++---- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/appconfig.py b/aws_lambda_powertools/utilities/parameters/appconfig.py index 8e10540b186..4abad0bc2ec 100644 --- a/aws_lambda_powertools/utilities/parameters/appconfig.py +++ b/aws_lambda_powertools/utilities/parameters/appconfig.py @@ -107,7 +107,12 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]: def get_app_config( - name: str, environment: str, application: Optional[str] = None, transform: Optional[str] = None, **sdk_options + name: str, + environment: str, + application: Optional[str] = None, + transform: Optional[str] = None, + force_update: bool = False, + **sdk_options ) -> Union[str, list, dict, bytes]: """ Retrieve a configuration value from AWS App Config. @@ -122,6 +127,8 @@ def get_app_config( Application of the configuration transform: str, optional Transforms the content from a JSON object ('json') or base64 binary string ('binary') + force_update: bool, optional + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -160,4 +167,4 @@ def get_app_config( sdk_options["ClientId"] = CLIENT_ID - return DEFAULT_PROVIDERS["appconfig"].get(name, transform=transform, **sdk_options) + return DEFAULT_PROVIDERS["appconfig"].get(name, transform=transform, force_update=force_update, **sdk_options) diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index acd3ba0ac40..1772a29d4a3 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -59,7 +59,7 @@ def get( are "json" for JSON strings and "binary" for base 64 encoded values. force_update: bool, optional - Force update even before a cached item has expired + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -132,7 +132,7 @@ def get_multiple( Raises an exception if any transform fails, otherwise this will return a None value for each transform that failed force_update: bool, optional - Force update even before a cached item has expired + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index e3981d22bcc..ee770bcafdb 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -93,7 +93,9 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]: raise NotImplementedError() -def get_secret(name: str, transform: Optional[str] = None, **sdk_options) -> Union[str, dict, bytes]: +def get_secret( + name: str, transform: Optional[str] = None, force_update: bool = False, **sdk_options +) -> Union[str, dict, bytes]: """ Retrieve a parameter value from AWS Secrets Manager @@ -103,6 +105,8 @@ def get_secret(name: str, transform: Optional[str] = None, **sdk_options) -> Uni Name of the parameter transform: str, optional Transforms the content from a JSON object ('json') or base64 binary string ('binary') + force_update: bool, optional + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the get_secret_value call @@ -139,4 +143,4 @@ def get_secret(name: str, transform: Optional[str] = None, **sdk_options) -> Uni if "secrets" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["secrets"] = SecretsProvider() - return DEFAULT_PROVIDERS["secrets"].get(name, transform=transform, **sdk_options) + return DEFAULT_PROVIDERS["secrets"].get(name, transform=transform, force_update=force_update, **sdk_options) diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index 5ead96e6427..cc0f368672c 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -111,7 +111,7 @@ def get( decrypt: bool, optional If the parameter value should be decrypted force_update: bool, optional - Force update even before a cached item has expired + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -202,7 +202,7 @@ def get_parameter( decrypt: bool, optional If the parameter values should be decrypted force_update: bool, optional - Force update even before a cached item has expired + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -246,7 +246,12 @@ def get_parameter( def get_parameters( - path: str, transform: Optional[str] = None, recursive: bool = True, decrypt: bool = False, **sdk_options + path: str, + transform: Optional[str] = None, + recursive: bool = True, + decrypt: bool = False, + force_update: bool = False, + **sdk_options ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: """ Retrieve multiple parameter values from AWS Systems Manager (SSM) Parameter Store @@ -261,6 +266,8 @@ def get_parameters( If this should retrieve the parameter values recursively or not, defaults to True decrypt: bool, optional If the parameter values should be decrypted + force_update: bool, optional + Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameters_by_path API call @@ -300,4 +307,4 @@ def get_parameters( sdk_options["recursive"] = recursive sdk_options["decrypt"] = decrypt - return DEFAULT_PROVIDERS["ssm"].get_multiple(path, transform=transform, **sdk_options) + return DEFAULT_PROVIDERS["ssm"].get_multiple(path, transform=transform, force_update=force_update, **sdk_options) From 3f30fb9c63635dad32d53ad84173754aad43a7f6 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Mon, 15 Mar 2021 03:54:27 -0700 Subject: [PATCH 4/5] chore: bump ci From e0e01841593c3c00f13c4d3bda282c27e7822135 Mon Sep 17 00:00:00 2001 From: Michael Brewer Date: Mon, 15 Mar 2021 10:40:53 -0700 Subject: [PATCH 5/5] refactor(parameters): Rename force_update from force_fetch --- .../utilities/parameters/appconfig.py | 6 +++--- .../utilities/parameters/base.py | 12 ++++++------ .../utilities/parameters/secrets.py | 6 +++--- .../utilities/parameters/ssm.py | 18 +++++++++--------- tests/functional/test_utilities_parameters.py | 8 ++++---- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/aws_lambda_powertools/utilities/parameters/appconfig.py b/aws_lambda_powertools/utilities/parameters/appconfig.py index 4abad0bc2ec..ad36395c452 100644 --- a/aws_lambda_powertools/utilities/parameters/appconfig.py +++ b/aws_lambda_powertools/utilities/parameters/appconfig.py @@ -111,7 +111,7 @@ def get_app_config( environment: str, application: Optional[str] = None, transform: Optional[str] = None, - force_update: bool = False, + force_fetch: bool = False, **sdk_options ) -> Union[str, list, dict, bytes]: """ @@ -127,7 +127,7 @@ def get_app_config( Application of the configuration transform: str, optional Transforms the content from a JSON object ('json') or base64 binary string ('binary') - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -167,4 +167,4 @@ def get_app_config( sdk_options["ClientId"] = CLIENT_ID - return DEFAULT_PROVIDERS["appconfig"].get(name, transform=transform, force_update=force_update, **sdk_options) + return DEFAULT_PROVIDERS["appconfig"].get(name, transform=transform, force_fetch=force_fetch, **sdk_options) diff --git a/aws_lambda_powertools/utilities/parameters/base.py b/aws_lambda_powertools/utilities/parameters/base.py index 1772a29d4a3..b07312f19d3 100644 --- a/aws_lambda_powertools/utilities/parameters/base.py +++ b/aws_lambda_powertools/utilities/parameters/base.py @@ -42,7 +42,7 @@ def get( name: str, max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, - force_update: bool = False, + force_fetch: bool = False, **sdk_options, ) -> Union[str, list, dict, bytes]: """ @@ -58,7 +58,7 @@ def get( Optional transformation of the parameter value. Supported values are "json" for JSON strings and "binary" for base 64 encoded values. - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -83,7 +83,7 @@ def get( # an acceptable tradeoff. key = (name, transform) - if not force_update and self._has_not_expired(key): + if not force_fetch and self._has_not_expired(key): return self.store[key].value try: @@ -112,7 +112,7 @@ def get_multiple( max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, raise_on_transform_error: bool = False, - force_update: bool = False, + force_fetch: bool = False, **sdk_options, ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: """ @@ -131,7 +131,7 @@ def get_multiple( raise_on_transform_error: bool, optional Raises an exception if any transform fails, otherwise this will return a None value for each transform that failed - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -147,7 +147,7 @@ def get_multiple( key = (path, transform) - if not force_update and self._has_not_expired(key): + if not force_fetch and self._has_not_expired(key): return self.store[key].value try: diff --git a/aws_lambda_powertools/utilities/parameters/secrets.py b/aws_lambda_powertools/utilities/parameters/secrets.py index ee770bcafdb..f14e4703ba8 100644 --- a/aws_lambda_powertools/utilities/parameters/secrets.py +++ b/aws_lambda_powertools/utilities/parameters/secrets.py @@ -94,7 +94,7 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]: def get_secret( - name: str, transform: Optional[str] = None, force_update: bool = False, **sdk_options + name: str, transform: Optional[str] = None, force_fetch: bool = False, **sdk_options ) -> Union[str, dict, bytes]: """ Retrieve a parameter value from AWS Secrets Manager @@ -105,7 +105,7 @@ def get_secret( Name of the parameter transform: str, optional Transforms the content from a JSON object ('json') or base64 binary string ('binary') - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the get_secret_value call @@ -143,4 +143,4 @@ def get_secret( if "secrets" not in DEFAULT_PROVIDERS: DEFAULT_PROVIDERS["secrets"] = SecretsProvider() - return DEFAULT_PROVIDERS["secrets"].get(name, transform=transform, force_update=force_update, **sdk_options) + return DEFAULT_PROVIDERS["secrets"].get(name, transform=transform, force_fetch=force_fetch, **sdk_options) diff --git a/aws_lambda_powertools/utilities/parameters/ssm.py b/aws_lambda_powertools/utilities/parameters/ssm.py index cc0f368672c..9c29436342a 100644 --- a/aws_lambda_powertools/utilities/parameters/ssm.py +++ b/aws_lambda_powertools/utilities/parameters/ssm.py @@ -92,7 +92,7 @@ def get( max_age: int = DEFAULT_MAX_AGE_SECS, transform: Optional[str] = None, decrypt: bool = False, - force_update: bool = False, + force_fetch: bool = False, **sdk_options ) -> Union[str, list, dict, bytes]: """ @@ -110,7 +110,7 @@ def get( values. decrypt: bool, optional If the parameter value should be decrypted - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Arguments that will be passed directly to the underlying API call @@ -127,7 +127,7 @@ def get( # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt - return super().get(name, max_age, transform, force_update, **sdk_options) + return super().get(name, max_age, transform, force_fetch, **sdk_options) def _get(self, name: str, decrypt: bool = False, **sdk_options) -> str: """ @@ -188,7 +188,7 @@ def _get_multiple(self, path: str, decrypt: bool = False, recursive: bool = Fals def get_parameter( - name: str, transform: Optional[str] = None, decrypt: bool = False, force_update: bool = False, **sdk_options + name: str, transform: Optional[str] = None, decrypt: bool = False, force_fetch: bool = False, **sdk_options ) -> Union[str, list, dict, bytes]: """ Retrieve a parameter value from AWS Systems Manager (SSM) Parameter Store @@ -201,7 +201,7 @@ def get_parameter( Transforms the content from a JSON object ('json') or base64 binary string ('binary') decrypt: bool, optional If the parameter values should be decrypted - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameter API call @@ -242,7 +242,7 @@ def get_parameter( # Add to `decrypt` sdk_options to we can have an explicit option for this sdk_options["decrypt"] = decrypt - return DEFAULT_PROVIDERS["ssm"].get(name, transform=transform, force_update=force_update, **sdk_options) + return DEFAULT_PROVIDERS["ssm"].get(name, transform=transform, force_fetch=force_fetch, **sdk_options) def get_parameters( @@ -250,7 +250,7 @@ def get_parameters( transform: Optional[str] = None, recursive: bool = True, decrypt: bool = False, - force_update: bool = False, + force_fetch: bool = False, **sdk_options ) -> Union[Dict[str, str], Dict[str, dict], Dict[str, bytes]]: """ @@ -266,7 +266,7 @@ def get_parameters( If this should retrieve the parameter values recursively or not, defaults to True decrypt: bool, optional If the parameter values should be decrypted - force_update: bool, optional + force_fetch: bool, optional Force update even before a cached item has expired, defaults to False sdk_options: dict, optional Dictionary of options that will be passed to the Parameter Store get_parameters_by_path API call @@ -307,4 +307,4 @@ def get_parameters( sdk_options["recursive"] = recursive sdk_options["decrypt"] = decrypt - return DEFAULT_PROVIDERS["ssm"].get_multiple(path, transform=transform, force_update=force_update, **sdk_options) + return DEFAULT_PROVIDERS["ssm"].get_multiple(path, transform=transform, force_fetch=force_fetch, **sdk_options) diff --git a/tests/functional/test_utilities_parameters.py b/tests/functional/test_utilities_parameters.py index 334174452bc..13c493ef5d6 100644 --- a/tests/functional/test_utilities_parameters.py +++ b/tests/functional/test_utilities_parameters.py @@ -1667,7 +1667,7 @@ def test_get_transform_method_preserve_auto_unhandled(key): def test_base_provider_get_multiple_force_update(mock_name, mock_value): """ - Test BaseProvider.get_multiple() with cached values and force_update is True + Test BaseProvider.get_multiple() with cached values and force_fetch is True """ class TestProvider(BaseProvider): @@ -1682,7 +1682,7 @@ def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: provider.store[(mock_name, None)] = ExpirableValue({"B": mock_value}, datetime.now() + timedelta(seconds=60)) - value = provider.get_multiple(mock_name, force_update=True) + value = provider.get_multiple(mock_name, force_fetch=True) assert isinstance(value, dict) assert value["A"] == mock_value @@ -1690,7 +1690,7 @@ def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: def test_base_provider_get_force_update(mock_name, mock_value): """ - Test BaseProvider.get() with cached values and force_update is True + Test BaseProvider.get() with cached values and force_fetch is True """ class TestProvider(BaseProvider): @@ -1704,7 +1704,7 @@ def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]: provider.store[(mock_name, None)] = ExpirableValue("not-value", datetime.now() + timedelta(seconds=60)) - value = provider.get(mock_name, force_update=True) + value = provider.get(mock_name, force_fetch=True) assert isinstance(value, str) assert value == mock_value