Re: Patch: HistoricalRevisions, atct_history support for annotation storage (used by newer ATCT)

Matt Hahnfeld <[email protected]>
Newsgroups gmane.comp.web.zope.plone.archetypes.devel
Message-ID <[email protected]>
> On Thu, 09 Mar 2006 20:41:52 +0000, Martin Aspeli wrote:
> 
>>> If this is going to go into Zope, which I think it should, we need to
>>> find a more generic way to handle the issue.  The current patch is short
>>> and simple, but it only handles __annotation__ attributes.  It provides a
>>> very simple fix for the issue at hand (and actually works very well for
>>> our needs!), but probably isn't something the Zope folks would want to  
>>> add
>>> to Zope.
>>
>> I see the problem. Perhaps we can replicate some of this code at the AT  
>> storage level and not have to use OFS.* directly at all instead?

Below is a proof of concept for a generic low-level solution.  I
implemented it as its own separate class, but it could probably subclass
the ZODB serializer, since it's almost entirely copied from there.  In my
testing, it works for ATDocuments (using annotation storage) on a clean
Plone 2.1.2/Zope 2.8.5 site using "zopectl debug".  I haven't had time to
test much beyond that (feedback welcome!).  It should work for other AT
field storages that use subobjects too (are there any?).

Because of the way this is implemented, something like this could probably
be a part of the AT or ATCT layer -- if it wasn't included in Zope.  As
you can see, this class essentially just overrides the Unpickler's
persistent_load to provide old versions of subobjects (err...  references
to other objects) when they are loaded from the ZODB. I also removed all
caching functionality, since you wouldn't want to use the cache when
you're getting a historical revision.

I'll work on cleaning this up over the next couple of days.

Feedback?

Matt Hahnfeld
[email protected]

---

import OFS.History
import ZODB.broken
import cPickle
import cStringIO

# TimeTraveler
# ZODB (deep) historical objects -- proof of concept
# Matt Hahnfeld 3/10/06
#
# Most of this code was stolen from ZODB/Connection.py
# or ZODB/serialize.py.  This should probably subclass
# ZODB.Serialize.ObjectReader.
#
# Usage:
#
# get tid from p._p_jar._storage.history(p._p_oid, size=10)
#
# from TimeTraveler import TimeTraveler
# p = app.my_plone_site.my_page
# tt = TimeTraveler(p,'\x03c\xff\xcd\x15X\x80\xcc')
# p_old = tt.get()
#
# p_old will be a deep copy of p for the tid specified.


class TimeTraveler:

    def __init__(self, obj, tid):
        self._obj = obj
        self._tid = tid
        self._conn = self._obj._p_jar
        self._storage = self._conn._storage
        self._factory = self._conn._db.classFactory

    def get(self):
        obj = self._get_object(self._obj._p_oid)
        return obj.__of__(self._obj.aq_parent)
        
    def _get_object(self, oid):
        print 'in getobj'
        pickle = self._storage.loadSerial(oid, self._tid)
        unpickler = self._get_unpickler(pickle)
        klass = unpickler.load()
        if isinstance(klass, tuple):
            # Here we have a separate class and args.
            # This could be an old record, so the class module ne a named
            # refernce
            klass, args = klass
            print 'klass is '+str(klass)
            if isinstance(klass, tuple):
                # Old module_name, class_name tuple
                klass = self._get_class(*klass)

            if args is None:
                args = ()
        else:
            # Definitely new style direct class reference
            args = ()

        if issubclass(klass, ZODB.broken.Broken):
            # We got a broken class. We might need to make it
            # PersistentBroken
            if not issubclass(klass, ZODB.broken.PersistentBroken):
                klass = ZODB.broken.persistentBroken(klass)

        obj = klass.__new__(klass, *args)
        state = unpickler.load()
        obj.__setstate__(state)

        # set other attributes
        obj._p_jar=OFS.History.HystoryJar(self._conn)
        obj._p_oid=oid
        obj._p_serial=self._tid
        obj._p_changed=0
        
        return obj

    def _get_unpickler(self, pickle):
        file = cStringIO.StringIO(pickle)
        unpickler = cPickle.Unpickler(file)
        unpickler.persistent_load = self._persistent_load
        factory = self._factory
        conn = self._conn

        def find_global(modulename, name):
            print 'in find global\n'
            return factory(conn, modulename, name)

        unpickler.find_global = find_global

        return unpickler
    
    def _get_class(self, module, name):
        return self._factory(self._conn, module, name)

    def _persistent_load(self, oid):
        print 'in persistent load\n'
        if isinstance(oid, list):
            # weakref
            [oid] = oid
            obj = WeakRef.__new__(WeakRef)
            obj.oid = oid
            obj.dm = self._conn
            return obj
        elif isinstance(oid, tuple):
            oid = oid[0]
        return self._get_object(oid)



-------------------------------------------------------
This SF.Net email is sponsored by xPML, a groundbreaking scripting language
that extends applications into web and mobile media. Attend the live webcast
and join the prime developer group breaking into this new coding territory!
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=110944&bid=241720&dat=121642
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.