mirror of
https://github.com/evennia/evennia.git
synced 2026-03-27 18:26:32 +01:00
A first test using PickledObjectField and a rewritten dbserialize module to store Attributes. No migrations set up yet.
This commit is contained in:
parent
4d5cd5352a
commit
75341ade6f
7 changed files with 678 additions and 140 deletions
|
|
@ -48,6 +48,8 @@ from src.typeclasses import managers
|
|||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils import logger, utils
|
||||
from src.utils.utils import make_iter, is_iter, to_unicode, to_str
|
||||
from src.utils.dbserialize import to_pickle, from_pickle
|
||||
from src.utils.picklefield import PickledObjectField
|
||||
|
||||
__all__ = ("Attribute", "TypeNick", "TypedObject")
|
||||
|
||||
|
|
@ -322,6 +324,7 @@ class Attribute(SharedMemoryModel):
|
|||
db_key = models.CharField('key', max_length=255, db_index=True)
|
||||
# access through the value property
|
||||
db_value = models.TextField('value', blank=True, null=True)
|
||||
db_value2 = PickledObjectField('value2', null=True)
|
||||
# Lock storage
|
||||
db_lock_storage = models.TextField('locks', blank=True)
|
||||
# references the object the attribute is linked to (this is set
|
||||
|
|
@ -409,29 +412,44 @@ class Attribute(SharedMemoryModel):
|
|||
"""
|
||||
if self.no_cache:
|
||||
# re-create data from database and cache it
|
||||
try:
|
||||
value = self.__from_attr(_PLOADS(to_str(self.db_value)))
|
||||
except pickle.UnpicklingError:
|
||||
value = self.db_value
|
||||
value = from_pickle(self.db_value2, db_obj=self)
|
||||
self.cached_value = value
|
||||
self.no_cache = False
|
||||
return value
|
||||
else:
|
||||
# normally the memory cache holds the latest data so no db access is needed.
|
||||
return self.cached_value
|
||||
#if self.no_cache:
|
||||
# # re-create data from database and cache it
|
||||
# try:
|
||||
# value = self.__from_attr(_PLOADS(to_str(self.db_value)))
|
||||
# except pickle.UnpicklingError:
|
||||
# value = self.db_value
|
||||
# self.cached_value = value
|
||||
# self.no_cache = False
|
||||
# return value
|
||||
#else:
|
||||
# # normally the memory cache holds the latest data so no db access is needed.
|
||||
# return self.cached_value
|
||||
|
||||
#@value.setter
|
||||
def __value_set(self, new_value):
|
||||
"""
|
||||
Setter. Allows for self.value = value. We make sure to cache everything.
|
||||
"""
|
||||
new_value = self.__to_attr(new_value)
|
||||
self.cached_value = self.__from_attr(new_value)
|
||||
to_store = to_pickle(new_value)
|
||||
self.cached_value = from_pickle(to_store, db_obj=self)
|
||||
self.no_cache = False
|
||||
self.db_value = to_unicode(_PDUMPS(to_str(new_value)))
|
||||
self.db_value2 = to_store
|
||||
self.save()
|
||||
# call attribute hook
|
||||
self.at_set(new_value)
|
||||
self.at_set(self.cached_value)
|
||||
|
||||
#new_value = self.__to_attr(new_value)
|
||||
#self.cached_value = self.__from_attr(new_value)
|
||||
#self.no_cache = False
|
||||
#self.db_value = to_unicode(_PDUMPS(to_str(new_value)))
|
||||
#self.save()
|
||||
## call attribute hook
|
||||
#self.at_set(new_value)
|
||||
|
||||
#@value.deleter
|
||||
def __value_del(self):
|
||||
|
|
@ -470,148 +488,148 @@ class Attribute(SharedMemoryModel):
|
|||
|
||||
# operators on various data
|
||||
|
||||
def __to_attr(self, data):
|
||||
"""
|
||||
Convert data to proper attr data format before saving
|
||||
#def __to_attr(self, data):
|
||||
# """
|
||||
# Convert data to proper attr data format before saving
|
||||
|
||||
We have to make sure to not store database objects raw, since
|
||||
this will crash the system. Instead we must store their IDs
|
||||
and make sure to convert back when the attribute is read back
|
||||
later.
|
||||
# We have to make sure to not store database objects raw, since
|
||||
# this will crash the system. Instead we must store their IDs
|
||||
# and make sure to convert back when the attribute is read back
|
||||
# later.
|
||||
|
||||
Due to this it's criticial that we check all iterables
|
||||
recursively, converting all found database objects to a form
|
||||
the database can handle. We handle lists, tuples and dicts
|
||||
(and any nested combination of them) this way, all other
|
||||
iterables are stored and returned as lists.
|
||||
# Due to this it's criticial that we check all iterables
|
||||
# recursively, converting all found database objects to a form
|
||||
# the database can handle. We handle lists, tuples and dicts
|
||||
# (and any nested combination of them) this way, all other
|
||||
# iterables are stored and returned as lists.
|
||||
|
||||
data storage format:
|
||||
(simple|dbobj|iter, <data>)
|
||||
where
|
||||
simple - a single non-db object, like a string or number
|
||||
dbobj - a single dbobj
|
||||
iter - any iterable object - will be looped over recursively
|
||||
to convert dbobj->id.
|
||||
# data storage format:
|
||||
# (simple|dbobj|iter, <data>)
|
||||
# where
|
||||
# simple - a single non-db object, like a string or number
|
||||
# dbobj - a single dbobj
|
||||
# iter - any iterable object - will be looped over recursively
|
||||
# to convert dbobj->id.
|
||||
|
||||
"""
|
||||
# """
|
||||
|
||||
def iter_db2id(item):
|
||||
"""
|
||||
recursively looping through stored iterables, replacing objects with ids.
|
||||
(Python only builds nested functions once, so there is no overhead for nesting)
|
||||
"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float): # check the most common types first, for speed
|
||||
return item
|
||||
elif hasattr(item, "id") and hasattr(item, "_db_model_name") and hasattr(item, "db_key"):
|
||||
db_model_name = item._db_model_name # don't use _GA here, could be typeclass
|
||||
if db_model_name == "typeclass":
|
||||
db_model_name = _GA(item.dbobj, "_db_model_name")
|
||||
return PackedDBobject(item.id, db_model_name, item.db_key)
|
||||
elif dtype == tuple:
|
||||
return tuple(iter_db2id(val) for val in item)
|
||||
elif dtype in (dict, PackedDict):
|
||||
return dict((key, iter_db2id(val)) for key, val in item.items())
|
||||
elif dtype in (set, PackedSet):
|
||||
return set(iter_db2id(val) for val in item)
|
||||
elif hasattr(item, '__iter__'):
|
||||
return list(iter_db2id(val) for val in item)
|
||||
else:
|
||||
return item
|
||||
# def iter_db2id(item):
|
||||
# """
|
||||
# recursively looping through stored iterables, replacing objects with ids.
|
||||
# (Python only builds nested functions once, so there is no overhead for nesting)
|
||||
# """
|
||||
# dtype = type(item)
|
||||
# if dtype in (basestring, int, float): # check the most common types first, for speed
|
||||
# return item
|
||||
# elif hasattr(item, "id") and hasattr(item, "_db_model_name") and hasattr(item, "db_key"):
|
||||
# db_model_name = item._db_model_name # don't use _GA here, could be typeclass
|
||||
# if db_model_name == "typeclass":
|
||||
# db_model_name = _GA(item.dbobj, "_db_model_name")
|
||||
# return PackedDBobject(item.id, db_model_name, item.db_key)
|
||||
# elif dtype == tuple:
|
||||
# return tuple(iter_db2id(val) for val in item)
|
||||
# elif dtype in (dict, PackedDict):
|
||||
# return dict((key, iter_db2id(val)) for key, val in item.items())
|
||||
# elif dtype in (set, PackedSet):
|
||||
# return set(iter_db2id(val) for val in item)
|
||||
# elif hasattr(item, '__iter__'):
|
||||
# return list(iter_db2id(val) for val in item)
|
||||
# else:
|
||||
# return item
|
||||
|
||||
dtype = type(data)
|
||||
# dtype = type(data)
|
||||
|
||||
if dtype in (basestring, int, float):
|
||||
return ("simple",data)
|
||||
elif hasattr(data, "id") and hasattr(data, "_db_model_name") and hasattr(data, 'db_key'):
|
||||
# all django models (objectdb,scriptdb,playerdb,channel,msg,typeclass)
|
||||
# have the protected property _db_model_name hardcoded on themselves for speed.
|
||||
db_model_name = data._db_model_name # don't use _GA here, could be typeclass
|
||||
if db_model_name == "typeclass":
|
||||
# typeclass cannot help us, we want the actual child object model name
|
||||
db_model_name = _GA(data.dbobj,"_db_model_name")
|
||||
return ("dbobj", PackedDBobject(data.id, db_model_name, data.db_key))
|
||||
elif hasattr(data, "__iter__"):
|
||||
return ("iter", iter_db2id(data))
|
||||
else:
|
||||
return ("simple", data)
|
||||
# if dtype in (basestring, int, float):
|
||||
# return ("simple",data)
|
||||
# elif hasattr(data, "id") and hasattr(data, "_db_model_name") and hasattr(data, 'db_key'):
|
||||
# # all django models (objectdb,scriptdb,playerdb,channel,msg,typeclass)
|
||||
# # have the protected property _db_model_name hardcoded on themselves for speed.
|
||||
# db_model_name = data._db_model_name # don't use _GA here, could be typeclass
|
||||
# if db_model_name == "typeclass":
|
||||
# # typeclass cannot help us, we want the actual child object model name
|
||||
# db_model_name = _GA(data.dbobj,"_db_model_name")
|
||||
# return ("dbobj", PackedDBobject(data.id, db_model_name, data.db_key))
|
||||
# elif hasattr(data, "__iter__"):
|
||||
# return ("iter", iter_db2id(data))
|
||||
# else:
|
||||
# return ("simple", data)
|
||||
|
||||
|
||||
def __from_attr(self, datatuple):
|
||||
"""
|
||||
Retrieve data from a previously stored attribute. This
|
||||
is always a dict with keys type and data.
|
||||
#def __from_attr(self, datatuple):
|
||||
# """
|
||||
# Retrieve data from a previously stored attribute. This
|
||||
# is always a dict with keys type and data.
|
||||
|
||||
datatuple comes from the database storage and has
|
||||
the following format:
|
||||
(simple|dbobj|iter, <data>)
|
||||
where
|
||||
simple - a single non-db object, like a string. is returned as-is.
|
||||
dbobj - a single dbobj-id. This id is retrieved back from the database.
|
||||
iter - an iterable. This is traversed iteratively, converting all found
|
||||
dbobj-ids back to objects. Also, all lists and dictionaries are
|
||||
returned as their PackedList/PackedDict counterparts in order to
|
||||
allow in-place assignment such as obj.db.mylist[3] = val. Mylist
|
||||
is then a PackedList that saves the data on the fly.
|
||||
"""
|
||||
# nested functions
|
||||
def id2db(data):
|
||||
"""
|
||||
Convert db-stored dbref back to object
|
||||
"""
|
||||
mclass = _CTYPEGET(model=data.db_model).model_class()
|
||||
try:
|
||||
return mclass.objects.dbref_search(data.id)
|
||||
# datatuple comes from the database storage and has
|
||||
# the following format:
|
||||
# (simple|dbobj|iter, <data>)
|
||||
# where
|
||||
# simple - a single non-db object, like a string. is returned as-is.
|
||||
# dbobj - a single dbobj-id. This id is retrieved back from the database.
|
||||
# iter - an iterable. This is traversed iteratively, converting all found
|
||||
# dbobj-ids back to objects. Also, all lists and dictionaries are
|
||||
# returned as their PackedList/PackedDict counterparts in order to
|
||||
# allow in-place assignment such as obj.db.mylist[3] = val. Mylist
|
||||
# is then a PackedList that saves the data on the fly.
|
||||
# """
|
||||
# # nested functions
|
||||
# def id2db(data):
|
||||
# """
|
||||
# Convert db-stored dbref back to object
|
||||
# """
|
||||
# mclass = _CTYPEGET(model=data.db_model).model_class()
|
||||
# try:
|
||||
# return mclass.objects.dbref_search(data.id)
|
||||
|
||||
except AttributeError:
|
||||
try:
|
||||
return mclass.objects.get(id=data.id)
|
||||
except mclass.DoesNotExist: # could happen if object was deleted in the interim.
|
||||
return None
|
||||
# except AttributeError:
|
||||
# try:
|
||||
# return mclass.objects.get(id=data.id)
|
||||
# except mclass.DoesNotExist: # could happen if object was deleted in the interim.
|
||||
# return None
|
||||
|
||||
def iter_id2db(item, parent=None):
|
||||
"""
|
||||
Recursively looping through stored iterables, replacing ids with actual objects.
|
||||
We return PackedDict and PackedLists instead of normal lists; this is needed in order for
|
||||
the user to do dynamic saving of nested in-place, such as obj.db.attrlist[2]=3. What is
|
||||
stored in the database are however always normal python primitives.
|
||||
"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float): # check the most common types first, for speed
|
||||
return item
|
||||
elif dtype == PackedDBobject:
|
||||
return id2db(item)
|
||||
elif dtype == tuple:
|
||||
return tuple([iter_id2db(val) for val in item])
|
||||
elif dtype in (dict, PackedDict):
|
||||
pdict = PackedDict(self)
|
||||
pdict.update(dict(zip([key for key in item.keys()],
|
||||
[iter_id2db(val, pdict) for val in item.values()])))
|
||||
pdict.parent = parent
|
||||
return pdict
|
||||
elif dtype in (set, PackedSet):
|
||||
pset = PackedSet(self)
|
||||
pset.update(set(iter_id2db(val) for val in item))
|
||||
return pset
|
||||
elif hasattr(item, '__iter__'):
|
||||
plist = PackedList(self)
|
||||
plist.extend(list(iter_id2db(val, plist) for val in item))
|
||||
plist.parent = parent
|
||||
return plist
|
||||
else:
|
||||
return item
|
||||
# def iter_id2db(item, parent=None):
|
||||
# """
|
||||
# Recursively looping through stored iterables, replacing ids with actual objects.
|
||||
# We return PackedDict and PackedLists instead of normal lists; this is needed in order for
|
||||
# the user to do dynamic saving of nested in-place, such as obj.db.attrlist[2]=3. What is
|
||||
# stored in the database are however always normal python primitives.
|
||||
# """
|
||||
# dtype = type(item)
|
||||
# if dtype in (basestring, int, float): # check the most common types first, for speed
|
||||
# return item
|
||||
# elif dtype == PackedDBobject:
|
||||
# return id2db(item)
|
||||
# elif dtype == tuple:
|
||||
# return tuple([iter_id2db(val) for val in item])
|
||||
# elif dtype in (dict, PackedDict):
|
||||
# pdict = PackedDict(self)
|
||||
# pdict.update(dict(zip([key for key in item.keys()],
|
||||
# [iter_id2db(val, pdict) for val in item.values()])))
|
||||
# pdict.parent = parent
|
||||
# return pdict
|
||||
# elif dtype in (set, PackedSet):
|
||||
# pset = PackedSet(self)
|
||||
# pset.update(set(iter_id2db(val) for val in item))
|
||||
# return pset
|
||||
# elif hasattr(item, '__iter__'):
|
||||
# plist = PackedList(self)
|
||||
# plist.extend(list(iter_id2db(val, plist) for val in item))
|
||||
# plist.parent = parent
|
||||
# return plist
|
||||
# else:
|
||||
# return item
|
||||
|
||||
typ, data = datatuple
|
||||
# typ, data = datatuple
|
||||
|
||||
if typ == 'simple':
|
||||
# single non-db objects
|
||||
return data
|
||||
elif typ == 'dbobj':
|
||||
# a single stored dbobj
|
||||
return id2db(data)
|
||||
elif typ == 'iter':
|
||||
# all types of iterables
|
||||
return iter_id2db(data)
|
||||
# if typ == 'simple':
|
||||
# # single non-db objects
|
||||
# return data
|
||||
# elif typ == 'dbobj':
|
||||
# # a single stored dbobj
|
||||
# return id2db(data)
|
||||
# elif typ == 'iter':
|
||||
# # all types of iterables
|
||||
# return iter_id2db(data)
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue