Anonymous avatar Anonymous committed b5dabc2

* Defer running of pkg_resources to look for external cache modules
until requested. #66
Unfortunately, a simple test against a "package" is not conceived
here. Would be best if an entrypoint were artificially shoved
into pkg_resources. and they say my code is hard to follow...

Comments (0)

Files changed (5)

 
 * Add ``webtest_varname`` option to configuration to optionally include
   the session value in the environ vars when using Beaker with WebTest.
+* Defer running of pkg_resources to look for external cache modules
+  until requested. #66
 
 Release 1.5.4 (6/16/2010)
 =========================
 Advanced users can add new backends in beaker.backends
 
 """
-    
+
 import warnings
 
 import beaker.container as container
 import beaker.util as util
 from beaker.exceptions import BeakerException, InvalidCacheBackendError
+from beaker.synchronization import _threading
 
 import beaker.ext.memcached as memcached
 import beaker.ext.database as database
 import beaker.ext.sqla as sqla
 import beaker.ext.google as google
 
+
+# Initialize the cache region dict
+cache_regions = {}
+cache_managers = {}
+
+class _backends(object):
+    initialized = False
+
+    def __init__(self, clsmap):
+        self._clsmap = clsmap
+        self._mutex = _threading.Lock()
+
+    def __getitem__(self, key):
+        try:
+            return self._clsmap[key]
+        except KeyError, e:
+            if not self.initialized:
+                self._mutex.acquire()
+                try:
+                    if not self.initialized:
+                        self._init()
+                        self.initialized = True
+
+                    return self._clsmap[key]
+                finally:
+                    self._mutex.release()
+
+            raise e
+
+    def _init(self):
+        try:
+            import pkg_resources
+
+            # Load up the additional entry point defined backends
+            for entry_point in pkg_resources.iter_entry_points('beaker.backends'):
+                try:
+                    namespace_manager = entry_point.load()
+                    name = entry_point.name
+                    if name in self._clsmap:
+                        raise BeakerException("NamespaceManager name conflict,'%s' "
+                                              "already loaded" % name)
+                    self._clsmap[name] = namespace_manager
+                except (InvalidCacheBackendError, SyntaxError):
+                    # Ignore invalid backends
+                    pass
+                except:
+                    import sys
+                    from pkg_resources import DistributionNotFound
+                    # Warn when there's a problem loading a NamespaceManager
+                    if not isinstance(sys.exc_info()[1], DistributionNotFound):
+                        import traceback
+                        from StringIO import StringIO
+                        tb = StringIO()
+                        traceback.print_exc(file=tb)
+                        warnings.warn(
+                            "Unable to load NamespaceManager "
+                            "entry point: '%s': %s" % (
+                                        entry_point, 
+                                        tb.getvalue()), 
+                                        RuntimeWarning, 2)
+        except ImportError:
+            pass
+
 # Initialize the basic available backends
-clsmap = {
+clsmap = _backends({
           'memory':container.MemoryNamespaceManager,
           'dbm':container.DBMNamespaceManager,
           'file':container.FileNamespaceManager,
           'ext:database':database.DatabaseNamespaceManager,
           'ext:sqla': sqla.SqlaNamespaceManager,
           'ext:google': google.GoogleNamespaceManager,
-          }
-
-# Initialize the cache region dict
-cache_regions = {}
-cache_managers = {}
-
-try:
-    import pkg_resources
-
-    # Load up the additional entry point defined backends
-    for entry_point in pkg_resources.iter_entry_points('beaker.backends'):
-        try:
-            NamespaceManager = entry_point.load()
-            name = entry_point.name
-            if name in clsmap:
-                raise BeakerException("NamespaceManager name conflict,'%s' "
-                                      "already loaded" % name)
-            clsmap[name] = NamespaceManager
-        except (InvalidCacheBackendError, SyntaxError):
-            # Ignore invalid backends
-            pass
-        except:
-            import sys
-            from pkg_resources import DistributionNotFound
-            # Warn when there's a problem loading a NamespaceManager
-            if not isinstance(sys.exc_info()[1], DistributionNotFound):
-                import traceback
-                from StringIO import StringIO
-                tb = StringIO()
-                traceback.print_exc(file=tb)
-                warnings.warn("Unable to load NamespaceManager entry point: '%s': "
-                              "%s" % (entry_point, tb.getvalue()), RuntimeWarning,
-                              2)
-except ImportError:
-    pass
-    
-
+          })
 
 
 def cache_region(region, *deco_args):
     """Decorate a function to cache itself using a cache region
-    
+
     The region decorator requires arguments if there are more than
     2 of the same named function, in the same module. This is
     because the namespace used for the functions cache is based on
     the functions name and the module.
-    
-    
+
+
     Example::
-        
+
         # Add cache region settings to beaker:
         beaker.cache.cache_regions.update(dict_of_config_region_options))
-        
+
         @cache_region('short_term', 'some_data')
         def populate_things(search_term, limit, offset):
             return load_the_data(search_term, limit, offset)
-        
+
         return load('rabbits', 20, 0)
-    
+
     .. note::
-        
+
         The function being decorated must only be called with
         positional arguments.
-    
+
     """
     cache = [None]
-    
+
     def decorate(func):
         namespace = util.func_namespace(func)
         def cached(*args):
             reg = cache_regions[region]
             if not reg.get('enabled', True):
                 return func(*args)
-            
+
             if not cache[0]:
                 cache[0] = Cache._get_cache(namespace, reg)
-            
+
             cache_key = " ".join(map(str, deco_args + args))
             def go():
                 return func(*args)
-            
+
             return cache[0].get_value(cache_key, createfunc=go)
         cached._arg_namespace = namespace
         cached._arg_region = region
 
 def region_invalidate(namespace, region, *args):
     """Invalidate a cache region namespace or decorated function
-    
+
     This function only invalidates cache spaces created with the
     cache_region decorator.
-    
+
     :param namespace: Either the namespace of the result to invalidate, or the
         cached function reference
-    
+
     :param region: The region the function was cached to. If the function was
         cached to a single region then this argument can be None
-    
+
     :param args: Arguments that were used to differentiate the cached
         function as well as the arguments passed to the decorated
         function
 
     Example::
-        
+
         # Add cache region settings to beaker:
         beaker.cache.cache_regions.update(dict_of_config_region_options))
-        
+
         def populate_things(invalidate=False):
-            
+
             @cache_region('short_term', 'some_data')
             def load(search_term, limit, offset):
                 return load_the_data(search_term, limit, offset)
-            
+
             # If the results should be invalidated first
             if invalidate:
                 region_invalidate(load, None, 'some_data',
                                         'rabbits', 20, 0)
             return load('rabbits', 20, 0)
-    
+
     """
     if callable(namespace):
         if not region:
                                     "namespace is required")
     else:
         region = cache_regions[region]
-    
+
     cache = Cache._get_cache(namespace, region)
     cache_key = " ".join(str(x) for x in args)
     cache.remove_value(cache_key)
     :param expiretime: seconds to keep cached data (legacy support)
 
     :param starttime: time when cache was cache was
-    
+
     """
     def __init__(self, namespace, type='memory', expiretime=None,
                  starttime=None, expire=None, **nsargs):
                 raise cls
         except KeyError:
             raise TypeError("Unknown cache implementation %r" % type)
-            
+
         self.namespace = cls(namespace, **nsargs)
         self.expiretime = expiretime or expire
         self.starttime = starttime
         self.nsargs = nsargs
-    
+
     @classmethod
     def _get_cache(cls, namespace, kw):
         key = namespace + str(kw)
         except KeyError:
             cache_managers[key] = cache = cls(namespace, **kw)
             return cache
-        
+
     def put(self, key, value, **kw):
         self._get_value(key, **kw).set_value(value)
     set_value = put
-    
+
     def get(self, key, **kw):
         """Retrieve a cached value from the container"""
         return self._get_value(key, **kw).get_value()
     get_value = get
-    
+
     def remove_value(self, key, **kw):
         mycontainer = self._get_value(key, **kw)
         if mycontainer.has_current_value():
 
         kw.setdefault('expiretime', self.expiretime)
         kw.setdefault('starttime', self.starttime)
-        
+
         return container.Value(key, self.namespace, **kw)
-    
+
     @util.deprecated("Specifying a "
             "'type' and other namespace configuration with cache.get()/put()/etc. "
             "is deprecated. Specify 'type' and other namespace configuration to "
         c = Cache(self.namespace.namespace, type=type, **kwargs)
         return c._get_value(key, expiretime=expiretime, createfunc=createfunc, 
                             starttime=starttime)
-    
+
     def clear(self):
         """Clear all the values from the namespace"""
         self.namespace.remove()
-    
+
     # dict interface
     def __getitem__(self, key):
         return self.get(key)
-    
+
     def __contains__(self, key):
         return self._get_value(key).has_current_value()
-    
+
     def has_key(self, key):
         return key in self
-    
+
     def __delitem__(self, key):
         self.remove_value(key)
-    
+
     def __setitem__(self, key, value):
         self.put(key, value)
 
 class CacheManager(object):
     def __init__(self, **kwargs):
         """Initialize a CacheManager object with a set of options
-        
+
         Options should be parsed with the
         :func:`~beaker.util.parse_cache_config_options` function to
         ensure only valid options are used.
-        
+
         """
         self.kwargs = kwargs
         self.regions = kwargs.pop('cache_regions', {})
-        
+
         # Add these regions to the module global
         cache_regions.update(self.regions)
-    
+
     def get_cache(self, name, **kwargs):
         kw = self.kwargs.copy()
         kw.update(kwargs)
         return Cache._get_cache(name, kw)
-    
+
     def get_cache_region(self, name, region):
         if region not in self.regions:
             raise BeakerException('Cache region not configured: %s' % region)
         kw = self.regions[region]
         return Cache._get_cache(name, kw)
-    
+
     def region(self, region, *args):
         """Decorate a function to cache itself using a cache region
-        
+
         The region decorator requires arguments if there are more than
         2 of the same named function, in the same module. This is
         because the namespace used for the functions cache is based on
         the functions name and the module.
-        
-        
+
+
         Example::
-            
+
             # Assuming a cache object is available like:
             cache = CacheManager(dict_of_config_options)
-            
-            
+
+
             def populate_things():
-                
+
                 @cache.region('short_term', 'some_data')
                 def load(search_term, limit, offset):
                     return load_the_data(search_term, limit, offset)
-                
+
                 return load('rabbits', 20, 0)
-        
+
         .. note::
-            
+
             The function being decorated must only be called with
             positional arguments.
-        
+
         """
         return cache_region(region, *args)
 
     def region_invalidate(self, namespace, region, *args):
         """Invalidate a cache region namespace or decorated function
-        
+
         This function only invalidates cache spaces created with the
         cache_region decorator.
-        
+
         :param namespace: Either the namespace of the result to invalidate, or the
            name of the cached function
-        
+
         :param region: The region the function was cached to. If the function was
             cached to a single region then this argument can be None
-        
+
         :param args: Arguments that were used to differentiate the cached
             function as well as the arguments passed to the decorated
             function
 
         Example::
-            
+
             # Assuming a cache object is available like:
             cache = CacheManager(dict_of_config_options)
-            
+
             def populate_things(invalidate=False):
-                
+
                 @cache.region('short_term', 'some_data')
                 def load(search_term, limit, offset):
                     return load_the_data(search_term, limit, offset)
-                
+
                 # If the results should be invalidated first
                 if invalidate:
                     cache.region_invalidate(load, None, 'some_data',
                                             'rabbits', 20, 0)
                 return load('rabbits', 20, 0)
-            
-        
+
+
         """
         return region_invalidate(namespace, region, *args)
         if callable(namespace):
                                     "namespace is required")
         else:
             region = self.regions[region]
-        
+
         cache = self.get_cache(namespace, **region)
         cache_key = " ".join(str(x) for x in args)
         cache.remove_value(cache_key)
 
             # Assuming a cache object is available like:
             cache = CacheManager(dict_of_config_options)
-            
-            
+
+
             def populate_things():
-                
+
                 @cache.cache('mycache', expire=15)
                 def load(search_term, limit, offset):
                     return load_the_data(search_term, limit, offset)
-                
+
                 return load('rabbits', 20, 0)
-        
+
         .. note::
-            
+
             The function being decorated must only be called with
             positional arguments. 
 
         """
         cache = [None]
         key = " ".join(str(x) for x in args)
-        
+
         def decorate(func):
             namespace = util.func_namespace(func)
             def cached(*args):
 
     def invalidate(self, func, *args, **kwargs):
         """Invalidate a cache decorated function
-        
+
         This function only invalidates cache spaces created with the
         cache decorator.
-        
+
         :param func: Decorated function to invalidate
-        
+
         :param args: Used to make the key unique for this function, as in region()
             above.
 
             function
 
         Example::
-            
+
             # Assuming a cache object is available like:
             cache = CacheManager(dict_of_config_options)
-            
-            
+
+
             def populate_things(invalidate=False):
-                
+
                 @cache.cache('mycache', type="file", expire=15)
                 def load(search_term, limit, offset):
                     return load_the_data(search_term, limit, offset)
-                
+
                 # If the results should be invalidated first
                 if invalidate:
                     cache.invalidate(load, 'mycache', 'rabbits', 20, 0, type="file")
                 return load('rabbits', 20, 0)
-        
+
         """
         namespace = func._arg_namespace
 

beaker/ext/memcached.py

 
 class MemcachedNamespaceManager(NamespaceManager):
     clients = SyncDict()
-    
+
     @classmethod
     def _init_dependencies(cls):
         global memcache
                 except ImportError:
                     raise InvalidCacheBackendError("Memcached cache backend requires either "
                                                         "the 'memcache' or 'cmemcache' library")
-        
+
     def __init__(self, namespace, url=None, data_dir=None, lock_dir=None, **params):
         NamespaceManager.__init__(self, namespace)
-       
+
         if not url:
             raise MissingCacheParameter("url is required") 
-        
+
         if lock_dir:
             self.lock_dir = lock_dir
         elif data_dir:
             self.lock_dir = data_dir + "/container_mcd_lock"
         if self.lock_dir:
-            verify_directory(self.lock_dir)            
-        
+            verify_directory(self.lock_dir)
+
         self.mc = MemcachedNamespaceManager.clients.get(url, memcache.Client, url.split(';'))
 
     def get_creation_lock(self, key):
 
     def __setitem__(self, key, value):
         self.set_value(key, value)
-        
+
     def __delitem__(self, key):
         self.mc.delete(self._format_key(key))
 
     def do_remove(self):
         self.mc.flush_all()
-    
+
     def keys(self):
         raise NotImplementedError("Memcache caching does not support iteration of all cache keys")
 
 with-coverage=True
 cover-package=beaker
 cover-inclusive=True
-with-doctest=True

tests/test_cache.py

     cache.set_value('test', 10)
     assert cache.has_key('test')
     assert cache['test'] == 10
-    
+
     # ensure that we can change a never-expiring value
     cache.set_value('test', 20, expiretime=1)
     assert cache.has_key('test')
     assert cache['test'] == 20
     time.sleep(1)
     assert not cache.has_key('test')
-    
+
     # test that we can change it before its expired
     cache.set_value('test', 30, expiretime=50)
     assert cache.has_key('test')
     assert cache['test'] == 30
-    
+
     cache.set_value('test', 40, expiretime=3)
     assert cache.has_key('test')
     assert cache['test'] == 40
     assert x == 16
     x = cache.get_value('test', createfunc=lambda: 18, expiretime=2)
     assert x == 16
-    
+
     cache.remove_value('test')
     assert not cache.has_key('test')
     x = cache.get_value('test', createfunc=lambda: 20, expiretime=2)
     assert x == 20
-    
-    
-    
+
+
+
 def test_has_key_multicache():
     cache = Cache('test', data_dir='./cache', type='dbm')
     o = object()
     cache = Cache('test', data_dir='./cache', type='dbm')
     assert cache.has_key("test")
 
-def test_unicode_keys():    
+def test_unicode_keys():
     cache = Cache('test', data_dir='./cache', type='dbm')
     o = object()
     cache.set_value(u'hiŏ', o)
     def create_func():
         called['here'] = True
         return 'howdy'
-    
+
     try:
         cache.get_value('key1')
     except KeyError:
         pass
     else:
         raise Exception("Failed to keyerror on nonexistent key")
-    
+
     assert 'howdy' == cache.get_value('key2', createfunc=create_func)
     assert called == {}
 
     res = app.get('/')
     assert 'test_key is: test value' in res
     assert 'test_key cleared' in res
-    
+
+def test_clsmap_nonexistent():
+    from beaker.cache import clsmap
+
+    try:
+        clsmap['fake']
+        assert False
+    except KeyError:
+        pass
+
+def test_clsmap_present():
+    from beaker.cache import clsmap
+
+    assert clsmap['memory']
+
+
 def test_legacy_cache():
     cache = Cache('newtests', data_dir='./cache', type='dbm')
-    
+
     cache.set_value('x', '1')
     assert cache.get_value('x') == '1'
-    
+
     cache.set_value('x', '2', type='file', data_dir='./cache')
     assert cache.get_value('x') == '1'
     assert cache.get_value('x', type='file', data_dir='./cache') == '2'
-    
+
     cache.remove_value('x')
     cache.remove_value('x', type='file', data_dir='./cache')
-    
+
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '5') == '5'
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '6', type='file', data_dir='./cache') == '6'
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '7') == '5'
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '10', type='file', data_dir='./cache') == '10'
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '11') == '9'
     assert cache.get_value('x', expiretime=1, createfunc=lambda: '12', type='file', data_dir='./cache') == '10'
-    
+
 
 def test_upgrade():
     # If we're on OSX, lets run this since its OSX dump files, otherwise
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.