Merge "Make possiblity to disable cached checked tests"
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