Skip to content
Browse files

Merge

  • Loading branch information...
2 parents 91becae + 9e19ff7 commit 616be39473a00f5650304e2bfe9371913ba3930d @bbangert committed
View
22 .gitignore
@@ -0,0 +1,22 @@
+*.egg
+*.egg-info
+*.pyc
+*$py.class
+*.pt.py
+*.txt.py
+*~
+.coverage
+.tox/
+nosetests.xml
+build/
+dist/
+bin/
+lib/
+include/
+.idea/
+distribute-*.tar.gz
+bookenv/
+jyenv/
+pypyenv/
+env*/
+tests/test.db
View
58 beaker/cache.py
@@ -1,7 +1,7 @@
"""This package contains the "front end" classes and functions
for Beaker caching.
-Included are the :class:`.Cache` and :class:`.CacheManager` classes,
+Included are the :class:`.Cache` and :class:`.CacheManager` classes,
as well as the function decorators :func:`.region_decorate`,
:func:`.region_invalidate`.
@@ -23,7 +23,7 @@
cache_regions = {}
"""Dictionary of 'region' arguments.
-A "region" is a string name that refers to a series of cache
+A "region" is a string name that refers to a series of cache
configuration arguments. An application may have multiple
"regions" - one which stores things in a memory cache, one
which writes data to files, etc.
@@ -48,6 +48,7 @@
cache_managers = {}
+
class _backends(object):
initialized = False
@@ -100,19 +101,19 @@ def _init(self):
warnings.warn(
"Unable to load NamespaceManager "
"entry point: '%s': %s" % (
- entry_point,
- tb.getvalue()),
+ entry_point,
+ tb.getvalue()),
RuntimeWarning, 2)
except ImportError:
pass
# Initialize the basic available backends
clsmap = _backends({
- 'memory':container.MemoryNamespaceManager,
- 'dbm':container.DBMNamespaceManager,
- 'file':container.FileNamespaceManager,
- 'ext:memcached':memcached.MemcachedNamespaceManager,
- 'ext:database':database.DatabaseNamespaceManager,
+ 'memory': container.MemoryNamespaceManager,
+ 'dbm': container.DBMNamespaceManager,
+ 'file': container.FileNamespaceManager,
+ 'ext:memcached': memcached.MemcachedNamespaceManager,
+ 'ext:database': database.DatabaseNamespaceManager,
'ext:sqla': sqla.SqlaNamespaceManager,
'ext:google': google.GoogleNamespaceManager,
})
@@ -125,7 +126,7 @@ def cache_region(region, *args):
Example::
from beaker.cache import cache_regions, cache_region
-
+
# configure regions
cache_regions.update({
'short_term':{
@@ -138,10 +139,10 @@ def cache_region(region, *args):
def load(search_term, limit, offset):
'''Load from a database given a search term, limit, offset.'''
return database.query(search_term)[offset:offset + limit]
-
+
The decorator can also be used with object methods. The ``self``
- argument is not part of the cache key. This is based on the
- actual string name ``self`` being in the first argument
+ argument is not part of the cache key. This is based on the
+ actual string name ``self`` being in the first argument
position (new in 1.6)::
class MyThing(object):
@@ -149,27 +150,27 @@ class MyThing(object):
def load(self, search_term, limit, offset):
'''Load from a database given a search term, limit, offset.'''
return database.query(search_term)[offset:offset + limit]
-
+
Classmethods work as well - use ``cls`` as the name of the class argument,
and place the decorator around the function underneath ``@classmethod``
(new in 1.6)::
-
+
class MyThing(object):
@classmethod
@cache_region('short_term', 'load_things')
def load(cls, search_term, limit, offset):
'''Load from a database given a search term, limit, offset.'''
return database.query(search_term)[offset:offset + limit]
-
+
:param region: String name of the region corresponding to the desired
caching arguments, established in :attr:`.cache_regions`.
-
+
:param \*args: Optional ``str()``-compatible arguments which will uniquely
identify the key used by this decorated function, in addition
to the positional arguments passed to the function itself at call time.
This is recommended as it is needed to distinguish between any two functions
or methods that have the same name (regardless of parent class or not).
-
+
.. note::
The function being decorated must only be called with
@@ -180,14 +181,15 @@ def load(cls, search_term, limit, offset):
forms the unique cache key.
.. note::
-
+
When a method on a class is decorated, the ``self`` or ``cls``
argument in the first position is
not included in the "key" used for caching. New in 1.6.
-
+
"""
return _cache_decorate(args, None, None, region)
+
def region_invalidate(namespace, region, *args):
"""Invalidate a cache region corresponding to a function
decorated with :func:`.cache_region`.
@@ -209,7 +211,7 @@ def region_invalidate(namespace, region, *args):
Example::
from beaker.cache import cache_regions, cache_region, region_invalidate
-
+
# configure regions
cache_regions.update({
'short_term':{
@@ -226,11 +228,11 @@ def load(search_term, limit, offset):
def invalidate_search(search_term, limit, offset):
'''Invalidate the cached storage for a given search term, limit, offset.'''
region_invalidate(load, 'short_term', 'load_data', search_term, limit, offset)
-
+
Note that when a method on a class is decorated, the first argument ``cls``
or ``self`` is not included in the cache key. This means you don't send
it to :func:`.region_invalidate`::
-
+
class MyThing(object):
@cache_region('short_term', 'some_data')
def load(self, search_term, limit, offset):
@@ -240,7 +242,7 @@ def load(self, search_term, limit, offset):
def invalidate_search(self, search_term, limit, offset):
'''Invalidate the cached storage for a given search term, limit, offset.'''
region_invalidate(self.load, 'short_term', 'some_data', search_term, limit, offset)
-
+
"""
if callable(namespace):
if not region:
@@ -331,7 +333,7 @@ def _legacy_get_value(self, key, type, **kw):
kwargs = self.nsargs.copy()
kwargs.update(kw)
c = Cache(self.namespace.namespace, type=type, **kwargs)
- return c._get_value(key, expiretime=expiretime, createfunc=createfunc,
+ return c._get_value(key, expiretime=expiretime, createfunc=createfunc,
starttime=starttime)
def clear(self):
@@ -474,7 +476,7 @@ def load(search_term, limit, offset):
.. note::
The function being decorated must only be called with
- positional arguments.
+ positional arguments.
"""
return _cache_decorate(args, self, kwargs, None)
@@ -521,6 +523,7 @@ def load(search_term, limit, offset):
key_length = kwargs.pop('key_length', 250)
_cache_decorator_invalidate(cache, key_length, args)
+
def _cache_decorate(deco_args, manager, kwargs, region):
"""Return a caching function decorator."""
@@ -529,6 +532,7 @@ def _cache_decorate(deco_args, manager, kwargs, region):
def decorate(func):
namespace = util.func_namespace(func)
skip_self = util.has_self_arg(func)
+
def cached(*args):
if not cache[0]:
if region is not None:
@@ -561,6 +565,7 @@ def cached(*args):
key_length = kwargs.pop('key_length', 250)
if len(cache_key) + len(namespace) > key_length:
cache_key = sha1(cache_key).hexdigest()
+
def go():
return func(*args)
@@ -571,6 +576,7 @@ def go():
return cached
return decorate
+
def _cache_decorator_invalidate(cache, key_length, args):
"""Invalidate a cache key based on function arguments."""
View
150 beaker/container.py
@@ -2,7 +2,10 @@
import beaker.util as util
if util.py3k:
- import dbm as anydbm
+ try:
+ import dbm as anydbm
+ except:
+ import dumbdbm as anydbm
else:
import anydbm
import cPickle
@@ -32,9 +35,9 @@ def debug(message, *args):
class NamespaceManager(object):
"""Handles dictionary operations and locking for a namespace of
values.
-
+
:class:`.NamespaceManager` provides a dictionary-like interface,
- implementing ``__getitem__()``, ``__setitem__()``, and
+ implementing ``__getitem__()``, ``__setitem__()``, and
``__contains__()``, as well as functions related to lock
acquisition.
@@ -62,7 +65,6 @@ def _init_dependencies(cls):
"""Initialize module-level dependent libraries required
by this :class:`.NamespaceManager`."""
-
def __init__(self, namespace):
self._init_dependencies()
self.namespace = namespace
@@ -71,35 +73,35 @@ def get_creation_lock(self, key):
"""Return a locking object that is used to synchronize
multiple threads or processes which wish to generate a new
cache value.
-
+
This function is typically an instance of
:class:`.FileSynchronizer`, :class:`.ConditionSynchronizer`,
- or :class:`.null_synchronizer`.
-
+ or :class:`.null_synchronizer`.
+
The creation lock is only used when a requested value
does not exist, or has been expired, and is only used
by the :class:`.Value` key-management object in conjunction
with a "createfunc" value-creation function.
-
+
"""
raise NotImplementedError()
def do_remove(self):
- """Implement removal of the entire contents of this
+ """Implement removal of the entire contents of this
:class:`.NamespaceManager`.
-
+
e.g. for a file-based namespace, this would remove
all the files.
-
+
The front-end to this method is the
:meth:`.NamespaceManager.remove` method.
-
+
"""
raise NotImplementedError()
def acquire_read_lock(self):
"""Establish a read lock.
-
+
This operation is called before a key is read. By
default the function does nothing.
@@ -107,7 +109,7 @@ def acquire_read_lock(self):
def release_read_lock(self):
"""Release a read lock.
-
+
This operation is called after a key is read. By
default the function does nothing.
@@ -115,31 +117,31 @@ def release_read_lock(self):
def acquire_write_lock(self, wait=True, replace=False):
"""Establish a write lock.
-
- This operation is called before a key is written.
- A return value of ``True`` indicates the lock has
+
+ This operation is called before a key is written.
+ A return value of ``True`` indicates the lock has
been acquired.
-
+
By default the function returns ``True`` unconditionally.
-
+
'replace' is a hint indicating the full contents
of the namespace may be safely discarded. Some backends
- may implement this (i.e. file backend won't unpickle the
+ may implement this (i.e. file backend won't unpickle the
current contents).
-
+
"""
return True
def release_write_lock(self):
"""Release a write lock.
-
+
This operation is called after a new value is written.
By default this function does nothing.
-
+
"""
def has_key(self, key):
- """Return ``True`` if the given key is present in this
+ """Return ``True`` if the given key is present in this
:class:`.Namespace`.
"""
return self.__contains__(key)
@@ -152,7 +154,7 @@ def __setitem__(self, key, value):
def set_value(self, key, value, expiretime=None):
"""Sets a value in this :class:`.NamespaceManager`.
-
+
This is the same as ``__setitem__()``, but
also allows an expiration time to be passed
at the same time.
@@ -168,17 +170,17 @@ def __delitem__(self, key):
def keys(self):
"""Return the list of all keys.
-
+
This method may not be supported by all
:class:`.NamespaceManager` implementations.
-
+
"""
raise NotImplementedError()
def remove(self):
- """Remove the entire contents of this
+ """Remove the entire contents of this
:class:`.NamespaceManager`.
-
+
e.g. for a file-based namespace, this would remove
all the files.
"""
@@ -199,37 +201,37 @@ def __init__(self, namespace):
def get_access_lock(self):
raise NotImplementedError()
- def do_open(self, flags, replace):
+ def do_open(self, flags, replace):
raise NotImplementedError()
- def do_close(self):
+ def do_close(self):
raise NotImplementedError()
- def acquire_read_lock(self):
+ def acquire_read_lock(self):
self.access_lock.acquire_read_lock()
try:
- self.open('r', checkcount = True)
+ self.open('r', checkcount=True)
except:
self.access_lock.release_read_lock()
raise
def release_read_lock(self):
try:
- self.close(checkcount = True)
+ self.close(checkcount=True)
finally:
self.access_lock.release_read_lock()
- def acquire_write_lock(self, wait=True, replace=False):
+ def acquire_write_lock(self, wait=True, replace=False):
r = self.access_lock.acquire_write_lock(wait)
try:
- if (wait or r):
- self.open('c', checkcount = True, replace=replace)
+ if (wait or r):
+ self.open('c', checkcount=True, replace=replace)
return r
except:
self.access_lock.release_write_lock()
raise
- def release_write_lock(self):
+ def release_write_lock(self):
try:
self.close(checkcount=True)
finally:
@@ -239,7 +241,7 @@ def open(self, flags, checkcount=False, replace=False):
self.mutex.acquire()
try:
if checkcount:
- if self.openers == 0:
+ if self.openers == 0:
self.do_open(flags, replace)
self.openers += 1
else:
@@ -253,7 +255,7 @@ def close(self, checkcount=False):
try:
if checkcount:
self.openers -= 1
- if self.openers == 0:
+ if self.openers == 0:
self.do_close()
else:
if self.openers > 0:
@@ -270,6 +272,7 @@ def remove(self):
finally:
self.access_lock.release_write_lock()
+
class Value(object):
"""Implements synchronization, expiration, and value-creation logic
for a single value stored in a :class:`.NamespaceManager`.
@@ -295,7 +298,7 @@ def has_value(self):
"""
self.namespace.acquire_read_lock()
try:
- return self.namespace.has_key(self.key)
+ return self.key in self.namespace
finally:
self.namespace.release_read_lock()
@@ -305,7 +308,7 @@ def can_have_value(self):
def has_current_value(self):
self.namespace.acquire_read_lock()
try:
- has_value = self.namespace.has_key(self.key)
+ has_value = self.key in self.namespace
if has_value:
try:
stored, expired, value = self._get_value()
@@ -401,8 +404,8 @@ def _get_value(self):
self.set_value(value, stored)
self.namespace.acquire_read_lock()
except TypeError:
- # occurs when the value is None. memcached
- # may yank the rug from under us in which case
+ # occurs when the value is None. memcached
+ # may yank the rug from under us in which case
# that's the result
raise KeyError(self.key)
return stored, expired, value
@@ -421,7 +424,7 @@ def clear_value(self):
self.namespace.acquire_write_lock()
try:
debug("clear_value")
- if self.namespace.has_key(self.key):
+ if self.key in self.namespace:
try:
del self.namespace[self.key]
except KeyError:
@@ -431,6 +434,7 @@ def clear_value(self):
finally:
self.namespace.release_write_lock()
+
class AbstractDictionaryNSManager(NamespaceManager):
"""A subclassable NamespaceManager that places data in a dictionary.
@@ -458,18 +462,18 @@ def __init__(self, namespace):
def get_creation_lock(self, key):
return NameLock(
- identifier="memorynamespace/funclock/%s/%s" %
+ identifier="memorynamespace/funclock/%s/%s" %
(self.namespace, key),
reentrant=True
)
- def __getitem__(self, key):
+ def __getitem__(self, key):
return self.dictionary[key]
- def __contains__(self, key):
+ def __contains__(self, key):
return self.dictionary.__contains__(key)
- def has_key(self, key):
+ def has_key(self, key):
return self.dictionary.__contains__(key)
def __setitem__(self, key, value):
@@ -484,6 +488,7 @@ def do_remove(self):
def keys(self):
return self.dictionary.keys()
+
class MemoryNamespaceManager(AbstractDictionaryNSManager):
""":class:`.NamespaceManager` that uses a Python dictionary for storage."""
@@ -494,11 +499,12 @@ def __init__(self, namespace, **kwargs):
self.dictionary = MemoryNamespaceManager.\
namespaces.get(self.namespace, dict)
+
class DBMNamespaceManager(OpenResourceNamespaceManager):
""":class:`.NamespaceManager` that uses ``dbm`` files for storage."""
- def __init__(self, namespace, dbmmodule=None, data_dir=None,
- dbm_dir=None, lock_dir=None,
+ def __init__(self, namespace, dbmmodule=None, data_dir=None,
+ dbm_dir=None, lock_dir=None,
digest_filenames=True, **kwargs):
self.digest_filenames = digest_filenames
@@ -523,7 +529,7 @@ def __init__(self, namespace, dbmmodule=None, data_dir=None,
self.dbm = None
OpenResourceNamespaceManager.__init__(self, namespace)
- self.file = util.encoded_path(root= self.dbm_dir,
+ self.file = util.encoded_path(root=self.dbm_dir,
identifiers=[self.namespace],
extension='.dbm',
digest_filenames=self.digest_filenames)
@@ -537,14 +543,14 @@ def get_access_lock(self):
def get_creation_lock(self, key):
return file_synchronizer(
- identifier = "dbmcontainer/funclock/%s/%s" % (
+ identifier="dbmcontainer/funclock/%s/%s" % (
self.namespace, key
),
lock_dir=self.lock_dir
)
def file_exists(self, file):
- if os.access(file, os.F_OK):
+ if os.access(file, os.F_OK):
return True
else:
for ext in ('db', 'dat', 'pag', 'dir'):
@@ -555,7 +561,7 @@ def file_exists(self, file):
def _checkfile(self):
if not self.file_exists(self.file):
- g = self.dbmmodule.open(self.file, 'c')
+ g = self.dbmmodule.open(self.file, 'c')
g.close()
def get_filenames(self):
@@ -585,11 +591,11 @@ def do_remove(self):
for f in self.get_filenames():
os.remove(f)
- def __getitem__(self, key):
+ def __getitem__(self, key):
return cPickle.loads(self.dbm[key])
- def __contains__(self, key):
- return self.dbm.has_key(key)
+ def __contains__(self, key):
+ return key in self.dbm
def __setitem__(self, key, value):
self.dbm[key] = cPickle.dumps(value)
@@ -603,11 +609,11 @@ def keys(self):
class FileNamespaceManager(OpenResourceNamespaceManager):
""":class:`.NamespaceManager` that uses binary files for storage.
-
- Each namespace is implemented as a single file storing a
+
+ Each namespace is implemented as a single file storing a
dictionary of key/value pairs, serialized using the Python
``pickle`` module.
-
+
"""
def __init__(self, namespace, data_dir=None, file_dir=None, lock_dir=None,
digest_filenames=True, **kwargs):
@@ -630,7 +636,7 @@ def __init__(self, namespace, data_dir=None, file_dir=None, lock_dir=None,
util.verify_directory(self.lock_dir)
OpenResourceNamespaceManager.__init__(self, namespace)
- self.file = util.encoded_path(root=self.file_dir,
+ self.file = util.encoded_path(root=self.file_dir,
identifiers=[self.namespace],
extension='.cache',
digest_filenames=self.digest_filenames)
@@ -644,10 +650,10 @@ def get_access_lock(self):
def get_creation_lock(self, key):
return file_synchronizer(
- identifier = "dbmcontainer/funclock/%s/%s" % (
+ identifier="dbmcontainer/funclock/%s/%s" % (
self.namespace, key
),
- lock_dir = self.lock_dir
+ lock_dir=self.lock_dir
)
def file_exists(self, file):
@@ -679,11 +685,11 @@ def do_remove(self):
pass
self.hash = {}
- def __getitem__(self, key):
+ def __getitem__(self, key):
return self.hash[key]
- def __contains__(self, key):
- return self.hash.has_key(key)
+ def __contains__(self, key):
+ return key in self.hash
def __setitem__(self, key, value):
self.hash[key] = value
@@ -701,10 +707,12 @@ def keys(self):
ContainerContext = dict
+
class ContainerMeta(type):
def __init__(cls, classname, bases, dict_):
namespace_classes[cls] = cls.namespace_class
return type.__init__(cls, classname, bases, dict_)
+
def __call__(self, key, context, namespace, createfunc=None,
expiretime=None, starttime=None, **kwargs):
if namespace in context:
@@ -715,23 +723,27 @@ def __call__(self, key, context, namespace, createfunc=None,
return Value(key, ns, createfunc=createfunc,
expiretime=expiretime, starttime=starttime)
+
class Container(object):
"""Implements synchronization and value-creation logic
for a 'value' stored in a :class:`.NamespaceManager`.
-
+
:class:`.Container` and its subclasses are deprecated. The
:class:`.Value` class is now used for this purpose.
-
+
"""
__metaclass__ = ContainerMeta
namespace_class = NamespaceManager
+
class FileContainer(Container):
namespace_class = FileNamespaceManager
+
class MemoryContainer(Container):
namespace_class = MemoryNamespaceManager
+
class DBMContainer(Container):
namespace_class = DBMNamespaceManager
View
3 beaker/converters.py
@@ -1,3 +1,5 @@
+
+
# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
def asbool(obj):
@@ -12,6 +14,7 @@ def asbool(obj):
"String is not true/false: %r" % obj)
return bool(obj)
+
def aslist(obj, sep=None, strip=True):
if isinstance(obj, (str, unicode)):
lst = obj.split(sep)
View
2 beaker/crypto/jcecrypto.py
@@ -16,6 +16,7 @@
# Initialization vector filled with zeros
_iv = IvParameterSpec(jarray.zeros(16, 'b'))
+
def aesEncrypt(data, key):
cipher = Cipher.getInstance('AES/CTR/NoPadding')
skeySpec = SecretKeySpec(key, 'AES')
@@ -25,6 +26,7 @@ def aesEncrypt(data, key):
# magic.
aesDecrypt = aesEncrypt
+
def getKeyLength():
maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding')
return min(maxlen, 256) / 8
View
31 beaker/crypto/pbkdf2.py
@@ -5,22 +5,22 @@
#
# Copyright (C) 2007 Dwayne C. Litzenberger <dlitz@dlitz.net>
# All rights reserved.
-#
+#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation.
-#
-# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+#
+# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Country of origin: Canada
@@ -74,9 +74,11 @@
from beaker.crypto.util import hmac as HMAC, hmac_sha1 as SHA1
+
def strxor(a, b):
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
+
class PBKDF2(object):
"""PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
@@ -121,7 +123,7 @@ def read(self, bytes):
while size < bytes:
i += 1
if i > 0xffffffff:
- # We could return "" here, but
+ # We could return "" here, but
raise OverflowError("derived key too long")
block = self.__f(i)
blocks.append(block)
@@ -137,7 +139,7 @@ def __f(self, i):
assert (1 <= i and i <= 0xffffffff)
U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
result = U
- for j in xrange(2, 1+self.__iterations):
+ for j in xrange(2, 1 + self.__iterations):
U = self.__prf(self.__passphrase, U)
result = strxor(result, U)
return result
@@ -192,6 +194,7 @@ def close(self):
del self.__buf
self.closed = True
+
def crypt(word, salt=None, iterations=None):
"""PBKDF2-based unix crypt(3) replacement.
@@ -249,6 +252,7 @@ def crypt(word, salt=None, iterations=None):
# crypt.
PBKDF2.crypt = staticmethod(crypt)
+
def _makesalt():
"""Return a 48-bit pseudorandom salt for crypt().
@@ -257,6 +261,7 @@ def _makesalt():
binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
return b64encode(binarysalt, "./")
+
def test_pbkdf2():
"""Module self-test"""
from binascii import a2b_hex
@@ -279,14 +284,14 @@ def test_pbkdf2():
raise RuntimeError("self-test failed")
# Test 3
- result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
+ result = PBKDF2("X" * 64, "pass phrase equals block size", 1200).hexread(32)
expected = ("139c30c0966bc32ba55fdbf212530ac9"
"c5ec59f1a452f5cc9ad940fea0598ed1")
if result != expected:
raise RuntimeError("self-test failed")
# Test 4
- result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
+ result = PBKDF2("X" * 65, "pass phrase exceeds block size", 1200).hexread(32)
expected = ("9ccad6d468770cd51b10e6a68721be61"
"1a8b4d282601db3b36be9246915ec82a")
if result != expected:
View
1 beaker/crypto/pycrypto.py
@@ -28,5 +28,6 @@ def aesDecrypt(data, key):
counter=Counter.new(128, initial_value=0))
return cipher.decrypt(data)
+
def getKeyLength():
return 32
View
3 beaker/docs/conf.py
@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import sys
+import os
# If your extensions are in another directory, add it here. If the directory
# is relative to the documentation root, use os.path.abspath to make it
View
2 beaker/exceptions.py
@@ -1,5 +1,6 @@
"""Beaker exception classes"""
+
class BeakerException(Exception):
pass
@@ -7,6 +8,7 @@ class BeakerException(Exception):
class BeakerWarning(RuntimeWarning):
"""Issued at runtime."""
+
class CreationAbortedError(Exception):
"""Deprecated."""
View
22 beaker/ext/database.py
@@ -14,6 +14,7 @@
pool = None
types = None
+
class DatabaseNamespaceManager(OpenResourceNamespaceManager):
metadatas = SyncDict()
tables = SyncDict()
@@ -65,9 +66,11 @@ def __init__(self, namespace, url=None, sa_opts=None, optimistic=False,
# Check to see if the table's been created before
url = url or sa_opts['sa.url']
table_key = url + table_name
+
def make_cache():
# Check to see if we have a connection pool open already
meta_key = url + table_name
+
def make_meta():
# SQLAlchemy pops the url, this ensures it sticks around
# later
@@ -99,10 +102,10 @@ def get_access_lock(self):
def get_creation_lock(self, key):
return file_synchronizer(
- identifier ="databasecontainer/funclock/%s/%s" % (
+ identifier="databasecontainer/funclock/%s/%s" % (
self.namespace, key
),
- lock_dir = self.lock_dir)
+ lock_dir=self.lock_dir)
def do_open(self, flags, replace):
# If we already loaded the data, don't bother loading it again
@@ -111,8 +114,8 @@ def do_open(self, flags, replace):
return
cache = self.cache
- result = sa.select([cache.c.data],
- cache.c.namespace==self.namespace
+ result = sa.select([cache.c.data],
+ cache.c.namespace == self.namespace
).execute().fetchone()
if not result:
self._is_new = True
@@ -138,24 +141,24 @@ def do_close(self):
created=datetime.now())
self._is_new = False
else:
- cache.update(cache.c.namespace==self.namespace).execute(
+ cache.update(cache.c.namespace == self.namespace).execute(
data=self.hash, accessed=datetime.now())
self.flags = None
def do_remove(self):
cache = self.cache
- cache.delete(cache.c.namespace==self.namespace).execute()
+ cache.delete(cache.c.namespace == self.namespace).execute()
self.hash = {}
# We can retain the fact that we did a load attempt, but since the
# file is gone this will be a new namespace should it be saved.
self._is_new = True
- def __getitem__(self, key):
+ def __getitem__(self, key):
return self.hash[key]
- def __contains__(self, key):
- return self.hash.has_key(key)
+ def __contains__(self, key):
+ return key in self.hash
def __setitem__(self, key, value):
self.hash[key] = value
@@ -166,5 +169,6 @@ def __delitem__(self, key):
def keys(self):
return self.hash.keys()
+
class DatabaseContainer(Container):
namespace_manager = DatabaseNamespaceManager
View
5 beaker/ext/google.py
@@ -10,6 +10,7 @@
db = None
+
class GoogleNamespaceManager(OpenResourceNamespaceManager):
tables = {}
@@ -103,8 +104,8 @@ def do_remove(self):
def __getitem__(self, key):
return self.hash[key]
- def __contains__(self, key):
- return self.hash.has_key(key)
+ def __contains__(self, key):
+ return key in self.hash
def __setitem__(self, key, value):
self.hash[key] = value
View
33 beaker/ext/memcached.py
@@ -3,7 +3,7 @@
from beaker.crypto.util import sha1
from beaker.exceptions import InvalidCacheBackendError, MissingCacheParameter
from beaker.synchronization import file_synchronizer
-from beaker.util import verify_directory, SyncDict
+from beaker.util import verify_directory, SyncDict, parse_memcached_behaviors
import warnings
MAX_KEY_LENGTH = 250
@@ -54,6 +54,11 @@ def _auto():
return clib
+def _is_configured_for_pylibmc(memcache_module_config, memcache_client):
+ return memcache_module_config == 'pylibmc' or \
+ memcache_client.__name__.startswith('pylibmc')
+
+
class MemcachedNamespaceManager(NamespaceManager):
"""Provides the :class:`.NamespaceManager` API over a memcache client library."""
@@ -64,8 +69,7 @@ def __new__(cls, *args, **kw):
memcache_client = _load_client(memcache_module)
- if memcache_module == 'pylibmc' or \
- memcache_client.__name__.startswith('pylibmc'):
+ if _is_configured_for_pylibmc(memcache_module, memcache_client):
return object.__new__(PyLibMCNamespaceManager)
else:
return object.__new__(MemcachedNamespaceManager)
@@ -88,7 +92,11 @@ def __init__(self, namespace, url,
if self.lock_dir:
verify_directory(self.lock_dir)
- self.mc = MemcachedNamespaceManager.clients.get(
+ # Check for pylibmc namespace manager, in which case client will be
+ # instantiated by subclass __init__, to handle behavior passing to the
+ # pylibmc client
+ if not _is_configured_for_pylibmc(memcache_module, _memcache_module):
+ self.mc = MemcachedNamespaceManager.clients.get(
(memcache_module, url),
_memcache_module.Client,
url.split(';'))
@@ -99,7 +107,7 @@ def get_creation_lock(self, key):
(self.namespace, key), lock_dir=self.lock_dir)
def _format_key(self, key):
- formated_key = self.namespace + '_' + key.replace(' ', '\302\267')
+ formated_key = (self.namespace + '_' + (key if isinstance(key, str) else key.decode('ascii'))).replace(' ', '\302\267')
if len(formated_key) > MAX_KEY_LENGTH:
formated_key = sha1(formated_key).hexdigest()
return formated_key
@@ -140,6 +148,21 @@ class PyLibMCNamespaceManager(MemcachedNamespaceManager):
def __init__(self, *arg, **kw):
super(PyLibMCNamespaceManager, self).__init__(*arg, **kw)
+
+ memcache_module = kw.get('memcache_module', 'auto')
+ _memcache_module = _client_libs[memcache_module]
+ protocol = kw.get('protocol', 'text')
+ username = kw.get('username', None)
+ password = kw.get('password', None)
+ url = kw.get('url')
+ behaviors = parse_memcached_behaviors(kw)
+
+ self.mc = MemcachedNamespaceManager.clients.get(
+ (memcache_module, url),
+ _memcache_module.Client,
+ servers=url.split(';'), behaviors=behaviors,
+ binary=(protocol == 'binary'), username=username,
+ password=password)
self.pool = pylibmc.ThreadMappedPool(self.mc)
def __getitem__(self, key):
View
6 beaker/ext/sqla.py
@@ -13,6 +13,7 @@
sa = None
+
class SqlaNamespaceManager(OpenResourceNamespaceManager):
binds = SyncDict()
tables = SyncDict()
@@ -61,7 +62,7 @@ def get_access_lock(self):
def get_creation_lock(self, key):
return file_synchronizer(
- identifier ="databasecontainer/funclock/%s" % self.namespace,
+ identifier="databasecontainer/funclock/%s" % self.namespace,
lock_dir=self.lock_dir)
def do_open(self, flags, replace):
@@ -108,7 +109,7 @@ def __getitem__(self, key):
return self.hash[key]
def __contains__(self, key):
- return self.hash.has_key(key)
+ return key in self.hash
def __setitem__(self, key, value):
self.hash[key] = value
@@ -123,6 +124,7 @@ def keys(self):
class SqlaContainer(Container):
namespace_manager = SqlaNamespaceManager
+
def make_cache_table(metadata, table_name='beaker_cache', schema_name=None):
"""Return a ``Table`` object suitable for storing cached values for the
namespace manager. Do not create the table."""
View
6 beaker/middleware.py
@@ -105,8 +105,8 @@ def __init__(self, wrap_app, config=None, environ_key='beaker.session',
config = config or {}
# Load up the default params
- self.options = dict(invalidate_corrupt=True, type=None,
- data_dir=None, key='beaker.session.id',
+ self.options = dict(invalidate_corrupt=True, type=None,
+ data_dir=None, key='beaker.session.id',
timeout=None, secret=None, log_file=None)
# Pull out any config args meant for beaker session. if there are any
@@ -144,7 +144,7 @@ def __call__(self, environ, start_response):
if 'paste.testing_variables' in environ and 'webtest_varname' in self.options:
environ['paste.testing_variables'][self.options['webtest_varname']] = session
- def session_start_response(status, headers, exc_info = None):
+ def session_start_response(status, headers, exc_info=None):
if session.accessed():
session.persist()
if session.__dict__['_headers']['set_cookie']:
View
27 beaker/session.py
@@ -45,12 +45,12 @@ def _session_id():
class SignedCookie(Cookie.BaseCookie):
"""Extends python cookie to give digital signature support"""
def __init__(self, secret, input=None):
- self.secret = secret
+ self.secret = secret.encode('UTF-8')
Cookie.BaseCookie.__init__(self, input)
def value_decode(self, val):
val = val.strip('"')
- sig = HMAC.new(self.secret, val[40:], SHA1).hexdigest()
+ sig = HMAC.new(self.secret, val[40:].encode('UTF-8'), SHA1).hexdigest()
# Avoid timing attacks
invalid_bits = 0
@@ -67,7 +67,7 @@ def value_decode(self, val):
return val[40:], val
def value_encode(self, val):
- sig = HMAC.new(self.secret, val, SHA1).hexdigest()
+ sig = HMAC.new(self.secret, val.encode('UTF-8'), SHA1).hexdigest()
return str(val), ("%s%s" % (sig, val))
@@ -90,7 +90,9 @@ class Session(dict):
regardless of the cookie being present or not to determine
whether session data is still valid.
:type timeout: int
+ :param cookie_expires: Expiration date for cookie
:param cookie_domain: Domain to use for the cookie.
+ :param cookie_path: Path 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.
@@ -102,8 +104,8 @@ class Session(dict):
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,
+ cookie_domain=None, cookie_path='/', secret=None,
+ secure=False, namespace_class=None, httponly=False,
encrypt_key=None, validate_key=None, **namespace_args):
if not type:
if data_dir:
@@ -127,7 +129,7 @@ def __init__(self, request, id=None, invalidate_corrupt=False,
# Default cookie domain/path
self._domain = cookie_domain
- self._path = '/'
+ self._path = cookie_path
self.was_invalidated = False
self.secret = secret
self.secure = secure
@@ -252,7 +254,7 @@ 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]
+ nonce = b64encode(os.urandom(6))[:8]
encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
self.validate_key + nonce, 1)
data = util.pickle.dumps(session_data, 2)
@@ -472,7 +474,9 @@ class CookieSession(Session):
regardless of the cookie being present or not to determine
whether session data is still valid.
:type timeout: int
+ :param cookie_expires: Expiration date for cookie
:param cookie_domain: Domain to use for the cookie.
+ :param cookie_path: Path 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.
@@ -482,8 +486,9 @@ class CookieSession(Session):
"""
def __init__(self, request, key='beaker.session.id', timeout=None,
- cookie_expires=True, cookie_domain=None, encrypt_key=None,
- validate_key=None, secure=False, httponly=False, **kwargs):
+ cookie_expires=True, cookie_domain=None, cookie_path='/',
+ encrypt_key=None, validate_key=None, secure=False,
+ httponly=False, **kwargs):
if not crypto.has_aes and encrypt_key:
raise InvalidCryptoBackendError("No AES library is installed, can't generate "
@@ -499,7 +504,7 @@ def __init__(self, request, key='beaker.session.id', timeout=None,
self.secure = secure
self.httponly = httponly
self._domain = cookie_domain
- self._path = '/'
+ self._path = cookie_path
try:
cookieheader = request['cookie']
@@ -614,7 +619,7 @@ def delete(self):
def invalidate(self):
"""Clear the contents and start a new session"""
- self.delete()
+ self.clear()
self['_id'] = _session_id()
View
52 beaker/synchronization.py
@@ -29,7 +29,7 @@
from beaker import util
from beaker.exceptions import LockError
-__all__ = ["file_synchronizer", "mutex_synchronizer", "null_synchronizer",
+__all__ = ["file_synchronizer", "mutex_synchronizer", "null_synchronizer",
"NameLock", "_threading"]
@@ -49,17 +49,18 @@ def __init__(self, reentrant):
self.lock = _threading.RLock()
else:
self.lock = _threading.Lock()
+
def __call__(self):
return self.lock
- def __init__(self, identifier = None, reentrant = False):
+ def __init__(self, identifier=None, reentrant=False):
if identifier is None:
self._lock = NameLock.NLContainer(reentrant)
else:
self._lock = NameLock.locks.get(identifier, NameLock.NLContainer,
reentrant)
- def acquire(self, wait = True):
+ def acquire(self, wait=True):
return self._lock().acquire(wait)
def release(self):
@@ -67,6 +68,8 @@ def release(self):
_synchronizers = util.WeakValuedRegistry()
+
+
def _synchronizer(identifier, cls, **kwargs):
return _synchronizers.sync_get((identifier, cls), cls, identifier, **kwargs)
@@ -85,14 +88,17 @@ def mutex_synchronizer(identifier, **kwargs):
class null_synchronizer(object):
"""A 'null' synchronizer, which provides the :class:`.SynchronizerImpl` interface
without any locking.
-
+
"""
def acquire_write_lock(self, wait=True):
return True
+
def acquire_read_lock(self):
pass
+
def release_write_lock(self):
pass
+
def release_read_lock(self):
pass
acquire = acquire_write_lock
@@ -102,7 +108,7 @@ def release_read_lock(self):
class SynchronizerImpl(object):
"""Base class for a synchronization object that allows
multiple readers, single writers.
-
+
"""
def __init__(self):
self._state = util.ThreadLocal()
@@ -127,9 +133,9 @@ def state(self):
def release_read_lock(self):
state = self.state
- if state.writing:
+ if state.writing:
raise LockError("lock is in writing state")
- if not state.reading:
+ if not state.reading:
raise LockError("lock is not in reading state")
if state.reentrantcount == 1:
@@ -138,10 +144,10 @@ def release_read_lock(self):
state.reentrantcount -= 1
- def acquire_read_lock(self, wait = True):
+ def acquire_read_lock(self, wait=True):
state = self.state
- if state.writing:
+ if state.writing:
raise LockError("lock is in writing state")
if state.reentrantcount == 0:
@@ -157,9 +163,9 @@ def acquire_read_lock(self, wait = True):
def release_write_lock(self):
state = self.state
- if state.reading:
+ if state.reading:
raise LockError("lock is in reading state")
- if not state.writing:
+ if not state.writing:
raise LockError("lock is not in writing state")
if state.reentrantcount == 1:
@@ -170,15 +176,15 @@ def release_write_lock(self):
release = release_write_lock
- def acquire_write_lock(self, wait = True):
+ def acquire_write_lock(self, wait=True):
state = self.state
- if state.reading:
+ if state.reading:
raise LockError("lock is in reading state")
if state.reentrantcount == 0:
x = self.do_acquire_write_lock(wait)
- if (wait or x):
+ if (wait or x):
state.reentrantcount += 1
state.writing = True
return x
@@ -215,8 +221,8 @@ def __init__(self, identifier, lock_dir):
lock_dir = lock_dir
self.filename = util.encoded_path(
- lock_dir,
- [identifier],
+ lock_dir,
+ [identifier],
extension='.lock'
)
@@ -288,7 +294,7 @@ def __init__(self, identifier):
# condition object to lock on
self.condition = _threading.Condition(_threading.Lock())
- def do_acquire_read_lock(self, wait = True):
+ def do_acquire_read_lock(self, wait=True):
self.condition.acquire()
try:
# see if a synchronous operation is waiting to start
@@ -305,7 +311,7 @@ def do_acquire_read_lock(self, wait = True):
finally:
self.condition.release()
- if not wait:
+ if not wait:
return True
def do_release_read_lock(self):
@@ -313,7 +319,7 @@ def do_release_read_lock(self):
try:
self.async -= 1
- # check if we are the last asynchronous reader thread
+ # check if we are the last asynchronous reader thread
# out the door.
if self.async == 0:
# yes. so if a sync operation is waiting, notifyAll to wake
@@ -326,7 +332,7 @@ def do_release_read_lock(self):
finally:
self.condition.release()
- def do_acquire_write_lock(self, wait = True):
+ def do_acquire_write_lock(self, wait=True):
self.condition.acquire()
try:
# here, we are not a synchronous reader, and after returning,
@@ -342,7 +348,7 @@ def do_acquire_write_lock(self, wait = True):
if self.current_sync_operation is not None:
return False
- # establish ourselves as the current sync
+ # establish ourselves as the current sync
# this indicates to other read/write operations
# that they should wait until this is None again
self.current_sync_operation = _threading.currentThread()
@@ -359,7 +365,7 @@ def do_acquire_write_lock(self, wait = True):
finally:
self.condition.release()
- if not wait:
+ if not wait:
return True
def do_release_write_lock(self):
@@ -369,7 +375,7 @@ def do_release_write_lock(self):
raise LockError("Synchronizer error - current thread doesnt "
"have the write lock")
- # reset the current sync operation so
+ # reset the current sync operation so
# another can get it
self.current_sync_operation = None
View
91 beaker/util.py
@@ -18,7 +18,7 @@
import inspect
py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0)
-py24 = sys.version_info < (2,5)
+py24 = sys.version_info < (2, 5)
jython = sys.platform.startswith('java')
if py3k or jython:
@@ -31,9 +31,10 @@
from threading import local as _tlocal
-__all__ = ["ThreadLocal", "Registry", "WeakValuedRegistry", "SyncDict",
+__all__ = ["ThreadLocal", "Registry", "WeakValuedRegistry", "SyncDict",
"encoded_path", "verify_directory"]
+
def function_named(fn, name):
"""Return a function with a given __name__.
@@ -44,13 +45,16 @@ def function_named(fn, name):
fn.__name__ = name
return fn
+
def skip_if(predicate, reason=None):
"""Skip a test if predicate is true."""
reason = reason or predicate.__name__
from nose import SkipTest
+
def decorate(fn):
fn_name = fn.__name__
+
def maybe(*args, **kw):
if predicate():
msg = "'%s' skipped: %s" % (
@@ -61,6 +65,7 @@ def maybe(*args, **kw):
return function_named(maybe, fn_name)
return decorate
+
def assert_raises(except_cls, callable_, *args, **kw):
"""Assert the given exception is raised by the given function + arguments."""
@@ -69,7 +74,7 @@ def assert_raises(except_cls, callable_, *args, **kw):
success = False
except except_cls, e:
success = True
-
+
# assert outside the block so it works for AssertionError too !
assert success, "Callable did not raise an exception"
@@ -87,6 +92,7 @@ def verify_directory(dir):
if tries > 5:
raise
+
def has_self_arg(func):
"""Return True if the given function has a 'self' argument."""
args = inspect.getargspec(func)
@@ -136,6 +142,7 @@ def get(self, default=None):
def remove(self):
del self._tlocal.value
+
class SyncDict(object):
"""
An efficient/threadsafe singleton map algorithm, a.k.a.
@@ -158,7 +165,7 @@ def __init__(self):
def get(self, key, createfunc, *args, **kwargs):
try:
- if self.has_key(key):
+ if key in self.dict:
return self.dict[key]
else:
return self.sync_get(key, createfunc, *args, **kwargs)
@@ -169,7 +176,7 @@ def sync_get(self, key, createfunc, *args, **kwargs):
self.mutex.acquire()
try:
try:
- if self.has_key(key):
+ if key in self.dict:
return self.dict[key]
else:
return self._create(key, createfunc, *args, **kwargs)
@@ -183,16 +190,20 @@ def _create(self, key, createfunc, *args, **kwargs):
return obj
def has_key(self, key):
- return self.dict.has_key(key)
+ return key in self.dict
def __contains__(self, key):
return self.dict.__contains__(key)
+
def __getitem__(self, key):
return self.dict.__getitem__(key)
+
def __setitem__(self, key, value):
self.dict.__setitem__(key, value)
+
def __delitem__(self, key):
return self.dict.__delitem__(key)
+
def clear(self):
self.dict.clear()
@@ -203,7 +214,9 @@ def __init__(self):
self.dict = weakref.WeakValueDictionary()
sha1 = None
-def encoded_path(root, identifiers, extension = ".enc", depth = 3,
+
+
+def encoded_path(root, identifiers, extension=".enc", depth=3,
digest_filenames=True):
"""Generate a unique file-accessible path from the given list of
@@ -289,6 +302,8 @@ def coerce_session_params(params):
"not a boolean, datetime, int, or timedelta instance."),
('cookie_domain', (str, types.NoneType), "Cookie domain must be a "
"string."),
+ ('cookie_path', (str, types.NoneType), "Cookie path must be a "
+ "string."),
('id', (str,), "Session id must be a string."),
('key', (str,), "Session key must be a string."),
('secret', (str, types.NoneType), "Session secret must be a string."),
@@ -331,13 +346,56 @@ def coerce_cache_params(params):
return verify_rules(params, rules)
+def coerce_memcached_behaviors(behaviors):
+ rules = [
+ ('cas', (bool, int), 'cas must be a boolean or an integer'),
+ ('no_block', (bool, int), 'no_block must be a boolean or an integer'),
+ ('receive_timeout', (int,), 'receive_timeout must be an integer'),
+ ('send_timeout', (int,), 'send_timeout must be an integer'),
+ ('ketama_hash', (str,), 'ketama_hash must be a string designating '
+ 'a valid hashing strategy option'),
+ ('_poll_timeout', (int,), '_poll_timeout must be an integer'),
+ ('auto_eject', (bool, int), 'auto_eject must be an integer'),
+ ('retry_timeout', (int,), 'retry_timeout must be an integer'),
+ ('_sort_hosts', (bool, int), '_sort_hosts must be an integer'),
+ ('_io_msg_watermark', (int,), '_io_msg_watermark must be an integer'),
+ ('ketama', (bool, int), 'ketama must be a boolean or an integer'),
+ ('ketama_weighted', (bool, int), 'ketama_weighted must be a boolean or '
+ 'an integer'),
+ ('_io_key_prefetch', (int, bool), '_io_key_prefetch must be a boolean '
+ 'or an integer'),
+ ('_hash_with_prefix_key', (bool, int), '_hash_with_prefix_key must be '
+ 'a boolean or an integer'),
+ ('tcp_nodelay', (bool, int), 'tcp_nodelay must be a boolean or an '
+ 'integer'),
+ ('failure_limit', (int,), 'failure_limit must be an integer'),
+ ('buffer_requests', (bool, int), 'buffer_requests must be a boolean '
+ 'or an integer'),
+ ('_socket_send_size', (int,), '_socket_send_size must be an integer'),
+ ('num_replicas', (int,), 'num_replicas must be an integer'),
+ ('remove_failed', (int,), 'remove_failed must be an integer'),
+ ('_noreply', (bool, int), '_noreply must be a boolean or an integer'),
+ ('_io_bytes_watermark', (int,), '_io_bytes_watermark must be an '
+ 'integer'),
+ ('_socket_recv_size', (int,), '_socket_recv_size must be an integer'),
+ ('distribution', (str,), 'distribution must be a string designating '
+ 'a valid distribution option'),
+ ('connect_timeout', (int,), 'connect_timeout must be an integer'),
+ ('hash', (str,), 'hash must be a string designating a valid hashing '
+ 'option'),
+ ('verify_keys', (bool, int), 'verify_keys must be a boolean or an integer'),
+ ('dead_timeout', (int,), 'dead_timeout must be an integer')
+ ]
+ return verify_rules(behaviors, rules)
+
+
def parse_cache_config_options(config, include_defaults=True):
"""Parse configuration options and validate for use with the
CacheManager"""
# Load default cache options
if include_defaults:
- options= dict(type='memory', data_dir=None, expire=None,
+ options = dict(type='memory', data_dir=None, expire=None,
log_file=None)
else:
options = {}
@@ -357,7 +415,7 @@ def parse_cache_config_options(config, include_defaults=True):
if regions:
region_configs = {}
for region in regions:
- if not region: # ensure region name is valid
+ if not region: # ensure region name is valid
continue
# Setup the default cache options
region_options = dict(data_dir=options.get('data_dir'),
@@ -376,6 +434,21 @@ def parse_cache_config_options(config, include_defaults=True):
options['cache_regions'] = region_configs
return options
+
+def parse_memcached_behaviors(config):
+ """Parse behavior options and validate for use with pylibmc
+ client/PylibMCNamespaceManager, or potentially other memcached
+ NamespaceManagers that support behaviors"""
+ behaviors = {}
+
+ for key, val in config.iteritems():
+ if key.startswith('behavior.'):
+ behaviors[key[9:]] = val
+
+ coerce_memcached_behaviors(behaviors)
+ return behaviors
+
+
def func_namespace(func):
"""Generates a unique namespace for a function"""
kls = None
View
14 tests/test_cookie_only.py
@@ -127,6 +127,20 @@ def test_cookie_id():
new_id = re.sub(r".*'_id': '(.*?)'.*", r'\1', res.body)
assert new_id == sess_id
+def test_invalidate_with_save_does_not_delete_session():
+ def invalidate_session_app(environ, start_response):
+ session = environ['beaker.session']
+ session.invalidate()
+ session.save()
+ start_response('200 OK', [('Content-type', 'text/plain')])
+ return ['Cookie is %s' % session]
+
+ options = {'session.encrypt_key':'666a19cf7f61c64c', 'session.validate_key':'hoobermas',
+ 'session.type':'cookie'}
+ app = TestApp(SessionMiddleware(invalidate_session_app, **options))
+ res = app.get('/')
+ assert 'expires=' not in res.headers.getall('Set-Cookie')[0]
+
if __name__ == '__main__':
from paste import httpserver
wsgi_app = SessionMiddleware(simple_app, {})
View
39 tests/test_memcached.py
@@ -2,9 +2,10 @@
import mock
import os
-from beaker.cache import clsmap, Cache, util
+from beaker.cache import clsmap, Cache, CacheManager, util
from beaker.middleware import CacheMiddleware, SessionMiddleware
from beaker.exceptions import InvalidCacheBackendError
+from beaker.util import parse_cache_config_options
from nose import SkipTest
import unittest
@@ -278,7 +279,8 @@ def test_dont_use_pylibmc_client(self):
assert isinstance(cache.namespace, memcached.MemcachedNamespaceManager)
def test_client(self):
- cache = Cache('test', data_dir='./cache', url=mc_url, type="ext:memcached")
+ cache = Cache('test', data_dir='./cache', url=mc_url, type="ext:memcached",
+ protocol='binary')
o = object()
cache.set_value("test", o)
assert cache.has_key("test")
@@ -287,3 +289,36 @@ def test_client(self):
assert "foo" not in cache
cache.remove_value("test")
assert not cache.has_key("test")
+
+ def test_client_behaviors(self):
+ config = {
+ 'cache.lock_dir':'./lock',
+ 'cache.data_dir':'./cache',
+ 'cache.type':'ext:memcached',
+ 'cache.url':mc_url,
+ 'cache.memcache_module':'pylibmc',
+ 'cache.protocol':'binary',
+ 'cache.behavior.ketama': 'True',
+ 'cache.behavior.cas':False,
+ 'cache.behavior.receive_timeout':'3600',
+ 'cache.behavior.send_timeout':1800,
+ 'cache.behavior.tcp_nodelay':1,
+ 'cache.behavior.auto_eject':"0"
+ }
+ cache_manager = CacheManager(**parse_cache_config_options(config))
+ cache = cache_manager.get_cache('test_behavior', expire=6000)
+
+ with cache.namespace.pool.reserve() as mc:
+ assert "ketama" in mc.behaviors
+ assert mc.behaviors["ketama"] == 1
+ assert "cas" in mc.behaviors
+ assert mc.behaviors["cas"] == 0
+ assert "receive_timeout" in mc.behaviors
+ assert mc.behaviors["receive_timeout"] == 3600
+ assert "send_timeout" in mc.behaviors
+ assert mc.behaviors["send_timeout"] == 1800
+ assert "tcp_nodelay" in mc.behaviors
+ assert mc.behaviors["tcp_nodelay"] == 1
+ assert "auto_eject" in mc.behaviors
+ assert mc.behaviors["auto_eject"] == 0
+
View
7 tests/test_session.py
@@ -3,6 +3,9 @@
import time
import warnings
+from nose import SkipTest
+
+from beaker.crypto import has_aes
from beaker.session import Session
from beaker import util
@@ -34,6 +37,8 @@ def test_save_load():
def test_save_load_encryption():
"""Test if the data is actually persistent across requests"""
+ if not has_aes:
+ raise SkipTest()
session = get_session(encrypt_key='666a19cf7f61c64c',
validate_key='hoobermas')
session[u'Suomi'] = u'Kimi Räikkönen'
@@ -54,6 +59,8 @@ def test_save_load_encryption():
def test_decryption_failure():
"""Test if the data fails without the right keys"""
+ if not has_aes:
+ raise SkipTest()
session = get_session(encrypt_key='666a19cf7f61c64c',
validate_key='hoobermas')
session[u'Suomi'] = u'Kimi Räikkönen'

0 comments on commit 616be39

Please sign in to comment.
Something went wrong with that request. Please try again.