Parcourir la source

Merge branch 'master' into trac848

Shane Kerr il y a 14 ans
Parent
commit
7c390c1149

+ 2 - 0
configure.ac

@@ -842,6 +842,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
            src/lib/log/tests/run_time_init_test.sh
+           src/lib/util/python/mkpywrapper.py
            tests/system/conf.sh
            tests/system/glue/setup.sh
            tests/system/glue/nsx1/b10-config.db
@@ -867,6 +868,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
            chmod +x src/lib/log/tests/run_time_init_test.sh
+           chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x tests/system/conf.sh
           ])
 AC_OUTPUT

+ 1 - 1
doc/Doxyfile

@@ -1165,7 +1165,7 @@ XML_DTD                =
 # and cross-referencing information) to the XML output. Note that
 # enabling this will significantly increase the size of the XML output.
 
-XML_PROGRAMLISTING     = YES
+XML_PROGRAMLISTING     = NO
 
 #---------------------------------------------------------------------------
 # configuration options for the AutoGen Definitions output

+ 8 - 0
src/bin/cfgmgr/plugins/tests/Makefile.am

@@ -3,6 +3,13 @@ PYTESTS = tsig_keys_test.py
 
 EXTRA_DIST = $(PYTESTS)
 
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
+endif
+
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 if ENABLE_PYTHON_COVERAGE
@@ -14,6 +21,7 @@ endif
 	echo Running test: $$pytest ; \
 	env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 

+ 3 - 0
src/lib/dns/python/Makefile.am

@@ -6,6 +6,9 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 pyexec_LTLIBRARIES = pydnspp.la
 pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc
+pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
+pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
+pydnspp_la_SOURCES += tsigerror_python_inc.cc
 pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 

+ 4 - 4
src/lib/dns/python/message_python.cc

@@ -336,15 +336,15 @@ Message_getRcode(s_Message* self) {
 
     rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
     if (rcode != NULL) {
-        rcode->rcode = NULL;
+        rcode->cppobj = NULL;
         try {
-            rcode->rcode = new Rcode(self->message->getRcode());
+            rcode->cppobj = new Rcode(self->message->getRcode());
         } catch (const InvalidMessageOperation& imo) {
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
         } catch (...) {
             PyErr_SetString(po_IscException, "Unexpected exception");
         }
-        if (rcode->rcode == NULL) {
+        if (rcode->cppobj == NULL) {
             Py_DECREF(rcode);
             return (NULL);
         }
@@ -360,7 +360,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
         return (NULL);
     }
     try {
-        self->message->setRcode(*rcode->rcode);
+        self->message->setRcode(*rcode->cppobj);
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());

+ 24 - 7
src/lib/dns/python/pydnspp.cc

@@ -32,20 +32,32 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
+
 #include <dns/exceptions.h>
 #include <dns/name.h>
 #include <dns/messagerenderer.h>
 
-#include <dns/python/pydnspp_common.h>
+#include "pydnspp_common.h"
 
+namespace isc {
+namespace dns {
+namespace python {
 // For our 'general' isc::Exceptions
-static PyObject* po_IscException;
-static PyObject* po_InvalidParameter;
+PyObject* po_IscException;
+PyObject* po_InvalidParameter;
 
 // For our own isc::dns::Exception
-static PyObject* po_DNSMessageBADVERS;
+PyObject* po_DNSMessageBADVERS;
+}
+}
+}
+
+#include "rcode_python.h"
+#include "tsigerror_python.h"
 
 // order is important here!
+using namespace isc::dns::python;
+
 #include <dns/python/messagerenderer_python.cc>
 #include <dns/python/name_python.cc>           // needs Messagerenderer
 #include <dns/python/rrclass_python.cc>        // needs Messagerenderer
@@ -58,14 +70,14 @@ static PyObject* po_DNSMessageBADVERS;
 #include <dns/python/tsigkey_python.cc>        // needs Name
 #include <dns/python/tsig_python.cc>           // needs tsigkey
 #include <dns/python/opcode_python.cc>
-#include <dns/python/rcode_python.cc>
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/message_python.cc>        // needs RRset, Question
 
 //
 // Definition of the module
 //
-static PyModuleDef pydnspp = {
+namespace {
+PyModuleDef pydnspp = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "pydnspp",
     "Python bindings for the classes in the isc::dns namespace.\n\n"
@@ -80,10 +92,11 @@ static PyModuleDef pydnspp = {
     NULL,
     NULL
 };
+}
 
 PyMODINIT_FUNC
 PyInit_pydnspp(void) {
-    PyObject *mod = PyModule_Create(&pydnspp);
+    PyObject* mod = PyModule_Create(&pydnspp);
     if (mod == NULL) {
         return (NULL);
     }
@@ -154,6 +167,10 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_TSIGError(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_TSIGContext(mod)) {
         return (NULL);
     }

+ 14 - 4
src/lib/dns/python/pydnspp_common.cc

@@ -15,6 +15,9 @@
 #include <Python.h>
 #include <pydnspp_common.h>
 
+namespace isc {
+namespace dns {
+namespace python {
 int
 readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
     PyObject* el = NULL;
@@ -44,8 +47,15 @@ readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
 }
 
 
-void addClassVariable(PyTypeObject& c, const char* name,
-                      PyObject* obj)
-{
-    PyDict_SetItemString(c.tp_dict, name, obj);
+int
+addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
+    if (obj == NULL) {
+        PyErr_SetString(PyExc_ValueError,
+                        "NULL object is specified for a class variable");
+        return (-1);
+    }
+    return (PyDict_SetItemString(c.tp_dict, name, obj));
+}
+}
+}
 }

+ 24 - 5
src/lib/dns/python/pydnspp_common.h

@@ -15,9 +15,22 @@
 #ifndef __LIBDNS_PYTHON_COMMON_H
 #define __LIBDNS_PYTHON_COMMON_H 1
 
-//
-// Shared functions for python/c API
-//
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+// For our 'general' isc::Exceptions
+extern PyObject* po_IscException;
+extern PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+extern PyObject* po_DNSMessageBADVERS;
 
 // This function reads 'bytes' from a sequence
 // This sequence can be anything that implements the Sequence interface,
@@ -31,6 +44,12 @@
 // case nothing is removed
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 
-void addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
-
+int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+} // namespace python
+} // namespace dns
+} // namespace isc
 #endif // __LIBDNS_PYTHON_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 80 - 77
src/lib/dns/python/rcode_python.cc

@@ -12,9 +12,17 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
 #include <dns/rcode.h>
 
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+
 using namespace isc::dns;
+using namespace isc::dns::python;
 
 //
 // Declaration of the custom exceptions (None for this class)
@@ -27,25 +35,14 @@ using namespace isc::dns;
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 
-namespace {
 //
 // Rcode
 //
 
-// We added a helper variable static_code here
-// Since we can create Rcodes dynamically with Rcode(int), but also
-// use the static globals (Rcode::NOERROR() etc), we use this
-// variable to see if the code came from one of the latter, in which
-// case Rcode_destroy should not free it (the other option is to
-// allocate new Rcodes for every use of the static ones, but this
-// seems more efficient).
-class s_Rcode : public PyObject {
-public:
-    s_Rcode() : rcode(NULL), static_code(false) {}
-    const Rcode* rcode;
-    bool static_code;
-};
+// Trivial constructor.
+s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
 
+namespace {
 int Rcode_init(s_Rcode* const self, PyObject* args);
 void Rcode_destroy(s_Rcode* const self);
 
@@ -118,57 +115,6 @@ PyMethodDef Rcode_methods[] = {
     { NULL, NULL, 0, NULL }
 };
 
-PyTypeObject rcode_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.Rcode",
-    sizeof(s_Rcode),                    // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)Rcode_destroy,          // tp_dealloc
-    NULL,                               // tp_print
-    NULL,                               // tp_getattr
-    NULL,                               // tp_setattr
-    NULL,                               // tp_reserved
-    NULL,                               // tp_repr
-    NULL,                               // tp_as_number
-    NULL,                               // tp_as_sequence
-    NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash 
-    NULL,                               // tp_call
-    Rcode_str,                          // tp_str
-    NULL,                               // tp_getattro
-    NULL,                               // tp_setattro
-    NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The Rcode class objects represent standard RCODEs"
-    "of the header section of DNS messages.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    (richcmpfunc)Rcode_richcmp,         // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    Rcode_methods,                      // tp_methods
-    NULL,                               // tp_members
-    NULL,                               // tp_getset
-    NULL,                               // tp_base
-    NULL,                               // tp_dict
-    NULL,                               // tp_descr_get
-    NULL,                               // tp_descr_set
-    0,                                  // tp_dictoffset
-    (initproc)Rcode_init,               // tp_init
-    NULL,                               // tp_alloc
-    PyType_GenericNew,                  // tp_new
-    NULL,                               // tp_free
-    NULL,                               // tp_is_gc
-    NULL,                               // tp_bases
-    NULL,                               // tp_mro
-    NULL,                               // tp_cache
-    NULL,                               // tp_subclasses
-    NULL,                               // tp_weaklist
-    NULL,                               // tp_del
-    0                                   // tp_version_tag
-};
-
 int
 Rcode_init(s_Rcode* const self, PyObject* args) {
     long code = 0;
@@ -193,9 +139,9 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
     }
     try {
         if (ext_code == -1) {
-            self->rcode = new Rcode(code);
+            self->cppobj = new Rcode(code);
         } else {
-            self->rcode = new Rcode(code, ext_code);
+            self->cppobj = new Rcode(code, ext_code);
         }
         self->static_code = false;
     } catch (const isc::OutOfRange& ex) {
@@ -211,27 +157,27 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
 void
 Rcode_destroy(s_Rcode* const self) {
     // Depending on whether we created the rcode or are referring
-    // to a global one, we do or do not delete self->rcode here
+    // to a global one, we do or do not delete self->cppobj here
     if (!self->static_code) {
-        delete self->rcode;
+        delete self->cppobj;
     }
-    self->rcode = NULL;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
 }
 
 PyObject*
 Rcode_getCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getCode()));
+    return (Py_BuildValue("I", self->cppobj->getCode()));
 }
 
 PyObject*
 Rcode_getExtendedCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getExtendedCode()));
+    return (Py_BuildValue("I", self->cppobj->getExtendedCode()));
 }
 
 PyObject*
 Rcode_toText(const s_Rcode* const self) {
-    return (Py_BuildValue("s", self->rcode->toText().c_str()));
+    return (Py_BuildValue("s", self->cppobj->toText().c_str()));
 }
 
 PyObject*
@@ -245,7 +191,7 @@ PyObject*
 Rcode_createStatic(const Rcode& rcode) {
     s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
     if (ret != NULL) {
-        ret->rcode = &rcode;
+        ret->cppobj = &rcode;
         ret->static_code = true;
     }
     return (ret);
@@ -357,10 +303,10 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
         return (NULL);
     case Py_EQ:
-        c = (*self->rcode == *other->rcode);
+        c = (*self->cppobj == *other->cppobj);
         break;
     case Py_NE:
-        c = (*self->rcode != *other->rcode);
+        c = (*self->cppobj != *other->cppobj);
         break;
     case Py_GT:
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
@@ -374,6 +320,61 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
     else
         Py_RETURN_FALSE;
 }
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject rcode_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.Rcode",
+    sizeof(s_Rcode),                    // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)Rcode_destroy,          // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    Rcode_str,                          // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The Rcode class objects represent standard RCODEs"
+    "of the header section of DNS messages.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    reinterpret_cast<richcmpfunc>(Rcode_richcmp),         // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    Rcode_methods,                      // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)Rcode_init,               // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
 
 // Module Initialization, all statics are initialized here
 bool
@@ -428,4 +429,6 @@ initModulePart_Rcode(PyObject* mod) {
 
     return (true);
 }
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 57 - 0
src/lib/dns/python/rcode_python.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_RCODE_H
+#define __PYTHON_RCODE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Rcode;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// We added a helper variable static_code here
+// Since we can create Rcodes dynamically with Rcode(int), but also
+// use the static globals (Rcode::NOERROR() etc), we use this
+// variable to see if the code came from one of the latter, in which
+// case Rcode_destroy should not free it (the other option is to
+// allocate new Rcodes for every use of the static ones, but this
+// seems more efficient).
+//
+// Follow-up note: we don't have to use the proxy function in the python lib;
+// we can just define class specific constants directly (see TSIGError).
+// We should make this cleanup later.
+class s_Rcode : public PyObject {
+public:
+    s_Rcode();
+    const Rcode* cppobj;
+    bool static_code;
+};
+
+extern PyTypeObject rcode_type;
+
+bool initModulePart_Rcode(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RCODE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 0
src/lib/dns/python/tests/Makefile.am

@@ -12,6 +12,7 @@ PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
 PYTESTS += tsig_python_test.py
+PYTESTS += tsigerror_python_test.py
 PYTESTS += tsigkey_python_test.py
 
 EXTRA_DIST = $(PYTESTS)

+ 97 - 0
src/lib/dns/python/tests/tsigerror_python_test.py

@@ -0,0 +1,97 @@
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGErrorTest(unittest.TestCase):
+    def test_from_code(self):
+        self.assertEqual(0, TSIGError(0).get_code())
+        self.assertEqual(18, TSIGError(18).get_code())
+        self.assertEqual(65535, TSIGError(65535).get_code())
+        self.assertRaises(ValueError, TSIGError, 65536)
+        self.assertRaises(ValueError, TSIGError, -1)
+        self.assertRaises(TypeError, TSIGError, "not yet supported")
+
+    def test_from_rcode(self):
+        # We use RCODE for code values from 0-15.
+        self.assertEqual(0, TSIGError(Rcode.NOERROR()).get_code())
+        self.assertEqual(15, TSIGError(Rcode(15)).get_code())
+
+        # From error code 16 TSIG errors define a separate space, so passing
+        # corresponding RCODE for such code values should be prohibited.
+        self.assertRaises(ValueError, TSIGError, Rcode(16))
+
+    def test_constants(self):
+        # We'll only test arbitrarily chosen subsets of the codes.
+        # This class is quite simple, so it should be suffice.
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError(16).get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError(17).get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError(18).get_code())
+
+        self.assertEqual(0, TSIGError.NOERROR.get_code())
+        self.assertEqual(9, TSIGError.NOTAUTH.get_code())
+        self.assertEqual(14, TSIGError.RESERVED14.get_code())
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError.BAD_SIG.get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError.BAD_KEY.get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError.BAD_TIME.get_code())
+
+    def test_equal(self):
+        self.assertTrue(TSIGError.NOERROR == TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) == TSIGError.NOERROR)
+
+        self.assertTrue(TSIGError.BAD_SIG == TSIGError(16))
+        self.assertTrue(TSIGError(16) == TSIGError.BAD_SIG)
+
+    def test_nequal(self):
+        self.assertTrue(TSIGError.BAD_KEY != TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) != TSIGError.BAD_KEY)
+
+    def test_to_text(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual("NOERROR", TSIGError(Rcode.NOERROR()).to_text())
+
+        # Well known TSIG errors
+        self.assertEqual("BADSIG", TSIGError.BAD_SIG.to_text())
+        self.assertEqual("BADKEY", TSIGError.BAD_KEY.to_text())
+        self.assertEqual("BADTIME", TSIGError.BAD_TIME.to_text())
+
+        # Unknown (or not yet supported) codes.  Simply converted as numeric.
+        self.assertEqual("19", TSIGError(19).to_text());
+        self.assertEqual("65535", TSIGError(65535).to_text());
+
+        # also check str() works same way
+        self.assertEqual("NOERROR", str(TSIGError(Rcode.NOERROR())))
+        self.assertEqual("BADSIG", str(TSIGError.BAD_SIG))
+
+    def test_to_rcode(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual(Rcode.NOERROR(), TSIGError(Rcode.NOERROR()).to_rcode())
+
+        # Well known TSIG errors
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_SIG.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_KEY.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_TIME.to_rcode())
+
+        # Unknown (or not yet supported) codes are treated as SERVFAIL.
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(19).to_rcode())
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(65535).to_rcode())
+
+        # Check there's no redundant refcount (which would cause leak)
+        self.assertEqual(1, sys.getrefcount(TSIGError.BAD_SIG.to_rcode()))
+
+if __name__ == '__main__':
+    unittest.main()

+ 0 - 1
src/lib/dns/python/tsig_python.cc

@@ -26,7 +26,6 @@ using namespace isc::dns;
 
 namespace {
 // The s_* Class simply covers one instantiation of the object
-
 class s_TSIGContext : public PyObject {
 public:
     TSIGContext* tsig_ctx;

+ 362 - 0
src/lib/dns/python/tsigerror_python.cc

@@ -0,0 +1,362 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigerror.h>
+
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+#include "tsigerror_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// TSIGError
+//
+
+// Trivial constructor.
+s_TSIGError::s_TSIGError() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "tsigerror_python_inc.cc"
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGError_init(s_TSIGError* self, PyObject* args);
+void TSIGError_destroy(s_TSIGError* self);
+
+// These are the functions we export
+PyObject* TSIGError_getCode(const s_TSIGError* const self);
+PyObject* TSIGError_toText(const s_TSIGError* const self);
+PyObject* TSIGError_toRcode(const s_TSIGError* const self);
+PyObject* TSIGError_str(PyObject* self);
+PyObject* TSIGError_richcmp(const s_TSIGError* const self,
+                            const s_TSIGError* const other, int op);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGError_methods[] = {
+    { "get_code", reinterpret_cast<PyCFunction>(TSIGError_getCode),
+      METH_NOARGS,
+      TSIGError_getCode_doc },
+    { "to_text", reinterpret_cast<PyCFunction>(TSIGError_toText), METH_NOARGS,
+      TSIGError_toText_doc },
+    { "to_rcode", reinterpret_cast<PyCFunction>(TSIGError_toRcode),
+      METH_NOARGS,
+      TSIGError_toRcode_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGError_init(s_TSIGError* self, PyObject* args) {
+    try {
+        // Constructor from the code value
+        long code = 0;
+        if (PyArg_ParseTuple(args, "l", &code)) {
+            if (code < 0 || code > 0xffff) {
+                PyErr_SetString(PyExc_ValueError, "TSIG error out of range");
+                return (-1);
+            }
+            self->cppobj = new TSIGError(code);
+            return (0);
+        }
+
+        // Constructor from Rcode
+        s_Rcode* py_rcode;
+        if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
+            self->cppobj = new TSIGError(*py_rcode->cppobj);
+            return (0);
+        }
+    } catch (const isc::OutOfRange& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(PyExc_ValueError, ex_what.c_str());
+        return (-1);
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIGError");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGError constructor");
+
+    return (-1);
+}
+
+void
+TSIGError_destroy(s_TSIGError* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGError_getCode(const s_TSIGError* const self) {
+    return (Py_BuildValue("I", self->cppobj->getCode()));
+}
+
+PyObject*
+TSIGError_toText(const s_TSIGError* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGError_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+TSIGError_toRcode(const s_TSIGError* const self) {
+    typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
+
+    try {
+        RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
+                                                            &rcode_type));
+        rcode_container.set(new Rcode(self->cppobj->toRcode()));
+        return (rcode_container.release());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError to Rcode: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError to Rcode");
+    }
+    return (NULL);
+}
+
+PyObject* 
+TSIGError_richcmp(const s_TSIGError* const self,
+                   const s_TSIGError* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGError
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigerror_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.TSIGError",
+    sizeof(s_TSIGError),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(TSIGError_destroy),       // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    TSIGError_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    TSIGError_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(TSIGError_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGError_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    reinterpret_cast<initproc>(TSIGError_init),            // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+namespace {
+// Trivial shortcut to create and install TSIGError constants.
+inline void
+installTSIGErrorConstant(const char* name, const TSIGError& val) {
+    TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
+    container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
+}
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGError(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&tsigerror_type) < 0) {
+        return (false);
+    }
+    void* p = &tsigerror_type;
+    if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigerror_type);
+
+    try {
+        // Constant class variables
+        // Error codes (bare values)
+        installClassVariable(tsigerror_type, "BAD_SIG_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
+        installClassVariable(tsigerror_type, "BAD_KEY_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
+        installClassVariable(tsigerror_type, "BAD_TIME_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
+
+        // Error codes (constant objects)
+        installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
+        installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
+        installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
+        installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
+        installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
+        installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
+        installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
+        installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
+        installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
+        installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
+        installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
+        installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
+        installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
+        installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
+        installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
+        installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
+        installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
+        installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
+        installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGError initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGError initialization");
+        return (false);
+    }
+
+    return (true);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 44 - 0
src/lib/dns/python/tsigerror_python.h

@@ -0,0 +1,44 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGERROR_H
+#define __PYTHON_TSIGERROR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGError;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGError : public PyObject {
+public:
+    s_TSIGError();
+    const TSIGError* cppobj;
+};
+
+extern PyTypeObject tsigerror_type;
+
+bool initModulePart_TSIGError(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGERROR_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 83 - 0
src/lib/dns/python/tsigerror_python_inc.cc

@@ -0,0 +1,83 @@
+namespace {
+const char* const TSIGError_doc = "\n\
+TSIG errors.\n\
+\n\
+\n\
+The TSIGError class objects represent standard errors related to TSIG\n\
+protocol operations as defined in related specifications, mainly in\n\
+RFC2845.\n\
+\n\
+TSIGError(error_code)\n\
+\n\
+Constructor from the code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Parameters:\n\
+  error_code: The underlying 16-bit error code value of the TSIGError.\n\
+\n\
+TSIGError(rcode)\n\
+\n\
+Constructor from Rcode.\n\
+\n\
+As defined in RFC2845, error code values from 0 to 15 (inclusive) are\n\
+derived from the DNS RCODEs, which are represented via the Rcode class\n\
+in this library. This constructor works as a converter from these\n\
+RCODEs to corresponding TSIGError objects.\n\
+\n\
+Exceptions:\n\
+  ValueError: Given rcode is not convertible to TSIGErrors.\n\
+\n\
+Parameters:\n\
+  rcode: the Rcode from which the TSIGError should be derived.\n\
+\n\
+";
+const char* const TSIGError_getCode_doc = "get_code() -> integer\n\
+\n\
+Returns the TSIGCode error code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Return Value(s):\n\
+  The underlying code value corresponding to the TSIGError.\n\
+";
+const char* const TSIGError_toText_doc = "to_text() -> string\n\
+\n\
+Convert the TSIGError to a string.\n\
+\n\
+For codes derived from RCODEs up to 15, this method returns the same\n\
+string as Rcode.to_text() for the corresponding code. For other pre-\n\
+defined code values (see TSIGError.CodeValue), this method returns a\n\
+string representation of the \"mnemonic' used for the enum and\n\
+constant objects as defined in RFC2845. For example, the string for\n\
+code value 16 is \"BADSIG\", etc. For other code values it returns a\n\
+string representation of the decimal number of the value, e.g. \"32\",\n\
+\"100\", etc.\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+Return Value(s):\n\
+  A string representation of the TSIGError.\n\
+";
+const char* const TSIGError_toRcode_doc = "to_rcode() -> Rcode\n\
+\n\
+Convert the TSIGError to a Rcode.\n\
+\n\
+This method returns an Rcode object that is corresponding to the TSIG\n\
+error. The returned Rcode is expected to be used by a verifying server\n\
+to specify the RCODE of a response when TSIG verification fails.\n\
+\n\
+Specifically, this method returns Rcode.NOTAUTH() for the TSIG\n\
+specific errors, BADSIG, BADKEY, BADTIME, as described in RFC2845. For\n\
+errors derived from the standard Rcode (code 0-15), it returns the\n\
+corresponding Rcode. For others, this method returns Rcode.SERVFAIL()\n\
+as a last resort.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+";
+}

+ 1 - 7
src/lib/dns/tsigerror.h

@@ -22,17 +22,11 @@
 
 namespace isc {
 namespace dns {
-
-class RRClass;
-
 /// TSIG errors
 ///
 /// The \c TSIGError class objects represent standard errors related to
 /// TSIG protocol operations as defined in related specifications, mainly
 /// in RFC2845.
-///
-/// (RCODEs) of the header section of DNS messages, and extended response
-/// codes as defined in the EDNS specification.
 class TSIGError {
 public:
     /// Constants for pre-defined TSIG error values.
@@ -58,7 +52,7 @@ public:
     ///
     /// \exception None
     ///
-    /// \param code The underlying 16-bit error code value of the \c TSIGError.
+    /// \param error_code The underlying 16-bit error code value of the \c TSIGError.
     explicit TSIGError(uint16_t error_code) : code_(error_code) {}
 
     /// Constructor from \c Rcode.

+ 1 - 1
src/lib/python/bind10_config.py.in

@@ -27,6 +27,7 @@ def reload():
                                            "@PACKAGE_NAME@",
                                            "msgq_socket").replace("${prefix}",
                                                                   "@prefix@")
+    PREFIX = "@prefix@"
 
     # If B10_FROM_SOURCE is set in the environment, we use data files
     # from a directory relative to the value of that variable, or, if defined,
@@ -43,7 +44,6 @@ def reload():
             DATA_PATH = os.environ["B10_FROM_SOURCE"]
         PLUGIN_PATHS = [DATA_PATH + '/src/bin/cfgmgr/plugins']
     else:
-        PREFIX = "@prefix@"
         DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
         PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
     # For testing the plugins so they can find their own spec files

+ 2 - 0
src/lib/util/Makefile.am

@@ -24,5 +24,7 @@ libutil_la_SOURCES += encode/binary_from_base16.h
 libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
 libutil_la_SOURCES += random/random_number_generator.h
 
+EXTRA_DIST = python/pycppwrapper_util.h
+
 libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 CLEANFILES = *.gcno *.gcda

+ 100 - 0
src/lib/util/python/mkpywrapper.py.in

@@ -0,0 +1,100 @@
+#!@PYTHON@
+
+# Copyright (C) 2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""This utility program generates a C++ header and implementation files
+that can be used as a template of C++ python binding for a C++ class.
+
+Usage: ./mkpywrapper.py ClassName
+(the script should be run on this directory)
+
+It will generate two files: classname_python.h and classname_python.cc,
+many of whose definitions are in the namespace isc::MODULE_NAME::python.
+By default MODULE_NAME will be 'dns' (because this tool is originally
+intended to be used for the C++ python binding of the DNS library), but
+can be changed via the -m command line option.
+
+The generated files contain code fragments that are commonly used in
+C++ python binding implementations.  It will define a class named
+s_ClassName which is a derived class of PyModule and can meet the
+requirement of the CPPPyObjectContainer template class (see
+pycppwrapper_util.h).  It also defines (and declares in the header file)
+"classname_type", which is of PyTypeObject and is intended to be used
+to define details of the python bindings for the ClassName class.
+
+In many cases the header file can be used as a startpoint of the
+binding development without modification.  But you may want to make
+ClassName::cppobj a constant variable (and you should if you can).
+Many definitions of classname_python.cc should also be able to be used
+just as defined, but some will need to be changed or removed.  In
+particular, you should at least adjust ClassName_init().  You'll
+probably also need to add more definitions to that file to provide
+complete features of the C++ class.
+"""
+
+import datetime, string, sys
+from optparse import OptionParser
+
+# Remember the current year to produce the copyright boilerplate
+YEAR = datetime.date.today().timetuple()[0]
+
+def dump_file(out_file, temp_file, class_name, module):
+    for line in temp_file.readlines():
+        line = line.replace("@YEAR@", str(YEAR))
+        line = line.replace("@CPPCLASS@_H", class_name.upper() + "_H")
+        line = line.replace("@CPPCLASS@", class_name)
+        line = line.replace("@cppclass@", class_name.lower())
+        line = line.replace("@MODULE@", module)
+        out_file.write(line)
+
+def dump_wrappers(class_name, output, module):
+    try:
+        if output == "-":
+            header_file = sys.stdout
+        else:
+            header_file = open(output + "_python.h", "w")
+        header_template_file = open("wrapper_template.h", "r")
+        if output == "-":
+            impl_file = sys.stdout
+        else:
+            impl_file = open(output + "_python.cc", "w")
+        impl_template_file = open("wrapper_template.cc", "r")
+    except:
+        sys.stderr.write('Failed to open C++ file(s)\n')
+        sys.exit(1)
+    dump_file(header_file, header_template_file, class_name, module)
+    dump_file(impl_file, impl_template_file, class_name, module)
+
+usage = '''usage: %prog [options] class_name'''
+
+if __name__ == "__main__":
+    parser = OptionParser(usage=usage)
+    parser.add_option('-o', '--output', action='store', dest='output',
+                      default=None, metavar='FILE',
+                      help='prefix of output file names [default: derived from the class name]')
+    parser.add_option('-m', '--module', action='store', dest='module',
+                      default='dns',
+                      help='C++ module name of the wrapper (for namespaces) [default: dns]')
+    (options, args) = parser.parse_args()
+
+    if len(args) == 0:
+        parser.error('input file is missing')
+
+    class_name = args[0]
+    if not options.output:
+        options.output = class_name.lower()
+
+    dump_wrappers(class_name, options.output, options.module)

+ 308 - 0
src/lib/util/python/pycppwrapper_util.h

@@ -0,0 +1,308 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYCPPWRAPPER_UTIL_H
+#define __PYCPPWRAPPER_UTIL_H 1
+
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+/**
+ * @file pycppwrapper_util.h
+ * @short Shared definitions for python/C(++) API
+ *
+ * This utility defines a set of convenient wrappers for the python C API
+ * to use it safely from our C++ bindings.  The python C API has many pitfalls
+ * such as not-so-consistent reference count policies.  Also, many existing
+ * examples are careless about error handling.  It's easy to find on the net
+ * example (even of "production use") python extensions like this:
+ *
+ * \code
+ *     new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
+ *     // new_exception can be NULL, in which case the call to
+ *     // PyModule_AddObject will cause a surprising disruption.
+ *     PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
+ *
+ * When using the python C API with C++, we should also be careful about
+ * exception safety.  The underlying C++ code (including standard C++ libraries
+ * and memory allocation) can throw exceptions, in which case we need to
+ * make sure any intermediate python objects are cleaned up (we also need to
+ * catch the C++ exceptions inside the binding and convert them to python
+ * errors, but that's a different subject).  This is not a trivial task
+ * because the python objects are represented as bare C pointers (so there's
+ * no destructor) and we need to address the exception safety along with python
+ * reference counters (so we cannot naively apply standard smart pointers).
+ *
+ * This utility tries to help address these issues.
+ *
+ * Also, it's intentional that this is a header-only utility.  This way the
+ * C++ loadable module won't depend on another C++ library (which is not
+ * necessarily wrong, but would increase management cost such as link-time
+ * troubles only for a small utility feature).
+ */
+
+namespace isc {
+namespace util {
+namespace python {
+
+/// This is thrown inside this utility when it finds a NULL pointer is passed
+/// when it should not be NULL.
+class PyCPPWrapperException : public isc::Exception {
+public:
+    PyCPPWrapperException(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// This helper class is similar to the standard autoptr and manages PyObject
+/// using some kind of RAII techniques.  It is, however, customized for the
+/// python C API.
+///
+/// A PyObjectContainer object is constructed with a pointer to PyObject,
+/// which is often just created dynamically.  The caller will eventually
+/// attach the object to a different python object (often a module or class)
+/// via specific methods or directly return it to the python interpreter.
+///
+/// There are two cases in destructing the object: with or without decreasing
+/// a reference to the PyObject.  If the object is intended to be an argument
+/// to another python C library that increases the reference to the object for
+/// itself, we should normally release our own reference; otherwise the
+/// reference will leak and the object won't be garbage collected.  Also, when
+/// an unexpected error happens in the form of C++ exception, we should
+/// release the reference to prevent resource leak.
+///
+/// In some other cases, we should simply give our reference to the caller.
+/// That is the case when the created object itself is a return value of
+/// an extended python method written in the C++ binding.  Likewise, some
+/// python C library functions "steal" the reference.  In these cases we
+/// should not decrease the reference; otherwise it would cause duplicate free.
+///
+/// By default, the destructor of this class releases the reference to the
+/// PyObject.  If this behavior is desirable, you can extract the original
+/// bare pointer to the PyObject by the \c get() method.  If you don't want
+/// the reference to be decreased, the original bare pointer should be
+/// extracted using the \c release() method.
+///
+/// There are two convenience methods for commonly used operations:
+/// \c installAsClassVariable() to add the PyObject as a class variable
+/// and \c installToModule to add the PyObject to a specified python module.
+/// These methods (at least to some extent) take care of the reference to
+/// the object (either release or keep) depending on the usage context so
+/// that the user don't have to worry about it.
+///
+/// On construction, this class expects the pointer can be NULL.
+/// If it happens it immediately throws a \c PyCPPWrapperException exception.
+/// This behavior is to convert failures in the python C API (such as
+/// PyObject_New() returning NULL) to C++ exception so that we can unify
+/// error handling in the style of C++ exceptions.
+///
+/// Examples 1: To create a tuple of two python objects, do this:
+///
+/// \code
+///     try {
+///         PyObjectContainer container0(Py_BuildValue("I", 0));
+///         PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
+///         return (Py_BuildValue("OO", container0.get(), container1.get()));
+///     } catch { ... set python exception, etc ... } \endcode
+///
+/// Commonly deployed buggy implementation to achieve this would be like this:
+/// \code
+///    return (Py_BuildValue("OO", Py_BuildValue("I", 0),
+///                          Py_BuildValue("s", cppobj.toText().c_str())));
+/// \endcode
+/// One clear bug of this code is that references to the element objects of
+/// the tuple will leak.
+/// (Assuming \c cppobj.toText() can throw) this code is also not exception
+/// safe; if \c cppobj.toText() throws the reference to the first object
+/// will leak, even if the code tried to do the necessary cleanup in the
+/// successful case.
+/// Further, this code naively passes the result of the first two calls to
+/// \c Py_BuildValue() to the third one even if they can be NULL.
+/// In this specific case, it happens to be okay because \c Py_BuildValue()
+/// accepts NULL and treats it as an indication of error.  But not all
+/// python C library works that way (remember, the API is so inconsistent)
+/// and we need to refer to the API manual every time we have to worry about
+/// passing a NULL object to a library function.  We'd certainly like to
+/// avoid such development overhead.  The code using \c PyObjectContainer
+/// addresses all these problems.
+///
+/// Examples 2: Install a (constant) variable to a class.
+///
+/// \code
+///    try {
+///        // installClassVariable is a wrapper of
+///        // PyObjectContainer::installAsClassVariable.  See below.
+///        installClassVariable(myclass_type, "SOME_CONSTANT",
+///                             Py_BuildValue("I", 0));
+///    } catch { ... }
+/// \endcode
+///
+/// Examples 3: Install a custom exception to a module.
+///
+/// \code
+///    PyObject* new_exception; // publicly visible
+///    ...
+///    try {
+///        new_exception = PyErr_NewException("mymodule.NewException",
+///                                           NULL, NULL);
+///        PyObjectContainer(new_exception).installToModule(mymodule,
+///                                                         "NewException");
+///    } catch { ... }
+/// \endcode
+///
+/// Note that \c installToModule() keeps the reference to \c new_exception
+/// by default.  This is a common practice when we introduce a custom
+/// exception in a python biding written in C/C++.  See the code comment
+/// of the method for more details.
+struct PyObjectContainer {
+    PyObjectContainer(PyObject* obj) : obj_(obj) {
+        if (obj_ == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
+                      "probably due to short memory");
+        }
+    }
+    virtual ~PyObjectContainer() {
+        if (obj_ != NULL) {
+            Py_DECREF(obj_);
+        }
+    }
+    PyObject* get() {
+        return (obj_);
+    }
+    PyObject* release() {
+        PyObject* ret = obj_;
+        obj_ = NULL;
+        return (ret);
+    }
+
+    // Install the enclosed PyObject to the specified python class 'pyclass'
+    // as a variable named 'name'.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
+        if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
+                      "probably due to short memory");
+        }
+        // Ownership successfully transferred to the class object.  We'll let
+        // it be released in the destructor.
+    }
+
+    // Install the enclosed PyObject to the specified module 'mod' as an
+    // object named 'name'.
+    // By default, this method explicitly keeps the reference to the object
+    // even after the module "steals" it.  To cancel this behavior and give
+    // the reference to the module completely, the third parameter 'keep_ref'
+    // should be set to false.
+    void installToModule(PyObject* mod, const char* name,
+                         bool keep_ref = true)
+    {
+        if (PyModule_AddObject(mod, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to add an object to "
+                      "module, probably due to short memory");
+        }
+        // PyModule_AddObject has "stolen" the reference, so unless we
+        // have to retain it ourselves we don't (shouldn't) decrease it.
+        // However, we actually often need to keep our own reference because
+        // objects added to a module are often referenced via non local
+        // C/C++ variables in various places of the C/C++ code.  In order
+        // for the code to run safely even if some buggy/evil python program
+        // performs 'del mod.obj', we need the extra reference.  See, e.g.:
+        // http://docs.python.org/py3k/c-api/init.html#Py_Initialize
+        // http://mail.python.org/pipermail/python-dev/2005-June/054238.html
+        if (keep_ref) {
+            Py_INCREF(obj_);
+        }
+        obj_ = NULL;
+    }
+
+protected:
+    PyObject* obj_;
+};
+
+/// This templated class is a derived class of \c PyObjectContainer and
+/// manages C++-class based python objects.
+///
+/// The template parameter \c PYSTRUCT must be a derived class (structure) of
+/// \c PyObject that has a member variable named \c cppobj, which must be a
+/// a pointer to \c CPPCLASS (the second template parameter).
+///
+/// For example, to define a custom python class based on a C++ class, MyClass,
+/// we'd define a class (struct) named \c s_MyClass like this:
+/// \code
+///    class s_MyClass : public PyObject {
+///    public:
+///       s_MyClass() : cppobj(NULL) {}
+///       MyClass* cppobj;
+///    };
+/// \endcode
+///
+/// And, to build and return a python version of MyClass object, write the
+/// following C++ code:
+/// \code
+///    typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
+///    try {
+///        // below, myclass_type is of \c PyTypeObject that defines
+///        // a python class (type) for MyClass
+///        MyContainer container(PyObject_New(s_MyClass, myclass_type));
+///        container.set(new MyClass());
+///        return (container.release()); // give the reference to the caller
+///    } catch { ... }
+/// \endcode
+///
+/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
+/// fails or resource leaks when new'ing \c MyClass results in an exception.
+/// Note that we use \c release() (derived from the base class) instead of
+/// \c get(); in this case we should simply pass the reference generated in
+/// \c PyObject_New() to the caller.
+template <typename PYSTRUCT, typename CPPCLASS>
+struct CPPPyObjectContainer : public PyObjectContainer {
+    CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
+
+    // This method associates a C++ object with the corresponding python
+    // object enclosed in this class.
+    void set(CPPCLASS* value) {
+        if (value == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+                      "probably due to short memory");
+        }
+        static_cast<PYSTRUCT*>(obj_)->cppobj = value;
+    }
+
+    // This is a convenience short cut to associate a C++ object with the
+    // python object and install it to the specified python class \c pyclass
+    // as a variable named \c name.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name,
+                                CPPCLASS* value)
+    {
+        set(value);
+        PyObjectContainer::installAsClassVariable(pyclass, name);
+    }
+};
+
+/// A shortcut function to install a python class variable.
+///
+/// It installs a python object \c obj to a specified class \c pyclass
+/// as a variable named \c name.
+inline void
+installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
+    PyObjectContainer(obj).installAsClassVariable(pyclass, name);
+}
+
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYCPPWRAPPER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 293 - 0
src/lib/util/python/wrapper_template.cc

@@ -0,0 +1,293 @@
+// Copyright (C) @YEAR@  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "@cppclass@_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::@MODULE@;
+using namespace isc::@MODULE@::python;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+//
+// @CPPCLASS@
+//
+
+// Trivial constructor.
+s_@CPPCLASS@::s_@CPPCLASS@() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int @CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args);
+void @CPPCLASS@_destroy(s_@CPPCLASS@* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* @CPPCLASS@_toText(const s_@CPPCLASS@* const self);
+PyObject* @CPPCLASS@_str(PyObject* self);
+PyObject* @CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
+                            const s_@CPPCLASS@* const other, int op);
+
+// This is quite specific pydnspp.  For other wrappers this should probably
+// be removed.
+PyObject* @CPPCLASS@_toWire(const s_@CPPCLASS@* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef @CPPCLASS@_methods[] = {
+    { "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS@_toText), METH_NOARGS,
+      "Returns the text representation" },
+    // This is quite specific pydnspp.  For other wrappers this should probably
+    // be removed:
+    { "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS@_toWire), METH_VARARGS,
+      "Converts the @CPPCLASS@ object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { NULL, NULL, 0, NULL }
+};
+
+// This is a template of typical code logic of python class initialization
+// with C++ backend.  You'll need to adjust it according to details of the
+// actual C++ class.
+int
+@CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args) {
+    try {
+        if (PyArg_ParseTuple(args, "REPLACE ME")) {
+            // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
+            self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct @CPPCLASS@ object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing @CPPCLASS@");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to @CPPCLASS@ constructor");
+
+    return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+@CPPCLASS@_destroy(s_@CPPCLASS@* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+@CPPCLASS@_toText(const s_@CPPCLASS@* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert @CPPCLASS@ object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting @CPPCLASS@ object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+@CPPCLASS@_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject* 
+@CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
+                   const s_@CPPCLASS@* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace @MODULE@ {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_@CPPCLASS@
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject @cppclass@_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.@CPPCLASS@",
+    sizeof(s_@CPPCLASS@),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(@CPPCLASS@_destroy),       // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    @CPPCLASS@_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The @CPPCLASS@ class objects is...(COMPLETE THIS)",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(@CPPCLASS@_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    @CPPCLASS@_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    reinterpret_cast<initproc>(@CPPCLASS@_init),            // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_@CPPCLASS@(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&@cppclass@_type) < 0) {
+        return (false);
+    }
+    void* p = &@cppclass@_type;
+    if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&@cppclass@_type);
+
+    // The following template is the typical procedure for installing class
+    // variables.  If the class doesn't have a class variable, remove the
+    // entire try-catch clauses.
+    try {
+        // Constant class variables
+        installClassVariable(@cppclass@_type, "REPLACE_ME",
+                             Py_BuildValue("REPLACE ME"));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in @CPPCLASS@ initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in @CPPCLASS@ initialization");
+        return (false);
+    }
+
+    return (true);
+}
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc

+ 44 - 0
src/lib/util/python/wrapper_template.h

@@ -0,0 +1,44 @@
+// Copyright (C) @YEAR@  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_@CPPCLASS@_H
+#define __PYTHON_@CPPCLASS@_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace @MODULE@ {
+class @CPPCLASS@;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_@CPPCLASS@ : public PyObject {
+public:
+    s_@CPPCLASS@();
+    @CPPCLASS@* cppobj;
+};
+
+extern PyTypeObject @cppclass@_type;
+
+bool initModulePart_@CPPCLASS@(PyObject* mod);
+
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
+#endif // __PYTHON_@CPPCLASS@_H
+
+// Local Variables:
+// mode: c++
+// End: