[Zope-Checkins] CVS: Zope3/lib/python/Persistence - cPersistence.c:1.5

Jeremy Hylton jeremy@zope.com
Mon, 24 Jun 2002 14:45:45 -0400


Update of /cvs-repository/Zope3/lib/python/Persistence
In directory cvs.zope.org:/tmp/cvs-serv22733

Modified Files:
	cPersistence.c 
Log Message:
Add _p_setattr() as simplest possible support for custom __setattr__().

Reorganize various setattr implements into a couple of helper
functions.  The tp_setattro field is set to persist_setattro.  If a
class defines an __setattr__, it will override the tp_setattro.  Thus,
it MUST call _p_setattr() before modifying the object state.

Also move methods, getsets, and members closer to type definitions.


=== Zope3/lib/python/Persistence/cPersistence.c 1.4 => 1.5 ===
 }
 
-static PyMethodDef base_persist_methods[] = {
-    {"_p_deactivate", (PyCFunction)persist_deactivate, METH_NOARGS, },
-    {"_p_activate", (PyCFunction)_PyPersist_Load, METH_NOARGS, },
-    {NULL}
-};
-
-static PyMethodDef persist_methods[] = {
-    {"__getstate__", (PyCFunction)persist_getstate, METH_NOARGS, },
-    {"__setstate__", persist_setstate, METH_O, },
-    {"_p_deactivate", (PyCFunction)persist_deactivate, METH_NOARGS, },
-    {"_p_activate", (PyCFunction)_PyPersist_Load, METH_NOARGS, },
-    {NULL}
-};
-
 static PyObject *
 persist_get_state(PyPersistObject *self)
 {
@@ -490,44 +476,13 @@
 }
 
 
-static PyGetSetDef base_persist_getsets[] = {
-    {"_p_changed", (getter)persist_get_state, (setter)persist_set_state},
-    {NULL}
-};
-
-static PyGetSetDef persist_getsets[] = {
-    {"_p_changed", (getter)persist_get_state, (setter)persist_set_state},
-    {"__dict__", (getter)persist_get_dict},
-    {NULL}
-};
-
-/* XXX should any of these be read-only? */
-
-static PyMemberDef persist_members[] = {
-    {"_p_jar", T_OBJECT, offsetof(PyPersistObject, po_dm)},
-    {"_p_oid", T_OBJECT, offsetof(PyPersistObject, po_oid)},
-    {"_p_serial", T_OBJECT, offsetof(PyPersistObject, po_serial)},
-    {"_p_atime", T_INT, offsetof(PyPersistObject, po_atime)},
-    /* XXX should this be exposed? */
-    {"_p_state", T_INT, offsetof(PyPersistObject, po_state), RO},
-    {NULL}
-};
-
-/* The crucial methods for a persistent object are the tp_getattr and
-   tp_setattr hooks, which allow the persistence machinery to
-   automatically detect changes to and accesses of the object's state.
-
-   The current implemenation probably isn't right, because it doesn't
-   even attempt to deal with a persistent classes that defines its own
-   __getattr__ or __getattribute__.  
+/* convert_name() returns a new reference to a string name
+   or sets an exception and returns NULL.
 */
 
 static PyObject *
-persist_getattro(PyPersistObject *self, PyObject *name)
+convert_name(PyObject *name)
 {
-    PyObject *attr;
-    char *s_name;
-
     /* The ifdef block is copied from PyObject_GenericGetAttr. */
 #ifdef Py_USING_UNICODE
     /* The Unicode to string conversion is done here because the
@@ -544,7 +499,25 @@
 	return NULL;
     } else
 	Py_INCREF(name);
+    return name;
+}
+
+/* The crucial methods for a persistent object are the tp_getattr and
+   tp_setattr hooks, which allow the persistence machinery to
+   automatically detect changes to and accesses of the object's state.
+
+   The current implemenation probably isn't right, because it doesn't
+   even attempt to deal with a persistent classes that defines its own
+   __getattr__ or __getattribute__.  
+*/
+
+static PyObject *
+persist_getattro(PyPersistObject *self, PyObject *name)
+{
+    PyObject *attr;
+    char *s_name;
 
+    name = convert_name(name);
     s_name = PyString_AS_STRING(name);
     /* If any attribute other than an _p_ attribute or __dict__ is
        accessed, make sure that the object state is loaded.  
@@ -569,41 +542,42 @@
 	_PyPersist_SetATime((PyPersistBaseObject *)self);
     }
 
+    /* will invoke an __getattr__ if it exists. */
     attr = PyObject_GenericGetAttr((PyObject *)self, name);
     Py_DECREF(name);
     return attr;
 }
 
+/* XXX why is __dict__ special? */
+
+/* persist_setattr_setup() will load the object's state if necessary.
+   Return values:
+
+   -1 : error occurred, exception set
+   0 : state not loaded, attribute name is _p_*, _v_*, or __dict__
+   1 : state loaded, attribute name is normal
+*/
+
 static int
-persist_setattro(PyPersistObject *self, PyObject *name, PyObject *value)
+persist_setattr_prep(PyPersistBaseObject *self, PyObject *name, 
+			PyObject *value)
 {
     char *s_name;
-    int r;
-
-    /* The ifdef block is copied from PyObject_GenericGetAttr. */
-#ifdef Py_USING_UNICODE
-    /* The Unicode to string conversion is done here because the
-       existing tp_setattro slots expect a string object as name
-       and we wouldn't want to break those. */
-    if (PyUnicode_Check(name)) {
-	name = PyUnicode_AsEncodedString(name, NULL, NULL);
-	if (name == NULL)
-	    return -1;
-    } else
-#endif
-    if (!PyString_Check(name)) {
-	PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
-	return -1;
-    } else
-	Py_INCREF(name);
 
+    assert(PyString_Check(name));
     s_name = PyString_AS_STRING(name);
-    /* If any attribute other than an _p_ or _v_ attribute or __dict__
-       is being assigned to, make sure that the object state is loaded.  
 
-       Implement with simple check on s_name[0] to avoid two strncmp()
-       calls for all attribute names that don't start with an
-       underscore.
+    /* XXX What will go wrong if someone assigns to an _p_ or _v_
+       attribute and we have no state loaded?  Is it safe?
+       The current setstate implementation will not delete old values 
+       and excludes _p_ and _v_ attributes from the pickle.
+    */
+
+    /* If any attribute other than an _p_ or _v_ attribute or __dict__ is 
+       being assigned to, make sure that the object state is loaded.  
+
+       Implement with simple check on s_name[0] to avoid multiple strncmp()
+       calls for all attribute names that don't start with an underscore.
     */
     if ((s_name[0] != '_')
 	|| ((strncmp(s_name, "_p_", 3) != 0)
@@ -625,17 +599,60 @@
 	    self->po_state = CHANGED;
 	    _PyPersist_SetATime((PyPersistBaseObject *)self);
 	}
+	return 1;
     }
+    return 0;
+}
 
-    /* XXX What will go wrong if someone assigns to an _p_ or _v_
-       attribute and we have no state loaded?  Is it safe?
-    */
+static int
+persist_setattro(PyPersistBaseObject *self, PyObject *name, PyObject *value)
+{
+    int r;
+
+    name = convert_name(name);
+    if (persist_setattr_prep(self, name, value) < 0)
+	return -1;
 
     r = PyObject_GenericSetAttr((PyObject *)self, name, value);
     Py_DECREF(name);
     return r;
 }
 
+/* Exported as _p_setattr() 
+
+   Returns True if the internal persistence machinery handled the setattr.
+   Returns False if it did not.
+*/
+
+static PyObject *
+persist_p_setattr(PyPersistBaseObject *self, PyObject *args)
+{
+    int r;
+    PyObject *res, *name, *value;
+
+    if (!PyArg_ParseTuple(args, "OO:_p_setattr", &name, &value))
+	return NULL;
+
+    name = convert_name(name);
+    r = persist_setattr_prep(self, name, value);
+    if (r < 0)
+	return NULL;
+    else if (r > 0)
+	res = Py_False;
+    else {
+	/* r == 0 implies the name is _p_, _v_, or __dict__,
+	   and that we should handle it.
+	*/
+	res = Py_True;
+	r = PyObject_GenericSetAttr((PyObject *)self, name, value);
+	if (r < 0)
+	    return NULL;
+    }
+    Py_INCREF(res);
+    Py_DECREF(name);
+    return res;
+}
+
 static void
 persist_dealloc(PyPersistObject *self)
 {
@@ -684,6 +701,45 @@
     }
     return 0;
 }
+
+static PyMethodDef base_persist_methods[] = {
+    {"_p_activate", (PyCFunction)_PyPersist_Load, METH_NOARGS, },
+    {"_p_deactivate", (PyCFunction)persist_deactivate, METH_NOARGS, },
+    {"_p_setattr", (PyCFunction)persist_p_setattr, METH_VARARGS, },
+    {NULL}
+};
+
+static PyMethodDef persist_methods[] = {
+    {"__getstate__", (PyCFunction)persist_getstate, METH_NOARGS, },
+    {"__setstate__", persist_setstate, METH_O, },
+    {"_p_activate", (PyCFunction)_PyPersist_Load, METH_NOARGS, },
+    {"_p_deactivate", (PyCFunction)persist_deactivate, METH_NOARGS, },
+    {"_p_setattr", (PyCFunction)persist_p_setattr, METH_VARARGS, },
+    {NULL}
+};
+
+static PyGetSetDef base_persist_getsets[] = {
+    {"_p_changed", (getter)persist_get_state, (setter)persist_set_state},
+    {NULL}
+};
+
+static PyGetSetDef persist_getsets[] = {
+    {"_p_changed", (getter)persist_get_state, (setter)persist_set_state},
+    {"__dict__", (getter)persist_get_dict},
+    {NULL}
+};
+
+/* XXX should any of these be read-only? */
+
+static PyMemberDef persist_members[] = {
+    {"_p_jar", T_OBJECT, offsetof(PyPersistObject, po_dm)},
+    {"_p_oid", T_OBJECT, offsetof(PyPersistObject, po_oid)},
+    {"_p_serial", T_OBJECT, offsetof(PyPersistObject, po_serial)},
+    {"_p_atime", T_INT, offsetof(PyPersistObject, po_atime)},
+    /* XXX should this be exposed? */
+    {"_p_state", T_INT, offsetof(PyPersistObject, po_state), RO},
+    {NULL}
+};
 
 /* We link this module statically for convenience.  If compiled as a shared
    library instead, some compilers don't allow addresses of Python objects