Hello community, here is the log from the commit of package python-Beaker for openSUSE:Factory checked in at 2012-02-02 17:59:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Beaker (Old) and /work/SRC/openSUSE:Factory/.python-Beaker.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-Beaker", Maintainer is "" Changes: -------- --- /work/SRC/openSUSE:Factory/python-Beaker/python-Beaker.changes 2011-11-14 13:33:08.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-Beaker.new/python-Beaker.changes 2012-02-02 17:59:52.000000000 +0100 @@ -1,0 +2,8 @@ +Wed Feb 1 10:43:39 UTC 2012 - saschpe@suse.de + +- Update to version 1.6.2: + * Updated dogpile lock so that it locks per namespace+key rather than on the + entire namespace. (#101) + * Added encryption option for any backend. Patch contributed by Toby Elliot. + +------------------------------------------------------------------- Old: ---- Beaker-1.6.1.tar.gz New: ---- Beaker-1.6.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Beaker.spec ++++++ --- /var/tmp/diff_new_pack.d5f7xr/_old 2012-02-02 17:59:53.000000000 +0100 +++ /var/tmp/diff_new_pack.d5f7xr/_new 2012-02-02 17:59:53.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-Beaker # -# Copyright (c) 2011 SUSE LINUX Products GmbH, Nuernberg, Germany. +# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,9 +16,8 @@ # - Name: python-Beaker -Version: 1.6.1 +Version: 1.6.2 Release: 0 Url: http://beaker.groovie.org Summary: A Session and Caching library with WSGI Middleware @@ -28,15 +27,13 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: python-devel BuildRequires: python-distribute -%if 0%{?suse_version} -%py_requires -%if 0%{?suse_version} > 1110 -BuildArch: noarch -%endif -%endif Provides: python-beaker = %{version} Obsoletes: python-beaker < %{version} -%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} +%if 0%{?suse_version} && 0%{?suse_version} <= 1110 +%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} +%else +BuildArch: noarch +%endif %description Beaker is a web session and general caching library that includes WSGI ++++++ Beaker-1.6.1.tar.gz -> Beaker-1.6.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/Beaker.egg-info/PKG-INFO new/Beaker-1.6.2/Beaker.egg-info/PKG-INFO --- old/Beaker-1.6.1/Beaker.egg-info/PKG-INFO 2011-10-20 18:54:26.000000000 +0200 +++ new/Beaker-1.6.2/Beaker.egg-info/PKG-INFO 2011-12-14 01:29:55.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: Beaker -Version: 1.6.1 +Version: 1.6.2 Summary: A Session and Caching library with WSGI Middleware Home-page: http://beaker.groovie.org Author: Ben Bangert, Mike Bayer, Philip Jenvey diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/CHANGELOG new/Beaker-1.6.2/CHANGELOG --- old/Beaker-1.6.1/CHANGELOG 2011-10-20 18:52:10.000000000 +0200 +++ new/Beaker-1.6.2/CHANGELOG 2011-12-14 01:01:15.000000000 +0100 @@ -1,3 +1,10 @@ +Release 1.6.2 (12/13/2011) +========================== + +* Updated dogpile lock so that it locks per namespace+key rather than on the + entire namespace. (#101) +* Added encryption option for any backend. Patch contributed by Toby Elliot. + Release 1.6.1 (10/20/2011) ========================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/PKG-INFO new/Beaker-1.6.2/PKG-INFO --- old/Beaker-1.6.1/PKG-INFO 2011-10-20 18:54:30.000000000 +0200 +++ new/Beaker-1.6.2/PKG-INFO 2011-12-14 01:29:57.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: Beaker -Version: 1.6.1 +Version: 1.6.2 Summary: A Session and Caching library with WSGI Middleware Home-page: http://beaker.groovie.org Author: Ben Bangert, Mike Bayer, Philip Jenvey diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/__init__.py new/Beaker-1.6.2/beaker/__init__.py --- old/Beaker-1.6.1/beaker/__init__.py 2011-10-20 18:50:46.000000000 +0200 +++ new/Beaker-1.6.2/beaker/__init__.py 2011-11-14 21:20:42.000000000 +0100 @@ -1 +1 @@ -__version__ = '1.6.1' +__version__ = '1.6.2' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/cache.py new/Beaker-1.6.2/beaker/cache.py --- old/Beaker-1.6.1/beaker/cache.py 2011-10-16 20:34:40.000000000 +0200 +++ new/Beaker-1.6.2/beaker/cache.py 2011-11-14 21:22:20.000000000 +0100 @@ -574,7 +574,10 @@ def _cache_decorator_invalidate(cache, key_length, args): """Invalidate a cache key based on function arguments.""" - cache_key = " ".join(map(str, args)) + try: + cache_key = " ".join(map(str, args)) + except UnicodeEncodeError: + cache_key = " ".join(map(unicode, args)) if len(cache_key) + len(cache.namespace_name) > key_length: cache_key = sha1(cache_key).hexdigest() cache.remove_value(cache_key) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/container.py new/Beaker-1.6.2/beaker/container.py --- old/Beaker-1.6.1/beaker/container.py 2011-03-23 04:21:56.000000000 +0100 +++ new/Beaker-1.6.2/beaker/container.py 2011-12-14 00:50:50.000000000 +0100 @@ -537,7 +537,9 @@ def get_creation_lock(self, key): return file_synchronizer( - identifier = "dbmcontainer/funclock/%s" % self.namespace, + identifier = "dbmcontainer/funclock/%s/%s" % ( + self.namespace, key + ), lock_dir=self.lock_dir ) @@ -642,7 +644,9 @@ def get_creation_lock(self, key): return file_synchronizer( - identifier = "filecontainer/funclock/%s" % self.namespace, + identifier = "dbmcontainer/funclock/%s/%s" % ( + self.namespace, key + ), lock_dir = self.lock_dir ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/ext/database.py new/Beaker-1.6.2/beaker/ext/database.py --- old/Beaker-1.6.1/beaker/ext/database.py 2011-03-23 04:21:56.000000000 +0100 +++ new/Beaker-1.6.2/beaker/ext/database.py 2011-12-14 00:50:50.000000000 +0100 @@ -96,7 +96,9 @@ def get_creation_lock(self, key): return file_synchronizer( - identifier ="databasecontainer/funclock/%s" % self.namespace, + identifier ="databasecontainer/funclock/%s/%s" % ( + self.namespace, key + ), lock_dir = self.lock_dir) def do_open(self, flags, replace): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/ext/memcached.py new/Beaker-1.6.2/beaker/ext/memcached.py --- old/Beaker-1.6.1/beaker/ext/memcached.py 2011-03-23 04:23:42.000000000 +0100 +++ new/Beaker-1.6.2/beaker/ext/memcached.py 2011-12-14 00:50:50.000000000 +0100 @@ -1,11 +1,13 @@ from __future__ import with_statement from beaker.container import NamespaceManager, Container from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter -from beaker.synchronization import file_synchronizer, null_synchronizer +from beaker.synchronization import file_synchronizer from beaker.util import verify_directory, SyncDict import warnings _client_libs = {} + + def _load_client(name='auto'): if name in _client_libs: return _client_libs[name] @@ -40,14 +42,15 @@ "of: 'pylibmc' or 'memcache' to be installed.") clients = { - 'pylibmc':_pylibmc, - 'cmemcache':_cmemcache, - 'memcache':_memcache, - 'auto':_auto + 'pylibmc': _pylibmc, + 'cmemcache': _cmemcache, + 'memcache': _memcache, + 'auto': _auto } _client_libs[name] = clib = clients[name]() return clib + class MemcachedNamespaceManager(NamespaceManager): """Provides the :class:`.NamespaceManager` API over a memcache client library.""" @@ -89,8 +92,8 @@ def get_creation_lock(self, key): return file_synchronizer( - identifier="memcachedcontainer/funclock/%s" % - self.namespace,lock_dir = self.lock_dir) + identifier="memcachedcontainer/funclock/%s/%s" % + (self.namespace, key), lock_dir=self.lock_dir) def _format_key(self, key): return self.namespace + '_' + key.replace(' ', '\302\267') @@ -125,6 +128,7 @@ "Memcache caching does not " "support iteration of all cache keys") + class PyLibMCNamespaceManager(MemcachedNamespaceManager): """Provide thread-local support for pylibmc.""" @@ -162,6 +166,7 @@ with self.pool.reserve() as mc: mc.flush_all() + class MemcachedContainer(Container): """Container class which invokes :class:`.MemcacheNamespaceManager`.""" namespace_class = MemcachedNamespaceManager diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/session.py new/Beaker-1.6.2/beaker/session.py --- old/Beaker-1.6.1/beaker/session.py 2011-06-20 17:55:36.000000000 +0200 +++ new/Beaker-1.6.2/beaker/session.py 2011-11-15 02:44:18.000000000 +0100 @@ -14,6 +14,7 @@ try: import uuid + def _session_id(): return uuid.uuid4().hex except ImportError: @@ -23,6 +24,7 @@ else: def getpid(): return '' + def _session_id(): id_str = "%f%s%f%s" % ( time.time(), @@ -39,6 +41,7 @@ else: return md5(md5(id_str).hexdigest()).hexdigest() + class SignedCookie(Cookie.BaseCookie): """Extends python cookie to give digital signature support""" def __init__(self, secret, input=None): @@ -71,25 +74,37 @@ class Session(dict): """Session object that uses container package for storage. - ``key`` - The name the cookie should be set to. - ``timeout`` - How long session data is considered valid. This is used - regardless of the cookie being present or not to determine - whether session data is still valid. - ``cookie_domain`` - Domain to use for the cookie. - ``secure`` - Whether or not the cookie should only be sent over SSL. - ``httponly`` - Whether or not the cookie should only be accessible by the browser - not by JavaScript. + :param invalidate_corrupt: How to handle corrupt data when loading. When + set to True, then corrupt data will be silently + invalidated and a new session created, + otherwise invalid data will cause an exception. + :type invalidate_corrupt: bool + :param use_cookies: Whether or not cookies should be created. When set to + False, it is assumed the user will handle storing the + session on their own. + :type use_cookies: bool + :param type: What data backend type should be used to store the underlying + session data + :param key: The name the cookie should be set to. + :param timeout: How long session data is considered valid. This is used + regardless of the cookie being present or not to determine + whether session data is still valid. + :type timeout: int + :param cookie_domain: Domain to use for the cookie. + :param secure: Whether or not the cookie should only be sent over SSL. + :param httponly: Whether or not the cookie should only be accessible by + the browser not by JavaScript. + :param encrypt_key: The key to use for the local session encryption, if not + provided the session will not be encrypted. + :param validate_key: The key used to sign the local encrypted session + """ def __init__(self, request, id=None, invalidate_corrupt=False, use_cookies=True, type=None, data_dir=None, key='beaker.session.id', timeout=None, cookie_expires=True, cookie_domain=None, secret=None, secure=False, - namespace_class=None, httponly=False, **namespace_args): + namespace_class=None, httponly=False, + encrypt_key=None, validate_key=None, **namespace_args): if not type: if data_dir: self.type = 'file' @@ -117,8 +132,11 @@ self.secret = secret self.secure = secure self.httponly = httponly + self.encrypt_key = encrypt_key + self.validate_key = validate_key self.id = id self.accessed_dict = {} + self.invalidate_corrupt = invalidate_corrupt if self.use_cookies: cookieheader = request.get('cookie', '') @@ -165,7 +183,7 @@ if expires is None: if self.cookie_expires is not True: if self.cookie_expires is False: - expires = datetime.fromtimestamp( 0x7FFFFFFF ) + expires = datetime.fromtimestamp(0x7FFFFFFF) elif isinstance(self.cookie_expires, timedelta): expires = datetime.utcnow() + self.cookie_expires elif isinstance(self.cookie_expires, datetime): @@ -176,8 +194,10 @@ else: expires = None if expires is not None: + if not self.cookie or self.key not in self.cookie: + self.cookie[self.key] = self.id self.cookie[self.key]['expires'] = \ - expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" ) + expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT") return expires def _update_cookie_out(self, set_cookie=True): @@ -228,6 +248,48 @@ path = property(_get_path, _set_path) + def _encrypt_data(self, session_data=None): + """Serialize, encipher, and base64 the session dict""" + session_data = session_data or self.copy() + if self.encrypt_key: + nonce = b64encode(os.urandom(40))[:8] + encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, + self.validate_key + nonce, 1) + data = util.pickle.dumps(session_data, 2) + return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key)) + else: + data = util.pickle.dumps(session_data, 2) + return b64encode(data) + + def _decrypt_data(self, session_data): + """Bas64, decipher, then un-serialize the data for the session + dict""" + if self.encrypt_key: + try: + nonce = session_data[:8] + encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, + self.validate_key + nonce, 1) + payload = b64decode(session_data[8:]) + data = crypto.aesDecrypt(payload, encrypt_key) + except: + # As much as I hate a bare except, we get some insane errors + # here that get tossed when crypto fails, so we raise the + # 'right' exception + if self.invalidate_corrupt: + return None + else: + raise + try: + return util.pickle.loads(data) + except: + if self.invalidate_corrupt: + return None + else: + raise + else: + data = b64decode(session_data) + return util.pickle.loads(data) + def _delete_cookie(self): self.request['set_cookie'] = True expires = datetime.utcnow().replace(year=2003) @@ -266,24 +328,34 @@ try: session_data = self.namespace['session'] + if (session_data is not None and self.encrypt_key): + session_data = self._decrypt_data(session_data) + # Memcached always returns a key, its None when its not # present if session_data is None: session_data = { - '_creation_time':now, - '_accessed_time':now + '_creation_time': now, + '_accessed_time': now } self.is_new = True except (KeyError, TypeError): session_data = { - '_creation_time':now, - '_accessed_time':now + '_creation_time': now, + '_accessed_time': now + } + self.is_new = True + + if session_data is None or len(session_data) == 0: + session_data = { + '_creation_time': now, + '_accessed_time': now } self.is_new = True if self.timeout is not None and \ now - session_data['_accessed_time'] > self.timeout: - timed_out= True + timed_out = True else: # Properly set the last_accessed time, which is different # than the *currently* _accessed_time @@ -294,7 +366,7 @@ # Update the current _accessed_time session_data['_accessed_time'] = now - + # Set the path if applicable if '_path' in session_data: self._path = session_data['_path'] @@ -334,6 +406,9 @@ else: data = dict(self.items()) + if self.encrypt_key: + data = self._encrypt_data(data) + # Save the data if not data and 'session' in self.namespace: del self.namespace['session'] @@ -385,30 +460,25 @@ """ self.namespace.release_write_lock() + class CookieSession(Session): """Pure cookie-based session Options recognized when using cookie-based sessions are slightly more restricted than general sessions. - ``key`` - The name the cookie should be set to. - ``timeout`` - How long session data is considered valid. This is used - regardless of the cookie being present or not to determine - whether session data is still valid. - ``encrypt_key`` - The key to use for the session encryption, if not provided the - session will not be encrypted. - ``validate_key`` - The key used to sign the encrypted session - ``cookie_domain`` - Domain to use for the cookie. - ``secure`` - Whether or not the cookie should only be sent over SSL. - ``httponly`` - Whether or not the cookie should only be accessible by the browser - not by JavaScript. + :param key: The name the cookie should be set to. + :param timeout: How long session data is considered valid. This is used + regardless of the cookie being present or not to determine + whether session data is still valid. + :type timeout: int + :param cookie_domain: Domain to use for the cookie. + :param secure: Whether or not the cookie should only be sent over SSL. + :param httponly: Whether or not the cookie should only be accessible by + the browser not by JavaScript. + :param encrypt_key: The key to use for the local session encryption, if not + provided the session will not be encrypted. + :param validate_key: The key used to sign the local encrypted session """ def __init__(self, request, key='beaker.session.id', timeout=None, @@ -452,7 +522,8 @@ if self.key in self.cookie and self.cookie[self.key].value is not None: self.is_new = False try: - self.update(self._decrypt_data()) + cookie_data = self.cookie[self.key].value + self.update(self._decrypt_data(cookie_data)) self._path = self.get('_path', '/') except: pass @@ -487,32 +558,6 @@ path = property(_get_path, _set_path) - def _encrypt_data(self): - """Serialize, encipher, and base64 the session dict""" - if self.encrypt_key: - nonce = b64encode(os.urandom(40))[:8] - encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, - self.validate_key + nonce, 1) - data = util.pickle.dumps(self.copy(), 2) - return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key)) - else: - data = util.pickle.dumps(self.copy(), 2) - return b64encode(data) - - def _decrypt_data(self): - """Bas64, decipher, then un-serialize the data for the session - dict""" - if self.encrypt_key: - nonce = self.cookie[self.key].value[:8] - encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, - self.validate_key + nonce, 1) - payload = b64decode(self.cookie[self.key].value[8:]) - data = crypto.aesDecrypt(payload, encrypt_key) - return util.pickle.loads(data) - else: - data = b64decode(self.cookie[self.key].value) - return util.pickle.loads(data) - def save(self, accessed_only=False): """Saves the data for this session to persistent storage""" if accessed_only and self.is_new: @@ -592,7 +637,7 @@ if self.__dict__['_sess'] is None: params = self.__dict__['_params'] environ = self.__dict__['_environ'] - self.__dict__['_headers'] = req = {'cookie_out':None} + self.__dict__['_headers'] = req = {'cookie_out': None} req['cookie'] = environ.get('HTTP_COOKIE') if params.get('type') == 'cookie': self.__dict__['_sess'] = CookieSession(req, **params) @@ -627,7 +672,7 @@ return iter(self._session().keys()) def __contains__(self, key): - return self._session().has_key(key) + return key in self._session() def get_by_id(self, id): """Loads a session given a session ID""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/beaker/util.py new/Beaker-1.6.2/beaker/util.py --- old/Beaker-1.6.1/beaker/util.py 2011-10-20 18:52:02.000000000 +0200 +++ new/Beaker-1.6.2/beaker/util.py 2011-12-14 00:50:50.000000000 +0100 @@ -297,6 +297,7 @@ ('encrypt_key', (str, types.NoneType), "Session validate_key must be " "a string."), ('secure', (bool, types.NoneType), "Session secure must be a boolean."), + ('httponly', (bool, types.NoneType), "Session httponly must be a boolean."), ('timeout', (int, types.NoneType), "Session timeout must be an " "integer."), ('auto', (bool, types.NoneType), "Session is created if accessed."), @@ -356,6 +357,8 @@ if regions: region_configs = {} for region in regions: + if not region: # ensure region name is valid + continue # Setup the default cache options region_options = dict(data_dir=options.get('data_dir'), lock_dir=options.get('lock_dir'), @@ -363,9 +366,10 @@ enabled=options['enabled'], expire=options.get('expire'), key_length=options.get('key_length', 250)) - region_len = len(region) + 1 + region_prefix = '%s.' % region + region_len = len(region_prefix) for key in options.keys(): - if key.startswith('%s.' % region): + if key.startswith(region_prefix): region_options[key[region_len:]] = options.pop(key) coerce_cache_params(region_options) region_configs[region] = region_options diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/setup.py new/Beaker-1.6.2/setup.py --- old/Beaker-1.6.1/setup.py 2011-06-20 17:55:36.000000000 +0200 +++ new/Beaker-1.6.2/setup.py 2011-11-14 21:08:51.000000000 +0100 @@ -15,7 +15,7 @@ ) pycryptopp = 'pycryptopp>=0.5.12' -tests_require = ['nose', 'webtest'] +tests_require = ['nose', 'webtest', 'Mock'] if not sys.platform.startswith('java') and not sys.platform == 'cli': tests_require.extend([pycryptopp, 'SQLALchemy']) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.6.1/tests/test_memcached.py new/Beaker-1.6.2/tests/test_memcached.py --- old/Beaker-1.6.1/tests/test_memcached.py 2011-03-23 04:21:56.000000000 +0100 +++ new/Beaker-1.6.2/tests/test_memcached.py 2011-11-14 21:18:19.000000000 +0100 @@ -1,4 +1,5 @@ # coding: utf-8 +import mock import os from beaker.cache import clsmap, Cache, util @@ -260,10 +261,6 @@ yield self.master memcache.ThreadMappedPool = ThreadMappedPool - def tearDown(self): - from beaker.ext import memcached - memcached.pylibmc = memcached.memcache = None - def test_uses_pylibmc_client(self): from beaker.ext import memcached cache = Cache('test', data_dir='./cache', @@ -272,11 +269,13 @@ assert isinstance(cache.namespace, memcached.PyLibMCNamespaceManager) def test_dont_use_pylibmc_client(self): - from beaker.ext import memcached - memcached.pylibmc = None - cache = Cache('test', data_dir='./cache', url=mc_url, type="ext:memcached") - assert not isinstance(cache.namespace, memcached.PyLibMCNamespaceManager) - assert isinstance(cache.namespace, memcached.MemcachedNamespaceManager) + from beaker.ext.memcached import _load_client + load_mock = mock.Mock() + load_mock.return_value = _load_client('memcache') + with mock.patch('beaker.ext.memcached._load_client', load_mock): + cache = Cache('test', data_dir='./cache', url=mc_url, type="ext:memcached") + assert not isinstance(cache.namespace, memcached.PyLibMCNamespaceManager) + assert isinstance(cache.namespace, memcached.MemcachedNamespaceManager) def test_client(self): cache = Cache('test', data_dir='./cache', url=mc_url, type="ext:memcached") -- To unsubscribe, e-mail: opensuse-commit+unsubscribe@opensuse.org For additional commands, e-mail: opensuse-commit+help@opensuse.org