Skip to content

Commit 3570d7f

Browse files
authored
feat(parameters): add clear_cache method for providers (#1194)
1 parent f30b056 commit 3570d7f

File tree

4 files changed

+158
-4
lines changed

4 files changed

+158
-4
lines changed

Diff for: aws_lambda_powertools/utilities/parameters/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from .appconfig import AppConfigProvider, get_app_config
8-
from .base import BaseProvider
8+
from .base import BaseProvider, clear_caches
99
from .dynamodb import DynamoDBProvider
1010
from .exceptions import GetParameterError, TransformParameterError
1111
from .secrets import SecretsProvider, get_secret
@@ -23,4 +23,5 @@
2323
"get_parameter",
2424
"get_parameters",
2525
"get_secret",
26+
"clear_caches",
2627
]

Diff for: aws_lambda_powertools/utilities/parameters/base.py

+8
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ def _get_multiple(self, path: str, **sdk_options) -> Dict[str, str]:
177177
"""
178178
raise NotImplementedError()
179179

180+
def clear_cache(self):
181+
self.store.clear()
182+
180183

181184
def get_transform_method(key: str, transform: Optional[str] = None) -> Optional[str]:
182185
"""
@@ -251,3 +254,8 @@ def transform_value(
251254
if raise_on_transform_error:
252255
raise TransformParameterError(str(exc))
253256
return None
257+
258+
259+
def clear_caches():
260+
"""Clear cached parameter values from all providers"""
261+
DEFAULT_PROVIDERS.clear()

Diff for: docs/utilities/parameters.md

+75-3
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,9 @@ The **`config`** and **`boto3_session`** parameters enable you to pass in a cust
518518

519519
## Testing your code
520520

521-
For unit testing your applications, you can mock the calls to the parameters utility to avoid calling AWS APIs. This
522-
can be achieved in a number of ways - in this example, we use the [pytest monkeypatch fixture](https://docs.pytest.org/en/latest/how-to/monkeypatch.html)
523-
to patch the `parameters.get_parameter` method:
521+
### Mocking parameter values
522+
523+
For unit testing your applications, you can mock the calls to the parameters utility to avoid calling AWS APIs. This can be achieved in a number of ways - in this example, we use the [pytest monkeypatch fixture](https://docs.pytest.org/en/latest/how-to/monkeypatch.html) to patch the `parameters.get_parameter` method:
524524

525525
=== "tests.py"
526526
```python
@@ -588,3 +588,75 @@ object named `get_parameter_mock`.
588588
assert return_val.get('message') == 'mock_value'
589589

590590
```
591+
592+
593+
### Clearing cache
594+
595+
Parameters utility caches all parameter values for performance and cost reasons. However, this can have unintended interference in tests using the same parameter name.
596+
597+
Within your tests, you can use `clear_cache` method available in [every provider](#built-in-provider-class). When using multiple providers or higher level functions like `get_parameter`, use `clear_caches` standalone function to clear cache globally.
598+
599+
600+
=== "clear_cache method"
601+
```python hl_lines="9"
602+
import pytest
603+
604+
from src import app
605+
606+
607+
@pytest.fixture(scope="function", autouse=True)
608+
def clear_parameters_cache():
609+
yield
610+
app.ssm_provider.clear_cache() # This will clear SSMProvider cache
611+
612+
@pytest.fixture
613+
def mock_parameter_response(monkeypatch):
614+
def mockreturn(name):
615+
return "mock_value"
616+
617+
monkeypatch.setattr(app.ssm_provider, "get", mockreturn)
618+
619+
# Pass our fixture as an argument to all tests where we want to mock the get_parameter response
620+
def test_handler(mock_parameter_response):
621+
return_val = app.handler({}, {})
622+
assert return_val.get('message') == 'mock_value'
623+
```
624+
625+
=== "global clear_caches"
626+
```python hl_lines="10"
627+
import pytest
628+
629+
from aws_lambda_powertools.utilities import parameters
630+
from src import app
631+
632+
633+
@pytest.fixture(scope="function", autouse=True)
634+
def clear_parameters_cache():
635+
yield
636+
parameters.clear_caches() # This will clear all providers cache
637+
638+
@pytest.fixture
639+
def mock_parameter_response(monkeypatch):
640+
def mockreturn(name):
641+
return "mock_value"
642+
643+
monkeypatch.setattr(app.ssm_provider, "get", mockreturn)
644+
645+
# Pass our fixture as an argument to all tests where we want to mock the get_parameter response
646+
def test_handler(mock_parameter_response):
647+
return_val = app.handler({}, {})
648+
assert return_val.get('message') == 'mock_value'
649+
```
650+
651+
=== "app.py"
652+
```python
653+
from aws_lambda_powertools.utilities import parameters
654+
from botocore.config import Config
655+
656+
ssm_provider = parameters.SSMProvider(config=Config(region_name="us-west-1"))
657+
658+
659+
def handler(event, context):
660+
value = ssm_provider.get("/my/parameter")
661+
return {"message": value}
662+
```

Diff for: tests/functional/test_utilities_parameters.py

+73
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,79 @@ def test_ssm_provider_get_cached(mock_name, mock_value, config):
505505
stubber.deactivate()
506506

507507

508+
def test_providers_global_clear_cache(mock_name, mock_value, monkeypatch):
509+
# GIVEN all providers are previously initialized
510+
# and parameters, secrets, and app config are fetched
511+
class TestProvider(BaseProvider):
512+
def _get(self, name: str, **kwargs) -> str:
513+
return mock_value
514+
515+
def _get_multiple(self, path: str, **kwargs) -> Dict[str, str]:
516+
...
517+
518+
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "ssm", TestProvider())
519+
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "secrets", TestProvider())
520+
monkeypatch.setitem(parameters.base.DEFAULT_PROVIDERS, "appconfig", TestProvider())
521+
522+
parameters.get_parameter(mock_name)
523+
parameters.get_secret(mock_name)
524+
parameters.get_app_config(name=mock_name, environment="test", application="test")
525+
526+
# WHEN clear_caches is called
527+
parameters.clear_caches()
528+
529+
# THEN all providers cache should be reset
530+
assert parameters.base.DEFAULT_PROVIDERS == {}
531+
532+
533+
def test_ssm_provider_clear_cache(mock_name, mock_value, config):
534+
# GIVEN a provider is initialized with a cached value
535+
provider = parameters.SSMProvider(config=config)
536+
provider.store[(mock_name, None)] = ExpirableValue(mock_value, datetime.now() + timedelta(seconds=60))
537+
538+
# WHEN clear_cache is called from within the provider instance
539+
provider.clear_cache()
540+
541+
# THEN store should be empty
542+
assert provider.store == {}
543+
544+
545+
def test_dynamodb_provider_clear_cache(mock_name, mock_value, config):
546+
# GIVEN a provider is initialized with a cached value
547+
provider = parameters.DynamoDBProvider(table_name="test", config=config)
548+
provider.store[(mock_name, None)] = ExpirableValue(mock_value, datetime.now() + timedelta(seconds=60))
549+
550+
# WHEN clear_cache is called from within the provider instance
551+
provider.clear_cache()
552+
553+
# THEN store should be empty
554+
assert provider.store == {}
555+
556+
557+
def test_secrets_provider_clear_cache(mock_name, mock_value, config):
558+
# GIVEN a provider is initialized with a cached value
559+
provider = parameters.SecretsProvider(config=config)
560+
provider.store[(mock_name, None)] = ExpirableValue(mock_value, datetime.now() + timedelta(seconds=60))
561+
562+
# WHEN clear_cache is called from within the provider instance
563+
provider.clear_cache()
564+
565+
# THEN store should be empty
566+
assert provider.store == {}
567+
568+
569+
def test_appconf_provider_clear_cache(mock_name, config):
570+
# GIVEN a provider is initialized with a cached value
571+
provider = parameters.AppConfigProvider(environment="test", application="test", config=config)
572+
provider.store[(mock_name, None)] = ExpirableValue(mock_value, datetime.now() + timedelta(seconds=60))
573+
574+
# WHEN clear_cache is called from within the provider instance
575+
provider.clear_cache()
576+
577+
# THEN store should be empty
578+
assert provider.store == {}
579+
580+
508581
def test_ssm_provider_get_expired(mock_name, mock_value, mock_version, config):
509582
"""
510583
Test SSMProvider.get() with a cached but expired value

0 commit comments

Comments
 (0)