Converted Tags to use the new caching scheme.
This commit is contained in:
parent
c77252ebcb
commit
2ed92e8149
2 changed files with 134 additions and 41 deletions
|
|
@ -598,6 +598,7 @@ class AttributeHandler(object):
|
||||||
[attr.delete() for attr in self._cache.values()]
|
[attr.delete() for attr in self._cache.values()]
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
self._catcache = {}
|
self._catcache = {}
|
||||||
|
self._cache_complete = False
|
||||||
|
|
||||||
def all(self, accessing_obj=None, default_access=True):
|
def all(self, accessing_obj=None, default_access=True):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ class Tag(models.Model):
|
||||||
any number of tags, stored via its db_tags property. Tagging
|
any number of tags, stored via its db_tags property. Tagging
|
||||||
similar objects will make it easier to quickly locate the group
|
similar objects will make it easier to quickly locate the group
|
||||||
later (such as when implementing zones). The main advantage of
|
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
|
limited in what data it can hold, and the tag key+category is
|
||||||
indexed for efficient lookup in the database. Tags are shared
|
indexed for efficient lookup in the database. Tags are shared
|
||||||
between objects - a new tag is only created if the key+category
|
between objects - a new tag is only created if the key+category
|
||||||
combination did not previously exist, making them unsuitable for
|
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).
|
used).
|
||||||
|
|
||||||
The 'db_data' field is intended as a documentation field for the
|
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'),)
|
index_together = (('db_key', 'db_category', 'db_tagtype'),)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u"%s" % self.db_key
|
return u"<Tag: %s>" % self.db_key
|
||||||
|
|
||||||
def __str__(self):
|
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.obj = obj
|
||||||
self._objid = obj.id
|
self._objid = obj.id
|
||||||
self._model = obj.__dbclass__.__name__.lower()
|
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):
|
def _fullcache(self):
|
||||||
"""
|
"Cache all tags of this object"
|
||||||
Cache all tags of this object.
|
|
||||||
|
|
||||||
"""
|
|
||||||
query = {"%s__id" % self._model : self._objid,
|
query = {"%s__id" % self._model : self._objid,
|
||||||
"tag__db_tagtype" : self._tagtype}
|
"tag__db_tagtype" : self._tagtype}
|
||||||
tagobjs = [conn.tag for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
|
tags = [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(),
|
self._cache = dict(("%s-%s" % (to_str(tag.db_key).lower(),
|
||||||
tagobj.db_category.lower() if tagobj.db_category else None),
|
tag.db_category.lower() if tag.db_category else None),
|
||||||
tagobj) for tagobj in tagobjs)
|
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):
|
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,
|
tagobj = self.obj.__class__.objects.create_tag(key=tagstr, category=category, data=data,
|
||||||
tagtype=self._tagtype)
|
tagtype=self._tagtype)
|
||||||
getattr(self.obj, self._m2m_fieldname).add(tagobj)
|
getattr(self.obj, self._m2m_fieldname).add(tagobj)
|
||||||
if self._cache is None:
|
self._setcache(tagstr, category, tagobj)
|
||||||
self._recache()
|
|
||||||
cachestring = "%s-%s" % (tagstr, category)
|
|
||||||
self._cache[cachestring] = 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.
|
Get the tag for the given key or list of tags.
|
||||||
|
|
||||||
|
|
@ -166,13 +266,10 @@ class TagHandler(object):
|
||||||
depending on `return_tagobj`.
|
depending on `return_tagobj`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
|
||||||
self._recache()
|
|
||||||
ret = []
|
ret = []
|
||||||
category = category.strip().lower() if category is not None else None
|
for keystr in make_iter(key):
|
||||||
searchkey = ["%s-%s" % (key.strip().lower(), category) if key is not None else None for key in make_iter(key)]
|
ret.extend([tag if return_tagobj else tag.db_key
|
||||||
ret = [val for val in (self._cache.get(keystr) for keystr in searchkey) if val]
|
for tag in self._getcache(key, category)])
|
||||||
ret = [to_str(tag.db_data) for tag in ret] if return_tagobj else ret
|
|
||||||
return ret[0] if len(ret) == 1 else (ret if ret else default)
|
return ret[0] if len(ret) == 1 else (ret if ret else default)
|
||||||
|
|
||||||
def remove(self, key, category=None):
|
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)
|
tagobj = self.obj.db_tags.filter(db_key=tagstr, db_category=category)
|
||||||
if tagobj:
|
if tagobj:
|
||||||
getattr(self.obj, self._m2m_fieldname).remove(tagobj[0])
|
getattr(self.obj, self._m2m_fieldname).remove(tagobj[0])
|
||||||
self._recache()
|
self._delcache(key, category)
|
||||||
|
|
||||||
def clear(self, category=None):
|
def clear(self, category=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -213,7 +310,9 @@ class TagHandler(object):
|
||||||
getattr(self.obj, self._m2m_fieldname).clear()
|
getattr(self.obj, self._m2m_fieldname).clear()
|
||||||
else:
|
else:
|
||||||
getattr(self.obj, self._m2m_fieldname).filter(db_category=category).delete()
|
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):
|
def all(self, category=None, return_key_and_category=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -232,21 +331,14 @@ class TagHandler(object):
|
||||||
`return_key_and_category` is set.
|
`return_key_and_category` is set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self._cache is None or not _TYPECLASS_AGGRESSIVE_CACHE:
|
if not self._cache_complete:
|
||||||
self._recache()
|
self._fullcache()
|
||||||
if category:
|
tags = sorted(self._getcache(None, category), key=lambda o:o.id)
|
||||||
category = category.strip().lower() if category is not None else None
|
if return_key_and_category:
|
||||||
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:
|
|
||||||
# return tuple (key, category)
|
# return tuple (key, category)
|
||||||
return [(to_str(p.db_key), to_str(p.db_category)) for p in matches]
|
return [(to_str(tag.db_key), to_str(tag.db_category)) for tag in tags]
|
||||||
else:
|
else:
|
||||||
return [to_str(p.db_key) for p in matches]
|
return [to_str(tag.db_key) for tag in tags]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue