Converted Tags to use the new caching scheme.

This commit is contained in:
Griatch 2016-09-03 20:00:45 +02:00
parent c77252ebcb
commit 2ed92e8149
2 changed files with 134 additions and 41 deletions

View file

@ -598,6 +598,7 @@ class AttributeHandler(object):
[attr.delete() for attr in self._cache.values()]
self._cache = {}
self._catcache = {}
self._cache_complete = False
def all(self, accessing_obj=None, default_access=True):
"""

View file

@ -30,12 +30,12 @@ class Tag(models.Model):
any number of tags, stored via its db_tags property. Tagging
similar objects will make it easier to quickly locate the group
later (such as when implementing zones). The main advantage of
tagging as opposed to using Attributes is speed; a tag is very
tagging as opposed to using tags is speed; a tag is very
limited in what data it can hold, and the tag key+category is
indexed for efficient lookup in the database. Tags are shared
between objects - a new tag is only created if the key+category
combination did not previously exist, making them unsuitable for
storing object-related data (for this a full Attribute should be
storing object-related data (for this a full tag should be
used).
The 'db_data' field is intended as a documentation field for the
@ -65,10 +65,10 @@ class Tag(models.Model):
index_together = (('db_key', 'db_category', 'db_tagtype'),)
def __unicode__(self):
return u"%s" % self.db_key
return u"<Tag: %s>" % self.db_key
def __str__(self):
return str(self.db_key)
return str("<Tag: %s>" % self.db_key)
#
@ -96,19 +96,122 @@ class TagHandler(object):
self.obj = obj
self._objid = obj.id
self._model = obj.__dbclass__.__name__.lower()
self._cache = None
self._cache = {}
# store category names fully cached
self._catcache = {}
# full cache was run on all tags
self._cache_complete = False
def _recache(self):
"""
Cache all tags of this object.
"""
def _fullcache(self):
"Cache all tags of this object"
query = {"%s__id" % self._model : self._objid,
"tag__db_tagtype" : self._tagtype}
tagobjs = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
self._cache = dict(("%s-%s" % (to_str(tagobj.db_key).lower(),
tagobj.db_category.lower() if tagobj.db_category else None),
tagobj) for tagobj in tagobjs)
tags = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
self._cache = dict(("%s-%s" % (to_str(tag.db_key).lower(),
tag.db_category.lower() if tag.db_category else None),
tag) for tag in tags)
self._cache_complete = True
def _getcache(self, key=None, category=None):
"""
Retrieve from cache or database (always caches)
Args:
key (str, optional): Tag key to query for
category (str, optional): Tag category
Returns:
args (list): Returns a list of zero or more matches
found from cache or database.
Notes:
When given a category only, a search for all objects
of that category is done and a the category *name* is is
stored. This tells the system on subsequent calls that the
list of cached tags of this category is up-to-date
and that the cache can be queried for category matches
without missing any.
The TYPECLASS_AGGRESSIVE_CACHE=False setting will turn off
caching, causing each tag access to trigger a
database lookup.
"""
key = key.strip().lower() if key else None
category = category.strip().lower() if category else None
if key:
cachekey = "%s-%s" % (key, category)
tag = _TYPECLASS_AGGRESSIVE_CACHE and self._cache.get(cachekey, None)
if tag:
return [tag] # return cached entity
else:
query = {"%s__id" % self._model : self._objid,
"tag__db_tagtype" : self._tagtype,
"tag__db_key__iexact" : key.lower(),
"tag__db_category__iexact" : category.lower() if category else None}
conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)
if conn:
tag = conn[0].tag
self._cache[cachekey] = tag
return [tag]
else:
# only category given (even if it's None) - we can't
# assume the cache to be complete unless we have queried
# for this category before
catkey = "-%s" % category
if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache:
return [tag for key, tag in self._cache.items() if key.endswith(catkey)]
else:
# we have to query to make this category up-date in the cache
query = {"%s__id" % self._model : self._objid,
"tag__db_tagtype" : self._tagtype,
"tag__db_category__iexact" : category.lower() if category else None}
tags = [conn.tag for conn in getattr(self.obj,
self._m2m_fieldname).through.objects.filter(**query)]
for tag in tags:
cachekey = "%s-%s" % (tag.db_key, category)
self._cache[cachekey] = tag
# mark category cache as up-to-date
self._catcache[catkey] = True
return tags
return []
def _setcache(self, key, category, tag_obj):
"""
Update cache.
Args:
key (str): A cleaned key string
category (str or None): A cleaned category name
tag_obj (tag): The newly saved tag
"""
if not key: # don't allow an empty key in cache
return
cachekey = "%s-%s" % (key, category)
catkey = "-%s" % category
self._cache[cachekey] = tag_obj
# mark that the category cache is no longer up-to-date
self._catcache.pop(catkey, None)
self._cache_complete = False
def _delcache(self, key, category):
"""
Remove tag from cache
Args:
key (str): A cleaned key string
category (str or None): A cleaned category name
"""
catkey = "-%s" % category
if key:
cachekey = "%s-%s" % (key, category)
self._cache.pop(cachekey, None)
else:
[self._cache.pop(key, None) for key in self._cache if key.endswith(catkey)]
# mark that the category cache is no longer up-to-date
self._catcache.pop(catkey, None)
self._cache_complete = False
def add(self, tag=None, category=None, data=None):
"""
@ -142,12 +245,9 @@ class TagHandler(object):
tagobj = self.obj.__class__.objects.create_tag(key=tagstr, category=category, data=data,
tagtype=self._tagtype)
getattr(self.obj, self._m2m_fieldname).add(tagobj)
if self._cache is None:
self._recache()
cachestring = "%s-%s" % (tagstr, category)
self._cache[cachestring] = tagobj
self._setcache(tagstr, category, tagobj)
def get(self, key, default=None, category=None, return_tagobj=False):
def get(self, key=None, default=None, category=None, return_tagobj=False):
"""
Get the tag for the given key or list of tags.
@ -166,13 +266,10 @@ class TagHandler(object):
depending on `return_tagobj`.
"""
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
self._recache()
ret = []
category = category.strip().lower() if category is not None else None
searchkey = ["%s-%s" % (key.strip().lower(), category) if key is not None else None for key in make_iter(key)]
ret = [val for val in (self._cache.get(keystr) for keystr in searchkey) if val]
ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret
for keystr in make_iter(key):
ret.extend([tag if return_tagobj else tag.db_key
for tag in self._getcache(key, category)])
return ret[0] if len(ret) == 1 else (ret if ret else default)
def remove(self, key, category=None):
@ -197,7 +294,7 @@ class TagHandler(object):
tagobj = self.obj.db_tags.filter(db_key=tagstr, db_category=category)
if tagobj:
getattr(self.obj, self._m2m_fieldname).remove(tagobj[0])
self._recache()
self._delcache(key, category)
def clear(self, category=None):
"""
@ -213,7 +310,9 @@ class TagHandler(object):
getattr(self.obj, self._m2m_fieldname).clear()
else:
getattr(self.obj, self._m2m_fieldname).filter(db_category=category).delete()
self._recache()
self._cache = {}
self._catcache = {}
self._cache_complete = False
def all(self, category=None, return_key_and_category=False):
"""
@ -232,21 +331,14 @@ class TagHandler(object):
`return_key_and_category` is set.
"""
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
self._recache()
if category:
category = category.strip().lower() if category is not None else None
matches = [tag for tag in self._cache.values() if tag.db_category == category]
else:
matches = self._cache.values()
if matches:
matches = sorted(matches, key=lambda o: o.id)
if return_key_and_category:
if not self._cache_complete:
self._fullcache()
tags = sorted(self._getcache(None, category), key=lambda o:o.id)
if return_key_and_category:
# return tuple (key, category)
return [(to_str(p.db_key), to_str(p.db_category)) for p in matches]
else:
return [to_str(p.db_key) for p in matches]
return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags]
else:
return [to_str(tag.db_key) for tag in tags]
return []
def __str__(self):