Skip to content

Commit 2d94c18

Browse files
committed
[ELI-619] - switching to global caching
1 parent 2a4efe5 commit 2d94c18

6 files changed

Lines changed: 30 additions & 31 deletions

File tree

poetry.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pyhamcrest = "^2.1.0"
3535
boto3 = "^1.40.57"
3636
botocore = "^1.40.76"
3737
aws-xray-sdk = "2.15.0"
38+
cachetools = "^7.0.1"
3839

3940
[tool.poetry.group.dev.dependencies]
4041
ruff = "^0.14.10"
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Literal
2+
import os
23

34
URL_PREFIX = "patient-check"
45
RULE_STOP_DEFAULT = False
@@ -8,9 +9,4 @@
89
CONSUMER_MAPPING_FILE_NAME = "consumer_mapping_config.json"
910
RESERVED_TEST_CONSUMER_IDS = {"test-consumer-1", "test-consumer-2", "test-consumer-3"}
1011

11-
TTL = {
12-
"test": 300,
13-
"dev": 300,
14-
"preprod": 300,
15-
"prod": 300,
16-
}
12+
CACHE_TTL_SECONDS = int(os.getenv("CONFIG_CACHE_TTL_SECONDS", "1800"))

src/eligibility_signposting_api/repos/campaign_repo.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@
77

88
from aws_xray_sdk.core import xray_recorder
99
from botocore.client import BaseClient
10+
from cachetools import TTLCache
1011
from wireup import Inject, service
1112

1213
from eligibility_signposting_api.model.campaign_config import CampaignConfig, Rules
13-
from eligibility_signposting_api.config.constants import TTL, RESERVED_TEST_CONSUMER_IDS
14+
from eligibility_signposting_api.config.constants import CACHE_TTL_SECONDS, RESERVED_TEST_CONSUMER_IDS
1415

1516
BucketName = NewType("BucketName", str)
1617

1718
logger = logging.getLogger(__name__)
1819

20+
campaign_config_cache: TTLCache[str, list[CampaignConfig]] = TTLCache(maxsize=1, ttl=CACHE_TTL_SECONDS)
21+
1922
@service
2023
class CampaignRepo:
2124
"""Repository class for Campaign Rules, which we can use to calculate a person's eligibility for vaccination.
@@ -30,38 +33,29 @@ def __init__(
3033
super().__init__()
3134
self.s3_client = s3_client
3235
self.bucket_name = bucket_name
33-
self._campaign_configs_cache: list[CampaignConfig] | None = None
34-
self._cache_expiry_epoch: float = 0.0
35-
self._cache_ttl_seconds: int = int(TTL.get(os.getenv("ENVIRONMENT"), 0))
3636

3737
def get_campaign_configs(self, consumer_id: str) -> Generator[CampaignConfig, None, None]:
38-
now = time.time()
39-
cache_enabled = self._cache_ttl_seconds > 0
40-
cache_valid = (
41-
cache_enabled
42-
and consumer_id not in RESERVED_TEST_CONSUMER_IDS
43-
and self._campaign_configs_cache is not None
44-
and now < self._cache_expiry_epoch
45-
)
38+
bypass = consumer_id in RESERVED_TEST_CONSUMER_IDS
39+
cache_key = "all_campaigns"
40+
cached = None if bypass else campaign_config_cache.get(cache_key)
4641

4742
with xray_recorder.in_subsegment("CampaignRepo.get_campaign_configs"):
48-
if cache_valid:
43+
if cached is not None:
4944
logger.info("Using cached campaign configs")
50-
yield from self._campaign_configs_cache
45+
yield from cached
5146
return
5247

5348
logger.info(
5449
"Refreshing campaign configs from S3 (consumer_id=%s, ttl_seconds=%s)",
5550
consumer_id,
56-
self._cache_ttl_seconds,
51+
CACHE_TTL_SECONDS,
5752
)
58-
campaign_configs = self._load_campaign_configs_from_s3()
53+
configs = self._load_campaign_configs_from_s3()
5954

60-
if cache_enabled and consumer_id not in RESERVED_TEST_CONSUMER_IDS:
61-
self._campaign_configs_cache = campaign_configs
62-
self._cache_expiry_epoch = now + self._cache_ttl_seconds
55+
if not bypass:
56+
campaign_config_cache[cache_key] = configs
6357

64-
yield from campaign_configs
58+
yield from configs
6559

6660
def _load_campaign_configs_from_s3(self) -> list[CampaignConfig]:
6761
campaign_configs: list[CampaignConfig] = []

tests/integration/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from eligibility_signposting_api.model.consumer_mapping import ConsumerCampaign, ConsumerId, ConsumerMapping
3434
from eligibility_signposting_api.processors.hashing_service import HashingService, HashSecretName
3535
from eligibility_signposting_api.repos import SecretRepo
36-
from eligibility_signposting_api.repos.campaign_repo import BucketName
36+
from eligibility_signposting_api.repos.campaign_repo import BucketName, campaign_config_cache
3737
from eligibility_signposting_api.repos.person_repo import TableName
3838
from tests.fixtures.builders.model import rule
3939
from tests.fixtures.builders.model.rule import RulesMapperFactory
@@ -103,6 +103,10 @@ def moto_server(request: pytest.FixtureRequest) -> URL:
103103
return url
104104

105105

106+
@pytest.fixture(autouse=True)
107+
def clear_cache():
108+
campaign_config_cache.clear()
109+
106110
def is_responsive(url: URL) -> bool:
107111
try:
108112
response = httpx.get(str(url))

tests/unit/repos/test_campaign_repo.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from eligibility_signposting_api.repos.campaign_repo import CampaignRepo, BucketName
7+
from eligibility_signposting_api.repos.campaign_repo import CampaignRepo, BucketName, campaign_config_cache
88
from tests.fixtures.builders.model.rule import CampaignConfigFactory
99

1010

@@ -13,6 +13,10 @@ def make_s3_body(payload: dict):
1313

1414

1515
class TestCampaignRepo:
16+
@pytest.fixture(autouse=True)
17+
def clear_cache(self):
18+
campaign_config_cache.clear()
19+
1620
@pytest.fixture
1721
def mock_s3_client(self):
1822
return MagicMock()

0 commit comments

Comments
 (0)