git » libjio » commit ee10f26

Add Python 3 bindings

author Alberto Bertogli
2009-03-27 00:34:19 UTC
committer Alberto Bertogli
2009-03-27 00:53:40 UTC
parent ec2368fcaacd4a325180fbd29eb526c6f8304891

Add Python 3 bindings

This patch adds Python 3 bindings, based on Python 2 bindings.

Signed-off-by: Alberto Bertogli <albertito@blitiri.com.ar>

INSTALL +6 -5
Makefile +11 -1
bindings/python3/libjio.c +688 -0
bindings/python3/setup.py +16 -0

diff --git a/INSTALL b/INSTALL
index f9b2794..ff96287 100644
--- a/INSTALL
+++ b/INSTALL
@@ -28,10 +28,11 @@ Special builds
 Python bindings
 ---------------
 
-In order to build the Python bindings, you should have libjio already
-installed.
-
-To build the bindings, run "make python2". To install them, run
-"make python2_install".
+The library comes with bindings for Python 2 and Python 3. In order to build
+them, you should have libjio already installed.
 
+ - To build the Python 2 bindings, run "make python2". To install them, run
+   "make python2_install".
+ - To build the Python 3 bindings, run "make python3". To install them, run
+   "make python3_install".
 
diff --git a/Makefile b/Makefile
index ea60e21..8128284 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,13 @@ python2:
 python2_install: python2
 	cd bindings/python2 && python setup.py install
 
+python3:
+	cd bindings/python3 && python3 setup.py build
+
+python3_install: python3
+	cd bindings/python3 && python3 setup.py install
+
+
 
 preload: all
 	install -d bindings/preload/build/
@@ -107,9 +114,12 @@ clean:
 	rm -f $(OBJS) libjio.a libjio.so libjio.pc jiofsck.o jiofsck
 	rm -f *.bb *.bbg *.da *.gcov *.gcno *.gcda gmon.out
 	rm -rf bindings/python2/build/
+	rm -rf bindings/python3/build/
 	rm -rf bindings/preload/build/
 
 
-.PHONY: default all install python2 python2_install preload preload_install \
+.PHONY: default all install \
+	python2 python2_install python3 python3_install \
+	preload preload_install \
 	clean
 
diff --git a/bindings/python3/libjio.c b/bindings/python3/libjio.c
new file mode 100644
index 0000000..0b119a6
--- /dev/null
+++ b/bindings/python3/libjio.c
@@ -0,0 +1,688 @@
+
+/*
+ * Python 3 bindings for libjio
+ * Alberto Bertogli (albertito@blitiri.com.ar)
+ */
+
+
+#include <Python.h>
+
+#include <libjio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+/*
+ * This module provides two classes and some functions.
+ *
+ * The classes are jfile (created with open()) and jtrans (created with
+ * jfile.new_trans()).
+ *
+ * The first one represents a journaled file where you operate using read(),
+ * write() and so on; to close it, just call del(). This is similar to the
+ * UNIX file.
+ *
+ * The second one represents a single transaction, which is composed of
+ * several operations that get added by its add() method. It gets commited
+ * with commit(), and rolled back with rollback().
+ *
+ * There rest of the module's functions are related to file checking, called
+ * jfsck() and jfsck_cleanup(), which are just wrappers to the real C
+ * functions.
+ */
+
+/*
+ * Type definitions
+ */
+
+/* jfile */
+typedef struct {
+	PyObject_HEAD
+	struct jfs *fs;
+} jfile_object;
+
+static PyTypeObject jfile_type;
+
+
+/* jtrans */
+typedef struct {
+	PyObject_HEAD
+	struct jtrans *ts;
+	jfile_object *jfile;
+} jtrans_object;
+
+static PyTypeObject jtrans_type;
+
+
+/*
+ * The jfile object
+ */
+
+/* delete */
+static void jf_dealloc(jfile_object *fp)
+{
+	if (fp->fs) {
+		jclose(fp->fs);
+		free(fp->fs);
+	}
+	PyObject_Del(fp);
+}
+
+/* fileno */
+PyDoc_STRVAR(jf_fileno__doc,
+"fileno()\n\
+\n\
+Return the file descriptor number for the file.\n");
+
+static PyObject *jf_fileno(jfile_object *fp, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ":fileno"))
+		return NULL;
+
+	return PyLong_FromLong(fp->fs->fd);
+}
+
+/* read */
+PyDoc_STRVAR(jf_read__doc,
+"read(size)\n\
+\n\
+Read at most size bytes from the file, returns the string with\n\
+the contents.\n\
+It's a wrapper to jread().\n");
+
+static PyObject *jf_read(jfile_object *fp, PyObject *args)
+{
+	long rv;
+	long len;
+	unsigned char *buf;
+	PyObject *r;
+
+	if (!PyArg_ParseTuple(args, "i:read", &len))
+		return NULL;
+
+	buf = malloc(len);
+	if (buf == NULL)
+		return PyErr_NoMemory();
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jread(fp->fs, buf, len);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0) {
+		r = PyErr_SetFromErrno(PyExc_IOError);
+	} else {
+		r = PyBytes_FromStringAndSize((char *) buf, rv);
+	}
+
+	free(buf);
+	return r;
+}
+
+/* pread */
+PyDoc_STRVAR(jf_pread__doc,
+"pread(size, offset)\n\
+\n\
+Read size bytes from the file at the given offset, return a string with the\n\
+contents.\n\
+It's a wrapper to jpread().\n");
+
+static PyObject *jf_pread(jfile_object *fp, PyObject *args)
+{
+	long rv;
+	long len;
+	long long offset;
+	unsigned char *buf;
+	PyObject *r;
+
+	if (!PyArg_ParseTuple(args, "iL:pread", &len, &offset))
+		return NULL;
+
+	buf = malloc(len);
+	if (buf == NULL)
+		return PyErr_NoMemory();
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jpread(fp->fs, buf, len, offset);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0) {
+		r = PyErr_SetFromErrno(PyExc_IOError);
+	} else {
+		r = PyBytes_FromStringAndSize((char *) buf, rv);
+	}
+
+	free(buf);
+	return r;
+}
+
+/* write */
+PyDoc_STRVAR(jf_write__doc,
+"write(buf)\n\
+\n\
+Write the contents of the given buffer (a string) to the file, returns the\n\
+number of bytes written.\n\
+It's a wrapper to jwrite().\n");
+
+static PyObject *jf_write(jfile_object *fp, PyObject *args)
+{
+	long rv;
+	unsigned char *buf;
+	int len;
+
+	if (!PyArg_ParseTuple(args, "s#:write", &buf, &len))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jwrite(fp->fs, buf, len);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* pwrite */
+PyDoc_STRVAR(jf_pwrite__doc,
+"pwrite(buf, offset)\n\
+\n\
+Write the contents of the given buffer (a string) to the file at the given\n\
+offset, returns the number of bytes written.\n\
+It's a wrapper to jpwrite().\n");
+
+static PyObject *jf_pwrite(jfile_object *fp, PyObject *args)
+{
+	long rv;
+	unsigned char *buf;
+	long long offset;
+	int len;
+
+	if (!PyArg_ParseTuple(args, "s#L:pwrite", &buf, &len, &offset))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jpwrite(fp->fs, buf, len, offset);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* truncate */
+PyDoc_STRVAR(jf_truncate__doc,
+"truncate(lenght)\n\
+\n\
+Truncate the file to the given size.\n\
+It's a wrapper to jtruncate().\n");
+
+static PyObject *jf_truncate(jfile_object *fp, PyObject *args)
+{
+	int rv;
+	long long lenght;
+
+	if (!PyArg_ParseTuple(args, "L:truncate", &lenght))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jtruncate(fp->fs, lenght);
+	Py_END_ALLOW_THREADS
+
+	if (rv != 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLongLong(rv);
+}
+
+/* lseek */
+PyDoc_STRVAR(jf_lseek__doc,
+"lseek(offset, whence)\n\
+\n\
+Reposition the file pointer to the given offset, according to the directive\n\
+whence as follows:\n\
+SEEK_SET    The offset is set relative to the beginning of the file.\n\
+SEEK_CUR    The offset is set relative to the current position.\n\
+SEEK_END    The offset is set relative to the end of the file.\n\
+\n\
+These constants are defined in the module. See lseek's manpage for more\n\
+information.\n\
+It's a wrapper to jlseek().\n");
+
+static PyObject *jf_lseek(jfile_object *fp, PyObject *args)
+{
+	long long rv;
+	int whence;
+	long long offset;
+
+	if (!PyArg_ParseTuple(args, "Li:lseek", &offset, &whence))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jlseek(fp->fs, offset, whence);
+	Py_END_ALLOW_THREADS
+
+	if (rv == -1)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLongLong(rv);
+}
+
+/* jsync */
+PyDoc_STRVAR(jf_jsync__doc,
+"jsync()\n\
+\n\
+Used with lingering transactions, see the library documentation for more\n\
+detailed information.\n\
+It's a wrapper to jsync().\n");
+
+static PyObject *jf_jsync(jfile_object *fp, PyObject *args)
+{
+	long rv;
+
+	if (!PyArg_ParseTuple(args, ":jsync"))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jsync(fp->fs);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* jmove_journal */
+PyDoc_STRVAR(jf_jmove_journal__doc,
+"jmove_journal(newpath)\n\
+\n\
+Moves the journal directory to the new path; note that there MUST NOT BE\n\
+anything else operating on the file.\n\
+It's a wrapper to jmove_journal().\n");
+
+static PyObject *jf_jmove_journal(jfile_object *fp, PyObject *args)
+{
+	long rv;
+	char *newpath;
+
+	if (!PyArg_ParseTuple(args, "s:jmove_journal", &newpath))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jmove_journal(fp->fs, newpath);
+	Py_END_ALLOW_THREADS
+
+	if (rv != 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* new_trans */
+PyDoc_STRVAR(jf_new_trans__doc,
+"new_trans()\n\
+\n\
+Returns an object representing a new empty transaction.\n\
+It's a wrapper to jtrans_init().\n");
+
+static PyObject *jf_new_trans(jfile_object *fp, PyObject *args)
+{
+	jtrans_object *tp;
+
+	if (!PyArg_ParseTuple(args, ":new_trans"))
+		return NULL;
+
+	tp = (jtrans_object *) jtrans_type.tp_alloc(&jtrans_type, 0);
+	if (tp == NULL)
+		return NULL;
+
+	tp->ts = malloc(sizeof(struct jtrans));
+	if(tp->ts == NULL) {
+		return PyErr_NoMemory();
+	}
+
+	/* increment the reference count, it's decremented on deletion */
+	tp->jfile = fp;
+	Py_INCREF(fp);
+
+	jtrans_init(fp->fs, tp->ts);
+
+	return (PyObject *) tp;
+}
+
+/* method table */
+static PyMethodDef jfile_methods[] = {
+	{ "fileno", (PyCFunction) jf_fileno, METH_VARARGS, jf_fileno__doc },
+	{ "read", (PyCFunction) jf_read, METH_VARARGS, jf_read__doc },
+	{ "pread", (PyCFunction) jf_pread, METH_VARARGS, jf_pread__doc },
+	{ "write", (PyCFunction) jf_write, METH_VARARGS, jf_write__doc },
+	{ "pwrite", (PyCFunction) jf_pwrite, METH_VARARGS, jf_pwrite__doc },
+	{ "truncate", (PyCFunction) jf_truncate, METH_VARARGS,
+		jf_truncate__doc },
+	{ "lseek", (PyCFunction) jf_lseek, METH_VARARGS, jf_lseek__doc },
+	{ "jsync", (PyCFunction) jf_jsync, METH_VARARGS, jf_jsync__doc },
+	{ "jmove_journal", (PyCFunction) jf_jmove_journal, METH_VARARGS,
+		jf_jmove_journal__doc },
+	{ "new_trans", (PyCFunction) jf_new_trans, METH_VARARGS,
+		jf_new_trans__doc },
+	{ NULL }
+};
+
+static PyTypeObject jfile_type = {
+	PyObject_HEAD_INIT(NULL)
+	.tp_name = "libjio.jfile",
+	.tp_itemsize = sizeof(jfile_object),
+	.tp_dealloc = (destructor) jf_dealloc,
+	.tp_methods = jfile_methods,
+};
+
+
+/*
+ * The jtrans object
+ */
+
+/* delete */
+static void jt_dealloc(jtrans_object *tp)
+{
+	if (tp->ts != NULL) {
+		jtrans_free(tp->ts);
+		free(tp->ts);
+	}
+	Py_DECREF(tp->jfile);
+	PyObject_Del(tp);
+}
+
+/* add */
+PyDoc_STRVAR(jt_add__doc,
+"add(buf, offset)\n\
+\n\
+Add an operation to write the given buffer at the given offset to the\n\
+transaction.\n\
+It's a wrapper to jtrans_add().\n");
+
+static PyObject *jt_add(jtrans_object *tp, PyObject *args)
+{
+	long rv;
+	int len;
+	long long offset;
+	unsigned char *buf;
+
+	if (!PyArg_ParseTuple(args, "s#L:add", &buf, &len, &offset))
+		return NULL;
+
+	rv = jtrans_add(tp->ts, buf, len, offset);
+	if (rv == 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* commit */
+PyDoc_STRVAR(jt_commit__doc,
+"commit()\n\
+\n\
+Commits a transaction.\n\
+It's a wrapper to jtrans_commit().\n");
+
+static PyObject *jt_commit(jtrans_object *tp, PyObject *args)
+{
+	long rv;
+
+	if (!PyArg_ParseTuple(args, ":commit"))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jtrans_commit(tp->ts);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* rollback */
+PyDoc_STRVAR(jt_rollback__doc,
+"rollback()\n\
+\n\
+Rollbacks a transaction.\n\
+It's a wrapper to jtrans_rollback().\n");
+
+static PyObject *jt_rollback(jtrans_object *tp, PyObject *args)
+{
+	long rv;
+
+	if (!PyArg_ParseTuple(args, ":rollback"))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jtrans_rollback(tp->ts);
+	Py_END_ALLOW_THREADS
+
+	if (rv < 0)
+		return PyErr_SetFromErrno(PyExc_IOError);
+
+	return PyLong_FromLong(rv);
+}
+
+/* method table */
+static PyMethodDef jtrans_methods[] = {
+	{ "add", (PyCFunction) jt_add, METH_VARARGS, jt_add__doc },
+	{ "commit", (PyCFunction) jt_commit, METH_VARARGS, jt_commit__doc },
+	{ "rollback", (PyCFunction) jt_rollback, METH_VARARGS, jt_rollback__doc },
+	{ NULL }
+};
+
+static PyTypeObject jtrans_type = {
+	PyObject_HEAD_INIT(NULL)
+	.tp_name = "libjio.jtrans",
+	.tp_itemsize = sizeof(jtrans_object),
+	.tp_dealloc = (destructor) jt_dealloc,
+	.tp_methods = jtrans_methods,
+};
+
+
+/*
+ * The module
+ */
+
+/* open */
+PyDoc_STRVAR(jf_open__doc,
+"open(name[, flags[, mode[, jflags]]])\n\
+\n\
+Opens a file, returns a file object.\n\
+The arguments flags, mode and jflags are the same as jopen(); the constants\n\
+needed are defined in the module.\n\
+It's a wrapper to jopen().\n");
+
+static PyObject *jf_open(PyObject *self, PyObject *args)
+{
+	int rv;
+	char *file;
+	int flags, mode, jflags;
+	jfile_object *fp;
+
+	flags = O_RDWR;
+	mode = 0600;
+	jflags = 0;
+
+	if (!PyArg_ParseTuple(args, "s|iii:open", &file, &flags, &mode,
+				&jflags))
+		return NULL;
+
+	fp = (jfile_object *) jfile_type.tp_alloc(&jfile_type, 0);
+	if (fp == NULL)
+		return NULL;
+
+	fp->fs = malloc(sizeof(struct jfs));
+	if (fp->fs == NULL) {
+		return PyErr_NoMemory();
+	}
+
+	rv = jopen(fp->fs, file, flags, mode, jflags);
+	if (rv < 0) {
+		free(fp->fs);
+		return PyErr_SetFromErrno(PyExc_IOError);
+	}
+
+	if (PyErr_Occurred()) {
+		free(fp->fs);
+		return NULL;
+	}
+
+	return (PyObject *) fp;
+}
+
+/* jfsck */
+PyDoc_STRVAR(jf_jfsck__doc,
+"jfsck(name[, jdir])\n\
+\n\
+Checks the integrity of the file with the given name, using (optionally) jdir\n\
+as the journal directory; returns a dictionary with all the different values\n\
+of the check (equivalent to the 'struct jfsck_result'). If the path is\n\
+incorrect, or there is no journal associated with it, an IOError will be\n\
+raised.\n\
+It's a wrapper to jfsck().\n");
+
+static PyObject *jf_jfsck(PyObject *self, PyObject *args)
+{
+	int rv;
+	char *name, *jdir = NULL;
+	struct jfsck_result res;
+	PyObject *dict;
+
+	if (!PyArg_ParseTuple(args, "s|s:jfsck", &name, &jdir))
+		return NULL;
+
+	dict = PyDict_New();
+	if (dict == NULL)
+		return PyErr_NoMemory();
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jfsck(name, jdir, &res);
+	Py_END_ALLOW_THREADS
+
+	if (rv == J_ENOMEM) {
+		Py_XDECREF(dict);
+		return PyErr_NoMemory();
+	} else if (rv != 0) {
+		Py_XDECREF(dict);
+		PyErr_SetObject(PyExc_IOError, PyLong_FromLong(rv));
+		return NULL;
+	}
+
+	PyDict_SetItemString(dict, "total", PyLong_FromLong(res.total));
+	PyDict_SetItemString(dict, "invalid", PyLong_FromLong(res.invalid));
+	PyDict_SetItemString(dict, "in_progress", PyLong_FromLong(res.in_progress));
+	PyDict_SetItemString(dict, "broken", PyLong_FromLong(res.broken));
+	PyDict_SetItemString(dict, "corrupt", PyLong_FromLong(res.corrupt));
+	PyDict_SetItemString(dict, "apply_error", PyLong_FromLong(res.apply_error));
+	PyDict_SetItemString(dict, "reapplied", PyLong_FromLong(res.reapplied));
+
+	return dict;
+}
+
+/* jfsck_cleanup */
+PyDoc_STRVAR(jf_jfsck_cleanup__doc,
+"jfsck_cleanup(name[, jdir])\n\
+\n\
+Clean the journal directory for the given file using (optionally) jdir as the\n\
+journal directory, and leave it ready to use.\n\
+It's a wrapper to jfsck_cleanup().\n");
+
+static PyObject *jf_jfsck_cleanup(PyObject *self, PyObject *args)
+{
+	long rv;
+	char *name, *jdir = NULL;
+
+	if (!PyArg_ParseTuple(args, "s|s:jfsck_cleanup", &name, &jdir))
+		return NULL;
+
+	Py_BEGIN_ALLOW_THREADS
+	rv = jfsck_cleanup(name, jdir);
+	Py_END_ALLOW_THREADS
+
+	if (rv != 1) {
+		PyErr_SetObject(PyExc_IOError, PyLong_FromLong(rv));
+		return NULL;
+	}
+
+	return PyLong_FromLong(rv);
+}
+
+
+static PyMethodDef module_methods[] = {
+	{ "open", jf_open, METH_VARARGS, jf_open__doc },
+	{ "jfsck", jf_jfsck, METH_VARARGS, jf_jfsck__doc },
+	{ "jfsck_cleanup", jf_jfsck_cleanup, METH_VARARGS,
+		jf_jfsck_cleanup__doc },
+	{ NULL, NULL, 0, NULL },
+};
+
+#define module_doc "libjio is a library to do transactional, journaled I/O\n" \
+	"You can find it at http://blitiri.com.ar/p/libjio/\n" \
+	"\n" \
+	"Use the open() method to create a file object, and then operate " \
+	"on it.\n" \
+	"Please read the documentation for more information.\n"
+
+static PyModuleDef libjio_module = {
+	PyModuleDef_HEAD_INIT,
+	.m_name = "libjio",
+	.m_doc = module_doc,
+	.m_size = -1,
+	.m_methods = module_methods,
+};
+
+PyMODINIT_FUNC PyInit_libjio(void)
+{
+	PyObject *m;
+
+	if (PyType_Ready(&jfile_type) < 0 ||
+			PyType_Ready(&jtrans_type) < 0)
+		return NULL;
+
+	m = PyModule_Create(&libjio_module);
+
+	Py_INCREF(&jfile_type);
+	PyModule_AddObject(m, "jfile", (PyObject *) &jfile_type);
+
+	Py_INCREF(&jtrans_type);
+	PyModule_AddObject(m, "jtrans", (PyObject *) &jtrans_type);
+
+	/* libjio's constants */
+	PyModule_AddIntConstant(m, "J_NOLOCK", J_NOLOCK);
+	PyModule_AddIntConstant(m, "J_NOROLLBACK", J_NOROLLBACK);
+	PyModule_AddIntConstant(m, "J_LINGER", J_LINGER);
+	PyModule_AddIntConstant(m, "J_COMMITTED", J_COMMITTED);
+	PyModule_AddIntConstant(m, "J_ROLLBACKED", J_ROLLBACKED);
+	PyModule_AddIntConstant(m, "J_ROLLBACKING", J_ROLLBACKING);
+	PyModule_AddIntConstant(m, "J_RDONLY", J_RDONLY);
+	PyModule_AddIntConstant(m, "J_ESUCCESS", J_ESUCCESS);
+	PyModule_AddIntConstant(m, "J_ENOENT", J_ENOENT);
+	PyModule_AddIntConstant(m, "J_ENOJOURNAL", J_ENOJOURNAL);
+	PyModule_AddIntConstant(m, "J_ENOMEM", J_ENOMEM);
+
+	/* open constants (at least the POSIX ones) */
+	PyModule_AddIntConstant(m, "O_RDONLY", O_RDONLY);
+	PyModule_AddIntConstant(m, "O_WRONLY", O_WRONLY);
+	PyModule_AddIntConstant(m, "O_RDWR", O_RDWR);
+	PyModule_AddIntConstant(m, "O_CREAT", O_CREAT);
+	PyModule_AddIntConstant(m, "O_EXCL", O_EXCL);
+	PyModule_AddIntConstant(m, "O_TRUNC", O_TRUNC);
+	PyModule_AddIntConstant(m, "O_APPEND", O_APPEND);
+	PyModule_AddIntConstant(m, "O_NONBLOCK", O_NONBLOCK);
+	PyModule_AddIntConstant(m, "O_NDELAY", O_NDELAY);
+	PyModule_AddIntConstant(m, "O_SYNC", O_SYNC);
+	PyModule_AddIntConstant(m, "O_ASYNC", O_ASYNC);
+
+	/* lseek constants */
+	PyModule_AddIntConstant(m, "SEEK_SET", SEEK_SET);
+	PyModule_AddIntConstant(m, "SEEK_CUR", SEEK_CUR);
+	PyModule_AddIntConstant(m, "SEEK_END", SEEK_END);
+
+	return m;
+}
+
diff --git a/bindings/python3/setup.py b/bindings/python3/setup.py
new file mode 100644
index 0000000..3332b82
--- /dev/null
+++ b/bindings/python3/setup.py
@@ -0,0 +1,16 @@
+
+from distutils.core import setup, Extension
+
+libjio = Extension("libjio",
+		libraries = ['jio'],
+		sources = ['libjio.c'])
+
+setup(
+	name = 'libjio',
+	description = "A library for journaled I/O",
+	author="Alberto Bertogli",
+	author_email="albertito@blitiri.com.ar",
+	url="http://blitiri.com.ar/p/libjio",
+	ext_modules = [libjio]
+)
+