Trunk: Merged the Devel-branch (branches/griatch) into /trunk. This constitutes a major refactoring of Evennia. Development will now continue in trunk. See the wiki and the past posts to the mailing list for info. /Griatch

This commit is contained in:
Griatch 2010-08-29 18:46:58 +00:00
parent df29defbcd
commit f83c2bddf8
222 changed files with 22304 additions and 14371 deletions

View file

@ -0,0 +1,9 @@
Copyright (c) 2009, David Cramer <dcramer@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

41
src/utils/idmapper/__init__.py Executable file
View file

@ -0,0 +1,41 @@
import os.path
import warnings
__version__ = (0, 2)
def _get_git_revision(path):
revision_file = os.path.join(path, 'refs', 'heads', 'master')
if not os.path.exists(revision_file):
return None
fh = open(revision_file, 'r')
try:
return fh.read()
finally:
fh.close()
def get_revision():
"""
:returns: Revision number of this branch/checkout, if available. None if
no revision number can be determined.
"""
package_dir = os.path.dirname(__file__)
checkout_dir = os.path.normpath(os.path.join(package_dir, '..'))
path = os.path.join(checkout_dir, '.git')
if os.path.exists(path):
return _get_git_revision(path)
return None
__build__ = get_revision()
def lazy_object(location):
def inner(*args, **kwargs):
parts = location.rsplit('.', 1)
warnings.warn('`idmapper.%s` is deprecated. Please use `%s` instead.' % (parts[1], location), DeprecationWarning)
imp = __import__(parts[0], globals(), locals(), [parts[1]], -1)
func = getattr(imp, parts[1])
if callable(func):
return func(*args, **kwargs)
return func
return inner
SharedMemoryModel = lazy_object('idmapper.models.SharedMemoryModel')

129
src/utils/idmapper/base.py Executable file
View file

@ -0,0 +1,129 @@
from weakref import WeakValueDictionary
from django.db.models.base import Model, ModelBase
from manager import SharedMemoryManager
class SharedMemoryModelBase(ModelBase):
def __new__(cls, name, bases, attrs):
super_new = super(ModelBase, cls).__new__
parents = [b for b in bases if isinstance(b, SharedMemoryModelBase)]
if not parents:
# If this isn't a subclass of Model, don't do anything special.
return super_new(cls, name, bases, attrs)
return super(SharedMemoryModelBase, cls).__new__(cls, name, bases, attrs)
def __call__(cls, *args, **kwargs):
"""
this method will either create an instance (by calling the default implementation)
or try to retrieve one from the class-wide cache by infering the pk value from
args and kwargs. If instance caching is enabled for this class, the cache is
populated whenever possible (ie when it is possible to infer the pk value).
"""
def new_instance():
return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs)
instance_key = cls._get_cache_key(args, kwargs)
# depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance
if instance_key is None:
return new_instance()
cached_instance = cls.get_cached_instance(instance_key)
if cached_instance is None:
cached_instance = new_instance()
cls.cache_instance(cached_instance)
return cached_instance
def _prepare(cls):
cls.__instance_cache__ = WeakValueDictionary()
super(SharedMemoryModelBase, cls)._prepare()
class SharedMemoryModel(Model):
# XXX: this is creating a model and it shouldn't be.. how do we properly
# subclass now?
__metaclass__ = SharedMemoryModelBase
def _get_cache_key(cls, args, kwargs):
"""
This method is used by the caching subsystem to infer the PK value from the constructor arguments.
It is used to decide if an instance has to be built or is already in the cache.
"""
result = None
# Quick hack for my composites work for now.
if hasattr(cls._meta, 'pks'):
pk = cls._meta.pks[0]
else:
pk = cls._meta.pk
# get the index of the pk in the class fields. this should be calculated *once*, but isn't atm
pk_position = cls._meta.fields.index(pk)
if len(args) > pk_position:
# if it's in the args, we can get it easily by index
result = args[pk_position]
elif pk.attname in kwargs:
# retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a
# a ForeignKey.
result = kwargs[pk.attname]
elif pk.name != pk.attname and pk.name in kwargs:
# ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
result = kwargs[pk.name]
if result is not None and isinstance(result, Model):
# if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key
result = result._get_pk_val()
return result
_get_cache_key = classmethod(_get_cache_key)
def get_cached_instance(cls, id):
"""
Method to retrieve a cached instance by pk value. Returns None when not found
(which will always be the case when caching is disabled for this class). Please
note that the lookup will be done even when instance caching is disabled.
"""
return cls.__instance_cache__.get(id)
get_cached_instance = classmethod(get_cached_instance)
def cache_instance(cls, instance):
"""
Method to store an instance in the cache.
"""
if instance._get_pk_val() is not None:
cls.__instance_cache__[instance._get_pk_val()] = instance
cache_instance = classmethod(cache_instance)
def _flush_cached_by_key(cls, key):
del cls.__instance_cache__[key]
_flush_cached_by_key = classmethod(_flush_cached_by_key)
def flush_cached_instance(cls, instance):
"""
Method to flush an instance from the cache. The instance will always be flushed from the cache,
since this is most likely called from delete(), and we want to make sure we don't cache dead objects.
"""
cls._flush_cached_by_key(instance._get_pk_val())
flush_cached_instance = classmethod(flush_cached_instance)
def save(self, *args, **kwargs):
super(SharedMemoryModel, self).save(*args, **kwargs)
self.__class__.cache_instance(self)
# TODO: This needs moved to the prepare stage (I believe?)
objects = SharedMemoryManager()
from django.db.models.signals import pre_delete
# Use a signal so we make sure to catch cascades.
def flush_singleton_cache(sender, instance, **kwargs):
# XXX: Is this the best way to make sure we can flush?
if isinstance(instance, SharedMemoryModel):
instance.__class__.flush_cached_instance(instance)
pre_delete.connect(flush_singleton_cache)
# XXX: It's to be determined if we should use this or not.
# def update_singleton_cache(sender, instance, **kwargs):
# if isinstance(instance.__class__, SharedMemoryModel):
# instance.__class__.cache_instance(instance)
# post_save.connect(flush_singleton_cache)

15
src/utils/idmapper/manager.py Executable file
View file

@ -0,0 +1,15 @@
from django.db.models.manager import Manager
class SharedMemoryManager(Manager):
# TODO: improve on this implementation
# We need a way to handle reverse lookups so that this model can
# still use the singleton cache, but the active model isn't required
# to be a SharedMemoryModel.
def get(self, **kwargs):
items = kwargs.keys()
inst = None
if len(items) == 1 and items[0] in ('pk', self.model._meta.pk.attname):
inst = self.model.get_cached_instance(kwargs[items[0]])
if inst is None:
inst = super(SharedMemoryManager, self).get(**kwargs)
return inst

2
src/utils/idmapper/models.py Executable file
View file

@ -0,0 +1,2 @@
from django.db.models import *
from base import SharedMemoryModel

70
src/utils/idmapper/tests.py Executable file
View file

@ -0,0 +1,70 @@
from django.test import TestCase
from base import SharedMemoryModel
from django.db import models
class Category(SharedMemoryModel):
name = models.CharField(max_length=32)
class RegularCategory(models.Model):
name = models.CharField(max_length=32)
class Article(SharedMemoryModel):
name = models.CharField(max_length=32)
category = models.ForeignKey(Category)
category2 = models.ForeignKey(RegularCategory)
class RegularArticle(models.Model):
name = models.CharField(max_length=32)
category = models.ForeignKey(Category)
category2 = models.ForeignKey(RegularCategory)
class SharedMemorysTest(TestCase):
# TODO: test for cross model relation (singleton to regular)
def setUp(self):
n = 0
category = Category.objects.create(name="Category %d" % (n,))
regcategory = RegularCategory.objects.create(name="Category %d" % (n,))
for n in xrange(0, 10):
Article.objects.create(name="Article %d" % (n,), category=category, category2=regcategory)
RegularArticle.objects.create(name="Article %d" % (n,), category=category, category2=regcategory)
def testSharedMemoryReferences(self):
article_list = Article.objects.all().select_related('category')
last_article = article_list[0]
for article in article_list[1:]:
self.assertEquals(article.category is last_article.category, True)
last_article = article
def testRegularReferences(self):
article_list = RegularArticle.objects.all().select_related('category')
last_article = article_list[0]
for article in article_list[1:]:
self.assertEquals(article.category2 is last_article.category2, False)
last_article = article
def testMixedReferences(self):
article_list = RegularArticle.objects.all().select_related('category')
last_article = article_list[0]
for article in article_list[1:]:
self.assertEquals(article.category is last_article.category, True)
last_article = article
article_list = Article.objects.all().select_related('category')
last_article = article_list[0]
for article in article_list[1:]:
self.assertEquals(article.category2 is last_article.category2, False)
last_article = article
def testObjectDeletion(self):
# This must execute first so its guaranteed to be in memory.
article_list = list(Article.objects.all().select_related('category'))
article = Article.objects.all()[0:1].get()
pk = article.pk
article.delete()
self.assertEquals(pk not in Article.__instance_cache__, True)