diff options
author | Tomek Mrugalski <tomasz@isc.org> | 2014-06-03 16:34:54 +0200 |
---|---|---|
committer | Tomek Mrugalski <tomasz@isc.org> | 2014-06-03 16:34:54 +0200 |
commit | a28843919086b628724c00e965d6717fb66fa63c (patch) | |
tree | b5e9e241db40a1f06b8edc8a2f879c8249afbeab /src/lib/util | |
parent | [3413] setproctitle check removed from configure.ac (diff) | |
download | kea-a28843919086b628724c00e965d6717fb66fa63c.tar.xz kea-a28843919086b628724c00e965d6717fb66fa63c.zip |
[3413] Several build steps no longer require python3
- *.spec files in src/lib/dns/tests/testsdata are now included in dist
- src/lib/util/pyunittests is removed
- src/lib/util/python trimmed down a lot
- fix for missing dhcp6_shutdown_test.sh in src/bin/dhcp6
- many python macros in configure.ac removed
(more of them to be removed in Makefiles)
Diffstat (limited to 'src/lib/util')
-rw-r--r-- | src/lib/util/Makefile.am | 3 | ||||
-rw-r--r-- | src/lib/util/io/Makefile.am | 11 | ||||
-rw-r--r-- | src/lib/util/io/fdshare_python.cc | 98 | ||||
-rw-r--r-- | src/lib/util/python/.gitignore | 3 | ||||
-rw-r--r-- | src/lib/util/python/Makefile.am | 5 | ||||
-rwxr-xr-x | src/lib/util/python/doxygen2pydoc.py.in | 680 | ||||
-rwxr-xr-x | src/lib/util/python/mkpywrapper.py.in | 100 | ||||
-rw-r--r-- | src/lib/util/python/pycppwrapper_util.h | 335 | ||||
-rw-r--r-- | src/lib/util/python/pythonize_constants.py | 57 | ||||
-rw-r--r-- | src/lib/util/python/wrapper_template.cc | 291 | ||||
-rw-r--r-- | src/lib/util/python/wrapper_template.h | 59 | ||||
-rw-r--r-- | src/lib/util/pyunittests/Makefile.am | 22 | ||||
-rw-r--r-- | src/lib/util/pyunittests/pyunittests_util.cc | 84 |
13 files changed, 3 insertions, 1745 deletions
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am index ffb84719d5..75a3d24844 100644 --- a/src/lib/util/Makefile.am +++ b/src/lib/util/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = . io unittests tests pyunittests python threads +SUBDIRS = . io unittests tests python threads AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util @@ -41,7 +41,6 @@ libkea_util_la_SOURCES += encode/binary_from_base16.h libkea_util_la_SOURCES += random/qid_gen.h random/qid_gen.cc libkea_util_la_SOURCES += random/random_number_generator.h -EXTRA_DIST = python/pycppwrapper_util.h libkea_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la CLEANFILES = *.gcno *.gcda diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am index 4011bde483..98ba21ec65 100644 --- a/src/lib/util/io/Makefile.am +++ b/src/lib/util/io/Makefile.am @@ -10,14 +10,3 @@ libkea_util_io_la_SOURCES += pktinfo_utilities.h libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la CLEANFILES = *.gcno *.gcda - -pyexec_LTLIBRARIES = libutil_io_python.la -# Python prefers .so, while some OSes (specifically MacOS) use a different -# suffix for dynamic objects. -module is necessary to work this around. -libutil_io_python_la_LDFLAGS = -module -avoid-version -libutil_io_python_la_SOURCES = fdshare_python.cc -libutil_io_python_la_LIBADD = libkea-util-io.la -libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES) -# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be -# placed after -Wextra defined in AM_CXXFLAGS -libutil_io_python_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS) diff --git a/src/lib/util/io/fdshare_python.cc b/src/lib/util/io/fdshare_python.cc deleted file mode 100644 index 249f8b08f8..0000000000 --- a/src/lib/util/io/fdshare_python.cc +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2010 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. - -#define PY_SSIZE_T_CLEAN -#include <Python.h> -#include <structmember.h> - -#include <config.h> - -#include "fd_share.h" - - -static PyObject* -fdshare_recv_fd(PyObject*, PyObject* args) { - int sock, fd; - if (!PyArg_ParseTuple(args, "i", &sock)) { - return (NULL); - } - fd = isc::util::io::recv_fd(sock); - return (Py_BuildValue("i", fd)); -} - -static PyObject* -fdshare_send_fd(PyObject*, PyObject* args) { - int sock, fd, result; - if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) { - return (NULL); - } - result = isc::util::io::send_fd(sock, fd); - return (Py_BuildValue("i", result)); -} - -static PyMethodDef fdshare_Methods[] = { - {"send_fd", fdshare_send_fd, METH_VARARGS, ""}, - {"recv_fd", fdshare_recv_fd, METH_VARARGS, ""}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - - -static PyModuleDef bind10_fdshare_python = { - { PyObject_HEAD_INIT(NULL) NULL, 0, NULL}, - "bind10_fdshare", - "Python bindings for fdshare", - -1, - fdshare_Methods, - NULL, - NULL, - NULL, - NULL -}; - -PyMODINIT_FUNC -PyInit_libutil_io_python(void) { - PyObject *mod = PyModule_Create(&bind10_fdshare_python); - if (mod == NULL) { - return (NULL); - } - - PyObject* FD_SYSTEM_ERROR = Py_BuildValue("i", - isc::util::io::FD_SYSTEM_ERROR); - if (FD_SYSTEM_ERROR == NULL) { - Py_XDECREF(mod); - return (NULL); - } - int ret = PyModule_AddObject(mod, "FD_SYSTEM_ERROR", FD_SYSTEM_ERROR); - if (ret == -1) { - Py_XDECREF(FD_SYSTEM_ERROR); - Py_XDECREF(mod); - return (NULL); - } - - PyObject* FD_OTHER_ERROR = Py_BuildValue("i", - isc::util::io::FD_OTHER_ERROR); - if (FD_OTHER_ERROR == NULL) { - Py_XDECREF(mod); - return (NULL); - } - ret = PyModule_AddObject(mod, "FD_OTHER_ERROR", FD_OTHER_ERROR); - if (-1 == ret) { - Py_XDECREF(FD_OTHER_ERROR); - Py_XDECREF(mod); - return (NULL); - } - - return (mod); -} - diff --git a/src/lib/util/python/.gitignore b/src/lib/util/python/.gitignore deleted file mode 100644 index 619c69fd65..0000000000 --- a/src/lib/util/python/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/doxygen2pydoc.py -/gen_wiredata.py -/mkpywrapper.py diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am index c7ddf3d175..dd584be2b2 100644 --- a/src/lib/util/python/Makefile.am +++ b/src/lib/util/python/Makefile.am @@ -1,3 +1,2 @@ -noinst_SCRIPTS = doxygen2pydoc.py gen_wiredata.py mkpywrapper.py const2hdr.py \ - pythonize_constants.py -EXTRA_DIST = const2hdr.py pythonize_constants.py +noinst_SCRIPTS = const2hdr.py gen_wiredata.py +EXTRA_DIST = const2hdr.py diff --git a/src/lib/util/python/doxygen2pydoc.py.in b/src/lib/util/python/doxygen2pydoc.py.in deleted file mode 100755 index 7aa74ec6c6..0000000000 --- a/src/lib/util/python/doxygen2pydoc.py.in +++ /dev/null @@ -1,680 +0,0 @@ -#!@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. - -r""" -A helper to semi-auto generate Python docstring text from C++ Doxygen -documentation. - -This script converts an XML-format doxygen documentation for C++ library -into a template Python docstring for the corresponding Python version -of the library. While it's not perfect and you'll still need to edit the -output by hand, but past experiments showed the script produces a pretty -good template. It will help provide more compatible documentation for -both C++ and Python versions of library from a unified source (C++ Doxygen -documentation) with minimizing error-prone and boring manual conversion. - -HOW TO USE IT - -1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml: - - % cd bind10/doc - % doxygen Doxyfile-xml - (XML files will be generated under bind10/doc/html/xml) - -2. Identify the xml file of the conversion target (C++ class, function, etc) - - This is a bit tricky. You'll probably need to do manual search. - For example, to identify the xml file for a C++ class - isc::datasrc::memory::ZoneWriter, you might do: - - % cd bind10/doc/html/xml - % grep ZoneWriter *.xml | grep 'kind="class"' - index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name> - - In this case the file under the d4/d3c directory (with .xml suffix) would - be the file you're looking for. - -3. Run this script for the xml file: - - % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc - - The template content is dumped to standard out (redirected to file - "output.cc" in this example). - - Sometimes the script produces additional output to standard error, - like this: - - Replaced camelCased terms: - resetMemorySegment => reset_memory_segment - getConfiguration => get_configuration - - In BIND 10 naming convention for methods is different for C++ and - Python. This script uses some heuristic guess to convert the - C++-style method names to likely Python-style ones, and the converted - method names are used in the dumped template. In many cases the guessed - names are correct, but you should check this list and make adjustments - by hand if necessary. - - If there's no standard error output, this type of conversion didn't - happen. - -4. Edit and copy the template - - The dumped template has the following organization: - - namespace { - #ifdef COPY_THIS_TO_MAIN_CC - { "cleanup", ZoneWriter_cleanup, METH_NOARGS, - ZoneWriter_cleanup_doc }, - { "install", ZoneWriter_install, METH_NOARGS, - ZoneWriter_install_doc }, - { "load", ZoneWriter_load, METH_VARARGS, - ZoneWriter_load_doc }, - #endif // COPY_THIS_TO_MAIN_CC - - const char* const ZoneWriter_doc = "\ - ... - "; - - const char* const ZoneWriter_install_doc = "\ - ... - "; - - ... - } - - The ifdef-ed block is a template for class methods information - to be added to the corresponding PyMethodDef structure array - (your wrapper C++ source would have something like ZoneWriter_methods - of this type). These lines should be copied there. As long as - the method names and corresponding wrapper function (such as - ZoneWriter_cleanup) are correct you shouldn't have to edit this part - (and they would be normally correct, unless the guessed method name - conversion was needed). - - The rest of the content is a sequence of constant C-string variables. - Usually the first variable corresponds to the class description, and - the rest are method descriptions (note that ZoneWriter_install_doc - is referenced from the ifdef-ed block). The content of this part - would generally make sense, but you'll often need to make some - adjsutments by hand. A common examples of such adjustment is to - replace "NULL" with "None". Also, it's not uncommon that some part - of the description simply doesn't apply to the Python version or - that Python specific notes are needed. So go through the description - carefully and make necessary changes. A common practice is to add - comments for a summary of adjustments like this: - - // Modifications: - // NULL->None - // - removed notes about derived classes (which doesn't apply for python) - const char* const ZoneWriter_doc = "\ - ... - "; - This note will help next time you need to auto-generate and edit the - template (probably because the original C++ document is updated). - - You can simply copy this part to the main C++ wrapper file, but since - it's relatively large a common practice is to maintain it in a separate - file that is exclusively included from the main file: if the name of - the main file is zonewriter_python.cc, the pydoc strings would be copied - in zonewriter_python_inc.cc, and the main file would have this line: - - #include "zonewriter_inc.cc" - - (In case you are C++ language police: it's okay to use the unnamed - name space for a file to be included because it's essentially a part - of the single .cc file, not expected to be included by others). - - In either case, the ifdef-ed part should be removed. - -ADVANCED FEATURES - -You can use a special "xmlonly" doxygen command in C++ doxygent document -in order to include Python code excerpt (while hiding it from the doxygen -output for the C++ version). This command will be converted to -a special XML tag in the XML output. - -The block enclosed by \xmlonly and \endxmlonly should contain -a verbatim XML tag named "pythonlisting", in which the python code should -be placed. -/// \code -/// Name name("example.com"); -/// std::cout << name.toText() << std::endl; -/// \endcode -/// -/// \xmlonly <pythonlisting> -/// name = Name("example.com") -/// print(name.to_text()) -/// </pythonlisting> \endxmlonly - -Note that there must be a blank line between \endcode and \xmlonly. -doxygen2pydoc assume the pythonlisting tag is in a separate <para> node. -This blank ensures doxygen will produce the XML file that meets the -assumption. - -INTERNAL MEMO (incomplete, and not very unredable yet) - -This simplified utility assumes the following structure: -... - <compounddef ...> - <compoundname>isc::dns::TSIGError</compoundname> - <sectiondef kind="user-defined"> - constructor, destructor - </sectiondef> - <sectiondef kind="public-type"> - .. - </sectiondef> - <sectiondef kind="public-func"> - <memberdef kind="function"...> - <type>return type (if any)</type> - <argsstring>(...) [const]</argsstring> - <name>method name</name> - <briefdescription>method's brief description</briefdescription> - <detaileddescription> - <para>...</para>... - <para> - <parameterlist kind="exception"> - <parameteritem> - <parameternamelist> - <parametername>Exception name</parametername> - </parameternamelist> - <parameterdescription> - <para>exception desc</para> - </parameterdescription> - </parameteritem> - </parameterlist> - <parameterlist kind="param"> - <parameteritem> - <parameternamelist> - <parametername>param name</parametername> - </parameternamelist> - <parameterdescription> - <para>param desc</para> - </parameterdescription> - </parameteritem> - ... - </parameterlist> - <simplesect kind="return">Return value</simplesect> - </para> - </detaileddescription> - </memberdef> - </sectiondef> - <sectiondef kind="public-static-attrib|user-defined"> - <memberdef kind="variable"...> - <name>class-specific-constant</name> - <initializer>value</initializer> - <brief|detaileddescription>paragraph(s)</brief|detaileddescription> - </sectiondef> - <briefdescription> - class's brief description - </briefdescription> - <detaileddescription> - class's detailed description - </detaileddescription> - </compounddef> -""" - -import re, string, sys, textwrap -from xml.dom.minidom import parse -from textwrap import fill, dedent, TextWrapper - -camel_replacements = {} -member_functions = [] -constructors = [] -class_variables = [] - -RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*') -RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])") -RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])") - -class Paragraph: - TEXT = 0 - ITEMIZEDLIST = 1 - CPPLISTING = 2 - PYLISTING = 3 - VERBATIM = 4 - - def __init__(self, xml_node): - if len(xml_node.getElementsByTagName("pythonlisting")) > 0: - self.type = self.PYLISTING - self.text = re.sub("///", "", get_text(xml_node)) - elif len(xml_node.getElementsByTagName("verbatim")) > 0: - self.type = self.VERBATIM - self.text = get_text(xml_node) - elif len(xml_node.getElementsByTagName("programlisting")) > 0: - # We ignore node containing a "programlisting" tag. - # They are C++ example code, and we are not interested in them - # in pydoc. - self.type = self.CPPLISTING - elif len(xml_node.getElementsByTagName("itemizedlist")) > 0: - self.type = self.ITEMIZEDLIST - self.items = [] - for item in xml_node.getElementsByTagName("listitem"): - self.items.append(get_text(item)) - else: - self.type = self.TEXT - - # A single textual paragraph could have multiple simple sections - # if it contains notes. - - self.texts = [] - subnodes = [] - for child in xml_node.childNodes: - if child.nodeType == child.ELEMENT_NODE and \ - child.nodeName == 'simplesect' and \ - child.getAttribute('kind') == 'note': - if len(subnodes) > 0: - self.texts.append(get_text_fromnodelist(subnodes)) - subnodes = [] - subtext = 'Note: ' - for t in child.childNodes: - subtext += get_text(t) - self.texts.append(subtext) - else: - subnodes.append(child) - if len(subnodes) > 0: - self.texts.append(get_text_fromnodelist(subnodes)) - - def dump(self, f, wrapper): - if self.type == self.CPPLISTING: - return - elif self.type == self.ITEMIZEDLIST: - for item in self.items: - item_wrapper = TextWrapper(\ - initial_indent=wrapper.initial_indent + "- ", - subsequent_indent=wrapper.subsequent_indent + " ") - dump_filled_text(f, item_wrapper, item) - f.write("\\n\\\n") - elif self.type == self.TEXT: - for text in self.texts: - if text != self.texts[0]: - f.write("\\n\\\n") - dump_filled_text(f, wrapper, text) - f.write("\\n\\\n") - else: - dump_filled_text(f, None, self.text) - f.write("\\n\\\n") - f.write("\\n\\\n") - -class NamedItem: - def __init__(self, name, desc): - self.name = name - self.desc = desc - - def dump(self, f, wrapper): - # we use deeper indent inside the item list. - new_initial_indent = wrapper.initial_indent + " " * 2 - new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11) - local_wrapper = TextWrapper(initial_indent=new_initial_indent, - subsequent_indent=new_subsequent_indent) - - # concatenate name and description with a fixed width (up to 10 chars) - # for the name, and wrap the entire text, then dump it to file. - dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc)) - f.write("\\n\\\n") - -class FunctionDefinition: - # function types - CONSTRUCTOR = 0 - COPY_CONSTRUCTOR = 1 - DESTRUCTOR = 2 - ASSIGNMENT_OP = 3 - OTHER = 4 - - def __init__(self): - self.type = self.OTHER - self.name = None - self.pyname = None - self.args = "" - self.ret_type = None - self.brief_desc = None - self.detailed_desc = [] - self.exceptions = [] - self.parameters = [] - self.returns = None - self.have_param = False - - def dump_doc(self, f, wrapper=TextWrapper()): - f.write(self.pyname + "(" + self.args + ")") - if self.ret_type is not None: - f.write(" -> " + self.ret_type) - f.write("\\n\\\n\\n\\\n") - - if self.brief_desc is not None: - dump_filled_text(f, wrapper, self.brief_desc) - f.write("\\n\\\n\\n\\\n") - - for para in self.detailed_desc: - para.dump(f, wrapper) - - if len(self.exceptions) > 0: - f.write(wrapper.fill("Exceptions:") + "\\n\\\n") - for ex_desc in self.exceptions: - ex_desc.dump(f, wrapper) - f.write("\\n\\\n") - if len(self.parameters) > 0: - f.write(wrapper.fill("Parameters:") + "\\n\\\n") - for param_desc in self.parameters: - param_desc.dump(f, wrapper) - f.write("\\n\\\n") - if self.returns is not None: - dump_filled_text(f, wrapper, "Return Value(s): " + self.returns) - f.write("\\n\\\n") - - def dump_pymethod_def(self, f, class_name): - f.write(' { "' + self.pyname + '", ') - f.write(class_name + '_' + self.name + ', ') - if len(self.parameters) == 0: - f.write('METH_NOARGS,\n') - else: - f.write('METH_VARARGS,\n') - f.write(' ' + class_name + '_' + self.name + '_doc },\n') - -class VariableDefinition: - def __init__(self, nodelist): - self.value = None - self.brief_desc = None - self.detailed_desc = [] - - for node in nodelist: - if node.nodeName == "name": - self.name = get_text(node) - elif node.nodeName == "initializer": - self.value = get_text(node) - elif node.nodeName == "briefdescription": - self.brief_desc = get_text(node) - elif node.nodeName == "detaileddescription": - for para in node.childNodes: - if para.nodeName != "para": - # ignore surrounding empty nodes - continue - self.detailed_desc.append(Paragraph(para)) - - def dump_doc(self, f, wrapper=TextWrapper()): - name_value = self.name - if self.value is not None: - name_value += ' = ' + self.value - dump_filled_text(f, wrapper, name_value) - f.write('\\n\\\n') - - desc_initial_indent = wrapper.initial_indent + " " - desc_subsequent_indent = wrapper.subsequent_indent + " " - desc_wrapper = TextWrapper(initial_indent=desc_initial_indent, - subsequent_indent=desc_subsequent_indent) - if self.brief_desc is not None: - dump_filled_text(f, desc_wrapper, self.brief_desc) - f.write("\\n\\\n\\n\\\n") - - for para in self.detailed_desc: - para.dump(f, desc_wrapper) - -def dump_filled_text(f, wrapper, text): - """Fill given text using wrapper, and dump it to the given file - appending an escaped CR at each end of line. - """ - filled_text = wrapper.fill(text) if wrapper is not None else text - f.write("".join(re.sub("\n", r"\\n\\\n", filled_text))) - -def camel_to_lowerscores(matchobj): - oldtext = matchobj.group(0) - newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext) - newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext) - newtext = newtext.lower() - camel_replacements[oldtext] = newtext - return newtext.lower() - -def cpp_to_python(text): - text = text.replace("::", ".") - text = text.replace('"', '\\"') - - # convert camelCase to "_"-concatenated format - # (e.g. getLength -> get_length) - return re.sub(RE_CAMELTERM, camel_to_lowerscores, text) - -def convert_type_name(type_name): - """Convert C++ type name to python type name using common conventions""" - # strip off leading 'const' and trailing '&/*' - type_name = re.sub("^const\S*", "", type_name) - type_name = re.sub("\S*[&\*]$", "", type_name) - - # We often typedef smart pointers as [Const]TypePtr. Convert them to - # just "Type" - type_name = re.sub("^Const", "", type_name) - type_name = re.sub("Ptr$", "", type_name) - - if type_name == "std::string": - return "string" - if re.search(r"(int\d+_t|size_t)", type_name): - return "integer" - return type_name - -def get_text(root, do_convert=True): - """Recursively extract bare text inside the specified node (root), - concatenate all extracted text and return the result. - """ - nodelist = root.childNodes - rc = [] - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - if do_convert: - rc.append(cpp_to_python(node.data)) - else: - rc.append(node.data) - elif node.nodeType == node.ELEMENT_NODE: - rc.append(get_text(node)) - # return the result, removing any leading newlines (that often happens for - # brief descriptions, which will cause lines not well aligned) - return re.sub("^(\n*)", "", ''.join(rc)) - -def get_text_fromnodelist(nodelist, do_convert=True): - """Recursively extract bare text inside the specified node (root), - concatenate all extracted text and return the result. - """ - rc = [] - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - if do_convert: - rc.append(cpp_to_python(node.data)) - else: - rc.append(node.data) - elif node.nodeType == node.ELEMENT_NODE: - rc.append(get_text(node)) - # return the result, removing any leading newlines (that often happens for - # brief descriptions, which will cause lines not well aligned) - return re.sub("^(\n*)", "", ''.join(rc)) - -def parse_parameters(nodelist): - rc = [] - for node in nodelist: - if node.nodeName != "parameteritem": - continue - # for simplicity, we assume one parametername and one - # parameterdescription for each parameter. - name = get_text(node.getElementsByTagName("parametername")[0]) - desc = get_text(node.getElementsByTagName("parameterdescription")[0]) - rc.append(NamedItem(name, desc)) - return rc - -def parse_function_description(func_def, nodelist): - for node in nodelist: - # nodelist contains beginning and ending empty text nodes. - # ignore them (otherwise they cause disruption below). - if node.nodeName != "para": - continue - - if node.getElementsByTagName("parameterlist"): - # within this node there may be exception list, parameter list, - # and description for return value. parse and store them - # seprately. - for paramlist in node.getElementsByTagName("parameterlist"): - if paramlist.getAttribute("kind") == "exception": - func_def.exceptions = \ - parse_parameters(paramlist.childNodes) - elif paramlist.getAttribute("kind") == "param": - func_def.parameters = \ - parse_parameters(paramlist.childNodes) - if node.getElementsByTagName("simplesect"): - simplesect = node.getElementsByTagName("simplesect")[0] - if simplesect.getAttribute("kind") == "return": - func_def.returns = get_text(simplesect) - else: - # for normal text, python listing and itemized list, append them - # to the list of paragraphs - func_def.detailed_desc.append(Paragraph(node)) - -def parse_function(func_def, class_name, nodelist): - for node in nodelist: - if node.nodeName == "name": - func_def.name = get_text(node, False) - func_def.pyname = cpp_to_python(func_def.name) - elif node.nodeName == "argsstring": - # extract parameter names only, assuming they immediately follow - # their type name + space, and are immeidatelly followed by - # either "," or ")". If it's a pointer or reference, */& is - # prepended to the parameter name without a space: - # e.g. (int var1, char *var2, Foo &var3) - args = get_text(node, False) - # extract parameter names, possibly with */& - func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args)) - # then remove any */& symbols - func_def.args = re.sub("[\*&]", "", func_def.args) - elif node.nodeName == "type" and node.hasChildNodes(): - func_def.ret_type = convert_type_name(get_text(node, False)) - elif node.nodeName == "param": - func_def.have_param = True - elif node.nodeName == "briefdescription": - func_def.brief_desc = get_text(node) - elif node.nodeName == "detaileddescription": - parse_function_description(func_def, node.childNodes) - # identify the type of function using the name and arg - if func_def.name == class_name and \ - re.search("^\(const " + class_name + " &[^,]*$", args): - # This function is ClassName(const ClassName& param), which is - # the copy constructor. - func_def.type = func_def.COPY_CONSTRUCTOR - elif func_def.name == class_name: - # if it's not the copy ctor but the function name == class name, - # it's a constructor. - func_def.type = func_def.CONSTRUCTOR - elif func_def.name == "~" + class_name: - func_def.type = func_def.DESTRUCTOR - elif func_def.name == "operator=": - func_def.type = func_def.ASSIGNMENT_OP - - # register the definition to the approriate list - if func_def.type == func_def.CONSTRUCTOR: - constructors.append(func_def) - elif func_def.type == func_def.OTHER: - member_functions.append(func_def) - -def parse_functions(class_name, nodelist): - for node in nodelist: - if node.nodeName == "memberdef" and \ - node.getAttribute("kind") == "function": - func_def = FunctionDefinition() - parse_function(func_def, class_name, node.childNodes) - -def parse_class_variables(class_name, nodelist): - for node in nodelist: - if node.nodeName == "memberdef" and \ - node.getAttribute("kind") == "variable": - class_variables.append(VariableDefinition(node.childNodes)) - -def dump(f, class_name, class_brief_doc, class_detailed_doc): - f.write("namespace {\n") - - f.write('#ifdef COPY_THIS_TO_MAIN_CC\n') - for func in member_functions: - func.dump_pymethod_def(f, class_name) - f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n') - - f.write("const char* const " + class_name + '_doc = "\\\n') - if class_brief_doc is not None: - f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc)))) - f.write("\\n\\\n") - f.write("\\n\\\n") - if len(class_detailed_doc) > 0: - for para in class_detailed_doc: - para.dump(f, wrapper=TextWrapper()) - - # dump constructors - for func in constructors: - indent = " " * 4 - func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent, - subsequent_indent=indent)) - - # dump class variables - if len(class_variables) > 0: - f.write("Class constant data:\\n\\\n") - for var in class_variables: - var.dump_doc(f) - - f.write("\";\n") - - for func in member_functions: - f.write("\n") - f.write("const char* const " + class_name + "_" + func.name + \ - "_doc = \"\\\n"); - func.dump_doc(f) - f.write("\";\n") - - f.write("} // unnamed namespace") # close namespace - -if __name__ == '__main__': - dom = parse(sys.argv[1]) - class_elements = dom.getElementsByTagName("compounddef")[0].childNodes - class_brief_doc = None - class_detailed_doc = [] - for node in class_elements: - if node.nodeName == "compoundname": - # class name is the last portion of the period-separated fully - # qualified class name. (this should exist) - class_name = re.split("\.", get_text(node))[-1] - if node.nodeName == "briefdescription": - # we assume a brief description consists at most one para - class_brief_doc = get_text(node) - elif node.nodeName == "detaileddescription": - # a detaild description consists of one or more paragraphs - for para in node.childNodes: - if para.nodeName != "para": # ignore surrounding empty nodes - continue - class_detailed_doc.append(Paragraph(para)) - elif node.nodeName == "sectiondef" and \ - node.getAttribute("kind") == "public-func": - parse_functions(class_name, node.childNodes) - elif node.nodeName == "sectiondef" and \ - node.getAttribute("kind") == "public-static-attrib": - parse_class_variables(class_name, node.childNodes) - elif node.nodeName == "sectiondef" and \ - node.getAttribute("kind") == "user-defined": - # there are two possiblities: functions and variables - for child in node.childNodes: - if child.nodeName != "memberdef": - continue - if child.getAttribute("kind") == "function": - parse_function(FunctionDefinition(), class_name, - child.childNodes) - elif child.getAttribute("kind") == "variable": - class_variables.append(VariableDefinition(child.childNodes)) - - dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc) - - if len(camel_replacements) > 0: - sys.stderr.write("Replaced camelCased terms:\n") - for oldterm in camel_replacements.keys(): - sys.stderr.write("%s => %s\n" % (oldterm, - camel_replacements[oldterm])) diff --git a/src/lib/util/python/mkpywrapper.py.in b/src/lib/util/python/mkpywrapper.py.in deleted file mode 100755 index 4bf7752edd..0000000000 --- a/src/lib/util/python/mkpywrapper.py.in +++ /dev/null @@ -1,100 +0,0 @@ -#!@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) diff --git a/src/lib/util/python/pycppwrapper_util.h b/src/lib/util/python/pycppwrapper_util.h deleted file mode 100644 index 31041baa3e..0000000000 --- a/src/lib/util/python/pycppwrapper_util.h +++ /dev/null @@ -1,335 +0,0 @@ -// 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. -/// -/// In some other cases, it would be convenient if it's possible to create -/// an "empty" container and reset it with a Python object later. -/// For example, we may want to create a temporary Python object in the -/// middle of a function and make sure that it's valid within the rest of -/// the function scope, while we want to make sure its reference is released -/// when the function returns (either normally or as a result of exception). -/// To allow this scenario, this class defines the default constructor -/// and the \c reset() method. The default constructor allows the class -/// object with an "empty" (NULL) Python object, while \c reset() allows -/// the stored object to be replaced with a new one. If there's a valid -/// object was already set, \c reset() releases its reference. -/// In general, it's safer to construct the container object with a valid -/// Python object pointer. The use of the default constructor and -/// \c reset() should therefore be restricted to cases where it's -/// absolutely necessary. -/// -/// 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() : obj_(NULL) {} - PyObjectContainer(PyObject* obj) : obj_(obj) { - if (obj_ == NULL) { - isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, " - "probably due to short memory"); - } - } - ~PyObjectContainer() { - if (obj_ != NULL) { - Py_DECREF(obj_); - } - } - void reset(PyObject* obj) { - if (obj == NULL) { - isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, " - "probably due to short memory"); - } - if (obj_ != NULL) { - Py_DECREF(obj_); - } - obj_ = 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 { - explicit 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: diff --git a/src/lib/util/python/pythonize_constants.py b/src/lib/util/python/pythonize_constants.py deleted file mode 100644 index cc6d9b23ff..0000000000 --- a/src/lib/util/python/pythonize_constants.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2013 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 script takes a C++ file with constants and converts it to a python -module. However, the syntax it parses is very limited (it doesn't understand -C++ at all, it just looks for lines containing the equal sign and strips -what it thinks might be type). - -The purpose is to keep the same values of constants in C++ and python. This -saves the work of keeping the constants in sync manually and is less error -prone. -''' - -import sys -import re - -if len(sys.argv) != 3: - sys.stderr.write("Usage: python3 ./pythonize_constants.py input.cc output.py\n") - sys.exit(1) - -[filename_in, filename_out] = sys.argv[1:3] - -# Ignore preprocessor, namespaces and the ends of namespaces. -ignore = re.compile('^(#|namespace|})') -comment = re.compile('^//(.*)') -constant = re.compile('^[a-zA-Z].*?([a-zA-Z_0-9]+\\s*=.*);') - -with open(filename_in) as file_in, open(filename_out, "w") as file_out: - file_out.write("# This file is generated from " + filename_in + "\n" + - "# by the pythonize_constants.py script.\n" + - "# Do not edit, all changes will be lost.\n\n") - for line in file_in: - if ignore.match(line): - continue - # Mangle comments to be python-like - line = comment.sub('#\\1', line) - # Extract the constant. - - # TODO: We may want to do something with the true vs. True and - # NULL vs. None and such. Left out for now, since none are in - # the input file currently. - line = constant.sub('\\1', line) - - file_out.write(line) diff --git a/src/lib/util/python/wrapper_template.cc b/src/lib/util/python/wrapper_template.cc deleted file mode 100644 index 780e69521c..0000000000 --- a/src/lib/util/python/wrapper_template.cc +++ /dev/null @@ -1,291 +0,0 @@ -// 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. - -// Enable this if you use s# variants with PyArg_ParseTuple(), see -// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers -//#define PY_SSIZE_T_CLEAN - -// Python.h needs to be placed at the head of the program file, see: -// http://docs.python.org/py3k/extending/extending.html#a-simple-example -#include <Python.h> - -#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; - -// -// @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; - -@REMOVE_THIS_ON_RELEASE@ -// 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(PyObject* po_self, PyObject* args, PyObject*) { - s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self); - try { - if (PyArg_ParseTuple(args, "REPLACE ME")) { - @REMOVE_THIS_ON_RELEASE@ - // 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(PyExc_SystemError, "Unexpected C++ exception"); - return (-1); - } - - @REMOVE_THIS_ON_RELEASE@ - // If we are here PyArg_ParseTuple() failed and TypeError should have - // been set. If the constructor is more complicated and the control - // could reach this point for other reasons, an appropriate Python - // exception should be set by PyErr_SetString. - - return (-1); -} - -@REMOVE_THIS_ON_RELEASE@ -// 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(PyObject* po_self) { - s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self); - delete self->cppobj; - self->cppobj = NULL; - Py_TYPE(self)->tp_free(self); -} - -@REMOVE_THIS_ON_RELEASE@ -// This should be able to be used without modification as long as the -// underlying C++ class has toText(). -PyObject* -@CPPCLASS@_toText(PyObject* po_self) { - const s_@CPPCLASS@* self = static_cast<const s_@CPPCLASS@*>(po_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*>(""))); -} - -@REMOVE_THIS_ON_RELEASE@ -// This is quite specific isc.dns. For other wrappers this should probably -// be removed. -PyObject* @CPPCLASS@_toWire(PyObject* self, PyObject* args) { -} - -PyObject* -@CPPCLASS@_richcmp(PyObject* po_self, PyObject* po_other, const int op) { - const s_@CPPCLASS@* const self = static_cast<const s_@CPPCLASS@*>(po_self); - const s_@CPPCLASS@* const other = - static_cast<const s_@CPPCLASS@*>(po_other); - - 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; - } -} - -// 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", @CPPCLASS@_toText, METH_NOARGS, - @CPPCLASS@_toText_doc }, - - @REMOVE_THIS_ON_RELEASE@ - // This is quite specific isc.dns. For other wrappers this should probably - // be removed: - { "to_wire", @CPPCLASS@_toWire, METH_VARARGS, - @CPPCLASS@_toWire_doc }, - { NULL, NULL, 0, NULL } -}; -} // 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) - "@MODULE@.@CPPCLASS@", - sizeof(s_@CPPCLASS@), // tp_basicsize - 0, // tp_itemsize - @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 - @CPPCLASS@_doc, - NULL, // tp_traverse - NULL, // tp_clear - // THIS MAY HAVE TO BE CHANGED TO NULL: - @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 - @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); - - @REMOVE_THIS_ON_RELEASE@ - // 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); -} - -PyObject* -create@CPPCLASS@Object(const @CPPCLASS@& source) { - @CPPCLASS@Container container(PyObject_New(s_@CPPCLASS@, - &@cppclass@_type)); - container.set(new @CPPCLASS@(source)); - return (container.release()); -} -} // namespace python -} // namespace @MODULE@ -} // namespace isc diff --git a/src/lib/util/python/wrapper_template.h b/src/lib/util/python/wrapper_template.h deleted file mode 100644 index 787a2969e2..0000000000 --- a/src/lib/util/python/wrapper_template.h +++ /dev/null @@ -1,59 +0,0 @@ -// 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); - -// Note: this utility function works only when @CPPCLASS@ is a copy -// constructable. -// And, it would only be useful when python binding needs to create this -// object frequently. Otherwise, it would (or should) probably be better to -// remove the declaration and definition of this function. -// -/// This is a simple shortcut to create a python @CPPCLASS@ object (in the -/// form of a pointer to PyObject) with minimal exception safety. -/// On success, it returns a valid pointer to PyObject with a reference -/// counter of 1; if something goes wrong it throws an exception (it never -/// returns a NULL pointer). -/// This function is expected to be called within a try block -/// followed by necessary setup for python exception. -PyObject* create@CPPCLASS@Object(const @CPPCLASS@& source); - -} // namespace python -} // namespace @MODULE@ -} // namespace isc -#endif // PYTHON_@CPPCLASS@_H - -// Local Variables: -// mode: c++ -// End: diff --git a/src/lib/util/pyunittests/Makefile.am b/src/lib/util/pyunittests/Makefile.am deleted file mode 100644 index 5ab868d852..0000000000 --- a/src/lib/util/pyunittests/Makefile.am +++ /dev/null @@ -1,22 +0,0 @@ -AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib -AM_CPPFLAGS += $(BOOST_INCLUDES) -AM_CXXFLAGS = $(B10_CXXFLAGS) - -noinst_LTLIBRARIES = pyunittests_util.la - -pyunittests_util_la_SOURCES = pyunittests_util.cc -pyunittests_util_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES) -pyunittests_util_la_LDFLAGS = $(PYTHON_LDFLAGS) -# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be -# placed after -Wextra defined in AM_CXXFLAGS -pyunittests_util_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS) - -# Python prefers .so, while some OSes (specifically MacOS) use a different -# suffix for dynamic objects. -module is necessary to work this around. -pyunittests_util_la_LDFLAGS += -module -avoid-version -pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libkea-util.la -pyunittests_util_la_LIBADD += $(PYTHON_LIB) - -# hack to trigger libtool to not create a convenience archive, -# resulting in shared modules -pyunittests_util_la_LDFLAGS += -rpath /nowhere diff --git a/src/lib/util/pyunittests/pyunittests_util.cc b/src/lib/util/pyunittests/pyunittests_util.cc deleted file mode 100644 index d266c84404..0000000000 --- a/src/lib/util/pyunittests/pyunittests_util.cc +++ /dev/null @@ -1,84 +0,0 @@ -// 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 <stdint.h> - -// see util/time_utilities.h -namespace isc { -namespace util { -namespace detail { -extern int64_t (*gettimeFunction)(); -} -} -} - -namespace { -int64_t fake_current_time; - -int64_t -getFakeTime() { - return (fake_current_time); -} - -PyObject* -fixCurrentTime(PyObject*, PyObject* args) { - PyObject* maybe_none; - if (PyArg_ParseTuple(args, "L", &fake_current_time)) { - isc::util::detail::gettimeFunction = getFakeTime; - } else if (PyArg_ParseTuple(args, "O", &maybe_none) && - maybe_none == Py_None) { - isc::util::detail::gettimeFunction = NULL; - } else { - PyErr_SetString(PyExc_TypeError, "Invalid arguments to " - "pyunittests_util.fix_current_time"); - return (NULL); - } - - PyErr_Clear(); - Py_RETURN_NONE; -} - -PyMethodDef PyUnittestsUtilMethods[] = { - { "fix_current_time", fixCurrentTime, METH_VARARGS, - "Fix the current system time at the specified (fake) value.\n\n" - "This is useful for testing modules that depend on the current time.\n" - "Note that it only affects C++ modules that use gettimeWrapper() " - "defined in libutil, which allows a hook for testing.\n" - "If an integer (signed 64bit) is given, the current time will be fixed " - "to that value; if None is specified (which is the default) the use of " - "faked time will be canceled." - }, - { NULL, NULL, 0, NULL} -}; - -PyModuleDef pyunittests_util = { - { PyObject_HEAD_INIT(NULL) NULL, 0, NULL}, - "pyunittests_util", - "This module is a collection of utilities useful for testing " - "the BIND 10 C++ binding modules.", - -1, - PyUnittestsUtilMethods, - NULL, - NULL, - NULL, - NULL -}; -} // end of unnamed namespace - -PyMODINIT_FUNC -PyInit_pyunittests_util(void) { - return (PyModule_Create(&pyunittests_util)); -} |