Add global cache for Testrail API based on Redis
Add caching for get_result_for_case request
PRODX-38927
Change-Id: Iffccc954acf16e037d6205517dbff626d0f22ed3
diff --git a/testrail_bot/control/celery_tasks/test_rail_api.py b/testrail_bot/control/celery_tasks/test_rail_api.py
index c359dbe..c441d37 100644
--- a/testrail_bot/control/celery_tasks/test_rail_api.py
+++ b/testrail_bot/control/celery_tasks/test_rail_api.py
@@ -7,6 +7,7 @@
from functools import lru_cache
from retry import retry
from .enums import StatusEnum
+from ..utils import cached
api = TestRailAPI(
"https://mirantis.testrail.com/",
@@ -14,7 +15,7 @@
settings.TESTRAIL_PASSWORD)
-@lru_cache
+@cached()
def get_project_id(project_name: str) -> Optional[int]:
project = list(filter(
lambda x: x["name"] == project_name,
@@ -25,29 +26,29 @@
return None
-@lru_cache
+@cached()
def get_suite_by_id(suite_id: int) -> dict:
return api.suites.get_suite(suite_id)
-@lru_cache
+@cached()
def get_suite_name_by_id(suite_id: int) -> str:
return api.suites.get_suite(suite_id)['name']
-@lru_cache
+@cached()
def get_suite_test_type(suite_id: int) -> str:
suite_name = get_suite_name_by_id(suite_id)
return suite_name.split(']')[1]
-@lru_cache
+@cached()
def get_plans(project_id: int, **kwargs) -> List[dict]:
plans = api.plans.get_plans(project_id=project_id, **kwargs)['plans']
return plans
-@lru_cache
+@cached()
def get_entries(plan_id: int) -> List[dict]:
return api.plans.get_plan(plan_id)["entries"]
@@ -114,6 +115,7 @@
return entries[0]["runs"][0]["id"]
+@cached()
@retry(ReadTimeout, delay=1, jitter=2, tries=3)
def get_result_for_case(run_id: int,
case_id: int,
@@ -146,6 +148,7 @@
api.results.add_result(test_id, **update_dict)
+@cached()
def is_testplan(plan_id):
try:
plan = api.plans.get_plan(plan_id)
diff --git a/testrail_bot/control/utils.py b/testrail_bot/control/utils.py
index 2dabafe..6c7738a 100644
--- a/testrail_bot/control/utils.py
+++ b/testrail_bot/control/utils.py
@@ -1,5 +1,8 @@
from parse import parse
-from typing import Dict, List
+from typing import Dict, List, Callable, Any
+from functools import wraps
+from django.core.cache import cache
+import difflib
def parse_title(test_name):
@@ -53,5 +56,39 @@
return result
+def replace_all(text: str, olds: str, new: str) -> str:
+ r = text
+ for _s in olds:
+ r = r.replace(_s, new)
+ return r
+
+
+def cached(timeout: int = None) -> Callable:
+ def decorator(func: Callable) -> Callable:
+ def wrapper(*args, **kwargs) -> Any:
+ cache_key = f'{func.__name__}_{args}_{kwargs}'
+ cache_key = replace_all(cache_key, "{}()\'\" .,:", "_")
+ result = cache.get(cache_key)
+ if result is None:
+ print(f"{func.__name__} MISS")
+ result = func(*args, **kwargs)
+ cache.set(cache_key, result, timeout=timeout)
+ return result
+
+ print(f"{func.__name__} hit")
+
+ # # FIXME Assert to test the caching mechanism
+ # _result = func(*args, **kwargs)
+ # for d in difflib.ndiff(str(result), str(_result)):
+ # if not d.startswith(" "):
+ # print(d)
+ # assert result == _result
+ # # ENDFIXME
+
+ return result
+ return wrapper
+ return decorator
+
+
if __name__ == "__main__":
pass
diff --git a/testrail_bot/testrail_bot/settings.py b/testrail_bot/testrail_bot/settings.py
index bc2f121..ca79a12 100644
--- a/testrail_bot/testrail_bot/settings.py
+++ b/testrail_bot/testrail_bot/settings.py
@@ -91,6 +91,13 @@
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+CACHES = {
+ "default": {
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
+ "LOCATION": "redis://redis:6379",
+ }
+}
+
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators