Source code for core.model.hasfulltablecache

# encoding: utf-8
# HasFullTableCache

from . import get_one

import logging

[docs]class HasFullTableCache(object): """A mixin class for ORM classes that maintain an in-memory cache of (hopefully) every item in the database table for performance reasons. """ RESET = object() # You MUST define your own class-specific '_cache' and '_id_cache' # variables, like so: # # _cache = HasFullTableCache.RESET # _id_cache = HasFullTableCache.RESET
[docs] @classmethod def reset_cache(cls): cls._cache = cls.RESET cls._id_cache = cls.RESET
[docs] def cache_key(self): raise NotImplementedError()
@classmethod def _cache_insert(cls, obj, cache, id_cache): """Cache an object for later retrieval, possibly by a different database session. """ key = obj.cache_key() id = obj.id try: if cache != cls.RESET: cache[key] = obj if id_cache != cls.RESET: id_cache[id] = obj except TypeError as e: # The cache was reset in between the time we checked for a # reset and the time we tried to put an object in the # cache. Stop trying to mess with the cache. pass
[docs] @classmethod def populate_cache(cls, _db): """Populate the in-memory caches from scratch with every single object from the database table. """ cache = {} id_cache = {} for obj in _db.query(cls): cls._cache_insert(obj, cache, id_cache) cls._cache = cache cls._id_cache = id_cache
@classmethod def _cache_lookup(cls, _db, cache, cache_name, cache_key, lookup_hook): """Helper method used by both by_id and by_cache_key. Looks up `cache_key` in `cache` and calls `lookup_hook` to find/create it if it's not in there. """ new = False obj = None if cache == cls.RESET: # The cache has been reset. Populate it with the contents # of the table. cls.populate_cache(_db) # Get the new value of the cache, replacing the value # that turned out to be cls.RESET. cache = getattr(cls, cache_name) if cache != cls.RESET: try: obj = cache.get(cache_key) except TypeError as e: # This shouldn't happen. Even if the actual cache was # reset just now, we still have a copy of the 'old' # cache which passed the 'cache != cls.RESET' test. pass if not obj: # Either this object didn't exist when the cache was # populated, or the cache was reset while we were trying # to look it up. # # Give up on the cache and go direct to the database, # creating the object if necessary. if lookup_hook: obj, new = lookup_hook() else: obj = None if not obj: # The object doesn't exist and couldn't be created. return obj, new # Stick the object in the caches, assuming they're not # currently in a reset state. cls._cache_insert(obj, cls._cache, cls._id_cache) if obj and obj not in _db: try: obj = _db.merge(obj, load=False) except Exception as e: logging.error( "Unable to merge cached object %r into database session", obj, exc_info=e ) # Try to look up a fresh copy of the object. obj, new = lookup_hook() if obj and obj in _db: logging.error("Was able to look up a fresh copy of %r", obj) return obj, new # That didn't work. Re-raise the original exception. logging.error("Unable to look up a fresh copy of %r", obj) raise return obj, new
[docs] @classmethod def by_id(cls, _db, id): """Look up an item by its unique database ID.""" def lookup_hook(): return get_one(_db, cls, id=id), False obj, is_new = cls._cache_lookup( _db, cls._id_cache, '_id_cache', id, lookup_hook ) return obj
[docs] @classmethod def by_cache_key(cls, _db, cache_key, lookup_hook): return cls._cache_lookup( _db, cls._cache, '_cache', cache_key, lookup_hook )