Commit 46088523 authored by Jelte Jansen's avatar Jelte Jansen
Browse files

partial python wrapper implementation, see src/lib/dns/python/README for details


git-svn-id: svn://bind10.isc.org/svn/bind10/experiments/python-binding@1711 e5f2f494-b856-4b98-b285-d166d9295462
parent c31aa171
......@@ -373,6 +373,7 @@ AC_CONFIG_FILES([Makefile
src/lib/config/tests/Makefile
src/lib/dns/Makefile
src/lib/dns/tests/Makefile
src/lib/dns/python/Makefile
src/lib/dns-python/Makefile
src/lib/exceptions/Makefile
src/lib/auth/Makefile
......@@ -409,6 +410,7 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
src/lib/config/tests/data_def_unittests_config.h
src/lib/python/isc/config/tests/config_test
src/lib/python/isc/cc/tests/cc_test
src/lib/dns/python/tests/libdns_python_test
src/lib/dns/gen-rdatacode.py
src/lib/dns/tests/testdata/gen-wiredata.py
], [
......@@ -427,5 +429,6 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
chmod +x src/bin/msgq/msgq_test
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
chmod +x src/lib/dns/python/tests/libdns_python_test
])
AC_OUTPUT
SUBDIRS = . tests
SUBDIRS = . tests python
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
if GCC_WERROR_OK
......
This diff is collapsed.
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
#lib_LTLIBRARIES = libdns_python_name.la libdns_python_rrset.la
#libdns_python_name_la_SOURCES = name_python.cc
#libdns_python_name_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
#libdns_python_name_la_LDFLAGS = $(PYTHON_LDFLAGS)
#lib_LTLIBRARIES = libdns_python_name.la libdns_python_rrset.la
lib_LTLIBRARIES = libdns_python.la
libdns_python_la_SOURCES = libdns_python.cc libdns_python_common.cc
libdns_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
libdns_python_la_LDFLAGS = $(PYTHON_LDFLAGS)
#libdns_python_rrset_la_SOURCES = rrset_python.cc
#libdns_python_rrset_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
#libdns_python_rrset_la_LDFLAGS = $(PYTHON_LDFLAGS) libdns_python_name.la
#libdns_python_rrset_la_SOURCES = rrset_python.cc
#libdns_python_rrset_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
#libdns_python_rrset_la_LDFLAGS = $(PYTHON_LDFLAGS)
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
libdns_python_la_LDFLAGS += -module
libdns_python_la_LIBADD = $(top_builddir)/src/lib/dns/libdns.la
libdns_python_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
libdns_python_la_LIBADD += $(PYTHON_LIB)
This diff is collapsed.
This is a partial implementation of the python wrappers for isc::dns.
Currently, when compiled the module is called libdns_python. If we
decide to use it we will add a default import under lib/python/isc.
To use it from the source tree, you must add src/lib/dns/python/.libs
to your PYTHONPATH environment variable. Within python you can then use
> import libdns_python
> rrc = libdns_python.RRClass("IN")
Notes:
this implementation is far from complete; currently wrappers for the
following classes are implemented:
full (though we might add some conveniences later, like __str__):
RRType, RRClass, RRTTL, Name, and MessageRenderer
partial:
RRset (doesn't have a way to iterate over rdata yet)
Rdata (only the most basic support, you can create one from a type,
class and string, and you can convert them to text, but that's about
it).
We did not implement the buffer classes; everywhere in the API where
buffers are used, you can pass a bytearray object.
The 'main' module is defined in libdns_python.cc.
There is a libdns_python_common.[cc|h] for helper functions.
Implementation notes:
Writing wrappers for a lot of classes is mostly a case of repetition.
There are a lot of things one must do, and that is why nearly everyone
immediately and continually has the urge to make generators for this.
We have added a little more documentation than is strictly necessary to
rrclass.h, for reference to new readers.
To keep it maintainable as the original API changes, we use two
techniques;
1. Full unittests. Or at least as full as possible. All unittests from
the C++ code will also appear in the python wrapper tests (currently
only done for rrtype and rrset as far as implemented), so if we
forget to update a wrapper our tests should fail.
2. Structure. I have tried to structure the wrapper files as much as
possible, see below.
Structure:
Since we are moving a lot of wrappers into one module, the specific
classes are split over several files, each one having the name of the
original header file they are wrapping (e.g. the wrapper for name.h
becomes name_python.cc).
At the top we first declare any exceptions, constants, and enums. These
are all called po_<C++-name> (the actual python name will be set once
they are added to the module in the module inialization function).
Each class needs a struct that contains a pointer to an instance of the
object (and any helper data). We call these structs s_<C++ class name>.
Then we declare (but not define!) all methods we will export to
python. These are named <C++ class name>_<C++ method name>.
We will also need an _init and _destroy function for all of these.
After the function declarations we define the method array; this
contains the name of the methods as they appear in python, the wrapping
function here, and documentation strings.
Next is the type description; this is used for the wrapper code to
convert native classes from and to python objects, and defines
type-specific pointers (for instance to the method table mentioned above,
but also to a __str__ function should one be defined, how the object
should behave when it is used as a Sequence, etc.). For most classes,
almost all values here shall be NULL.
This has the name <lowercase C++ class name>_type.
After that we define the exported functions we defined earlier. In some
cases these need the type we just defined, and the type needed the
function names, so for consistency, all functions are defined after,
but declared before the type.
This is repeated for every class we export.
Finally we define the function to add the class, constants, exceptions,
and enums to the module. This function is called from the init function
in libdns_python.cc, has the name
initModulePart_<c++ class name>, returns a boolean
(true on success, false on failure), and takes the module as a
PyObject*.
// Ok we want a lot of different parts of the DNS API in the python
// module, but not one big 10000-line file.
// So we split it up in several 'mini-modules'
// These would be the same as a single module, except for
// the init function, which has to be modified to a unique
// name initModulePart_<name>, and return true/false instead of
// NULL/*mod
//
// And of course care has to be taken that all identifiers be unique
//
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "config.h"
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
#include <dns/exceptions.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include "libdns_python_common.h"
//#include "buffer_python.cc"
// order is important here! (TODO: document dependencies)
#include "name_python.cc"
#include "messagerenderer_python.cc"
#include "rrclass_python.cc"
#include "rrtype_python.cc"
#include "rrttl_python.cc"
#include "rdata_python.cc"
#include "rrset_python.cc"
//
// Definition of the module
//
static PyModuleDef libdns_python = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"libdns_python",
"Python bindings for isc.dns",
-1,
NULL,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit_libdns_python(void)
{
PyObject *mod = PyModule_Create(&libdns_python);
if (mod == NULL) {
return NULL;
}
// for each part included above, we call its specific initializer
if (!initModulePart_Name(mod)) {
return NULL;
}
if (!initModulePart_MessageRenderer(mod)) {
return NULL;
}
if (!initModulePart_RRClass(mod)) {
return NULL;
}
if (!initModulePart_RRType(mod)) {
return NULL;
}
if (!initModulePart_RRTTL(mod)) {
return NULL;
}
if (!initModulePart_Rdata(mod)) {
return NULL;
}
if (!initModulePart_RRset(mod)) {
return NULL;
}
return mod;
}
// Copyright (C) 2009 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.
// $Id$
#include <Python.h>
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence)
{
PyObject* el = NULL;
for (size_t i = 0; i < len; i++) {
el = PySequence_GetItem(sequence, 0);
if (!el) {
PyErr_SetString(PyExc_TypeError,
"sequence too short");
return -1;
}
if (PyLong_Check(el)) {
long l = PyLong_AsLong(el);
if (l < 0 || l > 255) {
PyErr_SetString(PyExc_TypeError,
"number in fromWire sequence not between 0 and 255");
return -1;
}
data[i] = (uint8_t) PyLong_AsLong(el);
PySequence_DelItem(sequence, 0);
} else {
PyErr_SetString(PyExc_TypeError,
"not a number in fromWire sequence");
return -1;
}
}
return 0;
}
// Copyright (C) 2009 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.
// $Id$
//
// Shared functions for python/c API
//
// This function reads 'bytes' from a sequence
// This sequence can be anything that implements the Sequence interface,
// but must consist of Numbers between 0 and 255 for every value.
//
// The data is stored in *data.
// Data must be allocated and have at least len bytes available.
//
// The current implementation removes read items from the
// head of the sequence (even if it fails it removes everything that
// it successfully read)
int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
// Copyright (C) 2009 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.
// $Id$
#include <dns/messagerenderer.h>
// 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
using namespace isc::dns;
// MessageRenderer
// since we don't use *Buffer in the python version (but work with
// the already existing bytearray type where we use these custom buffers
// in c++, we need to keep track of one here.
typedef struct {
PyObject_HEAD
OutputBuffer* outputbuffer;
MessageRenderer* messagerenderer;
} s_MessageRenderer;
static int MessageRenderer_init(s_MessageRenderer* self);
static void MessageRenderer_destroy(s_MessageRenderer* self);
static PyObject* MessageRenderer_getData(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
static PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
static PyMethodDef MessageRenderer_methods[] = {
{ "get_data", (PyCFunction)MessageRenderer_getData, METH_NOARGS, "Return the data" },
{ "get_length", (PyCFunction)MessageRenderer_getLength, METH_NOARGS, "Return the length of the data" },
{ "is_truncated", (PyCFunction)MessageRenderer_isTruncated, METH_NOARGS, "Returns True if the data is truncated" },
{ "get_length_limit", (PyCFunction)MessageRenderer_getLengthLimit, METH_NOARGS, "Return the length limit of the data" },
{ NULL, NULL, 0, NULL }
};
static PyTypeObject messagerenderer_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"libdns_python.MessageRenderer",
sizeof(s_MessageRenderer), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)MessageRenderer_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 */
NULL, /* tp_str */
NULL, /* tp_getattro */
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"C++ MessageRenderer Object", /* tp_doc */
NULL, /* tp_traverse */
NULL, /* tp_clear */
NULL, /* tp_richcompare */
0, /* tp_weaklistoffset */
NULL, /* tp_iter */
NULL, /* tp_iternext */
MessageRenderer_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)MessageRenderer_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 */
// Note: not sure if the following are correct. Added them just to
// make the compiler happy.
NULL, /* tp_del */
0 /* tp_version_tag */
};
static int
MessageRenderer_init(s_MessageRenderer* self)
{
self->outputbuffer = new OutputBuffer(4096);
self->messagerenderer = new MessageRenderer(*self->outputbuffer);
return 0;
}
static void
MessageRenderer_destroy(s_MessageRenderer* self)
{
delete self->messagerenderer;
delete self->outputbuffer;
self->messagerenderer = NULL;
Py_TYPE(self)->tp_free(self);
}
static PyObject*
MessageRenderer_getData(s_MessageRenderer* self)
{
return Py_BuildValue("y#", self->messagerenderer->getData(), self->messagerenderer->getLength());
}
static PyObject*
MessageRenderer_getLength(s_MessageRenderer* self)
{
return Py_BuildValue("I", self->messagerenderer->getLength());
}
static PyObject*
MessageRenderer_isTruncated(s_MessageRenderer* self)
{
if (self->messagerenderer->isTruncated())
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
static PyObject*
MessageRenderer_getLengthLimit(s_MessageRenderer* self)
{
return Py_BuildValue("I", self->messagerenderer->getLengthLimit());
}
// end of MessageRenderer
// Module Initialization, all statics are initialized here
bool
initModulePart_MessageRenderer(PyObject* mod)
{
// Add the exceptions to the module
// Add the enums to the module
// Add the constants to the module
// Add the classes to the module
// We initialize the static description object with PyType_Ready(),
// then add it to the module
// NameComparisonResult
if (PyType_Ready(&messagerenderer_type) < 0) {
return false;
}
Py_INCREF(&messagerenderer_type);
PyModule_AddObject(mod, "MessageRenderer",
(PyObject*) &messagerenderer_type);
return true;
}
This diff is collapsed.
// Copyright (C) 2009 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.
// $Id$
#include <dns/rdata.h>
using namespace isc::dns;
using namespace isc::dns::rdata;
//
// Declaration of the custom exceptions
// Initialization and addition of these go in the initModulePart
// function at the end of this file
//
static PyObject* po_InvalidRdataLength;
static PyObject* po_InvalidRdataText;
static PyObject* po_CharStringTooLong;
//
// 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
//
// Rdata
//
// The s_* Class simply coverst one instantiation of the object
typedef struct {
PyObject_HEAD
RdataPtr rdata;
} s_Rdata;
//
// 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
static int Rdata_init(s_Rdata* self, PyObject* args);
static void Rdata_destroy(s_Rdata* self);
// These are the functions we export
static PyObject* Rdata_toText(s_Rdata* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
static PyObject* Rdata_str(PyObject* self);
static PyObject* Rdata_toWire(s_Rdata* self, PyObject* args);
// 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
static PyMethodDef Rdata_methods[] = {
{ "to_text", (PyCFunction)Rdata_toText, METH_NOARGS, "Return the string representation" },
{ "to_wire", (PyCFunction)Rdata_toWire, METH_VARARGS, "wire format" },
{ NULL, NULL, 0, NULL }
};
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_Rdata
// Most of the functions are not actually implemented and NULL here.
static PyTypeObject rdata_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"libdns_python.Rdata",
sizeof(s_Rdata), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Rdata_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 */
Rdata_str, /* tp_str */
NULL, /* tp_getattro */
NULL, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"C++ Rdata Object", /* tp_doc */
NULL, /* tp_traverse */
NULL, /* tp_clear */
NULL, /* tp_richcompare */
0, /* tp_weaklistoffset */
NULL, /* tp_iter */
NULL, /* tp_iternext */
Rdata_methods, /* tp_methods */
NULL, /* tp_members */
NULL,</