[PATCHES] Port tdb to Python 3

Petr Viktorin pviktori at redhat.com
Fri Jun 19 08:17:01 MDT 2015


On 06/12/2015 01:27 AM, Andrew Bartlett wrote:
> On Thu, 2015-06-11 at 14:55 +0200, Petr Viktorin wrote:
>> Hello,
>>
>> Here are initial patches porting tdb & ldb to Python 3. These libraries
>> deal a lot with text-like data, so I expect some discussion around them.
>> I'm not as familiar as others on the list with the use of these
>> libraries, and I didn't find relevant documentation, so I'll write out
>> some of my assumptions here; please correct me if I'm wrong.
>>
>> tdb is routinely used to store binary data, and in my understanding
>> that's its primary use case, so it should primarily have a "bytes"-based
>> interface. That's what these patches add.
>> If a text-based interface is needed in more than a few cases (i.e. if
>> manual encode/decode is expected to be a big pain), it can be added (see
>> below).
> 
> This seems reasonable.  TDB tries to be a pure key-value store, and
> while the values stored are often strings, and the keys are almost
> always strings, these are always cast to data/length pairs in the C
> interface, and the interface is of bytes. 

OK. Here are the TDB patches with a text (unicode) based interface on
top. These let you do e.g. `tdb.text['key']` to work with Unicode strings.
The text wrapper has the same API as a Tdb, so if you work purely with
text, you can open the database with `db = tdb.open(...).text`, and just
use the wrapper. The original bytes-based Tdb object is then in `db.raw`.

I wrote the text wrapper in Python, so as to not repeat all the code.

>> ldb, on the other hand, stores text, and I remember someone on this list
>> mentioned that LDAP is a text-only protocol. Is that really the case?
> 
> No, it isn't.  Yes, LDAP is primarily used for text, and when text is
> transferred over LDAP it is UTF8 encoded (if the server enforces that),
> but LDB doesn't enforce that in the default configuration. 
> 
> However, binary data is routinely transferred.  The C interface is of
> bytes.  The strings stored are not null terminated (but a trailing \0
> after the length is typically added for safety in the libraries, and is
> sadly assumed in too many places).
> 
> The way to know if an attribute is text or binary in proper LDAP is to
> consult the schema, but by default LDB is schema-less. 

OK, that's what I thought. A scheme similar to what I did with tdb here
should work.

> I hope this is of some assistance,

Yes! Thank you!


-- 
Petr Viktorin
-------------- next part --------------
From e42c57d675f1b3b499c328086757ee7c675b9142 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Mon, 8 Jun 2015 19:02:03 +0200
Subject: [PATCH 1/8] buildtools: Don't configure Python more than once

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 buildtools/wafsamba/samba_python.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/buildtools/wafsamba/samba_python.py b/buildtools/wafsamba/samba_python.py
index fb378ae..7df1d12 100644
--- a/buildtools/wafsamba/samba_python.py
+++ b/buildtools/wafsamba/samba_python.py
@@ -9,6 +9,10 @@ from Configure import conf
 @conf
 def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
     # enable tool to build python extensions
+    if conf.env.HAVE_PYTHON_H:
+        conf.check_python_version(version)
+        return
+
     interpreters = []
 
     if conf.env['EXTRA_PYTHON']:
-- 
2.1.0


From 7e3748b39534009020b8ac07590726cab3037508 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 2 Jun 2015 13:27:21 +0200
Subject: [PATCH 2/8] buildtools: Fix crash on invalid --extra-python option

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 buildtools/wafsamba/samba_python.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/buildtools/wafsamba/samba_python.py b/buildtools/wafsamba/samba_python.py
index 7df1d12..8ab077e 100644
--- a/buildtools/wafsamba/samba_python.py
+++ b/buildtools/wafsamba/samba_python.py
@@ -25,7 +25,7 @@ def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
         try:
             conf.check_python_version((3, 3, 0))
         except Exception:
-            warn('extra-python needs to be Python 3.3 or later')
+            Logs.warn('extra-python needs to be Python 3.3 or later')
             raise
         interpreters.append(conf.env['PYTHON'])
         conf.setenv('default')
-- 
2.1.0


From 3c8f8e387e9ab9dd3c21a6813b2c44fb39cdbe12 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Thu, 18 Jun 2015 12:35:49 +0200
Subject: [PATCH 3/8] pytdb: Allow nextkey() to be called

nextkey() was defined to take no arguments but expected one.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/pytdb.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
index 9320799..bf50258 100644
--- a/lib/tdb/pytdb.c
+++ b/lib/tdb/pytdb.c
@@ -480,7 +480,7 @@ static PyMethodDef tdb_object_methods[] = {
 		"Append data to an existing key." },
 	{ "firstkey", (PyCFunction)obj_firstkey, METH_NOARGS, "S.firstkey() -> data\n"
 		"Return the first key in this database." },
-	{ "nextkey", (PyCFunction)obj_nextkey, METH_NOARGS, "S.nextkey(key) -> data\n"
+	{ "nextkey", (PyCFunction)obj_nextkey, METH_VARARGS, "S.nextkey(key) -> data\n"
 		"Return the next key in this database." },
 	{ "delete", (PyCFunction)obj_delete, METH_VARARGS, "S.delete(key) -> None\n"
 		"Delete an entry." },
-- 
2.1.0


From 6703d58e586688995932f64e3871274e8130fa80 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 17:10:34 +0200
Subject: [PATCH 4/8] pytdb: Port to Python 3

- Use bytes for all data, text strings for repr()
- Use PyLong instead of PyInt on py3
- Use new module initialization
- Update tests
- Run tests in "make test"

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/pytdb.c                | 168 ++++++++++++++++++++++++++---------------
 lib/tdb/python/tests/simple.py |  62 +++++++--------
 lib/tdb/wscript                |   5 +-
 3 files changed, 141 insertions(+), 94 deletions(-)

diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
index bf50258..534e613 100644
--- a/lib/tdb/pytdb.c
+++ b/lib/tdb/pytdb.c
@@ -31,13 +31,25 @@
 /* Include tdb headers */
 #include <tdb.h>
 
+#if PY_MAJOR_VERSION >= 3
+#define PyStr_FromString PyUnicode_FromString
+#define PyStr_FromFormat PyUnicode_FromFormat
+#define PyInt_FromLong PyLong_FromLong
+#define PyInt_Check PyLong_Check
+#define PyInt_AsLong PyLong_AsLong
+#define Py_TPFLAGS_HAVE_ITER 0
+#else
+#define PyStr_FromString PyString_FromString
+#define PyStr_FromFormat PyString_FromFormat
+#endif
+
 typedef struct {
 	PyObject_HEAD
 	TDB_CONTEXT *ctx;
 	bool closed;
 } PyTdbObject;
 
-staticforward PyTypeObject PyTdb;
+static PyTypeObject PyTdb;
 
 static void PyErr_SetTDBError(TDB_CONTEXT *tdb)
 {
@@ -45,21 +57,21 @@ static void PyErr_SetTDBError(TDB_CONTEXT *tdb)
 		Py_BuildValue("(i,s)", tdb_error(tdb), tdb_errorstr(tdb)));
 }
 
-static TDB_DATA PyString_AsTDB_DATA(PyObject *data)
+static TDB_DATA PyBytes_AsTDB_DATA(PyObject *data)
 {
 	TDB_DATA ret;
-	ret.dptr = (unsigned char *)PyString_AsString(data);
-	ret.dsize = PyString_Size(data);
+	ret.dptr = (unsigned char *)PyBytes_AsString(data);
+	ret.dsize = PyBytes_Size(data);
 	return ret;
 }
 
-static PyObject *PyString_FromTDB_DATA(TDB_DATA data)
+static PyObject *PyBytes_FromTDB_DATA(TDB_DATA data)
 {
 	if (data.dptr == NULL && data.dsize == 0) {
 		Py_RETURN_NONE;
 	} else {
-		PyObject *ret = PyString_FromStringAndSize((const char *)data.dptr, 
-												   data.dsize);
+		PyObject *ret = PyBytes_FromStringAndSize((const char *)data.dptr,
+												  data.dsize);
 		free(data.dptr);
 		return ret;
     }
@@ -233,11 +245,11 @@ static PyObject *obj_get(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "O", &py_key))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
 
-	return PyString_FromTDB_DATA(tdb_fetch(self->ctx, key));
+	return PyBytes_FromTDB_DATA(tdb_fetch(self->ctx, key));
 }
 
 static PyObject *obj_append(PyTdbObject *self, PyObject *args)
@@ -251,10 +263,10 @@ static PyObject *obj_append(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "OO", &py_key, &py_data))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
-	data = PyString_AsTDB_DATA(py_data);
+	data = PyBytes_AsTDB_DATA(py_data);
 	if (!data.dptr)
 		return NULL;
 
@@ -267,7 +279,7 @@ static PyObject *obj_firstkey(PyTdbObject *self)
 {
 	PyErr_TDB_RAISE_IF_CLOSED(self);
 
-	return PyString_FromTDB_DATA(tdb_firstkey(self->ctx));
+	return PyBytes_FromTDB_DATA(tdb_firstkey(self->ctx));
 }
 
 static PyObject *obj_nextkey(PyTdbObject *self, PyObject *args)
@@ -279,11 +291,11 @@ static PyObject *obj_nextkey(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "O", &py_key))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
 	
-	return PyString_FromTDB_DATA(tdb_nextkey(self->ctx, key));
+	return PyBytes_FromTDB_DATA(tdb_nextkey(self->ctx, key));
 }
 
 static PyObject *obj_delete(PyTdbObject *self, PyObject *args)
@@ -296,7 +308,7 @@ static PyObject *obj_delete(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "O", &py_key))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
 	ret = tdb_delete(self->ctx, key);
@@ -314,7 +326,7 @@ static PyObject *obj_has_key(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "O", &py_key))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
 	ret = tdb_exists(self->ctx, key);
@@ -337,10 +349,10 @@ static PyObject *obj_store(PyTdbObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "OO|i", &py_key, &py_value, &flag))
 		return NULL;
 
-	key = PyString_AsTDB_DATA(py_key);
+	key = PyBytes_AsTDB_DATA(py_key);
 	if (!key.dptr)
 		return NULL;
-	value = PyString_AsTDB_DATA(py_value);
+	value = PyBytes_AsTDB_DATA(py_value);
 	if (!value.dptr)
 		return NULL;
 
@@ -389,7 +401,7 @@ static PyObject *tdb_iter_next(PyTdbIteratorObject *self)
 		return NULL;
 	current = self->current;
 	self->current = tdb_nextkey(self->iteratee->ctx, self->current);
-	ret = PyString_FromTDB_DATA(current);
+	ret = PyBytes_FromTDB_DATA(current);
 	return ret;
 }
 
@@ -538,7 +550,7 @@ static PyObject *obj_get_flags(PyTdbObject *self, void *closure)
 static PyObject *obj_get_filename(PyTdbObject *self, void *closure)
 {
 	PyErr_TDB_RAISE_IF_CLOSED(self);
-	return PyString_FromString(tdb_name(self->ctx));
+	return PyBytes_FromString(tdb_name(self->ctx));
 }
 
 static PyObject *obj_get_seqnum(PyTdbObject *self, void *closure)
@@ -571,9 +583,9 @@ static PyObject *tdb_object_repr(PyTdbObject *self)
 {
 	PyErr_TDB_RAISE_IF_CLOSED(self);
 	if (tdb_get_flags(self->ctx) & TDB_INTERNAL) {
-		return PyString_FromString("Tdb(<internal>)");
+		return PyStr_FromString("Tdb(<internal>)");
 	} else {
-		return PyString_FromFormat("Tdb('%s')", tdb_name(self->ctx));
+		return PyStr_FromFormat("Tdb('%s')", tdb_name(self->ctx));
 	}
 }
 
@@ -581,27 +593,27 @@ static void tdb_object_dealloc(PyTdbObject *self)
 {
 	if (!self->closed)
 		tdb_close(self->ctx);
-	self->ob_type->tp_free(self);
+	Py_TYPE(self)->tp_free(self);
 }
 
 static PyObject *obj_getitem(PyTdbObject *self, PyObject *key)
 {
 	TDB_DATA tkey, val;
 	PyErr_TDB_RAISE_IF_CLOSED(self);
-	if (!PyString_Check(key)) {
-		PyErr_SetString(PyExc_TypeError, "Expected string as key");
+	if (!PyBytes_Check(key)) {
+		PyErr_SetString(PyExc_TypeError, "Expected bytestring as key");
 		return NULL;
 	}
 
-	tkey.dptr = (unsigned char *)PyString_AsString(key);
-	tkey.dsize = PyString_Size(key);
+	tkey.dptr = (unsigned char *)PyBytes_AsString(key);
+	tkey.dsize = PyBytes_Size(key);
 
 	val = tdb_fetch(self->ctx, tkey);
 	if (val.dptr == NULL) {
 		PyErr_SetString(PyExc_KeyError, "No such TDB entry");
 		return NULL;
 	} else {
-		return PyString_FromTDB_DATA(val);
+		return PyBytes_FromTDB_DATA(val);
 	}
 }
 
@@ -610,22 +622,22 @@ static int obj_setitem(PyTdbObject *self, PyObject *key, PyObject *value)
 	TDB_DATA tkey, tval;
 	int ret;
 	PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self);
-	if (!PyString_Check(key)) {
-		PyErr_SetString(PyExc_TypeError, "Expected string as key");
+	if (!PyBytes_Check(key)) {
+		PyErr_SetString(PyExc_TypeError, "Expected bytestring as key");
 		return -1;
 	}
 
-	tkey = PyString_AsTDB_DATA(key);
+	tkey = PyBytes_AsTDB_DATA(key);
 
 	if (value == NULL) { 
 		ret = tdb_delete(self->ctx, tkey);
 	} else { 
-		if (!PyString_Check(value)) {
+		if (!PyBytes_Check(value)) {
 			PyErr_SetString(PyExc_TypeError, "Expected string as value");
 			return -1;
 		}
 
-		tval = PyString_AsTDB_DATA(value);
+		tval = PyBytes_AsTDB_DATA(value);
 
 		ret = tdb_store(self->ctx, tkey, tval, TDB_REPLACE);
 	}
@@ -662,46 +674,78 @@ static PyMethodDef tdb_methods[] = {
 	{ NULL }
 };
 
-void inittdb(void);
-void inittdb(void)
+#define MODULE_DOC "simple key-value database that supports multiple writers."
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+    PyModuleDef_HEAD_INIT,
+    .m_name = "tdb",
+    .m_doc = MODULE_DOC,
+    .m_size = -1,
+    .m_methods = tdb_methods,
+};
+#endif
+
+PyObject* module_init(void);
+PyObject* module_init(void)
 {
 	PyObject *m;
 
 	if (PyType_Ready(&PyTdb) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyTdbIterator) < 0)
-		return;
+		return NULL;
 
-	m = Py_InitModule3("tdb", tdb_methods,
-		"simple key-value database that supports multiple writers.");
+#if PY_MAJOR_VERSION >= 3
+	m = PyModule_Create(&moduledef);
+#else
+	m = Py_InitModule3("tdb", tdb_methods, MODULE_DOC);
+#endif
 	if (m == NULL)
-		return;
-
-	PyModule_AddObject(m, "REPLACE", PyInt_FromLong(TDB_REPLACE));
-	PyModule_AddObject(m, "INSERT", PyInt_FromLong(TDB_INSERT));
-	PyModule_AddObject(m, "MODIFY", PyInt_FromLong(TDB_MODIFY));
-
-	PyModule_AddObject(m, "DEFAULT", PyInt_FromLong(TDB_DEFAULT));
-	PyModule_AddObject(m, "CLEAR_IF_FIRST", PyInt_FromLong(TDB_CLEAR_IF_FIRST));
-	PyModule_AddObject(m, "INTERNAL", PyInt_FromLong(TDB_INTERNAL));
-	PyModule_AddObject(m, "NOLOCK", PyInt_FromLong(TDB_NOLOCK));
-	PyModule_AddObject(m, "NOMMAP", PyInt_FromLong(TDB_NOMMAP));
-	PyModule_AddObject(m, "CONVERT", PyInt_FromLong(TDB_CONVERT));
-	PyModule_AddObject(m, "BIGENDIAN", PyInt_FromLong(TDB_BIGENDIAN));
-	PyModule_AddObject(m, "NOSYNC", PyInt_FromLong(TDB_NOSYNC));
-	PyModule_AddObject(m, "SEQNUM", PyInt_FromLong(TDB_SEQNUM));
-	PyModule_AddObject(m, "VOLATILE", PyInt_FromLong(TDB_VOLATILE));
-	PyModule_AddObject(m, "ALLOW_NESTING", PyInt_FromLong(TDB_ALLOW_NESTING));
-	PyModule_AddObject(m, "DISALLOW_NESTING", PyInt_FromLong(TDB_DISALLOW_NESTING));
-	PyModule_AddObject(m, "INCOMPATIBLE_HASH", PyInt_FromLong(TDB_INCOMPATIBLE_HASH));
-
-	PyModule_AddObject(m, "__docformat__", PyString_FromString("restructuredText"));
-
-	PyModule_AddObject(m, "__version__", PyString_FromString(PACKAGE_VERSION));
+		return NULL;
+
+	PyModule_AddIntConstant(m, "REPLACE", TDB_REPLACE);
+	PyModule_AddIntConstant(m, "INSERT", TDB_INSERT);
+	PyModule_AddIntConstant(m, "MODIFY", TDB_MODIFY);
+
+	PyModule_AddIntConstant(m, "DEFAULT", TDB_DEFAULT);
+	PyModule_AddIntConstant(m, "CLEAR_IF_FIRST", TDB_CLEAR_IF_FIRST);
+	PyModule_AddIntConstant(m, "INTERNAL", TDB_INTERNAL);
+	PyModule_AddIntConstant(m, "NOLOCK", TDB_NOLOCK);
+	PyModule_AddIntConstant(m, "NOMMAP", TDB_NOMMAP);
+	PyModule_AddIntConstant(m, "CONVERT", TDB_CONVERT);
+	PyModule_AddIntConstant(m, "BIGENDIAN", TDB_BIGENDIAN);
+	PyModule_AddIntConstant(m, "NOSYNC", TDB_NOSYNC);
+	PyModule_AddIntConstant(m, "SEQNUM", TDB_SEQNUM);
+	PyModule_AddIntConstant(m, "VOLATILE", TDB_VOLATILE);
+	PyModule_AddIntConstant(m, "ALLOW_NESTING", TDB_ALLOW_NESTING);
+	PyModule_AddIntConstant(m, "DISALLOW_NESTING", TDB_DISALLOW_NESTING);
+	PyModule_AddIntConstant(m, "INCOMPATIBLE_HASH", TDB_INCOMPATIBLE_HASH);
+
+	PyModule_AddStringConstant(m, "__docformat__", "restructuredText");
+
+	PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
 
 	Py_INCREF(&PyTdb);
 	PyModule_AddObject(m, "Tdb", (PyObject *)&PyTdb);
 
 	Py_INCREF(&PyTdbIterator);
+
+    return m;
+}
+
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC PyInit_tdb(void);
+PyMODINIT_FUNC PyInit_tdb(void)
+{
+    return module_init();
+}
+#else
+void inittdb(void);
+void inittdb(void)
+{
+    module_init();
 }
+#endif
diff --git a/lib/tdb/python/tests/simple.py b/lib/tdb/python/tests/simple.py
index 4751f9b..39c319a 100644
--- a/lib/tdb/python/tests/simple.py
+++ b/lib/tdb/python/tests/simple.py
@@ -75,25 +75,25 @@ class SimpleTdbTests(TestCase):
         self.tdb.reopen()
 
     def test_store(self):
-        self.tdb.store("bar", "bla")
-        self.assertEquals("bla", self.tdb.get("bar"))
+        self.tdb.store(b"bar", b"bla")
+        self.assertEquals(b"bla", self.tdb.get(b"bar"))
 
     def test_getitem(self):
-        self.tdb["bar"] = "foo"
+        self.tdb[b"bar"] = b"foo"
         self.tdb.reopen()
-        self.assertEquals("foo", self.tdb["bar"])
+        self.assertEquals(b"foo", self.tdb[b"bar"])
 
     def test_delete(self):
-        self.tdb["bar"] = "foo"
-        del self.tdb["bar"]
-        self.assertRaises(KeyError, lambda: self.tdb["bar"])
+        self.tdb[b"bar"] = b"foo"
+        del self.tdb[b"bar"]
+        self.assertRaises(KeyError, lambda: self.tdb[b"bar"])
 
     def test_contains(self):
-        self.tdb["bla"] = "bloe"
-        self.assertTrue("bla" in self.tdb)
+        self.tdb[b"bla"] = b"bloe"
+        self.assertTrue(b"bla" in self.tdb)
 
     def test_keyerror(self):
-        self.assertRaises(KeyError, lambda: self.tdb["bla"])
+        self.assertRaises(KeyError, lambda: self.tdb[b"bla"])
 
     def test_hash_size(self):
         self.tdb.hash_size
@@ -108,51 +108,51 @@ class SimpleTdbTests(TestCase):
         self.tdb.filename
 
     def test_iterator(self):
-        self.tdb["bla"] = "1"
-        self.tdb["brainslug"] = "2"
+        self.tdb[b"bla"] = b"1"
+        self.tdb[b"brainslug"] = b"2"
         l = list(self.tdb)
         l.sort()
-        self.assertEquals(["bla", "brainslug"], l)
+        self.assertEquals([b"bla", b"brainslug"], l)
 
     def test_transaction_cancel(self):
-        self.tdb["bloe"] = "2"
+        self.tdb[b"bloe"] = b"2"
         self.tdb.transaction_start()
-        self.tdb["bloe"] = "1"
+        self.tdb[b"bloe"] = b"1"
         self.tdb.transaction_cancel()
-        self.assertEquals("2", self.tdb["bloe"])
+        self.assertEquals(b"2", self.tdb[b"bloe"])
 
     def test_transaction_commit(self):
-        self.tdb["bloe"] = "2"
+        self.tdb[b"bloe"] = b"2"
         self.tdb.transaction_start()
-        self.tdb["bloe"] = "1"
+        self.tdb[b"bloe"] = b"1"
         self.tdb.transaction_commit()
-        self.assertEquals("1", self.tdb["bloe"])
+        self.assertEquals(b"1", self.tdb[b"bloe"])
 
     def test_transaction_prepare_commit(self):
-        self.tdb["bloe"] = "2"
+        self.tdb[b"bloe"] = b"2"
         self.tdb.transaction_start()
-        self.tdb["bloe"] = "1"
+        self.tdb[b"bloe"] = b"1"
         self.tdb.transaction_prepare_commit()
         self.tdb.transaction_commit()
-        self.assertEquals("1", self.tdb["bloe"])
+        self.assertEquals(b"1", self.tdb[b"bloe"])
 
     def test_iterkeys(self):
-        self.tdb["bloe"] = "2"
-        self.tdb["bla"] = "25"
+        self.tdb[b"bloe"] = b"2"
+        self.tdb[b"bla"] = b"25"
         i = self.tdb.iterkeys()
-        self.assertEquals(set(["bloe", "bla"]), set([i.next(), i.next()]))
+        self.assertEquals(set([b"bloe", b"bla"]), set([next(i), next(i)]))
 
     def test_clear(self):
-        self.tdb["bloe"] = "2"
-        self.tdb["bla"] = "25"
+        self.tdb[b"bloe"] = b"2"
+        self.tdb[b"bla"] = b"25"
         self.assertEquals(2, len(list(self.tdb)))
         self.tdb.clear()
         self.assertEquals(0, len(list(self.tdb)))
 
     def test_repack(self):
-        self.tdb["foo"] = "abc"
-        self.tdb["bar"] = "def"
-        del self.tdb["foo"]
+        self.tdb[b"foo"] = b"abc"
+        self.tdb[b"bar"] = b"def"
+        del self.tdb[b"foo"]
         self.tdb.repack()
 
     def test_seqnum(self):
@@ -164,7 +164,7 @@ class SimpleTdbTests(TestCase):
 
     def test_len(self):
         self.assertEquals(0, len(list(self.tdb)))
-        self.tdb["entry"] = "value"
+        self.tdb[b"entry"] = b"value"
         self.assertEquals(1, len(list(self.tdb)))
 
     def test_add_flags(self):
diff --git a/lib/tdb/wscript b/lib/tdb/wscript
index b86671e..af43e3f 100644
--- a/lib/tdb/wscript
+++ b/lib/tdb/wscript
@@ -224,7 +224,10 @@ def testonly(ctx):
         print("testsuite returned %d" % ret)
         if ret != 0:
             ecode = ret
-    sys.exit(ecode)
+
+    pyret = samba_utils.RUN_PYTHON_TESTS(['python/tests/simple.py'])
+    print("python testsuite returned %d" % pyret)
+    sys.exit(ecode or pyret)
 
 # WAF doesn't build the unit tests for this, maybe because they don't link with tdb?
 # This forces it
-- 
2.1.0


From c61529b21f92c2462d96066cf352a238e4f10c0b Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 17:12:37 +0200
Subject: [PATCH 5/8] pytdb: Build for two versions of Python at once

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/wscript | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/lib/tdb/wscript b/lib/tdb/wscript
index af43e3f..50f7e31 100644
--- a/lib/tdb/wscript
+++ b/lib/tdb/wscript
@@ -95,8 +95,7 @@ def configure(conf):
 
     if not conf.env.disable_python:
         # also disable if we don't have the python libs installed
-        conf.find_program('python', var='PYTHON')
-        conf.check_tool('python')
+        conf.SAMBA_CHECK_PYTHON(mandatory=False)
         conf.check_python_version((2,4,2))
         conf.SAMBA_CHECK_PYTHON_HEADERS(mandatory=False)
         if not conf.env.HAVE_PYTHON_H:
@@ -179,12 +178,13 @@ def build(bld):
                                  includes='include', install=False)
 
     if not bld.CONFIG_SET('USING_SYSTEM_PYTDB'):
-        bld.SAMBA_PYTHON('pytdb',
-                         'pytdb.c',
-                         deps='tdb',
-                         enabled=not bld.env.disable_python,
-                         realname='tdb.so',
-                         cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
+        for env in bld.gen_python_environments(['PKGCONFIGDIR']):
+            bld.SAMBA_PYTHON('pytdb',
+                             'pytdb.c',
+                             deps='tdb',
+                             enabled=not bld.env.disable_python,
+                             realname='tdb.so',
+                             cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
 
 def testonly(ctx):
     '''run tdb testsuite'''
-- 
2.1.0


From 852d0a697848811fc91c4ac1a32c0a4319658042 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Fri, 22 May 2015 17:57:07 +0200
Subject: [PATCH 6/8] pytdb: Use new dict API on Python 3

- Remove has_key() method, add a fast "in" operator
- Rename iterkeys() to keys()

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/pytdb.c                | 42 ++++++++++++++++++++++++++++++++++--------
 lib/tdb/python/tests/simple.py | 16 +++++++++++++---
 2 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
index 534e613..7f7c145 100644
--- a/lib/tdb/pytdb.c
+++ b/lib/tdb/pytdb.c
@@ -316,26 +316,42 @@ static PyObject *obj_delete(PyTdbObject *self, PyObject *args)
 	Py_RETURN_NONE;
 }
 
-static PyObject *obj_has_key(PyTdbObject *self, PyObject *args)
+static int obj_contains(PyTdbObject *self, PyObject *py_key)
 {
 	TDB_DATA key;
 	int ret;
+	PyErr_TDB_RAISE_RETURN_MINUS_1_IF_CLOSED(self);
+
+	key = PyBytes_AsTDB_DATA(py_key);
+	if (!key.dptr) {
+		PyErr_BadArgument();
+		return -1;
+	}
+	ret = tdb_exists(self->ctx, key);
+	if (ret)
+		return 1;
+	return 0;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *obj_has_key(PyTdbObject *self, PyObject *args)
+{
+	int ret;
 	PyObject *py_key;
 	PyErr_TDB_RAISE_IF_CLOSED(self);
 
 	if (!PyArg_ParseTuple(args, "O", &py_key))
 		return NULL;
 
-	key = PyBytes_AsTDB_DATA(py_key);
-	if (!key.dptr)
+	ret = obj_contains(self, py_key);
+	if (ret == -1)
 		return NULL;
-	ret = tdb_exists(self->ctx, key);
-	if (ret != TDB_ERR_NOEXIST) {
-		PyErr_TDB_ERROR_IS_ERR_RAISE(ret, self->ctx);
-	}
+	if (ret)
+		Py_RETURN_TRUE;
+	Py_RETURN_FALSE;
 
-	return (ret == TDB_ERR_NOEXIST)?Py_False:Py_True;
 }
+#endif
 
 static PyObject *obj_store(PyTdbObject *self, PyObject *args)
 {
@@ -496,13 +512,19 @@ static PyMethodDef tdb_object_methods[] = {
 		"Return the next key in this database." },
 	{ "delete", (PyCFunction)obj_delete, METH_VARARGS, "S.delete(key) -> None\n"
 		"Delete an entry." },
+#if PY_MAJOR_VERSION < 3
 	{ "has_key", (PyCFunction)obj_has_key, METH_VARARGS, "S.has_key(key) -> None\n"
 		"Check whether key exists in this database." },
+#endif
 	{ "store", (PyCFunction)obj_store, METH_VARARGS, "S.store(key, data, flag=REPLACE) -> None"
 		"Store data." },
 	{ "add_flags", (PyCFunction)obj_add_flags, METH_VARARGS, "S.add_flags(flags) -> None" },
 	{ "remove_flags", (PyCFunction)obj_remove_flags, METH_VARARGS, "S.remove_flags(flags) -> None" },
+#if PY_MAJOR_VERSION >= 3
+	{ "keys", (PyCFunction)tdb_object_iter, METH_NOARGS, "S.iterkeys() -> iterator" },
+#else
 	{ "iterkeys", (PyCFunction)tdb_object_iter, METH_NOARGS, "S.iterkeys() -> iterator" },
+#endif
 	{ "clear", (PyCFunction)obj_clear, METH_NOARGS, "S.clear() -> None\n"
 		"Wipe the entire database." },
 	{ "repack", (PyCFunction)obj_repack, METH_NOARGS, "S.repack() -> None\n"
@@ -654,6 +676,9 @@ static PyMappingMethods tdb_object_mapping = {
 	.mp_subscript = (binaryfunc)obj_getitem,
 	.mp_ass_subscript = (objobjargproc)obj_setitem,
 };
+static PySequenceMethods tdb_object_seq = {
+	.sq_contains = (objobjproc)obj_contains,
+};
 static PyTypeObject PyTdb = {
 	.tp_name = "tdb.Tdb",
 	.tp_basicsize = sizeof(PyTdbObject),
@@ -664,6 +689,7 @@ static PyTypeObject PyTdb = {
 	.tp_repr = (reprfunc)tdb_object_repr,
 	.tp_dealloc = (destructor)tdb_object_dealloc,
 	.tp_as_mapping = &tdb_object_mapping,
+	.tp_as_sequence = &tdb_object_seq,
 	.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_ITER,
 	.tp_iter = (getiterfunc)tdb_object_iter,
 };
diff --git a/lib/tdb/python/tests/simple.py b/lib/tdb/python/tests/simple.py
index 39c319a..f9647bb 100644
--- a/lib/tdb/python/tests/simple.py
+++ b/lib/tdb/python/tests/simple.py
@@ -6,9 +6,12 @@
 # Copyright (C) 2007-2008 Jelmer Vernooij <jelmer at samba.org>
 # Published under the GNU LGPLv3 or later
 
-import tdb
+import sys
+import os
+import tempfile
 from unittest import TestCase
-import os, tempfile
+
+import tdb
 
 
 class OpenTdbTests(TestCase):
@@ -91,6 +94,10 @@ class SimpleTdbTests(TestCase):
     def test_contains(self):
         self.tdb[b"bla"] = b"bloe"
         self.assertTrue(b"bla" in self.tdb)
+        self.assertFalse(b"qwertyuiop" in self.tdb)
+        if sys.version_info < (3, 0):
+            self.assertTrue(self.tdb.has_key(b"bla"))
+            self.assertFalse(self.tdb.has_key(b"qwertyuiop"))
 
     def test_keyerror(self):
         self.assertRaises(KeyError, lambda: self.tdb[b"bla"])
@@ -139,7 +146,10 @@ class SimpleTdbTests(TestCase):
     def test_iterkeys(self):
         self.tdb[b"bloe"] = b"2"
         self.tdb[b"bla"] = b"25"
-        i = self.tdb.iterkeys()
+        if sys.version_info >= (3, 0):
+            i = self.tdb.keys()
+        else:
+            i = self.tdb.iterkeys()
         self.assertEquals(set([b"bloe", b"bla"]), set([next(i), next(i)]))
 
     def test_clear(self):
-- 
2.1.0


From f0e23d9b94f888b320994eb0a1806fb42a3525b1 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Thu, 18 Jun 2015 13:43:27 +0200
Subject: [PATCH 7/8] pyldb: Add a text-based interface for Python 3

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/_tdb_text.py | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/tdb/pytdb.c      |  18 +++++++
 lib/tdb/wscript      |   7 +++
 3 files changed, 163 insertions(+)
 create mode 100644 lib/tdb/_tdb_text.py

diff --git a/lib/tdb/_tdb_text.py b/lib/tdb/_tdb_text.py
new file mode 100644
index 0000000..c823bf8
--- /dev/null
+++ b/lib/tdb/_tdb_text.py
@@ -0,0 +1,138 @@
+# Text wrapper for tdb bindings
+#
+# Copyright (C) 2015 Petr Viktorin <pviktori at redhat.com>
+# Published under the GNU LGPLv3 or later
+
+import sys
+import functools
+
+import tdb
+
+
+class TdbTextWrapper(object):
+    """Text interface for a TDB file"""
+
+    def __init__(self, tdb):
+        self._tdb = tdb
+
+    @property
+    def raw(self):
+        return self._tdb
+
+    def get(self, key):
+        key = key.encode('utf-8')
+        result = self._tdb.get(key)
+        if result is not None:
+            return result.decode('utf-8')
+
+    def append(self, key, value):
+        key = key.encode('utf-8')
+        value = value.encode('utf-8')
+        self._tdb.append(key, value)
+
+    def firstkey(self):
+        result = self._tdb.firstkey()
+        if result:
+            return result.decode('utf-8')
+
+    def nextkey(self, key):
+        key = key.encode('utf-8')
+        result = self._tdb.nextkey(key)
+        if result is not None:
+            return result.decode('utf-8')
+
+    def delete(self, key):
+        key = key.encode('utf-8')
+        self._tdb.delete(key)
+
+    def store(self, key, value):
+        key = key.encode('utf-8')
+        value = value.encode('utf-8')
+        self._tdb.store(key, value)
+
+    def __iter__(self):
+        for key in iter(self._tdb):
+            yield key.decode('utf-8')
+
+    def __getitem__(self, key):
+        key = key.encode('utf-8')
+        result = self._tdb[key]
+        return result.decode('utf-8')
+
+    def __contains__(self, key):
+        key = key.encode('utf-8')
+        return key in self._tdb
+
+    def __repr__(self):
+        return '<TdbTextWrapper for %r>' % self._tdb
+
+    def __setitem__(self, key, value):
+        key = key.encode('utf-8')
+        value = value.encode('utf-8')
+        self._tdb[key] = value
+
+    def __delitem__(self, key):
+        key = key.encode('utf-8')
+        del self._tdb[key]
+
+    if sys.version_info > (3, 0):
+        keys = __iter__
+    else:
+        iterkeys = __iter__
+        has_key = __contains__
+
+
+## Add wrappers for functions and getters that don't deal with text
+
+def _add_wrapper(name):
+    orig = getattr(tdb.Tdb, name)
+
+    def wrapper(self, *args, **kwargs):
+        return orig(self._tdb, *args, **kwargs)
+    wrapper.__name__ = orig.__name__
+    wrapper.__doc__ = orig.__doc__
+
+    setattr(TdbTextWrapper, name, wrapper)
+
+for name in ("transaction_cancel",
+             "transaction_commit",
+             "transaction_prepare_commit",
+             "transaction_start",
+             "reopen",
+             "lock_all",
+             "unlock_all",
+             "read_lock_all",
+             "read_unlock_all",
+             "close",
+             "add_flags",
+             "remove_flags",
+             "clear",
+             "repack",
+             "enable_seqnum",
+             "increment_seqnum_nonblock",
+            ):
+    _add_wrapper(name)
+
+
+def _add_getter(name):
+    orig = getattr(tdb.Tdb, name)
+    doc = orig.__doc__
+
+    def getter(self):
+        return getattr(self._tdb, name)
+
+    def setter(self, value):
+        return setattr(self._tdb, name, value)
+
+    setattr(TdbTextWrapper, name, property(getter, setter, doc=doc))
+
+for name in ("hash_size",
+             "map_size",
+             "freelist_size",
+             "flags",
+             "max_dead",
+             "filename",
+             "seqnum",
+             "text",
+            ):
+    _add_getter(name)
diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
index 7f7c145..b0f9cd9 100644
--- a/lib/tdb/pytdb.c
+++ b/lib/tdb/pytdb.c
@@ -581,6 +581,22 @@ static PyObject *obj_get_seqnum(PyTdbObject *self, void *closure)
 	return PyInt_FromLong(tdb_get_seqnum(self->ctx));
 }
 
+static PyObject *obj_get_text(PyTdbObject *self, void *closure)
+{
+	PyObject *mod, *cls, *inst;
+	mod = PyImport_ImportModule("_tdb_text");
+	if (mod == NULL)
+		return NULL;
+	cls = PyObject_GetAttrString(mod, "TdbTextWrapper");
+	if (cls == NULL) {
+		Py_DECREF(mod);
+		return NULL;
+	}
+	inst = PyObject_CallFunction(cls, discard_const_p(char, "O"), self);
+	Py_DECREF(mod);
+	Py_DECREF(cls);
+	return inst;
+}
 
 static PyGetSetDef tdb_object_getsetters[] = {
 	{ discard_const_p(char, "hash_size"),
@@ -598,6 +614,8 @@ static PyGetSetDef tdb_object_getsetters[] = {
 	  discard_const_p(char, "The filename of this TDB file.") },
 	{ discard_const_p(char, "seqnum"),
 	  (getter)obj_get_seqnum, NULL, NULL },
+	{ discard_const_p(char, "text"),
+	  (getter)obj_get_text, NULL, NULL },
 	{ NULL }
 };
 
diff --git a/lib/tdb/wscript b/lib/tdb/wscript
index 50f7e31..f38d9ad 100644
--- a/lib/tdb/wscript
+++ b/lib/tdb/wscript
@@ -186,6 +186,13 @@ def build(bld):
                              realname='tdb.so',
                              cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
 
+        for env in bld.gen_python_environments(['PKGCONFIGDIR']):
+            bld.SAMBA_SCRIPT('_tdb_text.py',
+                             pattern='_tdb_text.py',
+                             installdir='python')
+
+            bld.INSTALL_FILES('${PYTHONARCHDIR}', '_tdb_text.py')
+
 def testonly(ctx):
     '''run tdb testsuite'''
     import Utils, samba_utils, shutil
-- 
2.1.0


From 90b72e902b9dac9f70abb577d425f5b3507681d7 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Wed, 17 Jun 2015 13:02:27 +0200
Subject: [PATCH 8/8] pytdb: Add tests for text interface

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/tdb/python/tests/simple.py | 164 +++++++++++++++++++++++++++++++++++------
 1 file changed, 141 insertions(+), 23 deletions(-)

diff --git a/lib/tdb/python/tests/simple.py b/lib/tdb/python/tests/simple.py
index f9647bb..b3136dd 100644
--- a/lib/tdb/python/tests/simple.py
+++ b/lib/tdb/python/tests/simple.py
@@ -46,19 +46,18 @@ class InternalTdbTests(TestCase):
         self.assertEquals(repr(self.tdb), "Tdb(<internal>)")
 
 
-class SimpleTdbTests(TestCase):
+class CommonTdbTests(TestCase):
+    """Tests common to both the text & bytes interfaces"""
+
+    use_text = False
 
     def setUp(self):
-        super(SimpleTdbTests, self).setUp()
+        super(CommonTdbTests, self).setUp()
         self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
                            os.O_CREAT|os.O_RDWR)
         self.assertNotEqual(None, self.tdb)
-
-    def tearDown(self):
-        del self.tdb
-
-    def test_repr(self):
-        self.assertTrue(repr(self.tdb).startswith("Tdb('"))
+        if self.use_text:
+            self.tdb = self.tdb.text
 
     def test_lockall(self):
         self.tdb.lock_all()
@@ -77,6 +76,39 @@ class SimpleTdbTests(TestCase):
     def test_reopen(self):
         self.tdb.reopen()
 
+    def test_hash_size(self):
+        self.tdb.hash_size
+
+    def test_map_size(self):
+        self.tdb.map_size
+
+    def test_freelist_size(self):
+        self.tdb.freelist_size
+
+    def test_name(self):
+        self.tdb.filename
+
+    def test_add_flags(self):
+        self.tdb.add_flags(tdb.NOMMAP)
+        self.tdb.remove_flags(tdb.NOMMAP)
+
+
+class TextCommonTdbTests(CommonTdbTests):
+
+    use_text = True
+
+
+class SimpleTdbTests(TestCase):
+
+    def setUp(self):
+        super(SimpleTdbTests, self).setUp()
+        self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+                           os.O_CREAT|os.O_RDWR)
+        self.assertNotEqual(None, self.tdb)
+
+    def test_repr(self):
+        self.assertTrue(repr(self.tdb).startswith("Tdb('"))
+
     def test_store(self):
         self.tdb.store(b"bar", b"bla")
         self.assertEquals(b"bla", self.tdb.get(b"bar"))
@@ -102,18 +134,6 @@ class SimpleTdbTests(TestCase):
     def test_keyerror(self):
         self.assertRaises(KeyError, lambda: self.tdb[b"bla"])
 
-    def test_hash_size(self):
-        self.tdb.hash_size
-
-    def test_map_size(self):
-        self.tdb.map_size
-
-    def test_freelist_size(self):
-        self.tdb.freelist_size
-
-    def test_name(self):
-        self.tdb.filename
-
     def test_iterator(self):
         self.tdb[b"bla"] = b"1"
         self.tdb[b"brainslug"] = b"2"
@@ -177,9 +197,107 @@ class SimpleTdbTests(TestCase):
         self.tdb[b"entry"] = b"value"
         self.assertEquals(1, len(list(self.tdb)))
 
-    def test_add_flags(self):
-        self.tdb.add_flags(tdb.NOMMAP)
-        self.tdb.remove_flags(tdb.NOMMAP)
+
+class TdbTextTests(TestCase):
+
+    def setUp(self):
+        super(TdbTextTests, self).setUp()
+        self.tdb = tdb.Tdb(tempfile.mkstemp()[1], 0, tdb.DEFAULT,
+                           os.O_CREAT|os.O_RDWR)
+        self.assertNotEqual(None, self.tdb)
+
+    def test_repr(self):
+        self.assertTrue(repr(self.tdb).startswith("Tdb('"))
+
+    def test_store(self):
+        self.tdb.text.store("bar", "bla")
+        self.assertEquals("bla", self.tdb.text.get("bar"))
+
+    def test_getitem(self):
+        self.tdb.text["bar"] = "foo"
+        self.tdb.reopen()
+        self.assertEquals("foo", self.tdb.text["bar"])
+
+    def test_delete(self):
+        self.tdb.text["bar"] = "foo"
+        del self.tdb.text["bar"]
+        self.assertRaises(KeyError, lambda: self.tdb.text["bar"])
+
+    def test_contains(self):
+        self.tdb.text["bla"] = "bloe"
+        self.assertTrue("bla" in self.tdb.text)
+        self.assertFalse("qwertyuiop" in self.tdb.text)
+        if sys.version_info < (3, 0):
+            self.assertTrue(self.tdb.text.has_key("bla"))
+            self.assertFalse(self.tdb.text.has_key("qwertyuiop"))
+
+    def test_keyerror(self):
+        self.assertRaises(KeyError, lambda: self.tdb.text["bla"])
+
+    def test_iterator(self):
+        self.tdb.text["bla"] = "1"
+        self.tdb.text["brainslug"] = "2"
+        l = list(self.tdb.text)
+        l.sort()
+        self.assertEquals(["bla", "brainslug"], l)
+
+    def test_transaction_cancel(self):
+        self.tdb.text["bloe"] = "2"
+        self.tdb.transaction_start()
+        self.tdb.text["bloe"] = "1"
+        self.tdb.transaction_cancel()
+        self.assertEquals("2", self.tdb.text["bloe"])
+
+    def test_transaction_commit(self):
+        self.tdb.text["bloe"] = "2"
+        self.tdb.transaction_start()
+        self.tdb.text["bloe"] = "1"
+        self.tdb.transaction_commit()
+        self.assertEquals("1", self.tdb.text["bloe"])
+
+    def test_transaction_prepare_commit(self):
+        self.tdb.text["bloe"] = "2"
+        self.tdb.transaction_start()
+        self.tdb.text["bloe"] = "1"
+        self.tdb.transaction_prepare_commit()
+        self.tdb.transaction_commit()
+        self.assertEquals("1", self.tdb.text["bloe"])
+
+    def test_iterkeys(self):
+        self.tdb.text["bloe"] = "2"
+        self.tdb.text["bla"] = "25"
+        if sys.version_info >= (3, 0):
+            i = self.tdb.text.keys()
+        else:
+            i = self.tdb.text.iterkeys()
+        self.assertEquals(set(["bloe", "bla"]), set([next(i), next(i)]))
+
+    def test_clear(self):
+        self.tdb.text["bloe"] = "2"
+        self.tdb.text["bla"] = "25"
+        self.assertEquals(2, len(list(self.tdb)))
+        self.tdb.clear()
+        self.assertEquals(0, len(list(self.tdb)))
+
+    def test_repack(self):
+        self.tdb.text["foo"] = "abc"
+        self.tdb.text["bar"] = "def"
+        del self.tdb.text["foo"]
+        self.tdb.repack()
+
+    def test_len(self):
+        self.assertEquals(0, len(list(self.tdb.text)))
+        self.tdb.text["entry"] = "value"
+        self.assertEquals(1, len(list(self.tdb.text)))
+
+    def test_text_and_binary(self):
+        text = u'\xfa\u0148\xef\xe7\xf8\xf0\xea'
+        bytestr = text.encode('utf-8')
+        self.tdb[b"entry"] = bytestr
+        self.tdb.text[u"entry2"] = text
+        self.assertEquals(self.tdb.text["entry"], text)
+        self.assertEquals(self.tdb[b"entry2"], bytestr)
+        assert self.tdb.text.raw == self.tdb
 
 
 class VersionTests(TestCase):
-- 
2.1.0


More information about the samba-technical mailing list