Commits

Andriy Kornatskyy committed 8d00021

Eliminate cache_factory concept; it is responsibility of pylibmc client to use pooling internally.

  • Participants
  • Parent commits 1707f94

Comments (0)

Files changed (12)

File doc/userguide.rst

 there do exist challenge: some caches are singletons and correctly
 provide inter-thread synchronization (thread safe), while others require
 an instance per thread (not thread safe), some sort of pooling is
-required. In order to provide an easy interchangeable approach for various cache
-implementations it is recommended use: factory method plus context
-manager.
+required. This challenge is transparently resolved.
 
-Let demonstrate this by examples::
+Here is an example how to configure pylibmc - `memcached`_ client written
+in C): ::
 
-    from wheezy.caching.memory import MemoryCache
-
-    # Singleton
-    cache = MemoryCache()
-    # Factory
-    cache_factory = lambda: memory
-    # Client code
-    with cache_factory() as cache:
-        cache.set(...)
-
-Above factory and context manager use is somewhat dummy since you are
-fine to work directly with cache. However what happens if at some point
-of time you will need to use some other cache implementation (e.g.
-pylibmc - `memcached`_ client written in C). The client code will not
-change, we just provide another ``cache_factory``::
-
-    from wheezy.caching.pools import EagerPool
-    from wheezy.caching.pools import Pooled
+    from wheezy.core.pooling import EagerPool
+    from wheezy.caching.pylibmc import MemcachedClient
     from wheezy.caching.pylibmc import client_factory
 
     # Cache Pool
     pool = EagerPool(lambda: client_factory(['/tmp/memcached.sock']), size=10)
     # Factory
-    cache_factory = lambda: Pooled(pool)
+    cache = MemcachedClient(pool)
+
     # Client code
-    with cache_factory() as cache:
-        cache.set(...)
+    cache.set(...)
 
-The client code remains unchanged, however we where able to switch to
-completely different cache implementation that requires pooling.
+The client code remains unchanged even some cache implementations
+require pooling to remain thread safe.
 
 CacheClient
 -----------
     default_cache = MemoryCache()
     membership_cache = MemoryCache()
     funds_cache = NullCache()
-    cache_factory = lambda: ClientCache({
-        'default': lambda: default_cache,
-        'membership': lambda: membership_cache,
-        'funds': lambda: funds_cache,
+    cache = ClientCache({
+        'default': default_cache,
+        'membership': membership_cache,
+        'funds': funds_cache,
     }, default_namespace='default')
 
 Application code is designed to work with a single cache by specifying
 namespace to use::
 
-    with cache_factory() as cache:
-        cache.add('x1', 1, namespace='default')
+    cache.add('x1', 1, namespace='default')
 
 At some point of time we might change our partitioning scheme so all
 namespaces reside in a single cache::
 
     default_cache = MemoryCache()
-    cache_factory = lambda: ClientCache({
-        'default': lambda: default_cache,
-        'membership': lambda: default_cache,
-        'funds': lambda: default_cache
+    cachey = ClientCache({
+        'default': default_cache,
+        'membership': default_cache,
+        'funds': default_cache
     }, default_namespace='default')
 
-That happened with no changes to application code, just configuration
+What happened with no changes to application code? Just configuration
 settings.
 
 MemoryCache
 
 Here is a typical use case::
 
-    from wheezy.caching.memcache import client_factory
+    from wheezy.caching.memcache import MemcachedClient
 
-    cache = client_factory(['unix:/tmp/memcached.sock'])
-    cache_factory = lambda: cache
+    cache = MemcachedClient(['unix:/tmp/memcached.sock'])
 
 You can specify key encoding function by passing ``key_encode`` argument that
 must be a callable that does key encoding. By default
 :py:meth:`~wheezy.caching.encoding.string_encode` is applied.
 
 All arguments passed to
-:py:meth:`~wheezy.caching.memcache.client_factory` are the same as to
+:py:meth:`~wheezy.caching.memcache.MemcachedClient` are the same as to
 original ``Client`` from python-memcache. Note, `python-memcached`_
 ``Client`` implementation is *thread local* object.
 
 
 Here is a typical use case::
 
-    from wheezy.caching.pools import EagerPool
-    from wheezy.caching.pools import Pooled
+    from wheezy.core.pooling import EagerPool
+    from wheezy.caching.pylibmc import MemcachedClient
     from wheezy.caching.pylibmc import client_factory
 
     pool = EagerPool(lambda: client_factory(['/tmp/memcached.sock']), size=10)
-    cache_factory = lambda: Pooled(pool)
+    cache = MemcachedClient(pool)
 
 You can specify key encoding function by passing ``key_encode`` argument that
 must be a callable that does key encoding. By default
 algorithm.
 
 Since `pylibmc`_ implementation is not thread safe it requires pooling,
-so we do here. :py:class:`~wheezy.caching.pools.EagerPool` holds
-a number of `pylibmc`_ instances, while
-:py:class:`~wheezy.caching.pools.Pooled` serves context manager purpose,
-effectively acquiring and returning item to the pool.
+so we do here. :py:class:`~wheezy.core.pooling.EagerPool` holds
+a number of `pylibmc`_ instances.
 
 Key Encoding
 ------------

File src/wheezy/caching/client.py

 class CacheClient(object):
     """ CacheClient serves mediator purpose between a single entry
         point that implements Cache and one or many namespaces
-        targeted to concrete cache factories.
+        targeted to concrete cache implementations.
 
         CacheClient let partition application cache by namespaces
         effectively hiding details from client code.
 
     def __init__(self, namespaces, default_namespace):
         """
-            ``namespaces`` - a mapping between namespace and cache factory.
+            ``namespaces`` - a mapping between namespace and cache.
             ``default_namespace`` - namespace to use in case it is not
                 specified in cache operation.
         """
         self.default_namespace = default_namespace
-        self.default = namespaces.pop(default_namespace)
         self.namespaces = namespaces
 
-    def __enter__(self):  # pragma: nocover
-        self.context = context = self.default()
-        self.default_cache = context.__enter__()
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):  # pragma: nocover
-        self.context.__exit__(exc_type, exc_value, traceback)
-        self.context = None
-
     def set(self, key, value, time=0, namespace=None):
         """ Sets a key's value, regardless of previous contents
             in cache.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.set(key, value, time, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.set(key, value, time, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].set(
+            key, value, time, namespace)
 
     def set_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Set multiple keys' values at once.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.set_multi(
-                mapping, time, key_prefix, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.set_multi(mapping, time, key_prefix, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].set_multi(
+            mapping, time, key_prefix, namespace)
 
     def add(self, key, value, time=0, namespace=None):
         """ Sets a key's value, if and only if the item is not
             already.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.add(key, value, time, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.add(
-                    key, value, time, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].add(
+            key, value, time, namespace)
 
     def add_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Adds multiple values at once, with no effect for keys
             already in cache.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.add_multi(
-                mapping, time, key_prefix, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.add_multi(
-                    mapping, time, key_prefix, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].add_multi(
+            mapping, time, key_prefix, namespace)
 
     def replace(self, key, value, time=0, namespace=None):
         """ Replaces a key's value, failing if item isn't already.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.replace(key, value, time, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.replace(key, value, time, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].replace(
+            key, value, time, namespace)
 
     def replace_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Replaces multiple values at once, with no effect for
             keys not in cache.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.replace_multi(
-                mapping, time, key_prefix, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.replace_multi(
-                    mapping, time, key_prefix, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].replace_multi(
+            mapping, time, key_prefix, namespace)
 
     def get(self, key, namespace=None):
         """ Looks up a single key.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.get(key, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.get(key, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].get(key, namespace)
 
     def get_multi(self, keys, key_prefix='', namespace=None):
         """ Looks up multiple keys from cache in one operation.
             This is the recommended way to do bulk loads.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.get_multi(keys, key_prefix, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.get_multi(keys, key_prefix, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].get_multi(
+            keys, key_prefix, namespace)
 
     def delete(self, key, seconds=0, namespace=None):
         """ Deletes a key from cache.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.delete(key, seconds, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.delete(key, seconds, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].delete(key, seconds, namespace)
 
     def delete_multi(self, keys, seconds=0, key_prefix='', namespace=None):
         """ Delete multiple keys at once.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.delete_multi(
-                keys, seconds, key_prefix, namespace)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.delete_multi(
-                    keys, seconds, key_prefix, namespace)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].delete_multi(
+            keys, seconds, key_prefix, namespace)
 
     def incr(self, key, delta=1, namespace=None, initial_value=None):
         """ Atomically increments a key's value. The value, if too
             exist and no initial_value is specified, the key's value
             will not be set.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.incr(
-                key, delta, namespace, initial_value)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.incr(key, delta, namespace, initial_value)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].incr(
+            key, delta, namespace, initial_value)
 
     def decr(self, key, delta=1, namespace=None, initial_value=None):
         """ Atomically decrements a key's value. The value, if too
             exist and no initial_value is specified, the key's value
             will not be set.
         """
-        if namespace is None or namespace == self.default_namespace:
-            return self.default_cache.decr(
-                key, delta, namespace, initial_value)
-        else:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                return cache.decr(key, delta, namespace, initial_value)
-            finally:
-                context.__exit__(None, None, None)
+        namespace = namespace or self.default_namespace
+        return self.namespaces[namespace].decr(
+            key, delta, namespace, initial_value)
 
     def flush_all(self):
         """ Deletes everything in cache.
         """
-        succeed = self.default_cache.flush_all()
-        for namespace in self.namespaces:
-            context = self.namespaces[namespace]()
-            cache = context.__enter__()
-            try:
-                succeed &= cache.flush_all()
-            finally:
-                context.__exit__(None, None, None)
+        succeed = True
+        for cache in self.namespaces.values():
+            succeed &= cache.flush_all()
         return succeed

File src/wheezy/caching/memcache.py

 
 try:
     Client = __import__('memcache', None, None, ['Client']).Client
-
-    def client_factory(*args, **kwargs):
-        """ Client factory for python-memcache.
-        """
-        key_encode = kwargs.pop('key_encode', None)
-        return MemcachedClient(Client(*args, **kwargs), key_encode)
 except ImportError:  # pragma: nocover
-    pass
+    Client = None
 
 
 class MemcachedClient(object):
         cache contract.
     """
 
-    def __init__(self, client, key_encode):
-        self.client = client
-        self.key_encode = key_encode or string_encode
-
-    def __enter__(self):  # pragma: nocover
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):  # pragma: nocover
-        pass
+    def __init__(self, *args, **kwargs):
+        self.key_encode = kwargs.pop('key_encode', string_encode)
+        self.client = Client(*args, **kwargs)
 
     def set(self, key, value, time=0, namespace=None):
         """ Sets a key's value, regardless of previous contents

File src/wheezy/caching/memory.py

 
 """ ``memory`` module.
 """
-
+from operator import itemgetter
 from time import time as unixtime
 
 from wheezy.caching.comp import allocate_lock
     """
     expired_keys = []
     for i in xrange(len(bucket_items) - 1, -1, -1):
-        (key, expires) = bucket_items[i]
+        key, expires = bucket_items[i]
         if expires < now:
             expired_keys.append(key)
             del bucket_items[i]
 class CacheItem(object):
     """ A single cache item stored in cache.
     """
-    __slots__ = ['key', 'value', 'expires']
+    __slots__ = ('key', 'value', 'expires')
 
     def __init__(self, key, value, expires):
         self.key = key
             (allocate_lock(), []) for i in xrange(0, buckets)]
         self.last_expire_bucket_id = -1
 
-    def __enter__(self):  # pragma: nocover
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):  # pragma: nocover
-        pass
-
     def set(self, key, value, time=0, namespace=None):
         """ Sets a key's value, regardless of previous contents
             in cache.

File src/wheezy/caching/null.py

         result no change to state.
     """
 
-    def __init__(self):
-        pass
-
-    def __enter__(self):  # pragma: nocover
-        return self
-
-    def __exit__(self, exc_type, exc_value, traceback):  # pragma: nocover
-        pass
-
     def set(self, key, value, time=0, namespace=None):
         """ Sets a key's value, regardless of previous contents
             in cache.

File src/wheezy/caching/pools.py

-
-""" ``pools`` module.
-"""
-
-from wheezy.caching.comp import Queue
-
-
-class EagerPool(object):
-    """ Eager pool implementation.
-
-        Allocates all pool items during initialization.
-    """
-
-    def __init__(self, create_factory, size):
-        pool = Queue(size)
-        for i in xrange(size):
-            pool.put(create_factory())
-        self.pool = pool
-        self.acquire = pool.get
-        self.get_back = pool.put
-
-
-class Pooled(object):
-    """ ``Pooled`` serves context manager purpose, effectively acquiring and
-        returning item to the pool.
-    """
-
-    def __init__(self, pool):
-        self.pool = pool
-
-    def __enter__(self):
-        self.item = item = self.pool.acquire()
-        return item
-
-    def __exit__(self, exc_type, exc_value, traceback):
-        self.pool.get_back(self.item)
-        self.item = None

File src/wheezy/caching/pylibmc.py

     def client_factory(*args, **kwargs):
         """ Client factory for pylibmc.
         """
-        key_encode = kwargs.pop('key_encode', None)
         kwargs.setdefault('binary', True)
         behaviors = kwargs.setdefault('behaviors', {})
         behaviors.setdefault('tcp_nodelay', True)
         behaviors.setdefault('ketama', True)
-        return MemcachedClient(Client(*args, **kwargs), key_encode)
+        return Client(*args, **kwargs)
 
     del c
 except ImportError:  # pragma: nocover
     """ A wrapper around pylibmc Client in order to adapt cache contract.
     """
 
-    def __init__(self, client, key_encode):
-        self.client = client
+    def __init__(self, pool, key_encode=None):
+        assert hasattr(pool, 'acquire')
+        assert hasattr(pool, 'get_back')
+        self.pool = pool
         self.key_encode = key_encode or string_encode
 
     def set(self, key, value, time=0, namespace=None):
         """ Sets a key's value, regardless of previous contents
             in cache.
         """
-        return self.client.set(self.key_encode(key), value, time)
+        key = self.key_encode(key)
+        try:
+            client = self.pool.acquire()
+            return client.set(key, value, time)
+        finally:
+            self.pool.get_back(client)
 
     def set_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Set multiple keys' values at once.
         """
         key_encode = self.key_encode
         keys, mapping = encode_keys(mapping, key_encode)
-        failed = self.client.set_multi(mapping, time, key_encode(key_prefix))
+        key_prefix = key_encode(key_prefix)
+        try:
+            client = self.pool.acquire()
+            failed = client.set_multi(mapping, time, key_prefix)
+        finally:
+            self.pool.get_back(client)
         return failed and [keys[key] for key in failed] or failed
 
     def add(self, key, value, time=0, namespace=None):
         """ Sets a key's value, if and only if the item is not
             already.
         """
-        return self.client.add(self.key_encode(key), value, time)
+        key = self.key_encode(key)
+        try:
+            client = self.pool.acquire()
+            return client.add(key, value, time)
+        finally:
+            self.pool.get_back(client)
 
     def add_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Adds multiple values at once, with no effect for keys
         """
         key_encode = self.key_encode
         keys, mapping = encode_keys(mapping, key_encode)
-        failed = self.client.add_multi(mapping, time, key_encode(key_prefix))
+        key_prefix = key_encode(key_prefix)
+        try:
+            client = self.pool.acquire()
+            failed = client.add_multi(mapping, time, key_prefix)
+        finally:
+            self.pool.get_back(client)
         return failed and [keys[key] for key in failed] or failed
 
     def replace(self, key, value, time=0, namespace=None):
         """ Replaces a key's value, failing if item isn't already.
         """
+        key = self.key_encode(key)
         try:
-            return self.client.replace(self.key_encode(key), value, time)
-        except NotFound:
-            return False
+            try:
+                client = self.pool.acquire()
+                return client.replace(key, value, time)
+            except NotFound:
+                return False
+        finally:
+            self.pool.get_back(client)
 
     def replace_multi(self, mapping, time=0, key_prefix='', namespace=None):
         """ Replaces multiple values at once, with no effect for
         """
         key_encode = self.key_encode
         failed = []
-        client = self.client
-        for key in mapping:
-            try:
-                client.replace(
-                    key_encode(key_prefix + key), mapping[key], time)
-            except NotFound:
-                failed.append(key)
+        mapping = [(key, key_encode(key_prefix + key), mapping[key])
+                   for key in mapping]
+        try:
+            client = self.pool.acquire()
+            for key, key_encoded, value in mapping:
+                try:
+                    client.replace(key_encoded, value, time)
+                except NotFound:
+                    failed.append(key)
+        finally:
+            self.pool.get_back(client)
         return failed
 
     def get(self, key, namespace=None):
         """ Looks up a single key.
         """
-        return self.client.get(self.key_encode(key))
+        key = self.key_encode(key)
+        try:
+            client = self.pool.acquire()
+            return client.get(key)
+        finally:
+            self.pool.get_back(client)
 
     def get_multi(self, keys, key_prefix='', namespace=None):
         """ Looks up multiple keys from cache in one operation.
         """
         key_encode = self.key_encode
         encoded_keys = map(key_encode, keys)
-        mapping = self.client.get_multi(encoded_keys, key_encode(key_prefix))
+        key_prefix = key_encode(key_prefix)
+        try:
+            client = self.pool.acquire()
+            mapping = client.get_multi(encoded_keys, key_prefix)
+        finally:
+            self.pool.get_back(client)
         if mapping:
             key_mapping = dict(zip(encoded_keys, keys))
             return dict([(key_mapping[key], mapping[key]) for key in mapping])
     def delete(self, key, seconds=0, namespace=None):
         """ Deletes a key from cache.
         """
-        return self.client.delete(self.key_encode(key))
+        key = self.key_encode(key)
+        try:
+            client = self.pool.acquire()
+            return client.delete(key)
+        finally:
+            self.pool.get_back(client)
 
     def delete_multi(self, keys, seconds=0, key_prefix='', namespace=None):
         """ Delete multiple keys at once.
         """
         key_encode = self.key_encode
-        return self.client.delete_multi(
-            map(key_encode, keys), key_encode(key_prefix))
+        keys = map(key_encode, keys)
+        key_prefix = key_encode(key_prefix)
+        try:
+            client = self.pool.acquire()
+            return client.delete_multi(keys, key_prefix)
+        finally:
+            self.pool.get_back(client)
 
     def incr(self, key, delta=1, namespace=None, initial_value=None):
         """ Atomically increments a key's value. The value, if too
         """
         key = self.key_encode(key)
         try:
-            return self.client.incr(key, delta)
-        except NotFound:
-            if initial_value is None:
-                return None
-            self.client.add(key, initial_value)
-            return self.client.incr(key, delta)
+            client = self.pool.acquire()
+            try:
+                return client.incr(key, delta)
+            except NotFound:
+                if initial_value is None:
+                    return None
+                client.add(key, initial_value)
+                return client.incr(key, delta)
+        finally:
+            self.pool.get_back(client)
 
     def decr(self, key, delta=1, namespace=None, initial_value=None):
         """ Atomically decrements a key's value. The value, if too
         """
         key = self.key_encode(key)
         try:
-            return self.client.decr(key, delta)
-        except NotFound:
-            if initial_value is None:
-                return None
-            self.client.add(key, initial_value)
-            return self.client.decr(key, delta)
+            client = self.pool.acquire()
+            try:
+                return client.decr(key, delta)
+            except NotFound:
+                if initial_value is None:
+                    return None
+                client.add(key, initial_value)
+                return client.decr(key, delta)
+        finally:
+            self.pool.get_back(client)
 
     def flush_all(self):
         """ Deletes everything in cache.
         """
-        self.client.flush_all()
+        try:
+            client = self.pool.acquire()
+            client.flush_all()
+        finally:
+            self.pool.get_back(client)
         return True

File src/wheezy/caching/tests/test_cache.py

 
-""" ``test_cache`` module.
+""" Unit tests for ``wheezy.caching``.
 """
 
 from wheezy.caching.comp import string_type

File src/wheezy/caching/tests/test_client.py

 
 cache1 = MemoryCache()
 cache2 = MemoryCache()
-cache_client = CacheClient(
+client = CacheClient(
     namespaces={
-        'cache1': lambda: cache1,
-        'cache2': lambda: cache2
+        'cache1': cache1,
+        'cache2': cache2
     },
     default_namespace='cache1')
-cache_factory = lambda: cache_client
 
 
 class CacheClientDefaultTestCase(TestCase, CacheTestMixin):
 
     def setUp(self):
-        self.context = cache_factory()
-        self.client = self.context.__enter__()
+        self.client = client
         self.namespace = None
 
     def tearDown(self):
         self.client.flush_all()
-        self.context.__exit__(None, None, None)
 
 
 class CacheClientDefaultByNameTestCase(TestCase, CacheTestMixin):
 
     def setUp(self):
-        self.context = cache_factory()
-        self.client = self.context.__enter__()
+        self.client = client
         self.namespace = 'cache1'
 
     def tearDown(self):
         self.client.flush_all()
-        self.context.__exit__(None, None, None)
 
 
 class CacheClientByNamespaceTestCase(TestCase, CacheTestMixin):
 
     def setUp(self):
-        self.context = cache_factory()
-        self.client = self.context.__enter__()
+        self.client = client
         self.namespace = 'cache2'
 
     def tearDown(self):
         self.client.flush_all()
-        self.context.__exit__(None, None, None)

File src/wheezy/caching/tests/test_memcache.py

 
-"""
+""" Unit tests for ``wheezy.caching.memcache``.
 """
 
 from unittest import TestCase
 from wheezy.caching.tests.test_cache import CacheTestMixin
 
 try:
-    from wheezy.caching.memcache import client_factory
-
-    client = client_factory(['unix:/tmp/memcached.sock'])
-    cache_factory = lambda: client
+    from wheezy.caching.memcache import MemcachedClient
 
     class MemcacheClientTestCase(TestCase, CacheTestMixin):
 
         def setUp(self):
-            self.context = cache_factory()
-            self.client = self.context.__enter__()
+            self.client = MemcachedClient(['unix:/tmp/memcached.sock'])
             self.namespace = None
 
         def tearDown(self):
             self.client.flush_all()
-            self.context.__exit__(None, None, None)
 
         def test_delete(self):
             assert self.client.delete('d')

File src/wheezy/caching/tests/test_memory.py

 
-"""
+""" Unit tests for ``wheezy.caching.memory``.
 """
 
 from unittest import TestCase
 from wheezy.caching.tests.test_cache import CacheTestMixin
 
 
-cache = MemoryCache()
-cache_factory = lambda: cache
-
-
 class MemoryCacheTestCase(TestCase, CacheTestMixin):
 
     def setUp(self):
-        self.context = cache_factory()
-        self.client = self.context.__enter__()
+        self.client = MemoryCache()
         self.namespace = None
 
     def tearDown(self):
         self.client.flush_all()
-        self.context.__exit__(None, None, None)

File src/wheezy/caching/tests/test_pylibmc.py

 
-"""
+""" Unit tests for ``wheezy.caching.pylibmc``.
 """
 
 from unittest import TestCase
 
+from wheezy.caching.comp import Queue
 from wheezy.caching.tests.test_cache import CacheTestMixin
 
+
 try:
-    from wheezy.caching.pools import EagerPool
-    from wheezy.caching.pools import Pooled
+    from wheezy.caching.pylibmc import MemcachedClient
     from wheezy.caching.pylibmc import client_factory
 
-    client_pool = EagerPool(lambda: client_factory(['/tmp/memcached.sock']), 1)
-    cache_factory = lambda: Pooled(client_pool)
+    class EagerPool(object):
+
+        def __init__(self, create_factory, size):
+            pool = Queue(size)
+            for i in xrange(size):
+                pool.put(create_factory())
+            self.pool = pool
+            self.acquire = pool.get
+            self.get_back = pool.put
+
+    client_pool = EagerPool(
+        lambda: client_factory(['/tmp/memcached.sock']), 1)
 
     class PylibmcClientTestCase(TestCase, CacheTestMixin):
 
         def setUp(self):
-            self.context = cache_factory()
-            self.client = self.context.__enter__()
+            self.client = MemcachedClient(client_pool)
             self.namespace = None
 
         def tearDown(self):
             self.client.flush_all()
-            self.context.__exit__(None, None, None)
 
         def test_delete_multi(self):
             mapping = {'d1': 1, 'd2': 2}