Cleaned up typeclasses, removing a lot of extranenous and ineffective code from the setattr/getattr methods that are called most often.

This commit is contained in:
Griatch 2012-02-05 21:04:10 +01:00
parent f306c5a6a2
commit 7b2a4e4467
5 changed files with 186 additions and 192 deletions

View file

@ -194,6 +194,7 @@ class ObjectDB(TypedObject):
self.cmdset.update(init_mode=True) self.cmdset.update(init_mode=True)
self.scripts = ScriptHandler(self) self.scripts = ScriptHandler(self)
self.nicks = ObjectNickHandler(self) self.nicks = ObjectNickHandler(self)
# store the attribute class
# Wrapper properties to easily set database fields. These are # Wrapper properties to easily set database fields. These are
# @property decorators that allows to access these fields using # @property decorators that allows to access these fields using
@ -420,9 +421,10 @@ class ObjectDB(TypedObject):
# #
# this is required to properly handle attributes and typeclass loading. # this is required to properly handle attributes and typeclass loading.
attribute_model_path = "src.objects.models" #attribute_model_path = "src.objects.models"
attribute_model_name = "ObjAttribute" #attribute_model_name = "ObjAttribute"
typeclass_paths = settings.OBJECT_TYPECLASS_PATHS typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
attribute_class = ObjAttribute
# this is used by all typedobjects as a fallback # this is used by all typedobjects as a fallback
try: try:

View file

@ -264,9 +264,10 @@ class PlayerDB(TypedObject):
default_typeclass_path = "src.players.player.Player" default_typeclass_path = "src.players.player.Player"
# this is required to properly handle attributes and typeclass loading # this is required to properly handle attributes and typeclass loading
attribute_model_path = "src.players.models" #attribute_model_path = "src.players.models"
attribute_model_name = "PlayerAttribute" #attribute_model_name = "PlayerAttribute"
typeclass_paths = settings.PLAYER_TYPECLASS_PATHS typeclass_paths = settings.PLAYER_TYPECLASS_PATHS
attribute_class = PlayerAttribute
# name property (wraps self.user.username) # name property (wraps self.user.username)
#@property #@property

View file

@ -27,6 +27,7 @@ Common examples of uses of Scripts:
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from src.typeclasses.models import Attribute, TypedObject from src.typeclasses.models import Attribute, TypedObject
from django.contrib.contenttypes.models import ContentType
from src.scripts.manager import ScriptManager from src.scripts.manager import ScriptManager
#------------------------------------------------------------ #------------------------------------------------------------
@ -244,9 +245,10 @@ class ScriptDB(TypedObject):
# #
# this is required to properly handle attributes and typeclass loading # this is required to properly handle attributes and typeclass loading
attribute_model_path = "src.scripts.models" #attribute_model_path = "src.scripts.models"
attribute_model_name = "ScriptAttribute" #attribute_model_name = "ScriptAttribute"
typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS typeclass_paths = settings.SCRIPT_TYPECLASS_PATHS
attribute_class = ScriptAttribute
# this is used by all typedobjects as a fallback # this is used by all typedobjects as a fallback
try: try:

View file

@ -41,6 +41,11 @@ from src.utils.utils import is_iter, has_parent
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
CTYPEGET = ContentType.objects.get
GA = object.__getattribute__
SA = object.__setattr__
DA = object.__delattr__
# used by Attribute to efficiently identify stored object types. # used by Attribute to efficiently identify stored object types.
# Note that these have to be updated if directory structure changes. # Note that these have to be updated if directory structure changes.
PARENTS = { PARENTS = {
@ -359,7 +364,7 @@ class Attribute(SharedMemoryModel):
# unpack a previously packed db_object # unpack a previously packed db_object
try: try:
#print "unpack:", item.id, item.db_model #print "unpack:", item.id, item.db_model
mclass = ContentType.objects.get(model=item.db_model).model_class() mclass = CTYPEGET(model=item.db_model).model_class()
try: try:
ret = mclass.objects.dbref_search(item.id) ret = mclass.objects.dbref_search(item.id)
except AttributeError: except AttributeError:
@ -618,7 +623,7 @@ class TypedObject(SharedMemoryModel):
#@property #@property
def typeclass_path_get(self): def typeclass_path_get(self):
"Getter. Allows for value = self.typeclass_path" "Getter. Allows for value = self.typeclass_path"
typeclass_path = object.__getattribute__(self, 'cached_typeclass_path') typeclass_path = GA(self, 'cached_typeclass_path')
if typeclass_path: if typeclass_path:
return typeclass_path return typeclass_path
return self.db_typeclass_path return self.db_typeclass_path
@ -627,7 +632,7 @@ class TypedObject(SharedMemoryModel):
"Setter. Allows for self.typeclass_path = value" "Setter. Allows for self.typeclass_path = value"
self.db_typeclass_path = value self.db_typeclass_path = value
self.save() self.save()
object.__setattr__(self, 'cached_typeclass_path', value) SA(self, 'cached_typeclass_path', value)
#@typeclass_path.deleter #@typeclass_path.deleter
def typeclass_path_del(self): def typeclass_path_del(self):
"Deleter. Allows for del self.typeclass_path" "Deleter. Allows for del self.typeclass_path"
@ -698,9 +703,10 @@ class TypedObject(SharedMemoryModel):
# Each subclass should set this property to their respective # Each subclass should set this property to their respective
# attribute model (ObjAttribute, PlayerAttribute etc). # attribute model (ObjAttribute, PlayerAttribute etc).
attribute_model_path = "src.typeclasses.models" #attribute_model_path = "src.typeclasses.models"
attribute_model_name = "Attribute" #attribute_model_name = "Attribute"
typeclass_paths = settings.OBJECT_TYPECLASS_PATHS typeclass_paths = settings.OBJECT_TYPECLASS_PATHS
attribute_class = Attribute # replaced by relevant attribute class for child
def __eq__(self, other): def __eq__(self, other):
return other and hasattr(other, 'id') and self.id == other.id return other and hasattr(other, 'id') and self.id == other.id
@ -720,15 +726,15 @@ class TypedObject(SharedMemoryModel):
have to be very careful to avoid loops. have to be very careful to avoid loops.
""" """
try: try:
return object.__getattribute__(self, propname) return GA(self, propname)
except AttributeError: except AttributeError:
# check if the attribute exists on the typeclass instead # check if the attribute exists on the typeclass instead
# (we make sure to not incur a loop by not triggering the # (we make sure to not incur a loop by not triggering the
# typeclass' __getattribute__, since that one would # typeclass' __getattribute__, since that one would
# try to look back to this very database object.) # try to look back to this very database object.)
typeclass = object.__getattribute__(self, 'typeclass') typeclass = GA(self, 'typeclass')
if typeclass: if typeclass:
return object.__getattribute__(typeclass, propname) return GA(typeclass, propname)
else: else:
raise AttributeError raise AttributeError
@ -752,17 +758,17 @@ class TypedObject(SharedMemoryModel):
types of objects that the game needs. This property types of objects that the game needs. This property
handles loading and initialization of the typeclass on the fly. handles loading and initialization of the typeclass on the fly.
Note: The liberal use of object.__getattribute__ and __setattr__ (instead Note: The liberal use of GA and __setattr__ (instead
of normal dot notation) is due to optimization: it avoids calling of normal dot notation) is due to optimization: it avoids calling
the custom self.__getattribute__ more than necessary. the custom self.__getattribute__ more than necessary.
""" """
path = object.__getattribute__(self, "cached_typeclass_path") path = GA(self, "cached_typeclass_path")
if not path: if not path:
path = object.__getattribute__(self, 'db_typeclass_path') path = GA(self, 'db_typeclass_path')
typeclass = object.__getattribute__(self, "cached_typeclass") typeclass = GA(self, "cached_typeclass")
try: try:
if typeclass and object.__getattribute__(typeclass, "path") == path: if typeclass and GA(typeclass, "path") == path:
# don't call at_init() when returning from cache # don't call at_init() when returning from cache
return typeclass return typeclass
except AttributeError: except AttributeError:
@ -771,25 +777,25 @@ class TypedObject(SharedMemoryModel):
errstring = "" errstring = ""
if not path: if not path:
# this means we should get the default obj without giving errors. # this means we should get the default obj without giving errors.
return object.__getattribute__(self, "get_default_typeclass")(cache=True, silent=True, save=True) return GA(self, "get_default_typeclass")(cache=True, silent=True, save=True)
else: else:
# handle loading/importing of typeclasses, searching all paths. # handle loading/importing of typeclasses, searching all paths.
# (self.typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS # (self.typeclass_paths is a shortcut to settings.TYPECLASS_*_PATHS
# where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed # where '*' is either OBJECT, SCRIPT or PLAYER depending on the typed
# entities). # entities).
typeclass_paths = [path] + ["%s.%s" % (prefix, path) for prefix in object.__getattribute__(self, 'typeclass_paths')] typeclass_paths = [path] + ["%s.%s" % (prefix, path) for prefix in GA(self, 'typeclass_paths')]
for tpath in typeclass_paths: for tpath in typeclass_paths:
# try to import and analyze the result # try to import and analyze the result
typeclass = object.__getattribute__(self, "_path_import")(tpath) typeclass = GA(self, "_path_import")(tpath)
if callable(typeclass): if callable(typeclass):
# we succeeded to import. Cache and return. # we succeeded to import. Cache and return.
object.__setattr__(self, 'db_typeclass_path', tpath) SA(self, 'db_typeclass_path', tpath)
object.__getattribute__(self, 'save')() GA(self, 'save')()
object.__setattr__(self, "cached_typeclass_path", tpath) SA(self, "cached_typeclass_path", tpath)
typeclass = typeclass(self) typeclass = typeclass(self)
object.__setattr__(self, "cached_typeclass", typeclass) SA(self, "cached_typeclass", typeclass)
try: try:
typeclass.at_init() typeclass.at_init()
except Exception: except Exception:
@ -802,8 +808,8 @@ class TypedObject(SharedMemoryModel):
errstring += "\n%s" % typeclass # this will hold a growing error message. errstring += "\n%s" % typeclass # this will hold a growing error message.
# If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling # If we reach this point we couldn't import any typeclasses. Return default. It's up to the calling
# method to use e.g. self.is_typeclass() to detect that the result is not the one asked for. # method to use e.g. self.is_typeclass() to detect that the result is not the one asked for.
object.__getattribute__(self, "_display_errmsg")(errstring) GA(self, "_display_errmsg")(errstring)
return object.__getattribute__(self, "get_default_typeclass")(cache=False, silent=False, save=False) return GA(self, "get_default_typeclass")(cache=False, silent=False, save=False)
#@typeclass.deleter #@typeclass.deleter
def typeclass_del(self): def typeclass_del(self):
@ -878,34 +884,34 @@ class TypedObject(SharedMemoryModel):
Default operation is to load a default typeclass. Default operation is to load a default typeclass.
""" """
defpath = object.__getattribute__(self, "default_typeclass_path") defpath = GA(self, "default_typeclass_path")
typeclass = object.__getattribute__(self, "_path_import")(defpath) typeclass = GA(self, "_path_import")(defpath)
# if not silent: # if not silent:
# #errstring = "\n\nUsing Default class '%s'." % defpath # #errstring = "\n\nUsing Default class '%s'." % defpath
# object.__getattribute__(self, "_display_errmsg")(errstring) # GA(self, "_display_errmsg")(errstring)
if not callable(typeclass): if not callable(typeclass):
# if typeclass still doesn't exist at this point, we're in trouble. # if typeclass still doesn't exist at this point, we're in trouble.
# fall back to hardcoded core class which is wrong for e.g. scripts/players etc. # fall back to hardcoded core class which is wrong for e.g. scripts/players etc.
failpath = defpath failpath = defpath
defpath = "src.objects.objects.Object" defpath = "src.objects.objects.Object"
typeclass = object.__getattribute__(self, "_path_import")(defpath) typeclass = GA(self, "_path_import")(defpath)
if not silent: if not silent:
#errstring = " %s\n%s" % (typeclass, errstring) #errstring = " %s\n%s" % (typeclass, errstring)
errstring = " Default class '%s' failed to load." % failpath errstring = " Default class '%s' failed to load." % failpath
errstring += "\n Using Evennia's default class '%s'." % defpath errstring += "\n Using Evennia's default class '%s'." % defpath
object.__getattribute__(self, "_display_errmsg")(errstring) GA(self, "_display_errmsg")(errstring)
if not callable(typeclass): if not callable(typeclass):
# if this is still giving an error, Evennia is wrongly configured or buggy # if this is still giving an error, Evennia is wrongly configured or buggy
raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath) raise Exception("CRITICAL ERROR: The final fallback typeclass %s cannot load!!" % defpath)
typeclass = typeclass(self) typeclass = typeclass(self)
if save: if save:
object.__setattr__(self, 'db_typeclass_path', defpath) SA(self, 'db_typeclass_path', defpath)
object.__getattribute__(self, 'save')() GA(self, 'save')()
if cache: if cache:
object.__setattr__(self, "cached_typeclass_path", defpath) SA(self, "cached_typeclass_path", defpath)
object.__setattr__(self, "cached_typeclass", typeclass) SA(self, "cached_typeclass", typeclass)
try: try:
typeclass.at_init() typeclass.at_init()
except Exception: except Exception:
@ -931,7 +937,7 @@ class TypedObject(SharedMemoryModel):
pass pass
typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in self.typeclass_paths] typeclasses = [typeclass] + ["%s.%s" % (path, typeclass) for path in self.typeclass_paths]
if exact: if exact:
current_path = object.__getattribute__(self, "cached_typeclass_path") current_path = GA(self, "cached_typeclass_path")
return typeclass and any([current_path == typec for typec in typeclasses]) return typeclass and any([current_path == typec for typec in typeclasses])
else: else:
# check parent chain # check parent chain
@ -1033,12 +1039,9 @@ class TypedObject(SharedMemoryModel):
attribute_name: (str) The attribute's name. attribute_name: (str) The attribute's name.
""" """
exec("from %s import %s" % (self.attribute_model_path, return self.attribute_class.objects.filter(db_obj=self).filter(
self.attribute_model_name)) db_key__iexact=attribute_name).count()
model = eval("%s" % self.attribute_model_name)
attr = model.objects.attr_namesearch(attribute_name, self)
return attr.count() > 0
def set_attribute(self, attribute_name, new_value=None): def set_attribute(self, attribute_name, new_value=None):
""" """
Sets an attribute on an object. Creates the attribute if need Sets an attribute on an object. Creates the attribute if need
@ -1049,53 +1052,47 @@ class TypedObject(SharedMemoryModel):
a str, the object will be stored as a pickle. a str, the object will be stored as a pickle.
""" """
attrib_obj = None attrib_obj = None
if self.has_attribute(attribute_name): attrclass = self.attribute_class
exec("from %s import %s" % (self.attribute_model_path, try:
self.attribute_model_name)) attrib_obj = attrclass.objects.filter(
model = eval("%s" % self.attribute_model_name) db_obj=self).filter(db_key__iexact=attribute_name)[0]
#print "attr: model:", self.attribute_model_name except IndexError:
attrib_obj = \ # no match; create new attribute
model.objects.filter( new_attrib = attrclass(db_key=attribute_name, db_obj=self)
db_obj=self).filter(
db_key__iexact=attribute_name)[0]
if attrib_obj:
# Save over the existing attribute's value.
#print "attr:overwrite: %s.%s = %s" % (attrib_obj.db_obj.key, attribute_name, new_value)
attrib_obj.value = new_value
else:
# Create a new attribute
exec("from %s import %s" % (self.attribute_model_path,
self.attribute_model_name))
new_attrib = eval("%s()" % self.attribute_model_name)
new_attrib.db_key = attribute_name
new_attrib.db_obj = self
new_attrib.value = new_value new_attrib.value = new_value
#print "attr:new: %s.%s = %s" % (new_attrib.db_obj.key, attribute_name, new_value) return
# re-set an old attribute value
attrib_obj.value = new_value
def get_attribute(self, attribute_name, default=None): def get_attribute(self, attribute_name, default=None):
""" """
Returns the value of an attribute on an object. You may need to Returns the value of an attribute on an object. You may need to
type cast the returned value from this function since the attribute type cast the returned value from this function since the attribute
can be of any type. can be of any type. Returns default if no match is found.
attribute_name: (str) The attribute's name. attribute_name: (str) The attribute's name.
default: What to return if no attribute is found default: What to return if no attribute is found
""" """
if self.has_attribute(attribute_name): attrib_obj = default
try: try:
exec("from %s import %s" % (self.attribute_model_path, attrib_obj = self.attribute_class.objects.filter(
self.attribute_model_name)) db_obj=self).filter(db_key__iexact=attribute_name)[0]
model = eval("%s" % self.attribute_model_name) except IndexError:
attrib = model.objects.filter( return default
db_obj=self).filter( return attrib_obj.value
db_key=attribute_name)[0]
except Exception: def get_attribute_raise(self, attribute_name):
# safety, if something goes wrong (like unsynced db), catch it. """
logger.log_trace() Returns value of an attribute. Raises AttributeError
return default if no match is found.
return attrib.value
else: attribute_name: (str) The attribute's name.
return default """
try:
return self.attribute_class.objects.filter(
db_obj=self).filter(db_key__iexact=attribute_name)[0].value
except IndexError:
raise AttributeError
def del_attribute(self, attribute_name): def del_attribute(self, attribute_name):
""" """
@ -1103,23 +1100,30 @@ class TypedObject(SharedMemoryModel):
attribute_name: (str) The attribute's name. attribute_name: (str) The attribute's name.
""" """
exec("from %s import %s" % (self.attribute_model_path, try:
self.attribute_model_name)) self.attribute_class.objects.filter(
model = eval("%s" % self.attribute_model_name) db_obj=self).filter(db_key__iexact=attribute_name)[0].delete()
#print "delete attr", model, attribute_name except IndexError:
pass
attrs = \ def del_attribute_raise(self, attribute_name):
model.objects.attr_namesearch(attribute_name, self) """
#print "found attrs:", attrs Removes and attribute. Raises AttributeError if
if attrs: attribute is not found.
attrs[0].delete()
attribute_name: (str) The attribute's name.
"""
try:
self.attribute_class.objects.filter(
db_obj=self).filter(db_key__iexact=attribute_name)[0].delete()
except IndexError:
raise AttributeError
def get_all_attributes(self): def get_all_attributes(self):
""" """
Returns all attributes defined on the object. Returns all attributes defined on the object.
""" """
attr_set_all = eval("self.%s_set.all()" % (self.attribute_model_name.lower())) return list(self.attribute_class.objects.filter(db_obj=self))
return [attr for attr in attr_set_all]
def attr(self, attribute_name=None, value=None, delete=False): def attr(self, attribute_name=None, value=None, delete=False):
""" """
@ -1131,8 +1135,7 @@ class TypedObject(SharedMemoryModel):
set delete=True to delete the named attribute. set delete=True to delete the named attribute.
Note that you cannot set the attribute Note that you cannot set the attribute
value to None using this method should you value to None using this method. Use set_attribute.
want that, use set_attribute for that.
""" """
if attribute_name == None: if attribute_name == None:
# act as a list method # act as a list method
@ -1166,34 +1169,29 @@ class TypedObject(SharedMemoryModel):
class DbHolder(object): class DbHolder(object):
"Holder for allowing property access of attributes" "Holder for allowing property access of attributes"
def __init__(self, obj): def __init__(self, obj):
object.__setattr__(self, 'obj', obj) SA(self, 'obj', obj)
def __getattribute__(self, attrname): def __getattribute__(self, attrname):
obj = object.__getattribute__(self, 'obj')
if attrname == 'all': if attrname == 'all':
# we allow for overwriting the all() method # we allow for overwriting the all() method
# with an attribute named 'all'. # with an attribute named 'all'.
attr = obj.get_attribute("all") attr = GA(self, 'obj').get_attribute("all")
if attr: if attr:
return attr return attr
return object.__getattribute__(self, 'all') return GA(self, 'all')
return obj.get_attribute(attrname) return GA(self, 'obj').get_attribute(attrname)
def __setattr__(self, attrname, value): def __setattr__(self, attrname, value):
obj = object.__getattribute__(self, 'obj') GA(self, 'obj').set_attribute(attrname, value)
obj.set_attribute(attrname, value)
def __delattr__(self, attrname): def __delattr__(self, attrname):
obj = object.__getattribute__(self, 'obj') GA(self, 'obj').del_attribute(attrname)
obj.del_attribute(attrname)
def all(self): def all(self):
obj = object.__getattribute__(self, 'obj') return GA(self, 'obj').get_all_attributes()
return obj.get_all_attributes()
self._db_holder = DbHolder(self) self._db_holder = DbHolder(self)
return self._db_holder return self._db_holder
#@db.setter #@db.setter
def db_set(self, value): def db_set(self, value):
"Stop accidentally replacing the db object" "Stop accidentally replacing the db object"
string = "Cannot assign directly to db object! " string = "Cannot assign directly to db object! "
string = "Use db.attr=value instead." string += "Use db.attr=value instead."
raise Exception(string) raise Exception(string)
#@db.deleter #@db.deleter
def db_del(self): def db_del(self):
@ -1202,9 +1200,7 @@ class TypedObject(SharedMemoryModel):
db = property(db_get, db_set, db_del) db = property(db_get, db_set, db_del)
# #
# NON-PERSISTENT store. If you want to loose data on server reboot # NON-PERSISTENT storage methods
# you should use this explicitly. Otherwise there is
# little point in using the non-persistent methods.
# #
def nattr(self, attribute_name=None, value=None, delete=False): def nattr(self, attribute_name=None, value=None, delete=False):
@ -1221,16 +1217,16 @@ class TypedObject(SharedMemoryModel):
if not val.startswith['_']] if not val.startswith['_']]
elif delete == True: elif delete == True:
if hasattr(self.ndb, attribute_name): if hasattr(self.ndb, attribute_name):
object.__delattr__(self.db, attribute_name) DA(self.db, attribute_name)
elif value == None: elif value == None:
# act as a getter. # act as a getter.
if hasattr(self.ndb, attribute_name): if hasattr(self.ndb, attribute_name):
object.__getattribute__(self.ndb, attribute_name) GA(self.ndb, attribute_name)
else: else:
return None return None
else: else:
# act as a setter # act as a setter
object.__setattr__(self.db, attribute_name, value) SA(self.db, attribute_name, value)
#@property #@property
def ndb_get(self): def ndb_get(self):
@ -1247,11 +1243,11 @@ class TypedObject(SharedMemoryModel):
"Holder for storing non-persistent attributes." "Holder for storing non-persistent attributes."
def all(self): def all(self):
return [val for val in self.__dict__.keys() return [val for val in self.__dict__.keys()
if not val.startswith['_']] if not val.startswith['_']]
def __getattribute__(self, key): def __getattribute__(self, key):
# return None if no matching attribute was found. # return None if no matching attribute was found.
try: try:
return object.__getattribute__(self, key) return GA(self, key)
except AttributeError: except AttributeError:
return None return None
self._ndb_holder = NdbHolder() self._ndb_holder = NdbHolder()
@ -1268,7 +1264,9 @@ class TypedObject(SharedMemoryModel):
raise Exception("Cannot delete the ndb object!") raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del) ndb = property(ndb_get, ndb_set, ndb_del)
#
# Lock / permission methods # Lock / permission methods
#
def access(self, accessing_obj, access_type='read', default=False): def access(self, accessing_obj, access_type='read', default=False):
""" """

View file

@ -13,14 +13,19 @@ used by the typesystem or django itself.
from src.utils import logger from src.utils import logger
from django.conf import settings from django.conf import settings
# these are called so many times it's worth to avoid lookup calls
GA = object.__getattribute__
SA = object.__setattr__
DA = object.__delattr__
# To ensure the sanity of the model, there are a # To ensure the sanity of the model, there are a
# few property names we won't allow the admin to # few property names we won't allow the admin to
# set on the typeclass just like that. Note that these are *not* related # set on the typeclass just like that. Note that these are *not* related
# to *in-game* safety (if you can edit typeclasses you have # to *in-game* safety (if you can edit typeclasses you have
# full access anyway), so no protection against changing # full access anyway), so no protection against changing
# e.g. 'locks' or 'permissions' should go here. # e.g. 'locks' or 'permissions' should go here.
PROTECTED = ['id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass',
'attr', 'save', 'delete'] 'attr', 'save', 'delete')
# If this is true, all non-protected property assignments # If this is true, all non-protected property assignments
# are directly stored to a database attribute # are directly stored to a database attribute
@ -69,24 +74,23 @@ class TypeClass(object):
""" """
# typecheck of dbobj - we can't allow it to be added here # typecheck of dbobj - we can't allow it to be added here
# unless it's really a TypedObject. # unless it's really a TypedObject.
dbobj_cls = object.__getattribute__(dbobj, '__class__') dbobj_cls = GA(dbobj, '__class__')
dbobj_mro = object.__getattribute__(dbobj_cls, '__mro__') dbobj_mro = GA(dbobj_cls, '__mro__')
if not any('src.typeclasses.models.TypedObject' if not any('src.typeclasses.models.TypedObject'
in str(mro) for mro in dbobj_mro): in str(mro) for mro in dbobj_mro):
raise Exception("dbobj is not a TypedObject: %s: %s" % \ raise Exception("dbobj is not a TypedObject: %s: %s" % \
(dbobj_cls, dbobj_mro)) (dbobj_cls, dbobj_mro))
object.__setattr__(self, 'dbobj', dbobj)
# store the needed things on the typeclass # store the needed things on the typeclass
object.__setattr__(self, '_protected_attrs', PROTECTED) SA(self, 'dbobj', dbobj)
# # sync the database object to this typeclass. # # sync the database object to this typeclass.
# cls = object.__getattribute__(self, '__class__') # cls = GA(self, '__class__')
# db_typeclass_path = "%s.%s" % (object.__getattribute__(cls, '__module__'), # db_typeclass_path = "%s.%s" % (GA(cls, '__module__'),
# object.__getattribute__(cls, '__name__')) # GA(cls, '__name__'))
# if not object.__getattribute__(dbobj, "db_typeclass_path") == db_typeclass_path: # if not GA(dbobj, "db_typeclass_path") == db_typeclass_path:
# object.__setattr__(dbobj, "db_typeclass_path", db_typeclass_path) # SA(dbobj, "db_typeclass_path", db_typeclass_path)
# object.__getattribute__(dbobj, "save")() # GA(dbobj, "save")()
def __getattribute__(self, propname): def __getattribute__(self, propname):
""" """
@ -98,7 +102,7 @@ class TypeClass(object):
accessible through getattr. accessible through getattr.
""" """
try: try:
dbobj = object.__getattribute__(self, 'dbobj') dbobj = GA(self, 'dbobj')
except AttributeError: except AttributeError:
dbobj = None dbobj = None
logger.log_trace("This is probably due to an unsafe reload.") logger.log_trace("This is probably due to an unsafe reload.")
@ -108,25 +112,20 @@ class TypeClass(object):
if propname.startswith('__') and propname.endswith('__'): if propname.startswith('__') and propname.endswith('__'):
# python specials are parsed as-is (otherwise things like # python specials are parsed as-is (otherwise things like
# isinstance() fail to identify the typeclass) # isinstance() fail to identify the typeclass)
return object.__getattribute__(self, propname) return GA(self, propname)
#print "get %s (dbobj:%s)" % (propname, type(dbobj)) #print "get %s (dbobj:%s)" % (propname, type(dbobj))
try: try:
return object.__getattribute__(self, propname) return GA(self, propname)
except AttributeError: except AttributeError:
try: try:
return object.__getattribute__(dbobj, propname) return GA(dbobj, propname)
except AttributeError: except AttributeError:
try: try:
if propname != 'ndb': if propname == 'ndb':
if not dbobj.has_attribute(propname):
raise AttributeError
else:
value = dbobj.get_attribute(propname)
else:
# get non-persistent data # get non-persistent data
ndb = object.__getattribute__(dbobj, 'ndb') return getattr(GA(dbobj, 'ndb'), propname)
value = getattr(ndb, propname) else:
return value return dbobj.get_attribute_raise(propname)
except AttributeError: except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s." string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj, raise AttributeError(string % (propname, dbobj,
@ -142,39 +141,38 @@ class TypeClass(object):
corresponding to a field on ObjectDB model. corresponding to a field on ObjectDB model.
""" """
#print "set %s -> %s" % (propname, value) #print "set %s -> %s" % (propname, value)
try: if propname in PROTECTED:
protected = object.__getattribute__(self, '_protected_attrs')
except AttributeError:
protected = PROTECTED
logger.log_trace("This is probably due to an unsafe reload.")
if propname in protected:
string = "%s: '%s' is a protected attribute name." string = "%s: '%s' is a protected attribute name."
string += " (protected: [%s])" % (", ".join(protected)) string += " (protected: [%s])" % (", ".join(PROTECTED))
logger.log_errmsg(string % (self.name, propname)) logger.log_errmsg(string % (self.name, propname))
else: return
try:
dbobj = GA(self, 'dbobj')
except AttributeError:
dbobj = None
logger.log_trace("This is probably due to an unsafe reload.")
if dbobj:
try: try:
dbobj = object.__getattribute__(self, 'dbobj') # only set value on propname if propname already exists
# on dbobj. __getattribute__ will raise attribute error otherwise.
GA(dbobj, propname)
SA(dbobj, propname, value)
except AttributeError: except AttributeError:
dbobj = None dbobj.set_attribute(propname, value)
logger.log_trace("This is probably due to an unsafe reload.") else:
if dbobj: # and hasattr(dbobj, propname): SA(self, propname, value)
#print " ---> dbobj"
if hasattr(dbobj, propname):
# only if attr already exists on dbobj, assign to it.
object.__setattr__(dbobj, propname, value)
else:
dbobj.set_attribute(propname, value)
else:
object.__setattr__(self, propname, value)
def __eq__(self, other): def __eq__(self, other):
""" """
dbobj-recognized comparison dbobj-recognized comparison
""" """
if hasattr(other, 'user'): try:
return other == self or other == self.dbobj or other == self.dbobj.user return other == self or other == GA(self, dbobj) or other == GA(self, dbobj).user
else: except AttributeError:
return other == self or other == self.dbobj # if self.dbobj.user fails it means the two previous comparisons failed already
return False
def __delattr__(self, propname): def __delattr__(self, propname):
@ -183,35 +181,28 @@ class TypeClass(object):
secondly on the dbobj.db. secondly on the dbobj.db.
Will not allow deletion of properties stored directly on dbobj. Will not allow deletion of properties stored directly on dbobj.
""" """
try: if propname in PROTECTED:
protected = object.__getattribute__(self, '_protected_attrs')
except AttributeError:
protected = PROTECTED
logger.log_trace("Thiis is probably due to an unsafe reload.")
if propname in protected:
string = "%s: '%s' is a protected attribute name." string = "%s: '%s' is a protected attribute name."
string += " (protected: [%s])" % (", ".join(protected)) string += " (protected: [%s])" % (", ".join(PROTECTED))
logger.log_errmsg(string % (self.name, propname)) logger.log_errmsg(string % (self.name, propname))
else: return
try:
object.__delattr__(self, propname)
except AttributeError:
# not on typeclass, try to delete on db/ndb
try:
dbobj = object.__getattribute__(self, 'dbobj')
except AttributeError:
logger.log_trace("This is probably due to an unsafe reload.")
return # ignore delete
try:
if not dbobj.has_attribute(propname):
raise AttributeError
dbobj.del_attribute(propname)
except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj,
dbobj.dbref,
dbobj.typeclass_path,))
try:
DA(self, propname)
except AttributeError:
# not on typeclass, try to delete on db/ndb
try:
dbobj = GA(self, 'dbobj')
except AttributeError:
logger.log_trace("This is probably due to an unsafe reload.")
return # ignore delete
try:
dbobj.del_attribute_raise(propname)
except AttributeError:
string = "Object: '%s' not found on %s(%s), nor on its typeclass %s."
raise AttributeError(string % (propname, dbobj,
dbobj.dbref,
dbobj.typeclass_path,))
def __str__(self): def __str__(self):
"represent the object" "represent the object"