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/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