mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-24 00:06:16 -04:00
Add cacheing
This commit is contained in:
@@ -100,6 +100,7 @@ def get_service_dict(issuer):
|
|||||||
return service_schema.dump(fetched).data
|
return service_schema.dump(fetched).data
|
||||||
|
|
||||||
|
|
||||||
|
@SerialisedService.cache
|
||||||
def get_service_model(issuer):
|
def get_service_model(issuer):
|
||||||
return SerialisedService(get_service_dict(issuer))
|
return SerialisedService(get_service_dict(issuer))
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ def get_api_keys_dict(issuer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@SerialisedAPIKeyCollection.cache
|
||||||
def get_api_keys_models(issuer):
|
def get_api_keys_models(issuer):
|
||||||
return SerialisedAPIKeyCollection(get_api_keys_dict(issuer))
|
return SerialisedAPIKeyCollection(get_api_keys_dict(issuer))
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ def get_template_dict(template_id, service_id):
|
|||||||
return template_dict
|
return template_dict
|
||||||
|
|
||||||
|
|
||||||
|
@SerialisedTemplate.cache
|
||||||
def get_template_model(template_id, service_id):
|
def get_template_model(template_id, service_id):
|
||||||
return SerialisedTemplate(get_template_dict(template_id, service_id))
|
return SerialisedTemplate(get_template_dict(template_id, service_id))
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,27 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import partial
|
||||||
|
from threading import RLock
|
||||||
|
|
||||||
|
import cachetools
|
||||||
|
|
||||||
|
caches = defaultdict(partial(cachetools.TTLCache, maxsize=1024, ttl=2))
|
||||||
|
locks = defaultdict(RLock)
|
||||||
|
|
||||||
|
|
||||||
class SerialisedModel(ABC):
|
class CacheMixin:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def cache(cls, func):
|
||||||
|
|
||||||
|
@cachetools.cached(cache=caches[cls.__name__], lock=locks[cls.__name__])
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class SerialisedModel(ABC, CacheMixin):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A SerialisedModel takes a dictionary, typically created by
|
A SerialisedModel takes a dictionary, typically created by
|
||||||
@@ -24,7 +44,7 @@ class SerialisedModel(ABC):
|
|||||||
return super().__dir__() + list(sorted(self.ALLOWED_PROPERTIES))
|
return super().__dir__() + list(sorted(self.ALLOWED_PROPERTIES))
|
||||||
|
|
||||||
|
|
||||||
class SerialisedModelCollection(ABC):
|
class SerialisedModelCollection(ABC, CacheMixin):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A SerialisedModelCollection takes a list of dictionaries, typically
|
A SerialisedModelCollection takes a list of dictionaries, typically
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import pytest
|
|||||||
from flask import json, current_app, request
|
from flask import json, current_app, request
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
from notifications_python_client.authentication import create_jwt_token
|
from notifications_python_client.authentication import create_jwt_token
|
||||||
|
from unittest.mock import call
|
||||||
|
|
||||||
from app import api_user
|
from app import api_user
|
||||||
from app.dao.api_key_dao import get_unsigned_secrets, save_model_api_key, get_unsigned_secret, expire_api_key
|
from app.dao.api_key_dao import get_unsigned_secrets, save_model_api_key, get_unsigned_secret, expire_api_key
|
||||||
from app.models import ApiKey, KEY_TYPE_NORMAL
|
from app.models import ApiKey, KEY_TYPE_NORMAL
|
||||||
from app.authentication.auth import AuthError, requires_admin_auth, requires_auth, GENERAL_TOKEN_ERROR_MESSAGE
|
from app.authentication.auth import AuthError, requires_admin_auth, requires_auth, GENERAL_TOKEN_ERROR_MESSAGE
|
||||||
|
from app.serialised_models import caches, get_model_api_keys, dao_fetch_service_by_id
|
||||||
|
|
||||||
from tests.conftest import set_config
|
from tests.conftest import set_config
|
||||||
|
|
||||||
@@ -457,3 +459,34 @@ def test_proxy_key_on_admin_auth_endpoint(notify_api, check_proxy_header, header
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert response.status_code == expected_status
|
assert response.status_code == expected_status
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_cache_service_and_api_key_lookups(mocker, client, sample_api_key):
|
||||||
|
|
||||||
|
mock_get_api_keys = mocker.patch(
|
||||||
|
'app.serialised_models.get_model_api_keys',
|
||||||
|
wraps=get_model_api_keys,
|
||||||
|
)
|
||||||
|
mock_get_service = mocker.patch(
|
||||||
|
'app.serialised_models.dao_fetch_service_by_id',
|
||||||
|
wraps=dao_fetch_service_by_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
token = __create_token(sample_api_key.service_id)
|
||||||
|
client.get('/notifications', headers={
|
||||||
|
'Authorization': f'Bearer {token}'
|
||||||
|
})
|
||||||
|
|
||||||
|
assert mock_get_api_keys.call_args_list == [
|
||||||
|
call(str(sample_api_key.service_id))
|
||||||
|
]
|
||||||
|
assert mock_get_service.call_args_list == [
|
||||||
|
call(str(sample_api_key.service_id))
|
||||||
|
]
|
||||||
|
|
||||||
|
assert caches['SerialisedService.from_id'].currsize == 1
|
||||||
|
assert caches['SerialisedService.from_id'].ttl == 2
|
||||||
|
|
||||||
|
assert caches['SerialisedAPIKeyCollection.from_service_id'].currsize == 1
|
||||||
|
assert caches['SerialisedAPIKeyCollection.from_service_id'].ttl == 2
|
||||||
|
|||||||
Reference in New Issue
Block a user