]> git.ipfire.org Git - thirdparty/rrdtool-1.x.git/commitdiff
python-rrdtool as replacement for the upstream Python bindings (#755)
authorChristian Kröger <commx@commx.ws>
Thu, 16 Feb 2017 16:06:37 +0000 (17:06 +0100)
committerTobias Oetiker <tobi@oetiker.ch>
Thu, 16 Feb 2017 16:06:37 +0000 (17:06 +0100)
* Replaced Python bindings with python-rrdtool

* Updated Makefile.in contents

* Added missing import

* Path correction

* Python 3 compatible print statements in acinclude.m4

* Updated configure script

* Added LIST command to Python binding

* Added missing var decls

* Added callback support for Python binding and initial tests suite for the binding

* Fixed some gcc compiler errors in pre-C99 mode

* setuptools is required to build the Python bindings

* Directory traversal

13 files changed:
.travis.yml
bindings/Makefile.am
bindings/Makefile.in
bindings/python/ACKNOWLEDGEMENT [deleted file]
bindings/python/AUTHORS [deleted file]
bindings/python/README [deleted file]
bindings/python/README.md [new file with mode: 0644]
bindings/python/rrdtoolmodule.c
bindings/python/setup.py
bindings/python/tests/__init__.py [new file with mode: 0644]
bindings/python/tests/test_fetch_cb.py [new file with mode: 0644]
configure
m4/acinclude.m4

index 02e41910d33e41b8a7f6559638fde886a8abaaeb..fdce9819c27810ec7497b7edf5c29dfa75b98e43 100644 (file)
@@ -9,7 +9,7 @@ perl:
 
 install: 
   - sudo apt-get update || true
-  - sudo apt-get install autopoint libdbi-dev tcl-dev lua5.1 liblua5.1-0-dev valgrind dc
+  - sudo apt-get install autopoint libdbi-dev tcl-dev lua5.1 liblua5.1-0-dev valgrind dc python-pip python-setuptools
   - sudo pip install cpp-coveralls
 
 before_script:
@@ -24,6 +24,7 @@ script:
   - make check TESTS_STYLE="valgrind-logfile"
   - sudo make install
   - cd bindings/perl-shared && make test
+  - cd ../python && python setup.py test
   - /opt/rrdtool-master/bin/rrdtool
   - /opt/rrdtool-master/share/rrdtool/examples/4charts.pl
 
index 547f041ff19f51fd523a27317a89ec94265f186b..aaa66794ba35d0f88fb137bfead475f95d449367 100644 (file)
@@ -19,7 +19,7 @@ EXTRA_DIST = perl-piped/MANIFEST perl-piped/README perl-piped/Makefile.PL perl-p
             perl-shared/ntmake-build perl-shared/MANIFEST perl-shared/README perl-shared/Makefile.PL perl-shared/RRDs.pm perl-shared/RRDs.ppd perl-shared/RRDs.xs perl-shared/t/base.t perl-shared/t/callback-long.t perl-shared/t/callback.t \
             ruby/CHANGES     ruby/README      ruby/extconf.rb  ruby/main.c      ruby/test.rb \
             dotnet/rrdlib.cs dotnet/rrd_binding_test.cs dotnet/rrdlib.sln dotnet/favicon.ico dotnet/dnrrdlib.csproj dotnet/Properties/AssemblyInfo.cs  dotnet/dnrrd_binding_test.csproj   dotnet/RrdException.cs \
-             python/ACKNOWLEDGEMENT python/AUTHORS python/COPYING python/README python/rrdtoolmodule.c python/setup.py
+             python/COPYING python/README.md python/rrdtoolmodule.c python/setup.py
 
 
 # add the following to the all target
index 3918dec894b15c571904dd32d9df8a66d97e5865..22c22faac17b16b6d2b954124900ec58099219e9 100644 (file)
@@ -402,7 +402,7 @@ EXTRA_DIST = perl-piped/MANIFEST perl-piped/README perl-piped/Makefile.PL perl-p
             perl-shared/ntmake-build perl-shared/MANIFEST perl-shared/README perl-shared/Makefile.PL perl-shared/RRDs.pm perl-shared/RRDs.ppd perl-shared/RRDs.xs perl-shared/t/base.t perl-shared/t/callback-long.t perl-shared/t/callback.t \
             ruby/CHANGES     ruby/README      ruby/extconf.rb  ruby/main.c      ruby/test.rb \
             dotnet/rrdlib.cs dotnet/rrd_binding_test.cs dotnet/rrdlib.sln dotnet/favicon.ico dotnet/dnrrdlib.csproj dotnet/Properties/AssemblyInfo.cs  dotnet/dnrrd_binding_test.csproj   dotnet/RrdException.cs \
-             python/ACKNOWLEDGEMENT python/AUTHORS python/COPYING python/README python/rrdtoolmodule.c python/setup.py
+             python/COPYING python/README.md python/rrdtoolmodule.c python/setup.py
 
 all: all-recursive
 
diff --git a/bindings/python/ACKNOWLEDGEMENT b/bindings/python/ACKNOWLEDGEMENT
deleted file mode 100644 (file)
index 8cf3658..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-ACKNOWLEDGMENT
-==============
-
-This is a list of people who have made contributions to py-rrdtool.
-
-Matthew W. Samsonoff <mws@rochester.rr.com>
-Brian E. Gallew <geek+python@cmu.edu>
diff --git a/bindings/python/AUTHORS b/bindings/python/AUTHORS
deleted file mode 100644 (file)
index b3b5713..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Hye-Shik Chang <perky@fallin.lv>
diff --git a/bindings/python/README b/bindings/python/README
deleted file mode 100644 (file)
index c234a6b..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-Based on Python-RRDtool 0.2.1
------------------------------
-
-The python-rrdtool provides a interface to rrdtool, the wonderful
-graphing and logging utility. This wrapper implementation has
-worked from the scratch (without SWIG), and it's under LGPL.
-
-This module have not documented yet. Please refer pydoc.
-
-
-Project
--------
-
-Homepage: http://www.nongnu.org/py-rrdtool/
-
-Mailing Lists:
-
-  CVS Checkins     py-rrdtool-cvs@nongnu.org
-  Users & Hackers  py-rrdtool-users@nongnu.org
-
-
-Original Author
----------------
-
-Hye-Shik Chang <perky@FreeBSD.org>
-
-Any comments, suggestions, and/or patches are very welcome.
-Thank you for using py-rrdtool!
-
-
-CHANGES
--------
-2008-05-19 - tobi
-* rewrote the info method to conform to rrdtool info standard
diff --git a/bindings/python/README.md b/bindings/python/README.md
new file mode 100644 (file)
index 0000000..cf325ef
--- /dev/null
@@ -0,0 +1,37 @@
+python-rrdtool
+==============
+
+Python bindings for [RRDtool](http://oss.oetiker.ch/rrdtool) with a native C extension.
+
+Supported Python versions: 2.6+, 3.3+.
+
+The bindings are based on the code of the original Python 2 bindings for rrdtool by Hye-Shik Chang, which are currently shipped as official bindings with rrdtool.
+
+Installation
+------------
+
+The easy way:
+
+    # pip install rrdtool
+
+**Note:** This requires rrdtool and it's development files (headers, libraries, dependencies) to be installed.
+
+In case you'd like to build the module on your own, you can obtain a copy of the repository and run `python setup.py install` in it's destination folder to build the native C extension.
+
+Usage
+-----
+
+```python
+import rrdtool
+
+# Create Round Robin Database
+rrdtool.create('test.rrd', '--start', 'now', '--step', '300', 'RRA:AVERAGE:0.5:1:1200', 'DS:temp:GAUGE:600:-273:5000')
+
+# Feed updates to the RRD
+rrdtool.update('test.rrd', 'N:32')
+```
+
+Documentation
+-------------
+
+You can find the latest documentation for this project at http://pythonhosted.org/rrdtool.
\ No newline at end of file
index 8e571d05a6183d1b0b4e1d2e482449099c08b76f..4a762abd16bf2b2c5a9a71f25f3285fe0f5c3e0e 100644 (file)
 /*
- * rrdtoolmodule.c
+ * python-rrdtool, Python bindings for rrdtool.
+ * Based on the rrdtool Python bindings for Python 2 from
+ * Hye-Shik Chang <perky@fallin.lv>.
  *
- * RRDTool Python binding
+ * Copyright 2012 Christian Jurk <commx@commx.ws>
  *
- * Author  : Hye-Shik Chang <perky@fallin.lv>
- * Date    : $Date: 2003/02/22 07:41:19 $
- * Created : 23 May 2002
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of the
+ * License, or (at your option) any later version.
  *
- * $Revision: 1.14 $
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
- *  ==========================================================================
- *  This file is part of py-rrdtool.
- *
- *  py-rrdtool is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU Lesser General Public License as published
- *  by the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  py-rrdtool is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser General Public License
- *  along with Foobar; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
  *
  */
 
-#ifdef UNUSED
-#elif defined(__GNUC__)
-# define UNUSED(x) x __attribute__((unused))
-#elif defined(__LCLINT__)
-# define UNUSED(x) /*@unused@*/ x
+#include <Python.h>
+#include <datetime.h>
+#include "rrd_config.h"
+#include "rrd_tool.h"
+
+/* Some macros to maintain compatibility between Python 2.x and 3.x */
+#if PY_MAJOR_VERSION >= 3
+#define HAVE_PY3K
+#define PyRRD_String_Check(x)                 PyUnicode_Check(x)
+#define PyRRD_String_FromString(x)            PyUnicode_FromString(x)
+#define PyRRD_String_AS_STRING(x)             PyUnicode_AsUTF8(x)
+#define PyRRD_String_FromStringAndSize(x, y)  PyBytes_FromStringAndSize(x, y)
+#define PyRRD_String_Size(x)                  PyUnicode_Size(x)
+#define PyRRD_Int_FromLong(x)                 PyLong_FromLong(x)
+#define PyRRD_Int_FromString(x, y, z)         PyLong_FromString(x,y,z)
+#define PyRRD_Long_Check(x)                   PyLong_Check(x)
 #else
-# define UNUSED(x) x
+#define PyRRD_String_Check(x)                 PyString_Check(x)
+#define PyRRD_String_FromString(x)            PyString_FromString(x)
+#define PyRRD_String_AS_STRING(x)             PyString_AS_STRING(x)
+#define PyRRD_String_FromStringAndSize(x, y)  PyString_FromStringAndSize(x, y)
+#define PyRRD_String_Size(x)                  PyString_Size(x)
+#define PyRRD_Int_FromLong(x)                 PyInt_FromLong(x)
+#define PyRRD_Int_FromString(x, y, z)         PyInt_FromString(x,y,z)
+#define PyRRD_Long_Check(x)                   (PyInt_Check(x) || PyLong_Check(x))
 #endif
 
+#ifndef Py_UNUSED
+#ifdef __GNUC__
+ #define Py_UNUSED(name) _unused_ ## name __attribute__((unused))
+#else
+ #define Py_UNUSED(name) _unused_ ## -name
+#endif
+#endif
 
-#include "rrd_config.h"
-static const char *__version__ = PACKAGE_VERSION;
+/** Binding version. */
+static const char *_version = "0.1.10";
 
-#include "Python.h"
-#include "rrd_tool.h"
-//#include "rrd.h"
-//#include "rrd_extra.h"
+/** Exception types. */
+static PyObject *rrdtool_OperationalError;
+static PyObject *rrdtool_ProgrammingError;
 
-static PyObject *ErrorObject;
+static char **rrdtool_argv = NULL;
+static int    rrdtool_argc = 0;
 
-/* forward declaration to keep compiler happy */
-void      initrrdtool(
-    void);
+/**
+ * PyRRD_DateTime_FromTS: convert UNIX timestamp (time_t)
+ * to Python datetime object.
+ *
+ * @param ts UNIX timestamp (time_t)
+ * @return Pointer to new PyObject (New Reference)
+ */
+static PyObject *
+PyRRD_DateTime_FromTS(time_t ts)
+{
+    PyObject *ret;
+    struct tm lt;
+
+    localtime_r(&ts, &lt);
+
+    ret = PyDateTime_FromDateAndTime(
+        lt.tm_year + 1900,
+        lt.tm_mon + 1,
+        lt.tm_mday,
+        lt.tm_hour,
+        lt.tm_min,
+        lt.tm_sec,
+        0);
 
-static int create_args(
-    char *command,
-    PyObject * args,
-    int *argc,
-    char ***argv)
+    return ret;
+}
+
+/**
+ * PyRRD_String_FromCF: get string representation of CF enum index
+ *
+ * @param cf enum cf_en
+ * @return Null-terminated string
+ */
+const char *
+PyRRD_String_FromCF(enum cf_en cf)
+{
+    switch (cf) {
+        case CF_AVERAGE:
+            return "AVERAGE";
+        case CF_MINIMUM:
+            return "MIN";
+        case CF_MAXIMUM:
+            return "MAX";
+        case CF_LAST:
+            return "LAST";
+        default:
+            return "INVALID";
+    }
+}
+
+/**
+ * Helper function to convert Python objects into a representation that the
+ * rrdtool functions can work with.
+ *
+ * @param command RRDtool command name
+ * @param args Command arguments
+ * @return Zero if the function succeeds, otherwise -1
+ */
+static int
+convert_args(char *command, PyObject *args)
 {
     PyObject *o, *lo;
-    int       args_count,
-              argv_count,
-              element_count,
-              i, j;
+    int i, j, args_count, argv_count, element_count;
 
+    argv_count = element_count = 0;
     args_count = PyTuple_Size(args);
-    element_count = 0;
+
     for (i = 0; i < args_count; i++) {
         o = PyTuple_GET_ITEM(args, i);
-        if (PyString_Check(o))
+
+        if (PyRRD_String_Check(o))
             element_count++;
         else if (PyList_CheckExact(o))
-                element_count += PyList_Size(o);
-             else {
-                 PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i);
-                 return -1;
-             }
+            element_count += PyList_Size(o);
+        else {
+            PyErr_Format(PyExc_TypeError,
+                         "Argument %d must be str or a list of str", i);
+            return -1;
+        }
     }
-   
-    *argv = PyMem_New(char *,
-                      element_count + 1);
 
-    if (*argv == NULL)
+    rrdtool_argv = PyMem_New(char *, element_count + 1);
+
+    if (rrdtool_argv == NULL)
         return -1;
 
-    argv_count = 0;
     for (i = 0; i < args_count; i++) {
         o = PyTuple_GET_ITEM(args, i);
-        if (PyString_Check(o)) {
-            argv_count++;
-            (*argv)[argv_count] = PyString_AS_STRING(o);
-        } else if (PyList_CheckExact(o))
-                   for (j = 0; j < PyList_Size(o); j++) {
-                       lo = PyList_GetItem(o, j);
-                       if (PyString_Check(lo)) {
-                           argv_count++;
-                           (*argv)[argv_count] = PyString_AS_STRING(lo);
-                       } else {
-                             PyMem_Del(*argv);
-                             PyErr_Format(PyExc_TypeError, "element %d in argument %d must be string", j, i);
-                             return -1;
-                       }
-                   }
-               else {
-                   PyMem_Del(*argv);
-                   PyErr_Format(PyExc_TypeError, "argument %d must be string or list of strings", i);
-                   return -1;
-               }
+
+        if (PyRRD_String_Check(o))
+            rrdtool_argv[++argv_count] = PyRRD_String_AS_STRING(o);
+        else if (PyList_CheckExact(o)) {
+            for (j = 0; j < PyList_Size(o); j++) {
+                lo = PyList_GetItem(o, j);
+
+                if (PyRRD_String_Check(lo))
+                    rrdtool_argv[++argv_count] = PyRRD_String_AS_STRING(lo);
+                else {
+                    PyMem_Del(rrdtool_argv);
+                    PyErr_Format(PyExc_TypeError,
+                      "Element %d in argument %d must be str", j, i);
+                    return -1;
+                }
+            }
+        } else {
+            PyMem_Del(rrdtool_argv);
+            PyErr_Format(rrdtool_ProgrammingError,
+              "Argument %d must be str or list of str", i);
+            return -1;
+        }
     }
 
-    (*argv)[0] = command;
-    *argc = element_count + 1;
+    rrdtool_argv[0] = command;
+    rrdtool_argc = element_count + 1;
 
     return 0;
 }
 
-static void destroy_args(
-    char ***argv)
+/**
+ * Destroy argument vector.
+ */
+static void
+destroy_args(void)
 {
-    PyMem_Del(*argv);
-    *argv = NULL;
+    PyMem_Del(rrdtool_argv);
+    rrdtool_argv = NULL;
 }
 
-static char PyRRD_create__doc__[] =
-    "create(args..): Set up a new Round Robin Database\n\
-    create filename [--start|-b start time] \
-[--step|-s step] [DS:ds-name:DST:heartbeat:min:max] \
-[RRA:CF:xff:steps:rows]";
+/**
+ * Convert RRDtool info to dict.
+ *
+ * @param data RRDtool info object
+ * @return Python dict object
+ */
+static PyObject *
+_rrdtool_util_info2dict(const rrd_info_t *data)
+{
+    PyObject *dict, *val;
 
-static PyObject *PyRRD_create(
-    PyObject UNUSED(*self),
-    PyObject * args)
+    dict = PyDict_New();
+
+    while (data) {
+        val = NULL;
+
+        switch (data->type) {
+            case RD_I_VAL:
+                if (isnan(data->value.u_val)) {
+                    Py_INCREF(Py_None);
+                    val = Py_None;
+                } else
+                    PyFloat_FromDouble(data->value.u_val);
+                break;
+
+            case RD_I_CNT:
+                val = PyLong_FromUnsignedLong(data->value.u_cnt);
+                break;
+
+            case RD_I_INT:
+                val = PyLong_FromLong(data->value.u_int);
+                break;
+
+            case RD_I_STR:
+                val = PyRRD_String_FromString(data->value.u_str);
+                break;
+
+            case RD_I_BLO:
+                val = PyRRD_String_FromStringAndSize(
+                    (char *)data->value.u_blo.ptr,
+                    data->value.u_blo.size);
+                break;
+            default:
+                break;
+        }
+
+        if (val != NULL) {
+            PyDict_SetItemString(dict, data->key, val);
+            Py_DECREF(val);
+        }
+
+        data = data->next;
+    }
+
+    return dict;
+}
+
+static char _rrdtool_create__doc__[] = "Create a new Round Robin Database.\n\n\
+  Usage: create(args...)\n\
+  Arguments:\n\n\
+    filename\n\
+    [-b|--start start time]\n\
+    [-s|--step step]\n\
+    [-t|--template template-file]\n\
+    [-r|--source source-file]\n\
+    [-O|--no-overwrite]\n\
+    [-d|--daemon address]\n\
+    [DS:ds-name[=mapped-ds-name[source-index]]:DST:heartbeat:min:max]\n\
+    [RRA:CF:xff:steps:rows]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html";
+
+static PyObject *
+_rrdtool_create(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    char    **argv;
-    int       argc, status;
+    PyObject *ret;
+    int status;
 
-    if (create_args("create", args, &argc, &argv) < 0)
+    if (convert_args("create", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_create(argc, argv);
+    status = rrd_create(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
     if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
         Py_INCREF(Py_None);
-        r = Py_None;
+        ret = Py_None;
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-static char PyRRD_update__doc__[] =
-    "update(args..): Store a new set of values into the rrd\n"
-    "    update filename [--template|-t ds-name[:ds-name]...] "
-    "N|timestamp:value[:value...] [timestamp:value[:value...] ...]";
+static char _rrdtool_dump__doc__[] = "Dump an RRD to XML.\n\n\
+  Usage: dump(args..)\n\
+  Arguments:\n\n\
+    [-h|--header {none,xsd,dtd}\n\
+    [-n|--no-header]\n\
+    [-d|--daemon address]\n\
+    file.rrd\n\
+    [file.xml]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrddump.en.html";
+
+static PyObject *
+_rrdtool_dump(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *ret;
+    int status;
+
+    if (convert_args("dump", args) == -1)
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    status = rrd_dump(rrdtool_argc, rrdtool_argv);
+    Py_END_ALLOW_THREADS
+
+    if (status != 0) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        Py_INCREF(Py_None);
+        ret = Py_None;
+    }
 
-static PyObject *PyRRD_update(
-    PyObject UNUSED(*self),
-    PyObject * args)
+    destroy_args();
+    return ret;
+}
+
+static char _rrdtool_update__doc__[] = "Store a new set of values into\
+ the RRD.\n\n\
+ Usage: update(args..)\n\
+ Arguments:\n\n\
+   filename\n\
+   [--template|-t ds-name[:ds-name]...]\n\
+   N|timestamp:value[:value...]\n\
+   [timestamp:value[:value...] ...]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdupdate.en.html";
+
+static PyObject *
+_rrdtool_update(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    char    **argv;
-    int       argc, status;
+    PyObject *ret;
+    int status;
 
-    if (create_args("update", args, &argc, &argv) < 0)
+    if (convert_args("update", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_update(argc, argv);
+    status = rrd_update(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
     if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
         Py_INCREF(Py_None);
-        r = Py_None;
+        ret = Py_None;
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-static char PyRRD_fetch__doc__[] =
-    "fetch(args..): fetch data from an rrd.\n"
-    "    fetch filename CF [--resolution|-r resolution] "
-    "[--start|-s start] [--end|-e end]";
+static char _rrdtool_updatev__doc__[] = "Store a new set of values into "\
+  "the Round Robin Database and return an info dictionary.\n\n\
+  This function works in the same manner as 'update', but will return an\n\
+  info dictionary instead of None.";
 
-static PyObject *PyRRD_fetch(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static PyObject *
+_rrdtool_updatev(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    rrd_value_t *data, *datai;
-    unsigned long step, ds_cnt;
-    time_t    start, end;
-    int       argc, status;
-    char    **argv, **ds_namv;
+    PyObject *ret;
+    rrd_info_t *data;
 
-    if (create_args("fetch", args, &argc, &argv) < 0)
+    if (convert_args("updatev", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_fetch(argc, argv, &start, &end, &step, &ds_cnt, &ds_namv, &data);
+    data = rrd_update_v(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
-    if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (data == NULL) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        /* Return :
-           ((start, end, step), (name1, name2, ...), [(data1, data2, ..), ...]) */
-        PyObject *range_tup, *dsnam_tup, *data_list, *t;
-        unsigned long i, j, row;
-        rrd_value_t dv;
+        ret = _rrdtool_util_info2dict(data);
+        rrd_info_free(data);
+    }
 
-        row = (end - start) / step;
+    destroy_args();
+    return ret;
+}
 
-        r = PyTuple_New(3);
+static char _rrdtool_fetch__doc__[] = "Fetch data from an RRD.\n\n\
+  Usage: fetch(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    CF\n\
+    [-r|--resolution resolution]\n\
+    [-s|--start start]\n\
+    [-e|--end end]\n\
+    [-a|--align-start]\n\
+    [-d|--daemon address]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdfetch.en.html";
+
+static PyObject *
+_rrdtool_fetch(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *ret, *range_tup, *dsnam_tup, *data_list, *t;
+    rrd_value_t *data, *datai, dv;
+    unsigned long step, ds_cnt, i, j, row;
+    time_t start, end;
+    char **ds_namv;
+    int status;
+
+    if (convert_args("fetch", args) == -1)
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    status = rrd_fetch(rrdtool_argc, rrdtool_argv, &start, &end, &step,
+                       &ds_cnt, &ds_namv, &data);
+    Py_END_ALLOW_THREADS
+
+    if (status == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        row = (end - start) / step;
+        ret = PyTuple_New(3);
         range_tup = PyTuple_New(3);
         dsnam_tup = PyTuple_New(ds_cnt);
         data_list = PyList_New(row);
-        PyTuple_SET_ITEM(r, 0, range_tup);
-        PyTuple_SET_ITEM(r, 1, dsnam_tup);
-        PyTuple_SET_ITEM(r, 2, data_list);
+
+        PyTuple_SET_ITEM(ret, 0, range_tup);
+        PyTuple_SET_ITEM(ret, 1, dsnam_tup);
+        PyTuple_SET_ITEM(ret, 2, data_list);
 
         datai = data;
 
-        PyTuple_SET_ITEM(range_tup, 0, PyInt_FromLong((long) start));
-        PyTuple_SET_ITEM(range_tup, 1, PyInt_FromLong((long) end));
-        PyTuple_SET_ITEM(range_tup, 2, PyInt_FromLong((long) step));
+        PyTuple_SET_ITEM(range_tup, 0, PyRRD_Int_FromLong((long) start));
+        PyTuple_SET_ITEM(range_tup, 1, PyRRD_Int_FromLong((long) end));
+        PyTuple_SET_ITEM(range_tup, 2, PyRRD_Int_FromLong((long) step));
 
         for (i = 0; i < ds_cnt; i++)
-            PyTuple_SET_ITEM(dsnam_tup, i, PyString_FromString(ds_namv[i]));
+            PyTuple_SET_ITEM(dsnam_tup, i, PyRRD_String_FromString(ds_namv[i]));
 
         for (i = 0; i < row; i++) {
             t = PyTuple_New(ds_cnt);
@@ -251,616 +462,995 @@ static PyObject *PyRRD_fetch(
                 if (isnan(dv)) {
                     PyTuple_SET_ITEM(t, j, Py_None);
                     Py_INCREF(Py_None);
-                } else {
+                } else
                     PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
-                }
             }
         }
 
         for (i = 0; i < ds_cnt; i++)
             rrd_freemem(ds_namv[i]);
-        rrd_freemem(ds_namv);   /* rrdtool don't use PyMem_Malloc :) */
+
+        rrd_freemem(ds_namv);
         rrd_freemem(data);
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
+}
+
+static char _rrdtool_flushcached__doc__[] = "Flush RRD files from memory.\n\n\
+  Usage: flushcached(args..)\n\
+  Arguments:\n\n\
+    [-d|--daemon address]\n\
+    filename\n\
+    [filename ...]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdflushcached.en.html";
+
+static PyObject *
+_rrdtool_flushcached(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *ret;
+    int status;
+
+    if (convert_args("flushcached", args) == -1)
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    status = rrd_flushcached(rrdtool_argc, rrdtool_argv);
+    Py_END_ALLOW_THREADS
+
+    if (status != 0) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else {
+        Py_INCREF(Py_None);
+        ret = Py_None;
+    }
+
+    destroy_args();
+    return ret;
 }
 
 #ifdef HAVE_RRD_GRAPH
 
-static char PyRRD_graph__doc__[] =
-    "graph(args..): Create a graph based on data from one or several RRD\n"
-    "    graph filename [-s|--start seconds] "
-    "[-e|--end seconds] [-x|--x-grid x-axis grid and label] "
-    "[-y|--y-grid y-axis grid and label] [--alt-y-grid] [--alt-y-mrtg] "
-    "[--alt-autoscale] [--alt-autoscale-max] [--units-exponent] value "
-    "[-v|--vertical-label text] [-w|--width pixels] [-h|--height pixels] "
-    "[-i|--interlaced] "
-    "[-f|--imginfo formatstring] [-a|--imgformat GIF|PNG|GD] "
-    "[-B|--background value] [-O|--overlay value] "
-    "[-U|--unit value] [-z|--lazy] [-o|--logarithmic] "
-    "[-u|--upper-limit value] [-l|--lower-limit value] "
-    "[-g|--no-legend] [-r|--rigid] [--step value] "
-    "[-b|--base value] [-c|--color COLORTAG#rrggbb] "
-    "[-t|--title title] [DEF:vname=rrd:ds-name:CF] "
-    "[CDEF:vname=rpn-expression] [PRINT:vname:CF:format] "
-    "[GPRINT:vname:CF:format] [COMMENT:text] "
-    "[HRULE:value#rrggbb[:legend]] [VRULE:time#rrggbb[:legend]] "
-    "[LINE{1|2|3}:vname[#rrggbb[:legend]]] "
-    "[AREA:vname[#rrggbb[:legend]]] " "[STACK:vname[#rrggbb[:legend]]]";
-
-static PyObject *PyRRD_graph(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_graph__doc__[] = "Create a graph based on one or more " \
+  "RRDs.\n\n\
+  Usage: graph(args..)\n\
+  Arguments:\n\n\
+    filename | -\n\
+    [-s|--start start]\n\
+    [-e|--end end]\n\
+    [-S|--step step]\n\
+    [-t|--title string]\n\
+    [-v|--vertical-label string]\n\
+    [-w|--width pixels]\n\
+    [-h|--height pixels]\n\
+    [-j|--only-graph]\n\
+    [-D|--full-size-mode]\n\
+    [-u|--upper-limit value]\n\
+    [-l|--lower-limit value]\n\
+    [-r|--rigid]\n\
+    [-A|--alt-autoscale]\n\
+    [-J|--alt-autoscale-min]\n\
+    [-M|--alt-autoscale-max]\n\
+    [-N|--no-gridfit]\n\
+    [-x|--x-grid (GTM:GST:MTM:MST:LTM:LST:LPR:LFM|none)]\n\
+    [-y|--y-grid (grid step:label factor|none)]\n\
+    [--week-fmt strftime format string]\n\
+    [--left-axis-formatter formatter-name]\n\
+    [--left-axis-format format-string]\n\
+    [-Y|--alt-y-grid]\n\
+    [-o|--logarithmic]\n\
+    [-X|--units-exponent value]\n\
+    [-L|--units-length value]\n\
+    [--units=si]\n\
+    [--right-axis scale:shift]\n\
+    [--right-axis-label label]\n\
+    [--right-axis-format format-string]\n\
+    [-g|--no-legend]\n\
+    [-F|--force-rules-legend]\n\
+    [--legend-position=(north|south|west|east)]\n\
+    [--legend-direction=(topdown|bottomup)]\n\
+    [-z|--lazy]\n\
+    [-d|--daemon address]\n\
+    [-f|--imginfo printfstr]\n\
+    [-c|--color COLORTAG#rrggbb[aa]]\n\
+    [--grid-dash on:off]\n\
+    [--border width]\n\
+    [--dynamic-labels]\n\
+    [-m|--zoom factor]\n\
+    [-n|--font FONTTAG:size:[font]]\n\
+    [-R|--font-render-mode {normal,light,mono}]\n\
+    [-B|--font-smoothing-threshold size]\n\
+    [-P|--pango-markup]\n\
+    [-G|--graph-render-mode {normal,mono}]\n\
+    [-E|--slope-mode]\n\
+    [-a|--imgformat {PNG,SVG,EPS,PDF,XML,XMLENUM,JSON,JSONTIME,CSV,TSV,SSV}]\n\
+    [-i|--interlaced]\n\
+    [-T|--tabwidth value]\n\
+    [-b|--base value]\n\
+    [-W|--watermark string]\n\
+    [-Z|--use-nan-for-all-missing-data]\n\
+    DEF:vname=rrdfile:ds-name:CF[:step=step][:start=time][:end=time]\n\
+    CDEF:vname=RPN expression\n\
+    VDEF=vname:RPN expression\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
+
+static PyObject *
+_rrdtool_graph(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    char    **argv, **calcpr;
-    int       argc, status, xsize, ysize, i;
-    double    ymin, ymax;
+    PyObject *ret;
+    int xsize, ysize, i, status;
+    double ymin, ymax;
+    char **calcpr;
 
-    if (create_args("graph", args, &argc, &argv) < 0)
+    if (convert_args("graph", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_graph(argc, argv, &calcpr, &xsize, &ysize, NULL, &ymin, &ymax);
+    status = rrd_graph(rrdtool_argc, rrdtool_argv, &calcpr, &xsize, &ysize,
+                       NULL, &ymin, &ymax);
     Py_END_ALLOW_THREADS
 
     if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        r = PyTuple_New(3);
+        ret = PyTuple_New(3);
 
-        PyTuple_SET_ITEM(r, 0, PyInt_FromLong((long) xsize));
-        PyTuple_SET_ITEM(r, 1, PyInt_FromLong((long) ysize));
+        PyTuple_SET_ITEM(ret, 0, PyRRD_Int_FromLong((long) xsize));
+        PyTuple_SET_ITEM(ret, 1, PyRRD_Int_FromLong((long) ysize));
 
         if (calcpr) {
             PyObject *e, *t;
 
             e = PyList_New(0);
-            PyTuple_SET_ITEM(r, 2, e);
+            PyTuple_SET_ITEM(ret, 2, e);
 
             for (i = 0; calcpr[i]; i++) {
-                t = PyString_FromString(calcpr[i]);
+                t = PyRRD_String_FromString(calcpr[i]);
                 PyList_Append(e, t);
                 Py_DECREF(t);
                 rrd_freemem(calcpr[i]);
             }
-            rrd_freemem(calcpr);
         } else {
             Py_INCREF(Py_None);
-            PyTuple_SET_ITEM(r, 2, Py_None);
+            PyTuple_SET_ITEM(ret, 2, Py_None);
         }
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-#endif /* HAVE_RRD_GRAPH */
-
-static char PyRRD_tune__doc__[] =
-    "tune(args...): Modify some basic properties of a Round Robin Database\n"
-    "    tune filename [--heartbeat|-h ds-name:heartbeat] "
-    "[--minimum|-i ds-name:min] [--maximum|-a ds-name:max] "
-    "[--data-source-type|-d ds-name:DST] [--data-source-rename|-r old-name:new-name]";
+static char _rrdtool_graphv__doc__[] = "Create a graph based on one or more " \
+  "RRDs and return data in RRDtool info format.\n\n\
+  This function works the same way as 'graph', but will return a info\n\
+  dictionary instead of None.\n\n\
+  Full documentation can be found at (graphv section):\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html";
 
-static PyObject *PyRRD_tune(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static PyObject *
+_rrdtool_graphv(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    char    **argv;
-    int       argc, status;
+    PyObject *ret;
+    rrd_info_t *data;
 
-    if (create_args("tune", args, &argc, &argv) < 0)
+    if (convert_args("graphv", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_tune(argc, argv);
+    data = rrd_graph_v(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
-    if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (data == NULL) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        Py_INCREF(Py_None);
-        r = Py_None;
+        ret = _rrdtool_util_info2dict(data);
+        rrd_info_free(data);
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-static char PyRRD_first__doc__[] =
-    "first(filename): Return the timestamp of the first data sample in an RRD";
-
-static PyObject *PyRRD_first(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_xport__doc__[] = "Dictionary representation of data " \
+  "stored in RRDs.\n\n\
+  Usage: xport(args..)\n\
+  Arguments:\n\n\
+    [-s[--start seconds]\n\
+    [-e|--end seconds]\n\
+    [-m|--maxrows rows]\n\
+    [--step value]\n\
+    [--json]\n\
+    [--enumds]\n\
+    [--daemon address]\n\
+    [DEF:vname=rrd:ds-name:CF]\n\
+    [CDEF:vname=rpn-expression]\n\
+    [XPORT:vname[:legend]]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdxport.en.html";
+
+static PyObject *
+_rrdtool_xport(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc, ts;
-    char    **argv;
+    PyObject *ret;
+    int xsize, status;
+    char **legend_v;
+    time_t start, end;
+    unsigned long step, col_cnt;
+    rrd_value_t *data, *datai;
 
-    if (create_args("first", args, &argc, &argv) < 0)
+    if (convert_args("xport", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    ts = rrd_first(argc, argv);
+    status = rrd_xport(rrdtool_argc, rrdtool_argv, &xsize, &start, &end, &step,
+                       &col_cnt, &legend_v, &data);
     Py_END_ALLOW_THREADS
 
-    if (ts == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (status == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
-    } else
-        r = PyInt_FromLong((long) ts);
+        ret = NULL;
+    } else {
+        PyObject *meta_dict, *data_list, *legend_list, *t;
+        rrd_value_t dv;
+        unsigned long i, j, row_cnt = (end - start) / step;
 
-    destroy_args(&argv);
-    return r;
-}
+        ret = PyDict_New();
+        meta_dict = PyDict_New();
+        legend_list = PyList_New(col_cnt);
+        data_list = PyList_New(row_cnt);
 
-static char PyRRD_last__doc__[] =
-    "last(filename): Return the timestamp of the last data sample in an RRD";
+        PyDict_SetItem(ret, PyRRD_String_FromString("meta"), meta_dict);
+        PyDict_SetItem(ret, PyRRD_String_FromString("data"), data_list);
 
-static PyObject *PyRRD_last(
-    PyObject UNUSED(*self),
-    PyObject * args)
-{
-    PyObject *r;
-    int       argc, ts;
-    char    **argv;
+        datai = data;
 
-    if (create_args("last", args, &argc, &argv) < 0)
-        return NULL;
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("start"),
+            PyRRD_Int_FromLong((long) start));
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("end"),
+            PyRRD_Int_FromLong((long) end));
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("step"),
+            PyRRD_Int_FromLong((long) step));
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("rows"),
+            PyRRD_Int_FromLong((long) row_cnt));
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("columns"),
+            PyRRD_Int_FromLong((long) col_cnt));
+        PyDict_SetItem(meta_dict,
+            PyRRD_String_FromString("legend"),
+            legend_list);
+
+        for (i = 0; i < col_cnt; i++)
+            PyList_SET_ITEM(legend_list, i, PyRRD_String_FromString(legend_v[i]));
 
-    Py_BEGIN_ALLOW_THREADS
-    ts = rrd_last(argc, argv);
-    Py_END_ALLOW_THREADS
+        for (i = 0; i < row_cnt; i++) {
+            t = PyTuple_New(col_cnt);
+            PyList_SET_ITEM(data_list, i, t);
 
-    if (ts == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
-        rrd_clear_error();
-        r = NULL;
-    } else
-        r = PyInt_FromLong((long) ts);
+            for (j = 0; j < col_cnt; j++) {
+                dv = *(datai++);
+
+                if (isnan(dv)) {
+                    PyTuple_SET_ITEM(t, j, Py_None);
+                    Py_INCREF(Py_None);
+                } else {
+                    PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
+                }
+            }
+        }
+
+        for (i = 0; i < col_cnt; i++)
+            rrd_freemem(legend_v[i]);
+
+        rrd_freemem(legend_v);
+        rrd_freemem(data);
+    }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+
+    return ret;
 }
 
-static char PyRRD_resize__doc__[] =
-    "resize(args...): alters the size of an RRA.\n"
-    "    resize filename rra-num GROW|SHRINK rows";
+#endif /* HAVE_RRD_GRAPH */
+
+static char _rrdtool_list__doc__[] = "List RRDs in storage.\n\n" \
+  "Usage: list(args..)\n\
+  Arguments:\n\n\
+    dirname\n\
+    [--daemon HOST]";
 
-static PyObject *PyRRD_resize(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static PyObject *
+_rrdtool_list(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    char    **argv;
-    int       argc, ts;
+    PyObject *ret, *str;
+    char *data, *ptr, *end;
 
-    if (create_args("resize", args, &argc, &argv) < 0)
+    if (convert_args("list", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    ts = rrd_resize(argc, argv);
+    data = rrd_list(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
-    if (ts == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (data == NULL) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        Py_INCREF(Py_None);
-        r = Py_None;
-    }
-
-    destroy_args(&argv);
-    return r;
-}
+        ret = PyList_New(0);
+        ptr = data;
+        end = strchr(ptr, '\n');
+
+        while (end) {
+            *end = '\0';
+            str = PyRRD_String_FromString(ptr);
+
+            if (PyList_Append(ret, str)) {
+                PyErr_SetString(rrdtool_OperationalError, "Failed to append to list");
+                ret = NULL;
+                break;
+            }
 
-static PyObject *PyDict_FromInfo(
-    rrd_info_t * data)
-{
-    PyObject *r;
+            ptr = end + 1;
 
-    r = PyDict_New();
-    while (data) {
-        PyObject *val = NULL;
+            if (strlen(ptr) == 0)
+                break;
 
-        switch (data->type) {
-        case RD_I_VAL:
-            val = isnan(data->value.u_val)
-                ? (Py_INCREF(Py_None), Py_None)
-                : PyFloat_FromDouble(data->value.u_val);
-            break;
-        case RD_I_CNT:
-            val = PyLong_FromUnsignedLong(data->value.u_cnt);
-            break;
-        case RD_I_INT:
-            val = PyLong_FromLong(data->value.u_int);
-            break;
-        case RD_I_STR:
-            val = PyString_FromString(data->value.u_str);
-            break;
-        case RD_I_BLO:
-            val =
-                PyString_FromStringAndSize((char *) data->value.u_blo.ptr,
-                                           data->value.u_blo.size);
-            break;
+            end = strchr(ptr, '\n');
         }
-        if (val) {
-            PyDict_SetItemString(r, data->key, val);
-            Py_DECREF(val);
-        }
-        data = data->next;
+
+        rrd_freemem(data);
     }
-    return r;
-}
 
-static char PyRRD_info__doc__[] =
-    "info(filename): extract header information from an rrd";
+    destroy_args();
+    return ret;
+}
 
-static PyObject *PyRRD_info(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_tune__doc__[] = "Modify some basic properties of a " \
+  "Round Robin Database.\n\n\
+  Usage: tune(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    [-h|--heartbeat ds-name:heartbeat]\n\
+    [-i|--minimum ds-name:min]\n\
+    [-a|--maximum ds-name:max]\n\
+    [-d|--data-source-type ds-name:DST]\n\
+    [-r|--data-source-rename old-name:new-name]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdtune.en.html";
+
+static PyObject *
+_rrdtool_tune(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc;
-    char    **argv;
-    rrd_info_t *data;
+    PyObject *ret;
+    int status;
 
-    if (create_args("info", args, &argc, &argv) < 0)
+    if (convert_args("tune", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    data = rrd_info(argc, argv);
+    status = rrd_tune(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
-    if (data == NULL) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (status == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        r = PyDict_FromInfo(data);
-        rrd_info_free(data);
+        Py_INCREF(Py_None);
+        ret = Py_None;
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-static char PyRRD_list__doc__[] =
-       "list(dirname): list RRDs in storage"
-       "    list [--daemon <remote>] dirname";
-
-static PyObject *PyRRD_list(PyObject UNUSED(*self), PyObject * args)
+static char _rrdtool_first__doc__[] = "Get the first UNIX timestamp of the "\
+  "first data sample in an Round Robin Database.\n\n\
+  Usage: first(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    [--rraindex number]\n\
+    [-d|--daemon address]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdfirst.en.html";
+
+static PyObject *
+_rrdtool_first(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r = NULL;
-    int       argc;
-    char    **argv;
-    char     *data;
-    char     *ptr;
-    char     *end;
-    PyObject *str;
-
-    if (create_args("list", args, &argc, &argv) < 0)
-        return NULL;
+    PyObject *ret;
+    int ts;
 
-    if ((data = rrd_list(argc, argv)) == NULL) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
-        rrd_clear_error();
-
-       goto out_free_args;
-    }
-
-       r = PyList_New(0);
+    if (convert_args("first", args) == -1)
+        return NULL;
 
-       ptr = data;
-       end = strchr(ptr, '\n');
+    Py_BEGIN_ALLOW_THREADS
+    ts = rrd_first(rrdtool_argc, rrdtool_argv);
+    Py_END_ALLOW_THREADS
 
-       while (end) {
-         *end = '\0';
-         str = PyString_FromString(ptr);
+    if (ts == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else
+        ret = PyRRD_Int_FromLong((long) ts);
 
-         if (PyList_Append(r, str)) {
-           PyErr_SetString(ErrorObject, "Failed to append to list");
-           r = NULL;
-           break;
-         }
+    destroy_args();
+    return ret;
+}
 
-         ptr = end + 1;
+static char _rrdtool_last__doc__[] = "Get the UNIX timestamp of the most "\
+  "recent data sample in an Round Robin Database.\n\n\
+  Usage: last(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    [-d|--daemon address]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html";
+
+static PyObject *
+_rrdtool_last(PyObject *Py_UNUSED(self), PyObject *args)
+{
+    PyObject *ret;
+    int ts;
 
-       if (strlen(ptr) == 0)
-               break;
+    if (convert_args("last", args) == -1)
+        return NULL;
 
-         end = strchr(ptr, '\n');
-       }
+    Py_BEGIN_ALLOW_THREADS
+    ts = rrd_last(rrdtool_argc, rrdtool_argv);
+    Py_END_ALLOW_THREADS
 
-    rrd_freemem(data);
+    if (ts == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
+        rrd_clear_error();
+        ret = NULL;
+    } else
+        ret = PyRRD_Int_FromLong((long) ts);
 
-out_free_args:
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-#ifdef HAVE_RRD_GRAPH
-static char PyRRD_graphv__doc__[] =
-    "graphv is called in the same manner as graph";
-
-static PyObject *PyRRD_graphv(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_resize__doc__[] = "Modify the number of rows in a "\
+ "Round Robin Database.\n\n\
+  Usage: resize(args..)\n\
+  Arguments:\n\n\
+    filename\n\
+    rra-num\n\
+    GROW|SHRINK\n\
+    rows\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdlast.en.html";
+
+static PyObject *
+_rrdtool_resize(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc;
-    char    **argv;
-    rrd_info_t *data;
+    PyObject *ret;
+    int status;
 
-    if (create_args("graphv", args, &argc, &argv) < 0)
+    if (convert_args("resize", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    data = rrd_graph_v(argc, argv);
+    status = rrd_resize(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
-    if (data == NULL) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+    if (status == -1) {
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        r = PyDict_FromInfo(data);
-        rrd_info_free(data);
+        Py_INCREF(Py_None);
+        ret = Py_None;
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-#endif /* HAVE_RRD_GRAPH */
-
-static char PyRRD_updatev__doc__[] =
-    "updatev is called in the same manner as update";
-
-static PyObject *PyRRD_updatev(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_info__doc__[] = "Extract header information from an "\
+ "Round Robin Database.\n\n\
+  Usage: info(filename, ...)\n\
+  Arguments:\n\n\
+    filename\n\
+    [-d|--daemon address]\n\
+    [-F|--noflush]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdinfo.en.html";
+
+static PyObject *
+_rrdtool_info(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc;
-    char    **argv;
+    PyObject *ret;
     rrd_info_t *data;
 
-    if (create_args("updatev", args, &argc, &argv) < 0)
+    if (convert_args("info", args) == -1)
         return NULL;
 
     Py_BEGIN_ALLOW_THREADS
-    data = rrd_update_v(argc, argv);
+    data = rrd_info(rrdtool_argc, rrdtool_argv);
     Py_END_ALLOW_THREADS
 
     if (data == NULL) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        r = PyDict_FromInfo(data);
+        ret = _rrdtool_util_info2dict(data);
         rrd_info_free(data);
     }
 
-    destroy_args(&argv);
-    return r;
+    destroy_args();
+    return ret;
 }
 
-static char PyRRD_flushcached__doc__[] =
-  "flush(args..): flush RRD files from memory\n"
-  "   flush [--daemon address] file [file ...]";
-
-static PyObject *PyRRD_flushcached(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_lastupdate__doc__[] = "Returns datetime and value stored "\
+ "for each datum in the most recent update of an RRD.\n\n\
+  Usage: lastupdate(filename, ...)\n\
+  Arguments:\n\n\
+    filename\n\
+    [-d|--daemon address]\n\n\
+  Full documentation can be found at:\n\
+  http://oss.oetiker.ch/rrdtool/doc/rrdlastupdate.en.html";
+
+static PyObject *
+_rrdtool_lastupdate(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc, status;
-    char    **argv;
+    PyObject *ret, *ds_dict, *lastupd;
+    int status;
+    time_t last_update;
+    char **ds_names, **last_ds;
+    unsigned long ds_cnt, i;
 
-    if (create_args("flushcached", args, &argc, &argv) < 0)
+    if (convert_args("lastupdate", args) == -1)
         return NULL;
+    else if (rrdtool_argc < 2) {
+        PyErr_SetString(rrdtool_ProgrammingError, "Missing filename argument");
+        return NULL;
+    }
 
     Py_BEGIN_ALLOW_THREADS
-    status = rrd_flushcached(argc, argv);
+    status = rrd_lastupdate_r(rrdtool_argv[1],
+                              &last_update,
+                              &ds_cnt,
+                              &ds_names,
+                              &last_ds);
     Py_END_ALLOW_THREADS
 
     if (status != 0) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
+        PyErr_SetString(rrdtool_OperationalError, rrd_get_error());
         rrd_clear_error();
-        r = NULL;
+        ret = NULL;
     } else {
-        Py_INCREF(Py_None);
-        r = Py_None;
+        /* convert last_update to Python datetime object */
+        ret = PyDict_New();
+        ds_dict = PyDict_New();
+        lastupd = PyRRD_DateTime_FromTS(last_update);
+
+        PyDict_SetItemString(ret, "date", lastupd);
+        PyDict_SetItemString(ret, "ds", ds_dict);
+
+        Py_DECREF(lastupd);
+        Py_DECREF(ds_dict);
+
+        for (i = 0; i < ds_cnt; i++) {
+            PyObject* val = Py_None;
+
+            double num;
+            if (sscanf(last_ds[i], "%lf", &num) == 1) {
+                val = PyFloat_FromDouble(num);
+            }
+
+            if (!val)
+                return NULL;
+
+            PyDict_SetItemString(ds_dict, ds_names[i], val);
+            Py_DECREF(val);
+
+            free(last_ds[i]);
+            free(ds_names[i]);
+        }
+
+        free(last_ds);
+        free(ds_names);
+
     }
 
-    destroy_args(&argv);
-    return r;
-}
+    destroy_args();
 
-#ifdef HAVE_RRD_GRAPH
+    return ret;
+}
 
-static char PyRRD_xport__doc__[] =
-    "xport(args..): dictionary representation of data stored in RRDs\n"
-    "    [-s|--start seconds] [-e|--end seconds] [-m|--maxrows rows]"
-    "[--step value] [--daemon address] [DEF:vname=rrd:ds-name:CF]"
-    "[CDEF:vname=rpn-expression] [XPORT:vname[:legend]]";
 
+/** An Python object which will hold an callable for fetch callbacks */
+static PyObject *_rrdtool_fetch_callable = NULL;
 
-static PyObject *PyRRD_xport(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static int
+_rrdtool_fetch_cb_wrapper(
+    const char *filename,
+    enum cf_en cf_idx,
+    time_t *start,
+    time_t *end,
+    unsigned long *step,
+    unsigned long *ds_cnt,
+    char ***ds_namv,
+    rrd_value_t **data)
 {
-    PyObject *r;
-    int       argc, status, xsize;
-    char    **argv, **legend_v;
-    time_t    start, end;
-    unsigned long step, col_cnt;
-    rrd_value_t *data, *datai;
+    PyObject *args;
+    PyObject *kwargs;
+    PyObject *ret = NULL;
+    PyObject *tmp;
+    PyObject *tmp_min_ts;
+    PyGILState_STATE gstate;
+    Py_ssize_t rowcount = 0;
+    int rc = -1;
+    unsigned int i, ii;
+
+    gstate = PyGILState_Ensure();
+
+    if (_rrdtool_fetch_callable == NULL) {
+        rrd_set_error("use rrdtool.register_fetch_cb to register a fetch callback");
+        goto gil_release_err;
+    }
 
-    if (create_args("xport", args, &argc, &argv) < 0)
-        return NULL;
+    args = PyTuple_New(0);
+    kwargs = PyDict_New();
 
-    Py_BEGIN_ALLOW_THREADS
-    status = rrd_xport(argc, argv, &xsize, &start, &end, &step, &col_cnt, &legend_v, &data);
-    Py_END_ALLOW_THREADS
+    /* minimum possible UNIX datetime */
+    tmp_min_ts = PyLong_FromLong(0);
 
-    if (status == -1) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
-        rrd_clear_error();
-        r = NULL;
+    PyObject *po_filename = PyRRD_String_FromString(filename);
+    PyDict_SetItemString(kwargs, "filename", po_filename);
+    Py_DECREF(po_filename);
+
+    PyObject *po_cfstr = PyRRD_String_FromString(PyRRD_String_FromCF(cf_idx));
+    PyDict_SetItemString(kwargs, "cf", po_cfstr);
+    Py_DECREF(po_cfstr);
+
+    PyObject *po_start = PyLong_FromLong(*start);
+    PyDict_SetItemString(kwargs, "start", po_start);
+    Py_DECREF(po_start);
+
+    PyObject *po_end = PyLong_FromLong(*end);
+    PyDict_SetItemString(kwargs, "end", po_end);
+    Py_DECREF(po_end);
+
+    PyObject *po_step = PyLong_FromUnsignedLong(*step);
+    PyDict_SetItemString(kwargs, "step", po_step);
+    Py_DECREF(po_step);
+
+    /* execute Python callback method */
+    ret = PyObject_Call(_rrdtool_fetch_callable, args, kwargs);
+    Py_DECREF(args);
+    Py_DECREF(kwargs);
+
+    if (ret == NULL) {
+        rrd_set_error("calling python callback failed");
+        goto gil_release_err;
+    }
+
+    /* handle return value of callback */
+    if (!PyDict_Check(ret)) {
+        rrd_set_error("expected callback method to be a dict");
+        goto gil_release_err;
+    }
+
+    tmp = PyDict_GetItemString(ret, "step");
+    if (tmp == NULL) {
+        rrd_set_error("expected 'step' key in callback return value");
+        goto gil_release_err;
+    } else if (!PyRRD_Long_Check(tmp)) {
+        rrd_set_error("the 'step' key in callback return value must be int");
+        goto gil_release_err;
+    } else
+        *step = PyLong_AsLong(tmp);
+
+    tmp = PyDict_GetItemString(ret, "start");
+    if (tmp == NULL) {
+        rrd_set_error("expected 'start' key in callback return value");
+        goto gil_release_err;
+    } else if (!PyRRD_Long_Check(tmp)) {
+        rrd_set_error("expected 'start' key in callback return value to be "
+            "of type int");
+        goto gil_release_err;
+    } else if (PyObject_RichCompareBool(tmp, tmp_min_ts, Py_EQ) || 
+               PyObject_RichCompareBool(tmp, po_start, Py_LT)) {
+        rrd_set_error("expected 'start' value in callback return dict to be "
+            "equal or earlier than passed start timestamp");
+        goto gil_release_err;
     } else {
-        PyObject *meta_dict, *data_list, *legend_list, *t;
-        unsigned long i, j;
-        rrd_value_t dv;
+        *start = PyLong_AsLong(po_start);
 
-        unsigned long row_cnt = ((end - start) / step);
+        if (*start == -1) {
+            rrd_set_error("expected 'start' value in callback return value to"
+                " not exceed LONG_MAX");
+            goto gil_release_err;
+        }
+    }
 
-        r = PyDict_New();
-        meta_dict = PyDict_New();
-        legend_list = PyList_New(col_cnt);
-        data_list = PyList_New(row_cnt);
-        PyDict_SetItem(r, PyString_FromString("meta"), meta_dict);
-        PyDict_SetItem(r, PyString_FromString("data"), data_list);
+    tmp = PyDict_GetItemString(ret, "data");
+    if (tmp == NULL) {
+        rrd_set_error("expected 'data' key in callback return value");
+        goto gil_release_err;
+    } else if (!PyDict_Check(tmp)) {
+        rrd_set_error("expected 'data' key in callback return value of type "
+            "dict");
+        goto gil_release_err;
+    } else {
+        *ds_cnt = (unsigned long)PyDict_Size(tmp);
+        *ds_namv = (char **)calloc(*ds_cnt, sizeof(char *));
 
-        datai = data;
+        if (*ds_namv == NULL) {
+            rrd_set_error("an error occured while allocating memory for "
+                "ds_namv when allocating memory for python callback");
+            goto gil_release_err;
+        }
+
+        PyObject *key, *value;
+        Py_ssize_t pos = 0;  /* don't use pos for indexing */
+        unsigned int x = 0;
+
+        while (PyDict_Next(tmp, &pos, &key, &value)) {
+            char *key_str = PyRRD_String_AS_STRING(key);
+
+            if (key_str == NULL) {
+                rrd_set_error("key of 'data' element from callback return "
+                    "value is not a string");
+                goto gil_release_free_dsnamv_err;
+            } else if (strlen(key_str) > DS_NAM_SIZE) {
+                rrd_set_error("key '%s' longer than the allowed maximum of %d "
+                    "byte", key_str, DS_NAM_SIZE - 1);
+                goto gil_release_free_dsnamv_err;
+            }
+
+            if ((((*ds_namv)[x]) = (char *)malloc(sizeof(char) * DS_NAM_SIZE)) == NULL) {
+                rrd_set_error("malloc fetch ds_namv entry");
+                goto gil_release_free_dsnamv_err;
+            }
+
+            strncpy((*ds_namv)[x], key_str, DS_NAM_SIZE - 1);
+            (*ds_namv)[x][DS_NAM_SIZE - 1] = '\0';
 
-        PyDict_SetItem(meta_dict, PyString_FromString("start"), PyInt_FromLong((long) start));
-        PyDict_SetItem(meta_dict, PyString_FromString("end"), PyInt_FromLong((long) end));
-        PyDict_SetItem(meta_dict, PyString_FromString("step"), PyInt_FromLong((long) step));
-        PyDict_SetItem(meta_dict, PyString_FromString("rows"), PyInt_FromLong((long) row_cnt));
-        PyDict_SetItem(meta_dict, PyString_FromString("columns"), PyInt_FromLong((long) col_cnt));
-        PyDict_SetItem(meta_dict, PyString_FromString("legend"), legend_list);
+            if (!PyList_Check(value)) {
+                rrd_set_error("expected 'data' dict values in callback return "
+                    "value of type list");
+                goto gil_release_free_dsnamv_err;
+            } else if (PyList_Size(value) > rowcount)
+                rowcount = PyList_Size(value);
 
-        for (i = 0; i < col_cnt; i++) {
-            PyList_SET_ITEM(legend_list, i, PyString_FromString(legend_v[i]));
+            ++x;
         }
 
-        for (i = 0; i < row_cnt; i++) {
-            t = PyTuple_New(col_cnt);
-            PyList_SET_ITEM(data_list, i, t);
+        *end = *start + *step * rowcount;
 
-            for (j = 0; j < col_cnt; j++) {
-                dv = *(datai++);
-                if (isnan(dv)) {
-                    PyTuple_SET_ITEM(t, j, Py_None);
-                    Py_INCREF(Py_None);
-                } else {
-                    PyTuple_SET_ITEM(t, j, PyFloat_FromDouble((double) dv));
+        if (((*data) = (rrd_value_t *)malloc(*ds_cnt * rowcount * sizeof(rrd_value_t))) == NULL) {
+            rrd_set_error("malloc fetch data area");
+            goto gil_release_free_dsnamv_err;
+        }
+
+        for (i = 0; i < *ds_cnt; i++) {
+            for (ii = 0; ii < (unsigned int)rowcount; ii++) {
+                char *ds_namv_i = (*ds_namv)[i];
+                double va;
+                PyObject *lstv = PyList_GetItem(PyDict_GetItemString(tmp, ds_namv_i), ii);
+
+                /* lstv may be NULL here in case an IndexError has been raised;
+                   in such case the rowcount is higher than the number of elements for
+                   the list of that ds. use DNAN as value for these then */
+                if (lstv == NULL || lstv == Py_None) {
+                    if (lstv == NULL)
+                        PyErr_Clear();
+                    va = DNAN;
+                }
+                else {
+                    va = PyFloat_AsDouble(lstv);
+                    if (va == -1.0 && PyErr_Occurred()) {
+                        PyObject *exc_type, *exc_value, *exc_value_str = NULL, *exc_tb;
+                        PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+
+                        if (exc_value != NULL) {
+                            exc_value_str = PyObject_Str(exc_value);
+                            char *exc_str = PyRRD_String_AS_STRING(exc_value_str);
+                            rrd_set_error(exc_str);
+                            Py_DECREF(exc_value);
+                        }
+
+                        Py_DECREF(exc_type);
+                        Py_DECREF(exc_value_str);
+                        if (exc_tb != NULL)
+                            Py_DECREF(exc_tb);
+                        goto gil_release_free_dsnamv_err;
+                    }
                 }
+
+                (*data)[i + ii * (*ds_cnt)] = va;
             }
         }
+    }
 
-        for (i = 0; i < col_cnt; i++) {
-            rrd_freemem(legend_v[i]);
+    /* success */
+    rc = 1;
+    goto gil_release;
+
+gil_release_free_dsnamv_err:
+    for (i = 0; i < *ds_cnt; i++) {
+        if ((*ds_namv)[i]) {
+            free((*ds_namv)[i]);
         }
-        rrd_freemem(legend_v);
-        rrd_freemem(data);
     }
-    destroy_args(&argv);
-    return r;
-}
 
-#endif
+    free(*ds_namv);
+
+gil_release_err:
+    rc = -1;
 
-static char PyRRD_dump__doc__[] =
-    "dump - dump an RRD to XML\n"
-    "[--header|-h {none,xsd,dtd}] [--no-header]file.rrd [file.xml]";
+gil_release:
+    if (ret != NULL)
+        Py_DECREF(ret);
+    PyGILState_Release(gstate);
+    return rc;
+}
 
-static PyObject *PyRRD_dump(
-    PyObject UNUSED(*self),
-    PyObject * args)
+static char _rrdtool_register_fetch_cb__doc__[] = "Register callback for "
+    "fetching data";
+
+static PyObject *
+_rrdtool_register_fetch_cb(PyObject *Py_UNUSED(self), PyObject *args)
 {
-    PyObject *r;
-    int       argc, status;
-    char    **argv;
+    PyObject *callable;
 
-    if (create_args("dump", args, &argc, &argv) < 0)
+    if (!PyArg_ParseTuple(args, "O", &callable))
+        return NULL;
+    else if (!PyCallable_Check(callable)) {
+        PyErr_SetString(rrdtool_ProgrammingError, "first argument must be callable");
         return NULL;
+    } else {
+        _rrdtool_fetch_callable = callable;
+        rrd_fetch_cb_register(_rrdtool_fetch_cb_wrapper);
+        Py_RETURN_NONE;
+    }
+}
 
-    Py_BEGIN_ALLOW_THREADS
-    status = rrd_dump(argc, argv);
-    Py_END_ALLOW_THREADS
+static char _rrdtool_clear_fetch_cb__doc__[] = "Clear callback for "
+    "fetching data";
 
-    if (status != 0) {
-        PyErr_SetString(ErrorObject, rrd_get_error());
-        rrd_clear_error();
-        r = NULL;
-    } else {
-        Py_INCREF(Py_None);
-        r = Py_None;
+static PyObject *
+_rrdtool_clear_fetch_cb(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+    if (_rrdtool_fetch_callable == NULL) {
+        PyErr_SetString(rrdtool_ProgrammingError, "no callback set");
+        return NULL;
     }
 
-    destroy_args(&argv);
-    return r;
+    _rrdtool_fetch_callable = NULL;
+    rrd_fetch_cb_register(NULL);
+    Py_RETURN_NONE;
+}
+
+static char _rrdtool_lib_version__doc__[] = "Get the version this binding "\
+  "was compiled against.";
+
+/**
+ * Returns a str object that contains the librrd version.
+ *
+ * @return librrd version (Python str object)
+ */
+static PyObject *
+_rrdtool_lib_version(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
+{
+    return PyRRD_String_FromString(rrd_strversion());
 }
 
-/* List of methods defined in the module */
-#define meth(name, func, doc) {name, (PyCFunction)func, METH_VARARGS, doc}
-
-static PyMethodDef _rrdtool_methods[] = {
-    meth("create", PyRRD_create, PyRRD_create__doc__),
-    meth("update", PyRRD_update, PyRRD_update__doc__),
-    meth("fetch", PyRRD_fetch, PyRRD_fetch__doc__),
-    meth("tune", PyRRD_tune, PyRRD_tune__doc__),
-    meth("first", PyRRD_first, PyRRD_first__doc__),
-    meth("last", PyRRD_last, PyRRD_last__doc__),
-    meth("resize", PyRRD_resize, PyRRD_resize__doc__),
-    meth("info", PyRRD_info, PyRRD_info__doc__),
-    meth("list", PyRRD_list, PyRRD_list__doc__),
-#ifdef HAVE_RRD_GRAPH    
-    meth("graph", PyRRD_graph, PyRRD_graph__doc__),
-    meth("graphv", PyRRD_graphv, PyRRD_graphv__doc__),
-    meth("xport", PyRRD_xport, PyRRD_xport__doc__),
+/** Method table. */
+static PyMethodDef rrdtool_methods[] = {
+    {"create", (PyCFunction)_rrdtool_create,
+     METH_VARARGS, _rrdtool_create__doc__},
+    {"dump", (PyCFunction)_rrdtool_dump,
+     METH_VARARGS, _rrdtool_dump__doc__},
+    {"update", (PyCFunction)_rrdtool_update,
+     METH_VARARGS, _rrdtool_update__doc__},
+    {"updatev", (PyCFunction)_rrdtool_updatev,
+     METH_VARARGS, _rrdtool_updatev__doc__},
+    {"fetch", (PyCFunction)_rrdtool_fetch,
+     METH_VARARGS, _rrdtool_fetch__doc__},
+    {"flushcached", (PyCFunction)_rrdtool_flushcached,
+     METH_VARARGS, _rrdtool_flushcached__doc__},
+#ifdef HAVE_RRD_GRAPH
+    {"graph", (PyCFunction)_rrdtool_graph,
+     METH_VARARGS, _rrdtool_graph__doc__},
+    {"graphv", (PyCFunction)_rrdtool_graphv,
+     METH_VARARGS, _rrdtool_graphv__doc__},
+    {"xport", (PyCFunction)_rrdtool_xport,
+     METH_VARARGS, _rrdtool_xport__doc__},
 #endif
-    meth("updatev", PyRRD_updatev, PyRRD_updatev__doc__),
-    meth("flushcached", PyRRD_flushcached, PyRRD_flushcached__doc__),
-    meth("dump", PyRRD_dump, PyRRD_dump__doc__),
+    {"list", (PyCFunction)_rrdtool_list,
+     METH_VARARGS, _rrdtool_list__doc__},
+    {"tune", (PyCFunction)_rrdtool_tune,
+     METH_VARARGS, _rrdtool_tune__doc__},
+    {"first", (PyCFunction)_rrdtool_first,
+     METH_VARARGS, _rrdtool_first__doc__},
+    {"last", (PyCFunction)_rrdtool_last,
+     METH_VARARGS, _rrdtool_last__doc__},
+    {"resize", (PyCFunction)_rrdtool_resize,
+     METH_VARARGS, _rrdtool_resize__doc__},
+    {"info", (PyCFunction)_rrdtool_info,
+     METH_VARARGS, _rrdtool_info__doc__},
+    {"lastupdate", (PyCFunction)_rrdtool_lastupdate,
+     METH_VARARGS, _rrdtool_lastupdate__doc__},
+    {"register_fetch_cb", (PyCFunction)_rrdtool_register_fetch_cb,
+     METH_VARARGS, _rrdtool_register_fetch_cb__doc__},
+    {"clear_fetch_cb", (PyCFunction)_rrdtool_clear_fetch_cb,
+     METH_NOARGS, _rrdtool_clear_fetch_cb__doc__},
+    {"lib_version", (PyCFunction)_rrdtool_lib_version,
+     METH_NOARGS, _rrdtool_lib_version__doc__},
     {NULL, NULL, 0, NULL}
 };
 
-#define SET_INTCONSTANT(dict, value) \
-            t = PyInt_FromLong((long)value); \
-            PyDict_SetItemString(dict, #value, t); \
-            Py_DECREF(t);
-#define SET_STRCONSTANT(dict, value) \
-            t = PyString_FromString(value); \
-            PyDict_SetItemString(dict, #value, t); \
-            Py_DECREF(t);
-
-/* Initialization function for the module */
-void initrrdtool(
-    void)
+/** Library init function. */
+#ifdef HAVE_PY3K
+static struct PyModuleDef rrdtoolmodule = {
+    .m_base = PyModuleDef_HEAD_INIT,
+    .m_name = "rrdtool",
+    .m_doc = "Python bindings for rrdtool",
+    .m_size = -1,
+    .m_methods = rrdtool_methods
+};
+
+#endif
+
+#ifdef HAVE_PY3K
+PyMODINIT_FUNC
+PyInit_rrdtool(void)
+#else
+void
+initrrdtool(void)
+#endif
 {
-    PyObject *m, *d, *t;
+    PyObject *m;
+
+    PyDateTime_IMPORT;  /* initialize PyDateTime_ functions */
 
-    /* Create the module and add the functions */
-    m = Py_InitModule("rrdtool", _rrdtool_methods);
+#ifdef HAVE_PY3K
+    m = PyModule_Create(&rrdtoolmodule);
+#else
+    m = Py_InitModule3("rrdtool",
+                       rrdtool_methods,
+                       "Python bindings for rrdtool");
+#endif
+
+    if (m == NULL)
+#ifdef HAVE_PY3K
+        return NULL;
+#else
+        return;
+#endif
 
-    /* Add some symbolic constants to the module */
-    d = PyModule_GetDict(m);
+    rrdtool_ProgrammingError = PyErr_NewException("rrdtool.ProgrammingError",
+                                                  NULL, NULL);
+    Py_INCREF(rrdtool_ProgrammingError);
+    PyModule_AddObject(m, "ProgrammingError", rrdtool_ProgrammingError);
 
-    SET_STRCONSTANT(d, __version__);
-    ErrorObject = PyErr_NewException("rrdtool.error", NULL, NULL);
-    PyDict_SetItemString(d, "error", ErrorObject);
+    rrdtool_OperationalError = PyErr_NewException("rrdtool.OperationalError",
+                                                  NULL, NULL);
+    Py_INCREF(rrdtool_OperationalError);
+    PyModule_AddObject(m, "OperationalError", rrdtool_OperationalError);
+    PyModule_AddStringConstant(m, "__version__", _version);
 
-    /* Check for errors */
-    if (PyErr_Occurred())
-        Py_FatalError("can't initialize the rrdtool module");
+#ifdef HAVE_PY3K
+    return m;
+#endif
 }
-
-/*
- * $Id: _rrdtoolmodule.c,v 1.14 2003/02/22 07:41:19 perky Exp $
- * ex: ts=8 sts=4 et
- */
index b8fbad88814ccaef5532fbc1a2d340af607f563e..d98095bd2d2babd9d7a654c170b360e837ab34ba 100644 (file)
@@ -1,60 +1,52 @@
-#! /usr/bin/env python
-#
-# setup.py
-#
-# py-rrdtool distutil setup
-#
-# Author  : Hye-Shik Chang <perky@fallin.lv>
-# Date    : $Date: 2003/02/14 02:38:16 $
-# Created : 24 May 2002
-#
-# $Revision: 1.7 $
-#
-#  ==========================================================================
-#  This file is part of py-rrdtool.
-#
-#  py-rrdtool is free software; you can redistribute it and/or modify
-#  it under the terms of the GNU Lesser General Public License as published
-#  by the Free Software Foundation; either version 2 of the License, or
-#  (at your option) any later version.
-#
-#  py-rrdtool is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU Lesser General Public License for more details.
-#
-#  You should have received a copy of the GNU Lesser General Public License
-#  along with Foobar; if not, write to the Free Software
-#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-#
+#!/usr/bin/env python
+import os
 
 try:
-    # Attempt to build using Distribute, which also supports bdist_wheel
-    from setuptools import setup
-    from setuptools.extension import Extension
+    from setuptools import setup, Extension
 except ImportError:
-    from distutils.core import setup, Extension
-import sys, os
+    sys.exit('The setup requires setuptools.')
 
 TOP_SRCDIR = os.environ.get('ABS_TOP_SRCDIR', '../..')
 TOP_BUILDDIR = os.environ.get('ABS_TOP_BUILDDIR', '../..')
 
-setup(name = "py-rrdtool",
-      version = "0.2.2",
-      description = "Python Interface to RRDTool",
-      author = "Hye-Shik Chang",
-      author_email = "perky@fallin.lv",
-      license = "LGPL",
-      url = "http://oss.oetiker.ch/rrdtool",
-      #packages = ['rrdtool'],
-      ext_modules = [
-          Extension(
-            "rrdtool",
-            ["rrdtoolmodule.c"],
-            libraries=['rrd'],
-            library_dirs=[ os.path.join(TOP_BUILDDIR, 'src', '.libs') ],
-            include_dirs=[ os.path.join(TOP_BUILDDIR, 'src'),
-                           os.path.join(TOP_SRCDIR, 'src') ],
-          )
-      ]
-)
+# package version
+package_version = '0.1.10'
+
+
+def main():
+    module = Extension('rrdtool',
+                       sources=['rrdtoolmodule.c'],
+                       library_dirs=[os.path.join(TOP_BUILDDIR, 'src', '.libs')],
+                       include_dirs=[os.path.join(TOP_BUILDDIR, 'src'),
+                                     os.path.join(TOP_SRCDIR, 'src')],
+                       libraries=['rrd'])
+
+    kwargs = dict(
+        name='rrdtool',
+        version=package_version,
+        description='Python bindings for rrdtool',
+        keywords=['rrdtool'],
+        author='Christian Kroeger, Hye-Shik Chang',
+        author_email='commx@commx.ws',
+        license='LGPL',
+        url='https://github.com/commx/python-rrdtool',
+        classifiers=['License :: OSI Approved',
+                     'Operating System :: POSIX',
+                     'Operating System :: Unix',
+                     'Operating System :: MacOS',
+                     'Programming Language :: C',
+                     'Programming Language :: Python',
+                     'Programming Language :: Python :: 2.7',
+                     'Programming Language :: Python :: 3.3',
+                     'Programming Language :: Python :: 3.4',
+                     'Programming Language :: Python :: 3.5',
+        ],
+        ext_modules=[module],
+        test_suite='tests'
+    )
+
+    setup(**kwargs)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/bindings/python/tests/__init__.py b/bindings/python/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/bindings/python/tests/test_fetch_cb.py b/bindings/python/tests/test_fetch_cb.py
new file mode 100644 (file)
index 0000000..184df88
--- /dev/null
@@ -0,0 +1,78 @@
+import base64
+import math
+import os
+import rrdtool
+import unittest
+import sys
+
+PY3 = sys.version_info >= (3, 0)
+
+
+class TestFetchCallback(unittest.TestCase):
+    if not PY3:
+        def assertRaisesRegex(self, *args, **kwargs):
+            return self.assertRaisesRegexp(*args, **kwargs)
+
+    def setUp(self):
+        self.graphv_args = [
+            '-',
+            '--title', 'Callback Demo',
+            '--start', '1424540800',
+            '--end', 'start+24h',
+            '--lower-limit=0',
+            '--interlaced',
+            '--imgformat', 'PNG',
+            '--width=450',
+            'DEF:a=cb//extrainfo:a:AVERAGE',
+            'DEF:b=cb//:b:AVERAGE',
+            'DEF:c=cb//:c:AVERAGE',
+            'LINE:a#00b6e4:a',
+            'LINE:b#10b634:b',
+            'LINE:c#503d14:c',
+            'VDEF:av=a,AVERAGE',
+            'PRINT:av:%8.6lf'
+        ]
+
+    def test_callback_return_type(self):
+        """
+        Test whether callback return type is checked correctly.
+        The callback must always return a dict.
+        """
+        def my_callback(*args, **kwargs):
+            return None
+
+        rrdtool.register_fetch_cb(my_callback)
+
+        self.assertRaisesRegex(
+            rrdtool.OperationalError,
+            'expected callback method to be a dict',
+            rrdtool.graphv,
+            self.graphv_args
+        )
+
+    def test_callback_args(self):
+        """
+        Test whether all required arguments are passed in kwargs.
+        """
+        def my_callback(*args, **kwargs):
+            required_args = ('filename', 'cf', 'start', 'end', 'step')
+            for k in required_args:
+                self.assertIn(k, kwargs)
+
+            items = int((kwargs['end'] - kwargs['start']) / kwargs['step'])
+            return {
+                'start': kwargs['start'],
+                'step': 300,
+                'data': {
+                    'a': [math.sin(x / 200) for x in range(0, items)],
+                    'b': [math.cos(x / 200) for x in range(10, items)],
+                    'c': [math.sin(x / 100) for x in range(100, items)],
+                }
+            }
+
+        rrdtool.register_fetch_cb(my_callback)
+        rrdtool.graphv(*self.graphv_args)
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
index 89056fbc62a1d98bc88e877b4f002d89bbd6270e..bf3683d61738d9fa699e5b77c55c4c9914ed5f9d 100755 (executable)
--- a/configure
+++ b/configure
@@ -22420,8 +22420,8 @@ $as_echo "$am_cv_python_pyexecdir" >&6; }
 
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for headers required to compile python extensions" >&5
 $as_echo_n "checking for headers required to compile python extensions... " >&6; }
-py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
-py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+py_prefix=`$PYTHON -c "import sys; print(sys.prefix)"`
+py_exec_prefix=`$PYTHON -c "import sys; print(sys.exec_prefix)"`
 PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
 if test "$py_prefix" != "$py_exec_prefix"; then
   PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
index 4b7197c16b5ef5fddc5944d1991defa56b0bed21..e6beff99e65593dde6f78bd551cc5d6bec48beeb 100644 (file)
@@ -426,8 +426,8 @@ AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
 [AC_REQUIRE([AM_PATH_PYTHON])
 AC_MSG_CHECKING(for headers required to compile python extensions)
 dnl deduce PYTHON_INCLUDES
-py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
-py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+py_prefix=`$PYTHON -c "import sys; print(sys.prefix)"`
+py_exec_prefix=`$PYTHON -c "import sys; print(sys.exec_prefix)"`
 PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
 if test "$py_prefix" != "$py_exec_prefix"; then
   PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"