"""Memcached Backends------------------Provides backends for talking to `memcached <http://memcached.org>`_."""fromdogpile.cache.apiimportCacheBackend,NO_VALUEfromdogpile.cacheimportutilimportrandomimporttime__all__='GenericMemcachedBackend','MemcachedBackend', \
'PylibmcBackend','BMemcachedBackend','MemcachedLock'classMemcachedLock(object):"""Simple distributed lock using memcached. This is an adaptation of the lock featured at http://amix.dk/blog/post/19386 """def__init__(self,client_fn,key):self.client_fn=client_fnself.key="_lock"+keydefacquire(self,wait=True):client=self.client_fn()i=0whileTrue:ifclient.add(self.key,1):returnTrueelifnotwait:returnFalseelse:sleep_time=(((i+1)*random.random())+2**i)/2.5time.sleep(sleep_time)ifi<15:i+=1defrelease(self):client=self.client_fn()client.delete(self.key)classGenericMemcachedBackend(CacheBackend):"""Base class for memcached backends. This base class accepts a number of paramters common to all backends. :param url: the string URL to connect to. Can be a single string or a list of strings. This is the only argument that's required. :param distributed_lock: boolean, when True, will use a memcached-lock as the dogpile lock (see :class:`.MemcachedLock`). Use this when multiple processes will be talking to the same memcached instance. When left at False, dogpile will coordinate on a regular threading mutex. :param memcached_expire_time: integer, when present will be passed as the ``time`` parameter to ``pylibmc.Client.set``. This is used to set the memcached expiry time for a value. .. note:: This parameter is **different** from Dogpile's own ``expiration_time``, which is the number of seconds after which Dogpile will consider the value to be expired. When Dogpile considers a value to be expired, it **continues to use the value** until generation of a new value is complete, when using :meth:`.CacheRegion.get_or_create`. Therefore, if you are setting ``memcached_expire_time``, you'll want to make sure it is greater than ``expiration_time`` by at least enough seconds for new values to be generated, else the value won't be available during a regeneration, forcing all threads to wait for a regeneration each time a value expires. The :class:`.GenericMemachedBackend` uses a ``threading.local()`` object to store individual client objects per thread, as most modern memcached clients do not appear to be inherently threadsafe. In particular, ``threading.local()`` has the advantage over pylibmc's built-in thread pool in that it automatically discards objects associated with a particular thread when that thread ends. """set_arguments={}"""Additional arguments which will be passed to the :meth:`set` method."""def__init__(self,arguments):self._imports()# using a plain threading.local here. threading.local# automatically deletes the __dict__ when a thread ends,# so the idea is that this is superior to pylibmc's# own ThreadMappedPool which doesn't handle this # automatically.self.url=util.to_list(arguments['url'])self.distributed_lock=arguments.get('distributed_lock',False)self.memcached_expire_time=arguments.get('memcached_expire_time',0)def_imports(self):"""client library imports go here."""raiseNotImplementedError()def_create_client(self):"""Creation of a Client instance goes here."""raiseNotImplementedError()@util.memoized_propertydef_clients(self):backend=selfclassClientPool(util.threading.local):def__init__(self):self.memcached=backend._create_client()returnClientPool()@propertydefclient(self):"""Return the memcached client. This uses a threading.local by default as it appears most modern memcached libs aren't inherently threadsafe. """returnself._clients.memcacheddefget_mutex(self,key):ifself.distributed_lock:returnMemcachedLock(lambda:self.client,key)else:returnNonedefget(self,key):value=self.client.get(self.format_key(key))ifvalueisNone:returnNO_VALUEelse:returnvaluedefset(self,key,value):self.client.set(self.format_key(key),value,**self.set_arguments)defdelete(self,key):self.client.delete(self.format_key(key))defformat_key(self,key):formated_key=key.replace(' ','\302\267')returnformated_keyclassMemcacheArgs(object):"""Mixin which provides support for the 'time' argument to set(), 'min_compress_len' to other methods. """def__init__(self,arguments):self.min_compress_len=arguments.get('min_compress_len',0)self.set_arguments={}if"memcached_expire_time"inarguments:self.set_arguments["time"]= \
arguments["memcached_expire_time"]if"min_compress_len"inarguments:self.set_arguments["min_compress_len"]= \
arguments["min_compress_len"]super(MemcacheArgs,self).__init__(arguments)classPylibmcBackend(MemcacheArgs,GenericMemcachedBackend):"""A backend for the `pylibmc <http://sendapatch.se/projects/pylibmc/index.html>`_ memcached client. A configuration illustrating several of the optional arguments described in the pylibmc documentation:: from dogpile.cache import make_region region = make_region().configure( 'dogpile.cache.pylibmc', expiration_time = 3600, arguments = { 'url':["127.0.0.1"], 'binary':True, 'behaviors':{"tcp_nodelay": True,"ketama":True} } ) Arguments accepted here include those of :class:`.GenericMemcachedBackend`, as well as those below. :param binary: sets the ``binary`` flag understood by ``pylibmc.Client``. :param behaviors: a dictionary which will be passed to ``pylibmc.Client`` as the ``behaviors`` parameter. :param min_compres_len: Integer, will be passed as the ``min_compress_len`` parameter to the ``pylibmc.Client.set`` method. """def__init__(self,arguments):self.binary=arguments.get('binary',False)self.behaviors=arguments.get('behaviors',{})super(PylibmcBackend,self).__init__(arguments)def_imports(self):globalpylibmcimportpylibmcdef_create_client(self):returnpylibmc.Client(self.url,binary=self.binary,behaviors=self.behaviors)classMemcachedBackend(MemcacheArgs,GenericMemcachedBackend):"""A backend using the standard `Python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_ library. Example:: from dogpile.cache import make_region region = make_region().configure( 'dogpile.cache.memcached', expiration_time = 3600, arguments = { 'url':"127.0.0.1:11211" } ) """def_imports(self):globalmemcacheimportmemcachedef_create_client(self):returnmemcache.Client(self.url)classBMemcachedBackend(GenericMemcachedBackend):"""A backend for the `python-binary-memcached <https://github.com/jaysonsantos/python-binary-memcached>`_ memcached client. This is a pure Python memcached client which includes the ability to authenticate with a memcached server using SASL. A typical configuration using username/password:: from dogpile.cache import make_region region = make_region().configure( 'dogpile.cache.bmemcached', expiration_time = 3600, arguments = { 'url':["127.0.0.1"], 'username':'scott', 'password':'tiger' } ) Arguments which can be passed to the ``arguments`` dictionary include: :param username: optional username, will be used for SASL authentication. :param password: optional password, will be used for SASL authentication. """def__init__(self,arguments):self.username=arguments.get('username',None)self.password=arguments.get('password',None)super(BMemcachedBackend,self).__init__(arguments)def_imports(self):globalbmemcachedimportbmemcachedclassRepairBMemcachedAPI(bmemcached.Client):"""Repairs BMemcached's non-standard method signatures, which was fixed in BMemcached ef206ed4473fec3b639e. """defadd(self,key,value):try:returnsuper(RepairBMemcachedAPI,self).add(key,value)exceptValueError:returnFalseself.Client=RepairBMemcachedAPIdef_create_client(self):returnself.Client(self.url,username=self.username,password=self.password)