[Zope3-checkins] CVS: Zope3/src/zope/hookable - __init__.py:1.1 _zope_hookable.c:1.1

Jim Fulton jim@zope.com
Sun, 18 May 2003 14:03:56 -0400


Update of /cvs-repository/Zope3/src/zope/hookable
In directory cvs.zope.org:/tmp/cvs-serv8898/src/zope/hookable

Added Files:
	__init__.py _zope_hookable.c 
Log Message:
Added a simple hooking mechanism.

See the doc string in __init__.py.

This has three advantages over the older hooking mechanism:

1. It is simpler to write hookable functions. You don't need to write
   a seprate function that just dispatches to the hook.

2. When analyzing profile data, you will see the actual callers of the
   hook function. Before, the hook function would only have the
   dispatcher as a caller (unless other functions called it directly
   without going through the dispatcher).

3. There is a small performance benefit, because the dispatcher is in
   C, rather than Python. In the long run, I suspect that this will
   save about 1-2 percent of execution time.  



=== Added File Zope3/src/zope/hookable/__init__.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Hookable object support

   Support the efficient creation of hookable objects, which are
   callable objects that are meant to be replaced by other callables,
   at least optionally.

   The idea is you create a function that does some default thing and
   make it hookable. Later, someone can modify what it does by calling
   it's sethook method and changing it's implementation.  All users of
   the function, including tose that imported it, will see the change.

   >>> def f41():
   ...     return 41
   >>> f = hookable(f41)
   >>> int(f.implementation is f.original)
   1
   >>> f()
   41
   >>> old = f.sethook(lambda: 42)
   >>> int(f.implementation is f.original)
   0
   >>> int(old is f41)
   1
   >>> f()
   42
   >>> f.original()
   41
   >>> f.implementation()
   42
   >>> f.reset()
   >>> f()
   41

   >>> del f.original
   Traceback (most recent call last):
     File "/usr/local/python/2.2.2/lib/python2.2/doctest.py", line 430, in _run_examples_inner
       compileflags, 1) in globs
     File "<string>", line 1, in ?
   TypeError: readonly attribute

   >>> del f.implementation
   Traceback (most recent call last):
     File "/usr/local/python/2.2.2/lib/python2.2/doctest.py", line 430, in _run_examples_inner
       compileflags, 1) in globs
     File "<string>", line 1, in ?
   TypeError: readonly attribute

   
$Id: __init__.py,v 1.1 2003/05/18 18:03:55 jim Exp $
"""
from _zope_hookable import *

# DocTest:
if __name__ == "__main__":
    import doctest, __main__
    doctest.testmod(__main__, isprivate=lambda *a: False)


=== Added File Zope3/src/zope/hookable/_zope_hookable.c ===
/* Copyright (c) 2003 Zope Corporation and Contributors.
   All Rights Reserved.

   This software is subject to the provisions of the Zope Public License,
   Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
   WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
   FOR A PARTICULAR PURPOSE.
*/
#include "Python.h"
#include "structmember.h"

typedef struct {
	PyObject_HEAD
        PyObject *old;
        PyObject *implementation;
} hookable;

static PyObject *
hookable_init(hookable *self, PyObject *args, PyObject *kwds)
{
  static char *kwlist[] = {"implementation:hookable", NULL};
  PyObject *implementation;

  if (! PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, 
                                    &implementation))
    return NULL; 

  Py_INCREF(implementation);
  Py_INCREF(implementation);
  Py_XDECREF(self->old);
  self->old = implementation;
  Py_XDECREF(self->implementation);
  self->implementation = implementation;

  Py_INCREF(Py_None);
  return Py_None;
}

static int
hookable_traverse(hookable *self, visitproc visit, void *arg)
{
  if (self->implementation != NULL && visit(self->implementation, arg) < 0)
    return -1;
  if (self->old != NULL 
      && self->old != self->implementation 
      && visit(self->old, arg) < 0
      )
    return -1;
      
  return 0;
}

static int
hookable_clear(hookable *self)
{
  Py_XDECREF(self->old);
  self->old = NULL;
  Py_XDECREF(self->implementation);
  self->implementation = NULL;
  return 0;
}


static void
hookable_dealloc(hookable *self)
{
  PyObject_GC_UnTrack((PyObject *)self);
  Py_XDECREF(self->old);
  Py_XDECREF(self->implementation);
  self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
hookable_sethook(hookable *self, PyObject *args, PyObject *kwds)
{
  static char *kwlist[] = {"implementation:sethook", NULL};
  PyObject *implementation, *old;

  if (! PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, 
                                    &implementation))
    return NULL; 

  old = self->implementation;
  Py_INCREF(implementation);
  self->implementation = implementation;

  if (old == NULL)
    {
      Py_INCREF(Py_None);
      return Py_None;
    }

  return old;
}

static PyObject *
hookable_reset(hookable *self)
{
  Py_XINCREF(self->old);
  Py_XDECREF(self->implementation);
  self->implementation = self->old;
  Py_INCREF(Py_None);
  return Py_None;
}

static struct PyMethodDef hookable_methods[] = {
  {"sethook",	(PyCFunction)hookable_sethook, METH_KEYWORDS,
   "Set the hook implementation for the hookable object"},
  {"reset",	(PyCFunction)hookable_reset, METH_NOARGS,
   "Reset the hook to the original value"},
  {NULL,		NULL}		/* sentinel */
};


static PyObject *
hookable_call(hookable *self, PyObject *args, PyObject *kw)
{
  if (self->implementation != NULL)
    return PyObject_Call(self->implementation, args, kw);
  PyErr_SetString(PyExc_TypeError, "Hookable has no implementation");
  return NULL;
}

static PyMemberDef hookable_members[] = {
  { "original", T_OBJECT_EX, offsetof(hookable, old), RO },
  { "implementation", T_OBJECT_EX, offsetof(hookable, implementation), RO },
  {NULL}	/* Sentinel */
};


static char Hookabletype__doc__[] = 
"Callable objects that support being overridden"
;

static PyTypeObject hookabletype = {
	PyObject_HEAD_INIT(NULL)
	/* ob_size           */ 0,
	/* tp_name           */ "zope.hookable."
                                "hookable",
	/* tp_basicsize      */ sizeof(hookable),
	/* tp_itemsize       */ 0,
	/* tp_dealloc        */ (destructor)&hookable_dealloc,
	/* tp_print          */ (printfunc)0,
	/* tp_getattr        */ (getattrfunc)0,
	/* tp_setattr        */ (setattrfunc)0,
	/* tp_compare        */ (cmpfunc)0,
	/* tp_repr           */ (reprfunc)0,
	/* tp_as_number      */ 0,
	/* tp_as_sequence    */ 0,
	/* tp_as_mapping     */ 0,
	/* tp_hash           */ (hashfunc)0,
	/* tp_call           */ (ternaryfunc)hookable_call,
	/* tp_str            */ (reprfunc)0,
        /* tp_getattro       */ (getattrofunc)0,
        /* tp_setattro       */ (setattrofunc)0,
        /* tp_as_buffer      */ 0,
        /* tp_flags          */ Py_TPFLAGS_DEFAULT
				| Py_TPFLAGS_BASETYPE 
                                | Py_TPFLAGS_HAVE_GC,
	/* tp_doc            */ Hookabletype__doc__,
        /* tp_traverse       */ (traverseproc)hookable_traverse,
        /* tp_clear          */ (inquiry)hookable_clear,
        /* tp_richcompare    */ (richcmpfunc)0,
        /* tp_weaklistoffset */ (long)0,
        /* tp_iter           */ (getiterfunc)0,
        /* tp_iternext       */ (iternextfunc)0,
        /* tp_methods        */ hookable_methods,
        /* tp_members        */ hookable_members,
        /* tp_getset         */ 0,
        /* tp_base           */ 0,
        /* tp_dict           */ 0, /* internal use */
        /* tp_descr_get      */ (descrgetfunc)0,
        /* tp_descr_set      */ (descrsetfunc)0,
        /* tp_dictoffset     */ 0,
        /* tp_init           */ (initproc)hookable_init,
        /* tp_alloc          */ (allocfunc)0,
        /* tp_new            */ (newfunc)PyType_GenericNew,
	/* tp_free           */ _PyObject_GC_Del, 
};

static struct PyMethodDef zch_methods[] = {
	{NULL,	 (PyCFunction)NULL, 0, NULL}		/* sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
init_zope_hookable(void)
{
  PyObject *m;
        
  if (PyType_Ready(&hookabletype) < 0)
    return;
        
  m = Py_InitModule3("_zope_hookable", zch_methods,
                     "Provide an efficient implementation for hookable objects"
                     );

  if (m == NULL)
    return;
        
  if (PyModule_AddObject(m, "hookable", (PyObject *)&hookabletype) < 0)
    return;
}