[Zodb-checkins] CVS: Zope3/src/zodb/code - class_.py:1.6.2.3

Jeremy Hylton jeremy@zope.com
Fri, 24 Jan 2003 16:55:01 -0500


Update of /cvs-repository/Zope3/src/zodb/code
In directory cvs.zope.org:/tmp/cvs-serv18777/code

Modified Files:
      Tag: new-pickle-branch
	class_.py 
Log Message:
Add a hook for the _p_jar class attribute that causes _p_activate() to
be called when it is assigned.  See comments for a detailed rationale.

Fix the repr() of unbound PersistentMethods.


=== Zope3/src/zodb/code/class_.py 1.6.2.2 => 1.6.2.3 ===
--- Zope3/src/zodb/code/class_.py:1.6.2.2	Fri Jan 24 13:20:43 2003
+++ Zope3/src/zodb/code/class_.py	Fri Jan 24 16:54:58 2003
@@ -126,6 +126,17 @@
 class ExtClassDataDescr(DataMixin, ExtClassDescr):
     pass
 
+class ExtClassHookDataDescr(ExtClassDataDescr):
+    # Calls a hook when clsset() is called.
+
+    def __init__(self, name, descr, val, hook):
+        super(ExtClassHookDataDescr, self).__init__(name, descr, val)
+        self.hook = hook
+        
+    def clsset(self, val):
+        self.val = val
+        self.hook()
+
 # The next three classes conspire to make a PersistentFunction
 # behave like a method when found in a class's __dict__.
 
@@ -138,12 +149,10 @@
 
     def __repr__(self):
         if self.im_self is None:
-            kind = "unbound"
+            fmt = "<persistent unbound method %s.%s>"
         else:
-            kind = "bound"
-        return ("<persistent %s method %s.%s of %s>"
-                % (kind, self.im_class.__name__, self.im_func.__name__,
-                   self.im_self))
+            fmt = "<persistent bound method %%s.%%s of %s>" % (self.im_self,)
+        return fmt % (self.im_class.__name__, self.im_func.__name__)
 
     def __call__(self, *args, **kwargs):
         if self.im_self is None:
@@ -196,13 +205,24 @@
 
 class PersistentClassMetaClass(PersistentMetaClass):
 
-    # an attempt to make persistent classes look just like other
+    # An attempt to make persistent classes look just like other
     # persistent objects by providing class attributes and methods
     # that behave like the persistence machinery.
 
-    # the chief limitation of this approach is that class.attr won't
+    # The chief limitation of this approach is that class.attr won't
     # always behave the way it does for normal classes
 
+    # A persistent class can never be a ghost, because there are too
+    # many places where Python will attempt to inspect the class
+    # without using getattr().  As a result, it would be impossible to
+    # guarantee that the class would be unghostified at the right
+    # time.  It's really difficult to guarantee this property without
+    # help from the connection, because a ghost can't be unghosted
+    # until after the connection sets its _p_jar.
+
+    # The hack solution is to have a hook for _p_jar that activates
+    # the object the first time it is set.
+
     __implements__ = IPersistent
 
     _pc_init = False
@@ -214,6 +234,7 @@
     def __new__(meta, name, bases, dict, state=UPTODATE):
         cls = super(PersistentClassMetaClass, meta).__new__(
             meta, name, bases, dict)
+
         # helper functions
         def extend_attr(attr, v):
             prev = findattr(cls, attr, None)
@@ -224,7 +245,6 @@
             setattr(cls, attr, ExtClassMethodDescr(attr, prev, m))
 
         extend_attr("_p_oid", None)
-        extend_attr("_p_jar", None)
         extend_attr("_p_atime", time.time() % 86400)
         extend_attr("_p_state", state)
         # XXX A persistent class needs a proprety for _p_changed
@@ -234,6 +254,12 @@
         extend_meth("_p_activate", meta._p_activate)
         extend_meth("_p_deactivate", meta._p_activate)
 
+        # Create a descriptor that calls _p_activate() when _p_jar is set.
+        inst_jar_descr = findattr(cls, "_p_jar", None)
+        setattr(cls, "_p_jar",
+                ExtClassHookDataDescr("_p_jar", inst_jar_descr, None,
+                                      getattr(cls, "_p_activate")))
+
         for k, v in dict.items():
             if isinstance(v, PersistentFunction):
                 setattr(cls, k, PersistentDescriptor(cls, v))
@@ -246,13 +272,6 @@
         extend_attr("__implements__", meta.__implements__)
 
         cls._pc_init = True
-        # A persistent class can never be a ghost, because there are too
-        # many places where Python will attempt to inspect the class
-        # without using getattr().  As a result, it would be impossible
-        # to guarantee that the class would be unghostified at the
-        # right time.
-        if state == GHOST:
-            cls._p_activate()
         return cls
 
     def __getattribute__(cls, name):
@@ -315,20 +334,36 @@
         for k, v in dict.items():
             setattr(cls, k, PersistentDescriptor(cls, v))
 
+    # XXX Should the object get marked as a ghost when it is, in fact,
+    # not a ghost?  The most obvious answer is no.  But if we don't
+    # then we need some other attribute that can be used to handle
+    # invalidations of classes and make _p_activate() work as expected.
+    # Need to decide on a good answer.
+
     def _p_deactivate(cls):
         # do nothing but mark the state change for now
         cls._p_state = GHOST
 
     def _p_activate(cls):
-        if cls._p_state == GHOST:
+        # The logic here is:
+        # If the class hasn't finished executing __new__(), don't
+        # try to load its state.
+        # If the class has a jar but no oid, it's a new object
+        # and doesn't have state in the database.
+
+        # XXX Why would an object be marked a ghost, have a jar, and
+        # not have an oid?
+        if cls._p_state == GHOST and cls._pc_init:
             dm = cls._p_jar
-            if dm is not None:
+            if dm is not None and cls._p_oid:
                 cls._p_state = CHANGED
                 try:
                     dm.setstate(cls)
                 finally:
                     # XXX Should really put in special inconsistent state
                     cls._p_state = UPTODATE
+            else:
+                pass # XXX should log here
 
     # Methods below here are not wrapped to be class-only attributes.
     # They are available as methods of classes using this metaclass.