[Zodb-checkins] CVS: ZODB4/ZODB - Serialize.py:1.1
Jeremy Hylton
jeremy@zope.com
Thu, 19 Sep 2002 14:19:08 -0400
Update of /cvs-repository/ZODB4/ZODB
In directory cvs.zope.org:/tmp/cvs-serv22270/ZODB
Added Files:
Serialize.py
Log Message:
Add new module that handles all serialization.
The current implementation actually only does the unpickling.
=== Added File ZODB4/ZODB/Serialize.py ===
"""Support for ZODB object serialization.
ZODB serializes objects using a custom format based on Python pickles.
When an object is unserialized, it can be loaded as either a ghost or
a real object. A ghost is a persistent object of the appropriate type
but without any state. The first time a ghost is accessed, the
persistence machinery traps access and loads the actual state. A
ghost allows many persistent objects to be loaded while minimizing the
memory consumption of referenced but otherwise unused objects.
Pickle format
-------------
ZODB pickles objects using a custom format. Each object pickle had
two parts: the class description and the object state. The class
description must provide enough information to call the class's
``__new__`` and create an empty object. Once the object exists, its
state is passed to ``__getstate__``.
The class metadata is a three-tuple contained the module name, the
class name, and a tuple of arguments to pass to ``__new__``. The last
element may be None if the only argument to ``__new__`` is the class.
"""
from cStringIO import StringIO
import cPickle
from types import StringType, TupleType
def getClass(module, name):
mod = __import__(module)
parts = module.split(".")
for part in parts[1:]:
mod = getattr(mod, part)
return getattr(mod, name)
def getClassMetadata(object):
klass = object.__class__
module = klass.__module__
classname = klass.__name__
if hasattr(object, "__getnewargs__"):
newargs = object.__getnewargs__()
else:
newargs = None
return module, classname, newargs
class Pickler:
pass
class BaseUnpickler:
# subclasses must define _persistent_load().
def _get_unpickler(self, pickle):
file = StringIO(pickle)
unpickler = cPickle.Unpickler(file)
unpickler.persistent_load = self._persistent_load
return unpickler
def _new_object(self, module, classname, newargs=None):
klass = getClass(module, classname)
if newargs is None:
object = klass.__new__(klass)
else:
object = klass.__new__(klass, *newargs)
return object
def getGhost(self, pickle):
unpickler = self._get_unpickler(pickle)
module, classname, newargs = unpickler.load()
return self._new_object(module, classname, newargs)
def getState(self, pickle):
unpickler = self._get_unpickler(pickle)
unpickler.load() # skip the class metadata
state = unpickler.load()
return state
def setGhostState(self, object, pickle):
state = self.getState(pickle)
object.__setstate__(state)
class ConnectionUnpickler(BaseUnpickler):
def __init__(self, conn, cache):
self._conn = conn
self._cache = cache
def _persistent_load(self, oid):
# persistent_load function to pass to Unpickler
if isinstance(oid, TupleType):
# XXX We get here via new_persistent_id()
# Quick instance reference. We know all we need to know
# to create the instance w/o hitting the db, so go for it!
oid, classmeta = oid
object = self._cache.get(oid)
if object is not None:
return object
object = self._new_object(*classmeta)
# XXX should be done by connection
object._p_oid = oid
object._p_jar = self._conn
object._p_changed = None
self._cache[oid] = object
return object
object = self._cache.get(oid)
if object is not None:
return object
return self._conn[oid]
class ResolveUnpickler(BaseUnpickler):
bad_classes = {}
def __init__(self, persistent_load):
self._persistent_load = persistent_load
def getGhost(self, pickle):
unpickler = self._get_unpickler(pickle)
classmeta = unpickler.load()
if classmeta in self.bad_classes:
return None
else:
return self._new_object(*classmeta)
def getResolveMethod(self, pickle):
ghost = self.getGhost(pickle)
if ghost is None:
return None
resolve = getattr(ghost, "_p_resolveConflict", None)
if resolve is None:
# XXX too bad. we just had classmeta.
classmeta = getClassMeta(ghost)
self.bad_classes[classmeta] = True
return None
else:
return resolve
def new_persistent_id(self, stack):
# XXX need a doc string. not sure if the one for persistent_id()
# below is correct.
# Create a special persistent_id that captures T and the subobject
# stack in a closure.
def persistent_id(object):
"""Test if an object is persistent, returning an oid if it is.
This function is used by the pickler to test whether an object
is persistent. If it isn't, the function returns None and the
object is included in the pickle for the current persistent
object.
If it is persistent, it returns the oid and sometimes a tuple
with other stuff.
"""
if not hasattr(object, '_p_oid'):
return None
oid = object._p_oid
# I'd like to write something like this --
# if isinstance(oid, types.MemberDescriptor):
# -- but I can't because the type doesn't have a canonical name.
# Instead, we'll assert that an oid must always be a string
if not (isinstance(oid, StringType) or oid is None):
return None
if oid is None or object._p_jar is not self:
oid = self.new_oid()
object._p_jar = self
object._p_oid = oid
stack.append(object)
return oid, getClassMetadata(object)
return persistent_id