Repository: festinuz/gecaso

Issue:
Remove 'pack' and 'unpack' methods from BaseStorage
[LightShadow suggests](https://www.reddit.com/r/Python/comments/6on8in/feedback_required_for_a_small_cache_library/dkix5kp/) that 'pack' and 'unpack' methods should not be part of BaseStorage.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 6b3b100..9db7af6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Gecaso
 
-[![PyPI version](https://badge.fury.io/py/gecaso.svg)](https://badge.fury.io/py/gecaso) ![master branch status](https://api.travis-ci.org/festinuz/gecaso.svg?branch=master)
+![master branch status](https://api.travis-ci.org/festinuz/gecaso.svg?branch=master)
 
 Gecaso provides you with the tools that help with creating cache for your specific task.
 
diff --git a/gecaso/__init__.py b/gecaso/__init__.py
index 5393200..e7ee9ac 100644
--- a/gecaso/__init__.py
+++ b/gecaso/__init__.py
@@ -1,7 +1,7 @@
 from gecaso.cache import cached
-from gecaso.storage import BaseStorage, LocalMemoryStorage, LRUStorage
-from gecaso.utils import asyncify
+from gecaso.storage import BaseStorage, MemoryStorage, LRUStorage
+from gecaso.utils import pack, unpack
 
 
 __all__ = [
-    'cached', 'BaseStorage', 'LocalMemoryStorage', 'LRUStorage', 'asyncify']
+    'cached', 'BaseStorage', 'MemoryStorage', 'LRUStorage', 'pack', 'unpack']
diff --git a/gecaso/cache.py b/gecaso/cache.py
index 66ae76c..55cfb5c 100644
--- a/gecaso/cache.py
+++ b/gecaso/cache.py
@@ -21,17 +21,14 @@ def cached(cache_storage, loop=None, **params):
 
 
 def _cached(cache_storage, loop, **params):
-    storage_get = utils.asyncify(cache_storage.get)
-    storage_set = utils.asyncify(cache_storage.set)
-
     def wrapper(function):
         async def wrapped_function(*args, **kwargs):
             key = utils.make_key(function, *args, **kwargs)
             try:
-                result = await storage_get(key)
+                result = await cache_storage.get(key)
             except KeyError:
                 result = await function(*args, **kwargs)
-                await storage_set(key, result, **params)
+                await cache_storage.set(key, result, **params)
             return result
 
         def sync_wrapped_function(*args, **kwargs):
diff --git a/gecaso/storage.py b/gecaso/storage.py
index 9d27b73..96b5d27 100644
--- a/gecaso/storage.py
+++ b/gecaso/storage.py
@@ -1,6 +1,5 @@
 import abc
 import time
-import pickle
 
 from . import utils
 
@@ -10,58 +9,37 @@ class BaseStorage(metaclass=abc.ABCMeta):
     from this class.
     """
     @abc.abstractmethod
-    def get(self, key):
+    async def get(self, key):
         """Must throw KeyError if key is not found"""
         pass
 
     @abc.abstractmethod
-    def set(self, key, value, **params):
+    async def set(self, key, value, **params):
         pass
 
     @abc.abstractmethod
-    def remove(self, *keys):
+    async def remove(self, *keys):
         pass
 
-    def pack(self, value, **params):
-        """Packs value and methods into a object which is then converted to
-        bytes using pickle library. Used to simplify storaging because bytes
-        can bestored almost anywhere.
-        """
-        result = utils.Namespace(value=value, params=params)
-        return pickle.dumps(result)
-
-    def unpack(self, value):
-        """Unpacks bytes object packed with 'pack' method. Returns packed value
-        and parameters.
-        """
-        result = pickle.loads(value)
-        return result.value, result.params
-
-    def verified_get(self, value, **params):
-        """Given value and params, returns value if all methods called from
-        params (method name is assumed as 'vfunc_PARAMNAME' and argument is
-        value of param) return 'True'; Else raises KeyError."""
-        if all([getattr(self, 'vfunc_'+f)(v) for f, v in params.items()]):
-            return value
-        else:
-            raise KeyError('Cached result didnt pass verification')
-
 
-class LocalMemoryStorage(BaseStorage):
+class MemoryStorage(BaseStorage):
     def __init__(self):
         self._storage = dict()
 
-    def get(self, key):
-        value, params = self.unpack(self._storage[key])
-        return self.verified_get(value, **params)
+    async def get(self, key):
+        value, params = utils.unpack(self._storage[key])
+        if all([getattr(self, 'vfunc_'+f)(v) for f, v in params.items()]):
+            return value
+        else:
+            raise KeyError('Cached result didnt pass verification')
 
-    def set(self, key, value, ttl=None):
+    async def set(self, key, value, ttl=None):
         params = dict()
         if ttl:
             params['ttl'] = time.time() + ttl
-        self._storage[key] = self.pack(value, **params)
+        self._storage[key] = utils.pack(value, **params)
 
-    def remove(self, *keys):
+    async def remove(self, *keys):
         for key in keys:
             self._storage.pop(key, None)
 
@@ -71,7 +49,7 @@ class LocalMemoryStorage(BaseStorage):
 
 class LRUStorage(BaseStorage):
     """Storage that provides LRUCache functionality when used with 'cached'
-    wrapper. If 'storage' argument is not provided, LocalMemoryStorage is used
+    wrapper. If 'storage' argument is not provided, MemoryStorage is used
     as default substorage. Any provided storage is expected to be inherited
     from BaseStorage.
     """
@@ -93,18 +71,15 @@ class LRUStorage(BaseStorage):
             self.next.prev = self.prev
 
     def __init__(self, storage=None, maxsize=128):
-        self._storage = storage or LocalMemoryStorage()
+        self._storage = storage or MemoryStorage()
         self._nodes = dict()
         self._maxsize = maxsize
         self._head = LRUStorage.Node()  # This empty node will always be last
-        self.storage_set = utils.asyncify(self._storage.set)
-        self.storage_get = utils.asyncify(self._storage.get)
-        self.storage_remove = utils.asyncify(self._storage.remove)
 
     async def get(self, key):
         node = self._nodes.pop(key)  # Throws KeyError on failure
         node.delete()
-        value = await self.storage_get(key)  # Throws KeyError on failure
+        value = await self._storage.get(key)  # Throws KeyError on failure
         self._nodes[key] = LRUStorage.Node(self._head, key)
         self._head = self._nodes[key]
         return value
@@ -113,7 +88,7 @@ class LRUStorage(BaseStorage):
         if len(self._nodes) > self._maxsize:
             last_node = self._head.prev.prev  # skipping over empty node
             await self.remove(last_node.key)
-        await self.storage_set(key, value, **params)
+        await self._storage.set(key, value, **params)
         self._nodes[key] = LRUStorage.Node(self._head, key)
         self._head = self._nodes[key]
 
@@ -121,4 +96,4 @@ class LRUStorage(BaseStorage):
         for key in keys:
             node = self._nodes.pop(key)
             node.delete()
-        await self.storage_remove(*keys)
+        await self._storage.remove(*keys)
diff --git a/gecaso/utils.py b/gecaso/utils.py
index 965c298..78654cb 100644
--- a/gecaso/utils.py
+++ b/gecaso/utils.py
@@ -1,3 +1,4 @@
+import pickle
 import inspect
 import functools
 
@@ -31,3 +32,20 @@ def asyncify(function):
         else:
             return function(*args, **kwargs)
     return wrap(function, new_function)
+
+
+def pack(value, **params):
+    """Packs value and params into a object which is then converted to
+    bytes using pickle library. Is used to simplify storaging because bytes
+    can bestored almost anywhere.
+    """
+    result = Namespace(value=value, params=params)
+    return pickle.dumps(result)
+
+
+def unpack(value):
+    """Unpacks bytes object packed with 'pack' function. Returns packed value
+    and parameters.
+    """
+    result = pickle.loads(value)
+    return result.value, result.params


Test patch:
diff --git a/tests/test_local.py b/tests/test_local.py
index 1f5df7e..b507bc6 100644
--- a/tests/test_local.py
+++ b/tests/test_local.py
@@ -7,30 +7,7 @@ import pytest
 import gecaso
 
 
-class LocalAsyncMemoryStorage(gecaso.BaseStorage):
-    def __init__(self):
-        self._storage = dict()
-
-    async def get(self, key):
-        value, params = self.unpack(self._storage[key])
-        return self.verified_get(value, **params)
-
-    async def set(self, key, value, ttl=None):
-        params = dict()
-        if ttl:
-            params['ttl'] = time.time() + ttl
-        self._storage[key] = self.pack(value, **params)
-
-    async def remove(self, *keys):
-        for key in keys:
-            self._storage.pop(key, None)
-
-    def vfunc_ttl(self, time_of_death):
-        return time_of_death > time.time()
-
-
-local_storage = gecaso.LocalMemoryStorage()
-local_async_storage = LocalAsyncMemoryStorage()
+local_storage = gecaso.MemoryStorage()
 
 
 def slow_echo(time_to_sleep):
@@ -43,7 +20,7 @@ async def slow_async_echo(time_to_sleep):
     return time_to_sleep
 
 
-@pytest.mark.parametrize("storage", [local_storage, local_async_storage])
+@pytest.mark.parametrize("storage", [local_storage])
 @pytest.mark.parametrize("echo_function", [slow_echo, slow_async_echo])
 @pytest.mark.parametrize("argument", [2])
 def test_local_function_cache(storage, echo_function, argument):
diff --git a/tests/test_lru.py b/tests/test_lru.py
index e4bec67..2691537 100644
--- a/tests/test_lru.py
+++ b/tests/test_lru.py
@@ -7,28 +7,6 @@ import pytest
 import gecaso
 
 
-class LocalAsyncMemoryStorage(gecaso.BaseStorage):
-    def __init__(self):
-        self._storage = dict()
-
-    async def get(self, key):
-        value, params = self.unpack(self._storage[key])
-        return self.verified_get(value, **params)
-
-    async def set(self, key, value, ttl=None):
-        params = dict()
-        if ttl:
-            params['ttl'] = time.time() + ttl
-        self._storage[key] = self.pack(value, **params)
-
-    async def remove(self, *keys):
-        for key in keys:
-            self._storage.pop(key, None)
-
-    def vfunc_ttl(self, time_of_death):
-        return time_of_death > time.time()
-
-
 def slow_echo(time_to_sleep):
     time.sleep(time_to_sleep)
     return time_to_sleep
@@ -39,11 +17,10 @@ async def slow_async_echo(time_to_sleep):
     return time_to_sleep
 
 
-local_storage = gecaso.LocalMemoryStorage()
-local_async_storage = LocalAsyncMemoryStorage()
+local_storage = gecaso.MemoryStorage()
 
 
-@pytest.mark.parametrize("storage", [local_storage, local_async_storage])
+@pytest.mark.parametrize("storage", [local_storage])
 @pytest.mark.parametrize("echo_func", [slow_echo, slow_async_echo])
 def test_lru_cache(storage, echo_func):
     lru_echo = gecaso.cached(gecaso.LRUStorage(storage, maxsize=4))(echo_func)