[PATCHES] Port ldb to Python 3

Petr Viktorin pviktori at redhat.com
Thu Jul 30 07:55:04 UTC 2015


Hello,
Thank you Andrew and Stefan for reviewing the patches for tdb!


These patches add support for Python 3 to the last of Samba's
stand-alone libraries, ldb.

There are some general fixes (mainly refcounting) thrown in.

Under Python 3, DNs, attribute names, filters, controls are always text
(unicode) strings, encoded to/from UTF-8 for storage. Attribute values
are byte strings.

When creating DNs and attribute values, both text and bytes are acepted.
This allows creating messages from homogeneous dicts (where values and
"dn" are all either text or bytes).

Similarly to the tdb bindings, LDB Messages and MessageElements have a
.text attribute, which offers a text view on the contents: any value
retrieved from it will be decoded using UTF-8. The wrapper is
implemented in a new Python module.


-- 
Petr Viktorin
-------------- next part --------------
From 988e35cde23f9480eb637faac41858d9920426b9 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Mon, 8 Jun 2015 14:17:12 +0200
Subject: [PATCH 1/9] ldb: Run the Python testsuite

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 buildtools/wafsamba/samba_utils.py |  5 ++++-
 lib/ldb/tests/test-tdb.sh          |  7 -------
 lib/ldb/wscript                    | 11 +++++++++--
 3 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/buildtools/wafsamba/samba_utils.py b/buildtools/wafsamba/samba_utils.py
index 540fe44..0ae58b3 100644
--- a/buildtools/wafsamba/samba_utils.py
+++ b/buildtools/wafsamba/samba_utils.py
@@ -386,7 +386,7 @@ def RUN_COMMAND(cmd,
     return -1
 
 
-def RUN_PYTHON_TESTS(testfiles, pythonpath=None):
+def RUN_PYTHON_TESTS(testfiles, pythonpath=None, extra_env=None):
     env = LOAD_ENVIRONMENT()
     if pythonpath is None:
         pythonpath = os.path.join(Utils.g_module.blddir, 'python')
@@ -394,6 +394,9 @@ def RUN_PYTHON_TESTS(testfiles, pythonpath=None):
     for interp in env.python_interpreters:
         for testfile in testfiles:
             cmd = "PYTHONPATH=%s %s %s" % (pythonpath, interp, testfile)
+            if extra_env:
+                for key, value in extra_env.items():
+                    cmd = "%s=%s %s" % (key, value, cmd)
             print('Running Python test with %s: %s' % (interp, testfile))
             ret = RUN_COMMAND(cmd)
             if ret:
diff --git a/lib/ldb/tests/test-tdb.sh b/lib/ldb/tests/test-tdb.sh
index 82eef69..91c48ab 100755
--- a/lib/ldb/tests/test-tdb.sh
+++ b/lib/ldb/tests/test-tdb.sh
@@ -7,9 +7,7 @@ if [ -n "$TEST_DATA_PREFIX" ]; then
 	PYDESTDIR="$TEST_DATA_PREFIX"
 else
 	LDB_URL="tdbtest.ldb"
-	PYDESTDIR="/tmp"
 fi
-mkdir $PYDESTDIR/tmp
 export LDB_URL
 
 PATH=$BINDIR:$PATH
@@ -38,8 +36,3 @@ $VALGRIND ldbadd $LDBDIR/tests/init.ldif || exit 1
 . $LDBDIR/tests/test-tdb-features.sh
 
 . $LDBDIR/tests/test-controls.sh
-
-which python >/dev/null 2>&1
-if [ $? -eq 0 ]; then
-	SELFTEST_PREFIX=$PYDESTDIR PYTHONPATH=$BINDIR/python python $LDBDIR/tests/python/api.py
-fi
diff --git a/lib/ldb/wscript b/lib/ldb/wscript
index 0e81932..ba1fa17 100755
--- a/lib/ldb/wscript
+++ b/lib/ldb/wscript
@@ -284,8 +284,15 @@ def test(ctx):
     cmd = 'tests/test-tdb.sh %s' % Utils.g_module.blddir
     ret = samba_utils.RUN_COMMAND(cmd)
     print("testsuite returned %d" % ret)
-    # FIXME: Run python testsuite
-    sys.exit(ret)
+
+    tmp_dir = os.path.join(test_prefix, 'tmp')
+    if not os.path.exists(tmp_dir):
+        os.mkdir(tmp_dir)
+    pyret = samba_utils.RUN_PYTHON_TESTS(
+        ['tests/python/api.py'],
+        extra_env={'SELFTEST_PREFIX': test_prefix})
+    print("Python testsuite returned %d" % pyret)
+    sys.exit(ret or pyret)
 
 def dist():
     '''makes a tarball for distribution'''
-- 
2.1.0


From c65e8f315077a0a0fef3f9cf164850ce6d813951 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 9 Jun 2015 10:31:22 +0200
Subject: [PATCH 2/9] ldb: Build for two Python versions at once

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

diff --git a/lib/ldb/wscript b/lib/ldb/wscript
index ba1fa17..5071dcd 100755
--- a/lib/ldb/wscript
+++ b/lib/ldb/wscript
@@ -121,17 +121,25 @@ def build(bld):
         bld.env.PKGCONFIGDIR = '${LIBDIR}/pkgconfig'
 
     if not bld.CONFIG_SET('USING_SYSTEM_PYLDB_UTIL'):
-        bld.SAMBA_LIBRARY('pyldb-util',
-                          deps='ldb',
-                          source='pyldb_util.c',
-                          public_headers='pyldb.h',
-                          public_headers_install=not private_library,
-                          vnum=VERSION,
-                          private_library=private_library,
-                          pc_files='pyldb-util.pc',
-                          pyembed=True,
-                          abi_directory='ABI',
-                          abi_match='pyldb_*')
+        for env in bld.gen_python_environments(['PKGCONFIGDIR']):
+            name = bld.pyembed_libname('pyldb-util')
+            bld.SAMBA_LIBRARY(name,
+                              deps='ldb',
+                              source='pyldb_util.c',
+                              public_headers='pyldb.h',
+                              public_headers_install=not private_library,
+                              vnum=VERSION,
+                              private_library=private_library,
+                              pc_files='pyldb-util.pc',
+                              pyembed=True,
+                              abi_directory='ABI',
+                              abi_match='pyldb_*')
+
+            if not bld.CONFIG_SET('USING_SYSTEM_LDB'):
+                bld.SAMBA_PYTHON('pyldb', 'pyldb.c',
+                                 deps='ldb ' + name,
+                                 realname='ldb.so',
+                                 cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
 
     if not bld.CONFIG_SET('USING_SYSTEM_LDB'):
         if Options.is_install:
@@ -167,11 +175,6 @@ def build(bld):
         t.env.LDB_VERSION = VERSION
 
 
-        bld.SAMBA_PYTHON('pyldb', 'pyldb.c',
-                         deps='ldb pyldb-util',
-                         realname='ldb.so',
-                         cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
-
         bld.SAMBA_MODULE('ldb_paged_results',
                          'modules/paged_results.c',
                          init_function='ldb_paged_results_init',
-- 
2.1.0


From 4c160d6a81178b6d68c48547dc88cea73cb38453 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Thu, 11 Jun 2015 10:16:48 +0200
Subject: [PATCH 3/9] pyldb: Properly increase refcount of returned values

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/pyldb.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index f18e06e..5567501 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -1800,7 +1800,7 @@ static PyObject *py_ldb_get_opaque(PyLdbObject *self, PyObject *args)
 
 	/* FIXME: More interpretation */
 
-	return Py_True;
+	Py_RETURN_TRUE;
 }
 
 static PyObject *py_ldb_set_opaque(PyLdbObject *self, PyObject *args)
@@ -2761,6 +2761,7 @@ static PyObject *py_ldb_msg_get(PyLdbMessageObject *self, PyObject *args, PyObje
 
 	if (el == NULL || (idx != -1 && el->num_values <= idx)) {
 		if (def != NULL) {
+			Py_INCREF(def);
 			return def;
 		}
 		Py_RETURN_NONE;
-- 
2.1.0


From 6cb2118976fa4b24e9ea2d27c4afe1078d5293da Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Wed, 10 Jun 2015 15:41:57 +0200
Subject: [PATCH 4/9] pyldb: Don't use the internal macro PyObject_REPR

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/pyldb.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index 5567501..50a3990 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -3004,10 +3004,16 @@ static PyGetSetDef py_ldb_msg_getset[] = {
 
 static PyObject *py_ldb_msg_repr(PyLdbMessageObject *self)
 {
-	PyObject *dict = PyDict_New(), *ret;
+	PyObject *dict = PyDict_New(), *ret, *repr;
 	if (PyDict_Update(dict, (PyObject *)self) != 0)
 		return NULL;
-	ret = PyString_FromFormat("Message(%s)", PyObject_REPR(dict));
+	repr = PyObject_Repr(dict);
+	if (repr == NULL) {
+		Py_DECREF(dict);
+		return NULL;
+	}
+	ret = PyString_FromFormat("Message(%s)", PyString_AsString(repr));
+	Py_DECREF(repr);
 	Py_DECREF(dict);
 	return ret;
 }
-- 
2.1.0


From 6444383ddc4d6b2e815240ba943d37824e4a1218 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Wed, 10 Jun 2015 15:40:34 +0200
Subject: [PATCH 5/9] pyldb: DECREF old debug function when resetting it

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/pyldb.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index 50a3990..f079d7d 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -839,6 +839,8 @@ static void py_ldb_debug(void *context, enum ldb_debug_level level, const char *
 	PyObject_CallFunction(fn, discard_const_p(char, "(i,O)"), level, PyString_FromFormatV(fmt, ap));
 }
 
+static PyObject *py_ldb_debug_func;
+
 static PyObject *py_ldb_set_debug(PyObject *self, PyObject *args)
 {
 	PyObject *cb;
@@ -847,8 +849,13 @@ static PyObject *py_ldb_set_debug(PyObject *self, PyObject *args)
 	if (!PyArg_ParseTuple(args, "O", &cb))
 		return NULL;
 
+	if (py_ldb_debug_func != NULL) {
+		Py_DECREF(py_ldb_debug_func);
+	}
+
 	Py_INCREF(cb);
-	/* FIXME: Where do we DECREF cb ? */
+	/* FIXME: DECREF cb when exiting program */
+	py_ldb_debug_func = cb;
 	ldb_ctx = pyldb_Ldb_AsLdbContext(self);
 	PyErr_LDB_ERROR_IS_ERR_RAISE(PyExc_LdbError,
 		ldb_set_debug(ldb_ctx, py_ldb_debug, cb),
-- 
2.1.0


From 1a4c5ac214e1ae62bff924da2b74a8e7c8293bd7 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 9 Jun 2015 10:36:26 +0200
Subject: [PATCH 6/9] pyldb: Add Python 3 compatibility (except strings)

- Use a macro for adding constants to module
(This also ensures that the Python constants have the same
name as the C ones. One existing misspelling is retained.)

- Use new module initialization for Python 3

- Use rich comparison for ldb objects

- Prepare tests for Python 3

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/pyldb.c             | 265 +++++++++++++++++++++++++++-----------------
 lib/ldb/tests/python/api.py |  17 +--
 2 files changed, 176 insertions(+), 106 deletions(-)

diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index f079d7d..e279f97 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -64,10 +64,27 @@ typedef inquiry lenfunc;
 typedef intargfunc ssizeargfunc;
 #endif
 
-#define SIGN(a) (((a) == 0)?0:((a) < 0?-1:1))
 
 
 
+static PyObject *richcmp(int cmp_val, int op)
+{
+	int ret;
+	switch (op) {
+		case Py_LT: ret = cmp_val < 0;  break;
+		case Py_LE: ret = cmp_val <= 0; break;
+		case Py_EQ: ret = cmp_val == 0; break;
+		case Py_NE: ret = cmp_val != 0; break;
+		case Py_GT: ret = cmp_val > 0;  break;
+		case Py_GE: ret = cmp_val >= 0; break;
+		default:
+			Py_INCREF(Py_NotImplemented);
+			return Py_NotImplemented;
+	}
+	return PyBool_FromLong(ret);
+}
+
+
 static PyObject *py_ldb_control_str(PyLdbControlObject *self)
 {
 	if (self->data != NULL) {
@@ -464,13 +481,15 @@ static PyObject *py_ldb_dn_check_special(PyLdbDnObject *self, PyObject *args)
 	return PyBool_FromLong(ldb_dn_check_special(self->dn, name));
 }
 
-static int py_ldb_dn_compare(PyLdbDnObject *dn1, PyLdbDnObject *dn2)
+static PyObject *py_ldb_dn_richcmp(PyObject *dn1, PyObject *dn2, int op)
 {
 	int ret;
-	ret = ldb_dn_compare(dn1->dn, dn2->dn);
-	if (ret < 0) ret = -1;
-	if (ret > 0) ret = 1;
-	return ret;
+	if (!pyldb_Dn_Check(dn2)) {
+		Py_INCREF(Py_NotImplemented);
+		return Py_NotImplemented;
+	}
+	ret = ldb_dn_compare(pyldb_Dn_AsDn(dn1), pyldb_Dn_AsDn(dn2));
+	return richcmp(ret, op);
 }
 
 static PyObject *py_ldb_dn_get_parent(PyLdbDnObject *self)
@@ -822,7 +841,7 @@ static PyTypeObject PyLdbDn = {
 	.tp_methods = py_ldb_dn_methods,
 	.tp_str = (reprfunc)py_ldb_dn_get_linearized,
 	.tp_repr = (reprfunc)py_ldb_dn_repr,
-	.tp_compare = (cmpfunc)py_ldb_dn_compare,
+	.tp_richcompare = (richcmpfunc)py_ldb_dn_richcmp,
 	.tp_as_sequence = &py_ldb_dn_seq,
 	.tp_doc = "A LDB distinguished name.",
 	.tp_new = py_ldb_dn_new,
@@ -2468,11 +2487,16 @@ static PySequenceMethods py_ldb_msg_element_seq = {
 	.sq_item = (ssizeargfunc)py_ldb_msg_element_find,
 };
 
-static int py_ldb_msg_element_cmp(PyLdbMessageElementObject *self, PyLdbMessageElementObject *other)
+static PyObject *py_ldb_msg_element_richcmp(PyObject *self, PyObject *other, int op)
 {
-	int ret = ldb_msg_element_compare(pyldb_MessageElement_AsMessageElement(self),
+	int ret;
+	if (!pyldb_MessageElement_Check(other)) {
+		Py_INCREF(Py_NotImplemented);
+		return Py_NotImplemented;
+	}
+	ret = ldb_msg_element_compare(pyldb_MessageElement_AsMessageElement(self),
 									  pyldb_MessageElement_AsMessageElement(other));
-	return SIGN(ret);
+	return richcmp(ret, op);
 }
 
 static PyObject *py_ldb_msg_element_iter(PyLdbMessageElementObject *self)
@@ -2638,7 +2662,7 @@ static PyTypeObject PyLdbMessageElement = {
 	.tp_repr = (reprfunc)py_ldb_msg_element_repr,
 	.tp_str = (reprfunc)py_ldb_msg_element_str,
 	.tp_methods = py_ldb_msg_element_methods,
-	.tp_compare = (cmpfunc)py_ldb_msg_element_cmp,
+	.tp_richcompare = (richcmpfunc)py_ldb_msg_element_richcmp,
 	.tp_iter = (getiterfunc)py_ldb_msg_element_iter,
 	.tp_as_sequence = &py_ldb_msg_element_seq,
 	.tp_new = py_ldb_msg_element_new,
@@ -3031,41 +3055,48 @@ static void py_ldb_msg_dealloc(PyLdbMessageObject *self)
 	PyObject_Del(self);
 }
 
-static int py_ldb_msg_compare(PyLdbMessageObject *py_msg1,
-			      PyLdbMessageObject *py_msg2)
+static PyObject *py_ldb_msg_richcmp(PyLdbMessageObject *py_msg1,
+			      PyLdbMessageObject *py_msg2, int op)
 {
-	struct ldb_message *msg1 = pyldb_Message_AsMessage(py_msg1),
-			   *msg2 = pyldb_Message_AsMessage(py_msg2);
+	struct ldb_message *msg1, *msg2;
 	unsigned int i;
 	int ret;
 
+	if (!PyLdbMessage_Check(py_msg1)) {
+		Py_INCREF(Py_NotImplemented);
+		return Py_NotImplemented;
+	}
+
+	msg1 = pyldb_Message_AsMessage(py_msg1),
+	msg2 = pyldb_Message_AsMessage(py_msg2);
+
 	if ((msg1->dn != NULL) || (msg2->dn != NULL)) {
 		ret = ldb_dn_compare(msg1->dn, msg2->dn);
 		if (ret != 0) {
-			return SIGN(ret);
+			return richcmp(ret, op);
 		}
 	}
 
 	ret = msg1->num_elements - msg2->num_elements;
 	if (ret != 0) {
-		return SIGN(ret);
+		return richcmp(ret, op);
 	}
 
 	for (i = 0; i < msg1->num_elements; i++) {
 		ret = ldb_msg_element_compare_name(&msg1->elements[i],
 						   &msg2->elements[i]);
 		if (ret != 0) {
-			return SIGN(ret);
+			return richcmp(ret, op);
 		}
 
 		ret = ldb_msg_element_compare(&msg1->elements[i],
 					      &msg2->elements[i]);
 		if (ret != 0) {
-			return SIGN(ret);
+			return richcmp(ret, op);
 		}
 	}
 
-	return 0;
+	return richcmp(0, op);
 }
 
 static PyTypeObject PyLdbMessage = {
@@ -3079,7 +3110,7 @@ static PyTypeObject PyLdbMessage = {
 	.tp_repr = (reprfunc)py_ldb_msg_repr,
 	.tp_flags = Py_TPFLAGS_DEFAULT,
 	.tp_iter = (getiterfunc)py_ldb_msg_iter,
-	.tp_compare = (cmpfunc)py_ldb_msg_compare,
+	.tp_richcompare = (richcmpfunc)py_ldb_msg_richcmp,
 	.tp_doc = "A LDB Message",
 };
 
@@ -3520,102 +3551,122 @@ static PyMethodDef py_ldb_global_methods[] = {
 	{ NULL }
 };
 
-void initldb(void)
+#define MODULE_DOC "An interface to LDB, a LDAP-like API that can either to talk an embedded database (TDB-based) or a standards-compliant LDAP server."
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+	PyModuleDef_HEAD_INIT,
+	.m_name = "ldb",
+	.m_doc = MODULE_DOC,
+	.m_size = -1,
+	.m_methods = py_ldb_global_methods,
+};
+#endif
+
+static PyObject* module_init(void)
 {
 	PyObject *m;
 
 	if (PyType_Ready(&PyLdbDn) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbMessage) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbMessageElement) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdb) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbModule) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbTree) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbResult) < 0)
-		return;
+		return NULL;
 
 	if (PyType_Ready(&PyLdbControl) < 0)
-		return;
+		return NULL;
 
-	m = Py_InitModule3("ldb", py_ldb_global_methods, 
-		"An interface to LDB, a LDAP-like API that can either to talk an embedded database (TDB-based) or a standards-compliant LDAP server.");
+#if PY_MAJOR_VERSION >= 3
+	m = PyModule_Create(&moduledef);
+#else
+	m = Py_InitModule3("ldb", py_ldb_global_methods, MODULE_DOC);
+#endif
 	if (m == NULL)
-		return;
-
-	PyModule_AddObject(m, "SEQ_HIGHEST_SEQ", PyInt_FromLong(LDB_SEQ_HIGHEST_SEQ));
-	PyModule_AddObject(m, "SEQ_HIGHEST_TIMESTAMP", PyInt_FromLong(LDB_SEQ_HIGHEST_TIMESTAMP));
-	PyModule_AddObject(m, "SEQ_NEXT", PyInt_FromLong(LDB_SEQ_NEXT));
-	PyModule_AddObject(m, "SCOPE_DEFAULT", PyInt_FromLong(LDB_SCOPE_DEFAULT));
-	PyModule_AddObject(m, "SCOPE_BASE", PyInt_FromLong(LDB_SCOPE_BASE));
-	PyModule_AddObject(m, "SCOPE_ONELEVEL", PyInt_FromLong(LDB_SCOPE_ONELEVEL));
-	PyModule_AddObject(m, "SCOPE_SUBTREE", PyInt_FromLong(LDB_SCOPE_SUBTREE));
-
-	PyModule_AddObject(m, "CHANGETYPE_NONE", PyInt_FromLong(LDB_CHANGETYPE_NONE));
-	PyModule_AddObject(m, "CHANGETYPE_ADD", PyInt_FromLong(LDB_CHANGETYPE_ADD));
-	PyModule_AddObject(m, "CHANGETYPE_DELETE", PyInt_FromLong(LDB_CHANGETYPE_DELETE));
-	PyModule_AddObject(m, "CHANGETYPE_MODIFY", PyInt_FromLong(LDB_CHANGETYPE_MODIFY));
-
-	PyModule_AddObject(m, "FLAG_MOD_ADD", PyInt_FromLong(LDB_FLAG_MOD_ADD));
-	PyModule_AddObject(m, "FLAG_MOD_REPLACE", PyInt_FromLong(LDB_FLAG_MOD_REPLACE));
-	PyModule_AddObject(m, "FLAG_MOD_DELETE", PyInt_FromLong(LDB_FLAG_MOD_DELETE));
-
-	PyModule_AddObject(m, "SUCCESS", PyInt_FromLong(LDB_SUCCESS));
-	PyModule_AddObject(m, "ERR_OPERATIONS_ERROR", PyInt_FromLong(LDB_ERR_OPERATIONS_ERROR));
-	PyModule_AddObject(m, "ERR_PROTOCOL_ERROR", PyInt_FromLong(LDB_ERR_PROTOCOL_ERROR));
-	PyModule_AddObject(m, "ERR_TIME_LIMIT_EXCEEDED", PyInt_FromLong(LDB_ERR_TIME_LIMIT_EXCEEDED));
-	PyModule_AddObject(m, "ERR_SIZE_LIMIT_EXCEEDED", PyInt_FromLong(LDB_ERR_SIZE_LIMIT_EXCEEDED));
-	PyModule_AddObject(m, "ERR_COMPARE_FALSE", PyInt_FromLong(LDB_ERR_COMPARE_FALSE));
-	PyModule_AddObject(m, "ERR_COMPARE_TRUE", PyInt_FromLong(LDB_ERR_COMPARE_TRUE));
-	PyModule_AddObject(m, "ERR_AUTH_METHOD_NOT_SUPPORTED", PyInt_FromLong(LDB_ERR_AUTH_METHOD_NOT_SUPPORTED));
-	PyModule_AddObject(m, "ERR_STRONG_AUTH_REQUIRED", PyInt_FromLong(LDB_ERR_STRONG_AUTH_REQUIRED));
-	PyModule_AddObject(m, "ERR_REFERRAL", PyInt_FromLong(LDB_ERR_REFERRAL));
-	PyModule_AddObject(m, "ERR_ADMIN_LIMIT_EXCEEDED", PyInt_FromLong(LDB_ERR_ADMIN_LIMIT_EXCEEDED));
-	PyModule_AddObject(m, "ERR_UNSUPPORTED_CRITICAL_EXTENSION", PyInt_FromLong(LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION));
-	PyModule_AddObject(m, "ERR_CONFIDENTIALITY_REQUIRED", PyInt_FromLong(LDB_ERR_CONFIDENTIALITY_REQUIRED));
-	PyModule_AddObject(m, "ERR_SASL_BIND_IN_PROGRESS", PyInt_FromLong(LDB_ERR_SASL_BIND_IN_PROGRESS));
-	PyModule_AddObject(m, "ERR_NO_SUCH_ATTRIBUTE", PyInt_FromLong(LDB_ERR_NO_SUCH_ATTRIBUTE));
-	PyModule_AddObject(m, "ERR_UNDEFINED_ATTRIBUTE_TYPE", PyInt_FromLong(LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE));
-	PyModule_AddObject(m, "ERR_INAPPROPRIATE_MATCHING", PyInt_FromLong(LDB_ERR_INAPPROPRIATE_MATCHING));
-	PyModule_AddObject(m, "ERR_CONSTRAINT_VIOLATION", PyInt_FromLong(LDB_ERR_CONSTRAINT_VIOLATION));
-	PyModule_AddObject(m, "ERR_ATTRIBUTE_OR_VALUE_EXISTS", PyInt_FromLong(LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS));
-	PyModule_AddObject(m, "ERR_INVALID_ATTRIBUTE_SYNTAX", PyInt_FromLong(LDB_ERR_INVALID_ATTRIBUTE_SYNTAX));
-	PyModule_AddObject(m, "ERR_NO_SUCH_OBJECT", PyInt_FromLong(LDB_ERR_NO_SUCH_OBJECT));
-	PyModule_AddObject(m, "ERR_ALIAS_PROBLEM", PyInt_FromLong(LDB_ERR_ALIAS_PROBLEM));
-	PyModule_AddObject(m, "ERR_INVALID_DN_SYNTAX", PyInt_FromLong(LDB_ERR_INVALID_DN_SYNTAX));
-	PyModule_AddObject(m, "ERR_ALIAS_DEREFERINCING_PROBLEM", PyInt_FromLong(LDB_ERR_ALIAS_DEREFERENCING_PROBLEM));
-	PyModule_AddObject(m, "ERR_INAPPROPRIATE_AUTHENTICATION", PyInt_FromLong(LDB_ERR_INAPPROPRIATE_AUTHENTICATION));
-	PyModule_AddObject(m, "ERR_INVALID_CREDENTIALS", PyInt_FromLong(LDB_ERR_INVALID_CREDENTIALS));
-	PyModule_AddObject(m, "ERR_INSUFFICIENT_ACCESS_RIGHTS", PyInt_FromLong(LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS));
-	PyModule_AddObject(m, "ERR_BUSY", PyInt_FromLong(LDB_ERR_BUSY));
-	PyModule_AddObject(m, "ERR_UNAVAILABLE", PyInt_FromLong(LDB_ERR_UNAVAILABLE));
-	PyModule_AddObject(m, "ERR_UNWILLING_TO_PERFORM", PyInt_FromLong(LDB_ERR_UNWILLING_TO_PERFORM));
-	PyModule_AddObject(m, "ERR_LOOP_DETECT", PyInt_FromLong(LDB_ERR_LOOP_DETECT));
-	PyModule_AddObject(m, "ERR_NAMING_VIOLATION", PyInt_FromLong(LDB_ERR_NAMING_VIOLATION));
-	PyModule_AddObject(m, "ERR_OBJECT_CLASS_VIOLATION", PyInt_FromLong(LDB_ERR_OBJECT_CLASS_VIOLATION));
-	PyModule_AddObject(m, "ERR_NOT_ALLOWED_ON_NON_LEAF", PyInt_FromLong(LDB_ERR_NOT_ALLOWED_ON_NON_LEAF));
-	PyModule_AddObject(m, "ERR_NOT_ALLOWED_ON_RDN", PyInt_FromLong(LDB_ERR_NOT_ALLOWED_ON_RDN));
-	PyModule_AddObject(m, "ERR_ENTRY_ALREADY_EXISTS", PyInt_FromLong(LDB_ERR_ENTRY_ALREADY_EXISTS));
-	PyModule_AddObject(m, "ERR_OBJECT_CLASS_MODS_PROHIBITED", PyInt_FromLong(LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED));
-	PyModule_AddObject(m, "ERR_AFFECTS_MULTIPLE_DSAS", PyInt_FromLong(LDB_ERR_AFFECTS_MULTIPLE_DSAS));
-	PyModule_AddObject(m, "ERR_OTHER", PyInt_FromLong(LDB_ERR_OTHER));
-
-	PyModule_AddObject(m, "FLG_RDONLY", PyInt_FromLong(LDB_FLG_RDONLY));
-	PyModule_AddObject(m, "FLG_NOSYNC", PyInt_FromLong(LDB_FLG_NOSYNC));
-	PyModule_AddObject(m, "FLG_RECONNECT", PyInt_FromLong(LDB_FLG_RECONNECT));
-	PyModule_AddObject(m, "FLG_NOMMAP", PyInt_FromLong(LDB_FLG_NOMMAP));
-
-	PyModule_AddObject(m, "__docformat__", PyString_FromString("restructuredText"));
+		return NULL;
+
+#define ADD_LDB_INT(val) PyModule_AddIntConstant(m, #val, LDB_ ## val)
+
+	ADD_LDB_INT(SEQ_HIGHEST_SEQ);
+	ADD_LDB_INT(SEQ_HIGHEST_TIMESTAMP);
+	ADD_LDB_INT(SEQ_NEXT);
+	ADD_LDB_INT(SCOPE_DEFAULT);
+	ADD_LDB_INT(SCOPE_BASE);
+	ADD_LDB_INT(SCOPE_ONELEVEL);
+	ADD_LDB_INT(SCOPE_SUBTREE);
+
+	ADD_LDB_INT(CHANGETYPE_NONE);
+	ADD_LDB_INT(CHANGETYPE_ADD);
+	ADD_LDB_INT(CHANGETYPE_DELETE);
+	ADD_LDB_INT(CHANGETYPE_MODIFY);
+
+	ADD_LDB_INT(FLAG_MOD_ADD);
+	ADD_LDB_INT(FLAG_MOD_REPLACE);
+	ADD_LDB_INT(FLAG_MOD_DELETE);
+
+	ADD_LDB_INT(SUCCESS);
+	ADD_LDB_INT(ERR_OPERATIONS_ERROR);
+	ADD_LDB_INT(ERR_PROTOCOL_ERROR);
+	ADD_LDB_INT(ERR_TIME_LIMIT_EXCEEDED);
+	ADD_LDB_INT(ERR_SIZE_LIMIT_EXCEEDED);
+	ADD_LDB_INT(ERR_COMPARE_FALSE);
+	ADD_LDB_INT(ERR_COMPARE_TRUE);
+	ADD_LDB_INT(ERR_AUTH_METHOD_NOT_SUPPORTED);
+	ADD_LDB_INT(ERR_STRONG_AUTH_REQUIRED);
+	ADD_LDB_INT(ERR_REFERRAL);
+	ADD_LDB_INT(ERR_ADMIN_LIMIT_EXCEEDED);
+	ADD_LDB_INT(ERR_UNSUPPORTED_CRITICAL_EXTENSION);
+	ADD_LDB_INT(ERR_CONFIDENTIALITY_REQUIRED);
+	ADD_LDB_INT(ERR_SASL_BIND_IN_PROGRESS);
+	ADD_LDB_INT(ERR_NO_SUCH_ATTRIBUTE);
+	ADD_LDB_INT(ERR_UNDEFINED_ATTRIBUTE_TYPE);
+	ADD_LDB_INT(ERR_INAPPROPRIATE_MATCHING);
+	ADD_LDB_INT(ERR_CONSTRAINT_VIOLATION);
+	ADD_LDB_INT(ERR_ATTRIBUTE_OR_VALUE_EXISTS);
+	ADD_LDB_INT(ERR_INVALID_ATTRIBUTE_SYNTAX);
+	ADD_LDB_INT(ERR_NO_SUCH_OBJECT);
+	ADD_LDB_INT(ERR_ALIAS_PROBLEM);
+	ADD_LDB_INT(ERR_INVALID_DN_SYNTAX);
+	ADD_LDB_INT(ERR_ALIAS_DEREFERENCING_PROBLEM);
+	ADD_LDB_INT(ERR_INAPPROPRIATE_AUTHENTICATION);
+	ADD_LDB_INT(ERR_INVALID_CREDENTIALS);
+	ADD_LDB_INT(ERR_INSUFFICIENT_ACCESS_RIGHTS);
+	ADD_LDB_INT(ERR_BUSY);
+	ADD_LDB_INT(ERR_UNAVAILABLE);
+	ADD_LDB_INT(ERR_UNWILLING_TO_PERFORM);
+	ADD_LDB_INT(ERR_LOOP_DETECT);
+	ADD_LDB_INT(ERR_NAMING_VIOLATION);
+	ADD_LDB_INT(ERR_OBJECT_CLASS_VIOLATION);
+	ADD_LDB_INT(ERR_NOT_ALLOWED_ON_NON_LEAF);
+	ADD_LDB_INT(ERR_NOT_ALLOWED_ON_RDN);
+	ADD_LDB_INT(ERR_ENTRY_ALREADY_EXISTS);
+	ADD_LDB_INT(ERR_OBJECT_CLASS_MODS_PROHIBITED);
+	ADD_LDB_INT(ERR_AFFECTS_MULTIPLE_DSAS);
+	ADD_LDB_INT(ERR_OTHER);
+
+	ADD_LDB_INT(FLG_RDONLY);
+	ADD_LDB_INT(FLG_NOSYNC);
+	ADD_LDB_INT(FLG_RECONNECT);
+	ADD_LDB_INT(FLG_NOMMAP);
+
+	/* Historical misspelling */
+	PyModule_AddIntConstant(m, "ERR_ALIAS_DEREFERINCING_PROBLEM", LDB_ERR_ALIAS_DEREFERENCING_PROBLEM);
+
+	PyModule_AddStringConstant(m, "__docformat__", "restructuredText");
 
 	PyExc_LdbError = PyErr_NewException(discard_const_p(char, "_ldb.LdbError"), NULL, NULL);
 	PyModule_AddObject(m, "LdbError", PyExc_LdbError);
@@ -3637,9 +3688,9 @@ void initldb(void)
 	PyModule_AddObject(m, "Tree", (PyObject *)&PyLdbTree);
 	PyModule_AddObject(m, "Control", (PyObject *)&PyLdbControl);
 
-	PyModule_AddObject(m, "__version__", PyString_FromString(PACKAGE_VERSION));
+	PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
 
-#define ADD_LDB_STRING(val)  PyModule_AddObject(m, #val, PyString_FromString(LDB_## val))
+#define ADD_LDB_STRING(val)  PyModule_AddStringConstant(m, #val, LDB_## val)
 
 	ADD_LDB_STRING(SYNTAX_DN);
 	ADD_LDB_STRING(SYNTAX_DIRECTORY_STRING);
@@ -3649,4 +3700,20 @@ void initldb(void)
 	ADD_LDB_STRING(SYNTAX_UTC_TIME);
 	ADD_LDB_STRING(OID_COMPARATOR_AND);
 	ADD_LDB_STRING(OID_COMPARATOR_OR);
+
+	return m;
 }
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC PyInit_ldb(void);
+PyMODINIT_FUNC PyInit_ldb(void)
+{
+	return module_init();
+}
+#else
+void initldb(void);
+void initldb(void)
+{
+	module_init();
+}
+#endif
diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py
index d101de8..408c364 100755
--- a/lib/ldb/tests/python/api.py
+++ b/lib/ldb/tests/python/api.py
@@ -54,7 +54,7 @@ class SimpleLdb(TestCase):
 
     def test_set_create_perms(self):
         x = ldb.Ldb()
-        x.set_create_perms(0600)
+        x.set_create_perms(0o600)
 
     def test_modules_none(self):
         x = ldb.Ldb()
@@ -423,7 +423,7 @@ class DnTests(TestCase):
 
     def test_parse_ldif(self):
         msgs = self.ldb.parse_ldif("dn: foo=bar\n")
-        msg = msgs.next()
+        msg = next(msgs)
         self.assertEquals("foo=bar", str(msg[1].dn))
         self.assertTrue(isinstance(msg[1], ldb.Message))
         ldif = self.ldb.write_ldif(msg[1], ldb.CHANGETYPE_NONE)
@@ -431,9 +431,9 @@ class DnTests(TestCase):
 
     def test_parse_ldif_more(self):
         msgs = self.ldb.parse_ldif("dn: foo=bar\n\n\ndn: bar=bar")
-        msg = msgs.next()
+        msg = next(msgs)
         self.assertEquals("foo=bar", str(msg[1].dn))
-        msg = msgs.next()
+        msg = next(msgs)
         self.assertEquals("bar=bar", str(msg[1].dn))
 
     def test_canonical_string(self):
@@ -475,7 +475,10 @@ class LdbMsgTests(TestCase):
     def test_repr(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "dc=foo29")
         self.msg["dc"] = "foo"
-        self.assertEquals("Message({'dn': Dn('dc=foo29'), 'dc': MessageElement(['foo'])})", repr(self.msg))
+        self.assertIn(repr(self.msg), [
+            "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement(['foo'])})",
+            "Message({'dc': MessageElement(['foo']), 'dn': Dn('dc=foo29')})",
+        ])
 
     def test_len(self):
         self.assertEquals(0, len(self.msg))
@@ -549,8 +552,8 @@ class LdbMsgTests(TestCase):
     def test_msg_diff(self):
         l = ldb.Ldb()
         msgs = l.parse_ldif("dn: foo=bar\nfoo: bar\nbaz: do\n\ndn: foo=bar\nfoo: bar\nbaz: dont\n")
-        msg1 = msgs.next()[1]
-        msg2 = msgs.next()[1]
+        msg1 = next(msgs)[1]
+        msg2 = next(msgs)[1]
         msgdiff = l.msg_diff(msg1, msg2)
         self.assertEquals("foo=bar", msgdiff.get("dn").__str__())
         self.assertRaises(KeyError, lambda: msgdiff["foo"])
-- 
2.1.0


From 2ebf2f377ed6aad007245dd333ae396dad31e581 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Wed, 10 Jun 2015 10:21:24 +0200
Subject: [PATCH 7/9] pyldb: Modernize test suite

This gets rid of deprecation warnings for the old method names.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/tests/python/api.py | 232 ++++++++++++++++++++++----------------------
 1 file changed, 116 insertions(+), 116 deletions(-)

diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py
index 408c364..e5f7ee4 100755
--- a/lib/ldb/tests/python/api.py
+++ b/lib/ldb/tests/python/api.py
@@ -24,17 +24,17 @@ class NoContextTests(TestCase):
         self.assertFalse(ldb.valid_attr_name("24foo"))
 
     def test_timestring(self):
-        self.assertEquals("19700101000000.0Z", ldb.timestring(0))
-        self.assertEquals("20071119191012.0Z", ldb.timestring(1195499412))
+        self.assertEqual("19700101000000.0Z", ldb.timestring(0))
+        self.assertEqual("20071119191012.0Z", ldb.timestring(1195499412))
 
     def test_string_to_time(self):
-        self.assertEquals(0, ldb.string_to_time("19700101000000.0Z"))
-        self.assertEquals(1195499412, ldb.string_to_time("20071119191012.0Z"))
+        self.assertEqual(0, ldb.string_to_time("19700101000000.0Z"))
+        self.assertEqual(1195499412, ldb.string_to_time("20071119191012.0Z"))
 
     def test_binary_encode(self):
         encoded = ldb.binary_encode('test\\x')
         decoded = ldb.binary_decode(encoded)
-        self.assertEquals(decoded, 'test\\x')
+        self.assertEqual(decoded, 'test\\x')
 
 class SimpleLdb(TestCase):
 
@@ -58,27 +58,27 @@ class SimpleLdb(TestCase):
 
     def test_modules_none(self):
         x = ldb.Ldb()
-        self.assertEquals([], x.modules())
+        self.assertEqual([], x.modules())
 
     def test_modules_tdb(self):
         x = ldb.Ldb(filename())
-        self.assertEquals("[<ldb module 'tdb'>]", repr(x.modules()))
+        self.assertEqual("[<ldb module 'tdb'>]", repr(x.modules()))
 
     def test_search(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
 
     def test_search_controls(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(len(l.search(controls=["paged_results:0:5"])), 0)
+        self.assertEqual(len(l.search(controls=["paged_results:0:5"])), 0)
 
     def test_search_attrs(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(len(l.search(ldb.Dn(l, ""), ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
+        self.assertEqual(len(l.search(ldb.Dn(l, ""), ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
 
     def test_search_string_dn(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(len(l.search("", ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
+        self.assertEqual(len(l.search("", ldb.SCOPE_SUBTREE, "(dc=*)", ["dc"])), 0)
 
     def test_search_attr_string(self):
         l = ldb.Ldb(filename())
@@ -88,11 +88,11 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         l.set_opaque("my_opaque", l)
         self.assertTrue(l.get_opaque("my_opaque") is not None)
-        self.assertEquals(None, l.get_opaque("unknown"))
+        self.assertEqual(None, l.get_opaque("unknown"))
 
     def test_search_scope_base(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(len(l.search(ldb.Dn(l, "dc=foo1"), 
+        self.assertEqual(len(l.search(ldb.Dn(l, "dc=foo1"), 
                           ldb.SCOPE_ONELEVEL)), 0)
 
     def test_delete(self):
@@ -124,29 +124,29 @@ class SimpleLdb(TestCase):
 
     def test_get_config_basedn(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(None, l.get_config_basedn())
+        self.assertEqual(None, l.get_config_basedn())
 
     def test_get_root_basedn(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(None, l.get_root_basedn())
+        self.assertEqual(None, l.get_root_basedn())
 
     def test_get_schema_basedn(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(None, l.get_schema_basedn())
+        self.assertEqual(None, l.get_schema_basedn())
 
     def test_get_default_basedn(self):
         l = ldb.Ldb(filename())
-        self.assertEquals(None, l.get_default_basedn())
+        self.assertEqual(None, l.get_default_basedn())
 
     def test_add(self):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo4")
         m["bla"] = "bla"
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
-            self.assertEquals(len(l.search()), 1)
+            self.assertEqual(len(l.search()), 1)
         finally:
             l.delete(ldb.Dn(l, "dc=foo4"))
 
@@ -155,27 +155,27 @@ class SimpleLdb(TestCase):
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo4")
         m["bla"] = "bla"
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         self.assertRaises(ldb.LdbError, lambda: l.add(m,["search_options:1:2"]))
 
     def test_add_dict(self):
         l = ldb.Ldb(filename())
         m = {"dn": ldb.Dn(l, "dc=foo5"),
              "bla": "bla"}
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
-            self.assertEquals(len(l.search()), 1)
+            self.assertEqual(len(l.search()), 1)
         finally:
             l.delete(ldb.Dn(l, "dc=foo5"))
 
     def test_add_dict_string_dn(self):
         l = ldb.Ldb(filename())
         m = {"dn": "dc=foo6", "bla": "bla"}
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
-            self.assertEquals(len(l.search()), 1)
+            self.assertEqual(len(l.search()), 1)
         finally:
             l.delete(ldb.Dn(l, "dc=foo6"))
 
@@ -184,11 +184,11 @@ class SimpleLdb(TestCase):
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo7")
         m["bla"] = "bla"
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
             l.rename(ldb.Dn(l, "dc=foo7"), ldb.Dn(l, "dc=bar"))
-            self.assertEquals(len(l.search()), 1)
+            self.assertEqual(len(l.search()), 1)
         finally:
             l.delete(ldb.Dn(l, "dc=bar"))
 
@@ -197,12 +197,12 @@ class SimpleLdb(TestCase):
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo8")
         m["bla"] = "bla"
-        self.assertEquals(len(l.search()), 0)
+        self.assertEqual(len(l.search()), 0)
         l.add(m)
-        self.assertEquals(len(l.search()), 1)
+        self.assertEqual(len(l.search()), 1)
         try:
             l.rename("dc=foo8", "dc=bar")
-            self.assertEquals(len(l.search()), 1)
+            self.assertEqual(len(l.search()), 1)
         finally:
             l.delete(ldb.Dn(l, "dc=bar"))
 
@@ -213,17 +213,17 @@ class SimpleLdb(TestCase):
         m["bla"] = ["1234"]
         l.add(m)
         rm = l.search(m.dn)[0]
-        self.assertEquals(["1234"], list(rm["bla"]))
+        self.assertEqual(["1234"], list(rm["bla"]))
         try:
             m = ldb.Message()
             m.dn = ldb.Dn(l, "dc=modifydelete")
             m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla")
-            self.assertEquals(ldb.FLAG_MOD_DELETE, m["bla"].flags())
+            self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags())
             l.modify(m)
             rm = l.search(m.dn)[0]
-            self.assertEquals(1, len(rm))
+            self.assertEqual(1, len(rm))
             rm = l.search(m.dn, attrs=["bla"])
-            self.assertEquals(0, len(rm))
+            self.assertEqual(0, len(rm))
         finally:
             l.delete(ldb.Dn(l, "dc=modifydelete"))
 
@@ -237,11 +237,11 @@ class SimpleLdb(TestCase):
             m = ldb.Message()
             m.dn = ldb.Dn(l, "dc=add")
             m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
-            self.assertEquals(ldb.FLAG_MOD_ADD, m["bla"].flags())
+            self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
             l.modify(m)
             rm = l.search(m.dn)[0]
-            self.assertEquals(2, len(rm))
-            self.assertEquals(["1234", "456"], list(rm["bla"]))
+            self.assertEqual(2, len(rm))
+            self.assertEqual(["1234", "456"], list(rm["bla"]))
         finally:
             l.delete(ldb.Dn(l, "dc=add"))
 
@@ -255,13 +255,13 @@ class SimpleLdb(TestCase):
             m = ldb.Message()
             m.dn = ldb.Dn(l, "dc=modify2")
             m["bla"] = ldb.MessageElement(["789"], ldb.FLAG_MOD_REPLACE, "bla")
-            self.assertEquals(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
+            self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
             l.modify(m)
             rm = l.search(m.dn)[0]
-            self.assertEquals(2, len(rm))
-            self.assertEquals(["789"], list(rm["bla"]))
+            self.assertEqual(2, len(rm))
+            self.assertEqual(["789"], list(rm["bla"]))
             rm = l.search(m.dn, attrs=["bla"])[0]
-            self.assertEquals(1, len(rm))
+            self.assertEqual(1, len(rm))
         finally:
             l.delete(ldb.Dn(l, "dc=modify2"))
 
@@ -275,19 +275,19 @@ class SimpleLdb(TestCase):
             m = ldb.Message()
             m.dn = ldb.Dn(l, "dc=add")
             m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
-            self.assertEquals(ldb.FLAG_MOD_ADD, m["bla"].flags())
+            self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
             l.modify(m)
             rm = l.search(m.dn)[0]
-            self.assertEquals(2, len(rm))
-            self.assertEquals(["1234", "456"], list(rm["bla"]))
+            self.assertEqual(2, len(rm))
+            self.assertEqual(["1234", "456"], list(rm["bla"]))
 
             # Now create another modify, but switch the flags before we do it
             m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
             m["bla"].set_flags(ldb.FLAG_MOD_DELETE)
             l.modify(m)
             rm = l.search(m.dn, attrs=["bla"])[0]
-            self.assertEquals(1, len(rm))
-            self.assertEquals(["1234"], list(rm["bla"]))
+            self.assertEqual(1, len(rm))
+            self.assertEqual(["1234"], list(rm["bla"]))
         finally:
             l.delete(ldb.Dn(l, "dc=add"))
 
@@ -307,7 +307,7 @@ class SimpleLdb(TestCase):
         m["foo"] = ["bar"]
         l.add(m)
         l.transaction_cancel()
-        self.assertEquals(0, len(l.search(ldb.Dn(l, "dc=foo10"))))
+        self.assertEqual(0, len(l.search(ldb.Dn(l, "dc=foo10"))))
 
     def test_set_debug(self):
         def my_report_fn(level, text):
@@ -326,7 +326,7 @@ class SimpleLdb(TestCase):
             "displayname" : "foo\0bar",
         })
         res = l.search(expression="(dn=dc=somedn)")
-        self.assertEquals("foo\0bar", res[0]["displayname"][0])
+        self.assertEqual("foo\0bar", res[0]["displayname"][0])
 
     def test_no_crash_broken_expr(self):
         l = ldb.Ldb(filename())
@@ -348,21 +348,21 @@ class DnTests(TestCase):
     def test_eq(self):
         x = ldb.Dn(self.ldb, "dc=foo11,bar=bloe")
         y = ldb.Dn(self.ldb, "dc=foo11,bar=bloe")
-        self.assertEquals(x, y)
+        self.assertEqual(x, y)
         y = ldb.Dn(self.ldb, "dc=foo11,bar=blie")
-        self.assertNotEquals(x, y)
+        self.assertNotEqual(x, y)
 
     def test_str(self):
         x = ldb.Dn(self.ldb, "dc=foo12,bar=bloe")
-        self.assertEquals(x.__str__(), "dc=foo12,bar=bloe")
+        self.assertEqual(x.__str__(), "dc=foo12,bar=bloe")
 
     def test_repr(self):
         x = ldb.Dn(self.ldb, "dc=foo13,bla=blie")
-        self.assertEquals(x.__repr__(), "Dn('dc=foo13,bla=blie')")
+        self.assertEqual(x.__repr__(), "Dn('dc=foo13,bla=blie')")
 
     def test_get_casefold(self):
         x = ldb.Dn(self.ldb, "dc=foo14,bar=bloe")
-        self.assertEquals(x.get_casefold(), "DC=FOO14,BAR=bloe")
+        self.assertEqual(x.get_casefold(), "DC=FOO14,BAR=bloe")
 
     def test_validate(self):
         x = ldb.Dn(self.ldb, "dc=foo15,bar=bloe")
@@ -370,11 +370,11 @@ class DnTests(TestCase):
 
     def test_parent(self):
         x = ldb.Dn(self.ldb, "dc=foo16,bar=bloe")
-        self.assertEquals("bar=bloe", x.parent().__str__())
+        self.assertEqual("bar=bloe", x.parent().__str__())
 
     def test_parent_nonexistent(self):
         x = ldb.Dn(self.ldb, "@BLA")
-        self.assertEquals(None, x.parent())
+        self.assertEqual(None, x.parent())
 
     def test_is_valid(self):
         x = ldb.Dn(self.ldb, "dc=foo18,dc=bloe")
@@ -396,53 +396,53 @@ class DnTests(TestCase):
 
     def test_len(self):
         x = ldb.Dn(self.ldb, "dc=foo21,bar=bloe")
-        self.assertEquals(2, len(x))
+        self.assertEqual(2, len(x))
         x = ldb.Dn(self.ldb, "dc=foo21")
-        self.assertEquals(1, len(x))
+        self.assertEqual(1, len(x))
 
     def test_add_child(self):
         x = ldb.Dn(self.ldb, "dc=foo22,bar=bloe")
         self.assertTrue(x.add_child(ldb.Dn(self.ldb, "bla=bloe")))
-        self.assertEquals("bla=bloe,dc=foo22,bar=bloe", x.__str__())
+        self.assertEqual("bla=bloe,dc=foo22,bar=bloe", x.__str__())
 
     def test_add_base(self):
         x = ldb.Dn(self.ldb, "dc=foo23,bar=bloe")
         base = ldb.Dn(self.ldb, "bla=bloe")
         self.assertTrue(x.add_base(base))
-        self.assertEquals("dc=foo23,bar=bloe,bla=bloe", x.__str__())
+        self.assertEqual("dc=foo23,bar=bloe,bla=bloe", x.__str__())
 
     def test_add(self):
         x = ldb.Dn(self.ldb, "dc=foo24")
         y = ldb.Dn(self.ldb, "bar=bla")
-        self.assertEquals("dc=foo24,bar=bla", str(x + y))
+        self.assertEqual("dc=foo24,bar=bla", str(x + y))
 
     def test_remove_base_components(self):
         x = ldb.Dn(self.ldb, "dc=foo24,dc=samba,dc=org")
         x.remove_base_components(len(x)-1)
-        self.assertEquals("dc=foo24", str(x))
+        self.assertEqual("dc=foo24", str(x))
 
     def test_parse_ldif(self):
         msgs = self.ldb.parse_ldif("dn: foo=bar\n")
         msg = next(msgs)
-        self.assertEquals("foo=bar", str(msg[1].dn))
+        self.assertEqual("foo=bar", str(msg[1].dn))
         self.assertTrue(isinstance(msg[1], ldb.Message))
         ldif = self.ldb.write_ldif(msg[1], ldb.CHANGETYPE_NONE)
-        self.assertEquals("dn: foo=bar\n\n", ldif)
+        self.assertEqual("dn: foo=bar\n\n", ldif)
 
     def test_parse_ldif_more(self):
         msgs = self.ldb.parse_ldif("dn: foo=bar\n\n\ndn: bar=bar")
         msg = next(msgs)
-        self.assertEquals("foo=bar", str(msg[1].dn))
+        self.assertEqual("foo=bar", str(msg[1].dn))
         msg = next(msgs)
-        self.assertEquals("bar=bar", str(msg[1].dn))
+        self.assertEqual("bar=bar", str(msg[1].dn))
 
     def test_canonical_string(self):
         x = ldb.Dn(self.ldb, "dc=foo25,bar=bloe")
-        self.assertEquals("/bloe/foo25", x.canonical_str())
+        self.assertEqual("/bloe/foo25", x.canonical_str())
 
     def test_canonical_ex_string(self):
         x = ldb.Dn(self.ldb, "dc=foo26,bar=bloe")
-        self.assertEquals("/bloe\nfoo26", x.canonical_ex_str())
+        self.assertEqual("/bloe\nfoo26", x.canonical_ex_str())
 
     def test_ldb_is_child_of(self):
         """Testing ldb_dn_compare_dn"""
@@ -465,12 +465,12 @@ class LdbMsgTests(TestCase):
 
     def test_init_dn(self):
         self.msg = ldb.Message(ldb.Dn(ldb.Ldb(), "dc=foo27"))
-        self.assertEquals("dc=foo27", str(self.msg.dn))
+        self.assertEqual("dc=foo27", str(self.msg.dn))
 
     def test_iter_items(self):
-        self.assertEquals(0, len(self.msg.items()))
+        self.assertEqual(0, len(self.msg.items()))
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "dc=foo28")
-        self.assertEquals(1, len(self.msg.items()))
+        self.assertEqual(1, len(self.msg.items()))
 
     def test_repr(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "dc=foo29")
@@ -481,7 +481,7 @@ class LdbMsgTests(TestCase):
         ])
 
     def test_len(self):
-        self.assertEquals(0, len(self.msg))
+        self.assertEqual(0, len(self.msg))
 
     def test_notpresent(self):
         self.assertRaises(KeyError, lambda: self.msg["foo"])
@@ -493,43 +493,43 @@ class LdbMsgTests(TestCase):
         self.msg.add(ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla"))
 
     def test_elements_empty(self):
-        self.assertEquals([], self.msg.elements())
+        self.assertEqual([], self.msg.elements())
 
     def test_elements(self):
         el = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
         self.msg.add(el)
-        self.assertEquals([el], self.msg.elements())
+        self.assertEqual([el], self.msg.elements())
 
     def test_add_value(self):
-        self.assertEquals(0, len(self.msg))
+        self.assertEqual(0, len(self.msg))
         self.msg["foo"] = ["foo"]
-        self.assertEquals(1, len(self.msg))
+        self.assertEqual(1, len(self.msg))
 
     def test_add_value_multiple(self):
-        self.assertEquals(0, len(self.msg))
+        self.assertEqual(0, len(self.msg))
         self.msg["foo"] = ["foo", "bla"]
-        self.assertEquals(1, len(self.msg))
-        self.assertEquals(["foo", "bla"], list(self.msg["foo"]))
+        self.assertEqual(1, len(self.msg))
+        self.assertEqual(["foo", "bla"], list(self.msg["foo"]))
 
     def test_set_value(self):
         self.msg["foo"] = ["fool"]
-        self.assertEquals(["fool"], list(self.msg["foo"]))
+        self.assertEqual(["fool"], list(self.msg["foo"]))
         self.msg["foo"] = ["bar"]
-        self.assertEquals(["bar"], list(self.msg["foo"]))
+        self.assertEqual(["bar"], list(self.msg["foo"]))
 
     def test_keys(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
         self.msg["foo"] = ["bla"]
         self.msg["bar"] = ["bla"]
-        self.assertEquals(["dn", "foo", "bar"], self.msg.keys())
+        self.assertEqual(["dn", "foo", "bar"], self.msg.keys())
 
     def test_dn(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
-        self.assertEquals("@BASEINFO", self.msg.dn.__str__())
+        self.assertEqual("@BASEINFO", self.msg.dn.__str__())
 
     def test_get_dn(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
-        self.assertEquals("@BASEINFO", self.msg.get("dn").__str__())
+        self.assertEqual("@BASEINFO", self.msg.get("dn").__str__())
 
     def test_get_invalid(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
@@ -537,17 +537,17 @@ class LdbMsgTests(TestCase):
 
     def test_get_other(self):
         self.msg["foo"] = ["bar"]
-        self.assertEquals("bar", self.msg.get("foo")[0])
-        self.assertEquals("bar", self.msg.get("foo", idx=0))
-        self.assertEquals(None, self.msg.get("foo", idx=1))
-        self.assertEquals("", self.msg.get("foo", default='', idx=1))
+        self.assertEqual("bar", self.msg.get("foo")[0])
+        self.assertEqual("bar", self.msg.get("foo", idx=0))
+        self.assertEqual(None, self.msg.get("foo", idx=1))
+        self.assertEqual("", self.msg.get("foo", default='', idx=1))
 
     def test_get_default(self):
-        self.assertEquals(None, self.msg.get("tatayoyo", idx=0))
-        self.assertEquals("anniecordie", self.msg.get("tatayoyo", "anniecordie"))
+        self.assertEqual(None, self.msg.get("tatayoyo", idx=0))
+        self.assertEqual("anniecordie", self.msg.get("tatayoyo", "anniecordie"))
 
     def test_get_unknown(self):
-        self.assertEquals(None, self.msg.get("lalalala"))
+        self.assertEqual(None, self.msg.get("lalalala"))
 
     def test_msg_diff(self):
         l = ldb.Ldb()
@@ -555,14 +555,14 @@ class LdbMsgTests(TestCase):
         msg1 = next(msgs)[1]
         msg2 = next(msgs)[1]
         msgdiff = l.msg_diff(msg1, msg2)
-        self.assertEquals("foo=bar", msgdiff.get("dn").__str__())
+        self.assertEqual("foo=bar", msgdiff.get("dn").__str__())
         self.assertRaises(KeyError, lambda: msgdiff["foo"])
-        self.assertEquals(1, len(msgdiff))
+        self.assertEqual(1, len(msgdiff))
 
     def test_equal_empty(self):
         msg1 = ldb.Message()
         msg2 = ldb.Message()
-        self.assertEquals(msg1, msg2)
+        self.assertEqual(msg1, msg2)
 
     def test_equal_simplel(self):
         db = ldb.Ldb(filename())
@@ -570,12 +570,12 @@ class LdbMsgTests(TestCase):
         msg1.dn = ldb.Dn(db, "foo=bar")
         msg2 = ldb.Message()
         msg2.dn = ldb.Dn(db, "foo=bar")
-        self.assertEquals(msg1, msg2)
+        self.assertEqual(msg1, msg2)
         msg1['foo'] = 'bar'
         msg2['foo'] = 'bar'
-        self.assertEquals(msg1, msg2)
+        self.assertEqual(msg1, msg2)
         msg2['foo'] = 'blie'
-        self.assertNotEquals(msg1, msg2)
+        self.assertNotEqual(msg1, msg2)
         msg2['foo'] = 'blie'
 
     def test_from_dict(self):
@@ -585,8 +585,8 @@ class LdbMsgTests(TestCase):
         # check different types of input Flags
         for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]:
             m = ldb.Message.from_dict(l, rec, flags)
-            self.assertEquals(rec["a1"], list(m["a1"]))
-            self.assertEquals(flags, m["a1"].flags())
+            self.assertEqual(rec["a1"], list(m["a1"]))
+            self.assertEqual(flags, m["a1"].flags())
         # check input params
         self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE)
         self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE)
@@ -617,43 +617,43 @@ class MessageElementTests(TestCase):
         x = ldb.MessageElement(["foo"])
         y = ldb.MessageElement(["foo"])
         z = ldb.MessageElement(["bzr"])
-        self.assertEquals(x, y)
-        self.assertNotEquals(x, z)
+        self.assertEqual(x, y)
+        self.assertNotEqual(x, z)
 
     def test_create_iterable(self):
         x = ldb.MessageElement(["foo"])
-        self.assertEquals(["foo"], list(x))
+        self.assertEqual(["foo"], list(x))
 
     def test_repr(self):
         x = ldb.MessageElement(["foo"])
-        self.assertEquals("MessageElement(['foo'])", repr(x))
+        self.assertEqual("MessageElement(['foo'])", repr(x))
         x = ldb.MessageElement(["foo", "bla"])
-        self.assertEquals(2, len(x))
-        self.assertEquals("MessageElement(['foo','bla'])", repr(x))
+        self.assertEqual(2, len(x))
+        self.assertEqual("MessageElement(['foo','bla'])", repr(x))
 
     def test_get_item(self):
         x = ldb.MessageElement(["foo", "bar"])
-        self.assertEquals("foo", x[0])
-        self.assertEquals("bar", x[1])
-        self.assertEquals("bar", x[-1])
+        self.assertEqual("foo", x[0])
+        self.assertEqual("bar", x[1])
+        self.assertEqual("bar", x[-1])
         self.assertRaises(IndexError, lambda: x[45])
 
     def test_len(self):
         x = ldb.MessageElement(["foo", "bar"])
-        self.assertEquals(2, len(x))
+        self.assertEqual(2, len(x))
 
     def test_eq(self):
         x = ldb.MessageElement(["foo", "bar"])
         y = ldb.MessageElement(["foo", "bar"])
-        self.assertEquals(y, x)
+        self.assertEqual(y, x)
         x = ldb.MessageElement(["foo"])
-        self.assertNotEquals(y, x)
+        self.assertNotEqual(y, x)
         y = ldb.MessageElement(["foo"])
-        self.assertEquals(y, x)
+        self.assertEqual(y, x)
 
     def test_extended(self):
         el = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
-        self.assertEquals("MessageElement(['456'])", repr(el))
+        self.assertEqual("MessageElement(['456'])", repr(el))
 
 
 class ModuleTests(TestCase):
@@ -684,9 +684,9 @@ class ModuleTests(TestCase):
             os.unlink(name)
         l = ldb.Ldb(name)
         l.add({"dn": "@MODULES", "@LIST": "bla"})
-        self.assertEquals([], ops)
+        self.assertEqual([], ops)
         l = ldb.Ldb(name)
-        self.assertEquals(["init"], ops)
+        self.assertEqual(["init"], ops)
 
 class LdbResultTests(TestCase):
 
@@ -718,7 +718,7 @@ class LdbResultTests(TestCase):
 
     def test_return_type(self):
         res = self.l.search()
-        self.assertEquals(str(res), "<ldb result>")
+        self.assertEqual(str(res), "<ldb result>")
 
     def test_get_msgs(self):
         res = self.l.search()
@@ -751,8 +751,8 @@ class LdbResultTests(TestCase):
     def test_create_control(self):
         self.assertRaises(ValueError, ldb.Control, self.l, "tatayoyo:0")
         c = ldb.Control(self.l, "relax:1")
-        self.assertEquals(c.critical, True)
-        self.assertEquals(c.oid, "1.3.6.1.4.1.4203.666.5.12")
+        self.assertEqual(c.critical, True)
+        self.assertEqual(c.oid, "1.3.6.1.4.1.4203.666.5.12")
 
     def test_iter_refs(self):
         res = self.l.search().referals
-- 
2.1.0


From 87e334665f583f4c490350f293a1038954476441 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Tue, 9 Jun 2015 17:44:40 +0200
Subject: [PATCH 8/9] pyldb: Split text/byte strings for compatibility with
 Python 3

Compatibility with Python 2, and backwards compatibility on Python 2,
is kept.

Under Python 3, DNs, attribute names, filters, controls are always text
(unicode) strings, encoded to/from UTF-8 for storage.
Attribute values are byte strings.

When creating DNs and attribute values, both text and bytes are accepted.
This allows creating messages from homogeneous dicts.

LDB Messages and MessageElements have a .text attribute, which offers
a text view on the contents: any value retrieved from it will be a text
string. The wrapper is implemented in a new Python module.

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/_ldb_text.py | 148 +++++++++++++++++++++++++
 lib/ldb/pyldb.c      | 305 ++++++++++++++++++++++++++++++++++++---------------
 lib/ldb/pyldb_util.c |  27 +++--
 lib/ldb/wscript      |   8 ++
 4 files changed, 387 insertions(+), 101 deletions(-)
 create mode 100644 lib/ldb/_ldb_text.py

diff --git a/lib/ldb/_ldb_text.py b/lib/ldb/_ldb_text.py
new file mode 100644
index 0000000..f6f1ac0
--- /dev/null
+++ b/lib/ldb/_ldb_text.py
@@ -0,0 +1,148 @@
+# Text wrapper for ldb bindings
+#
+# Copyright (C) 2015 Petr Viktorin <pviktori at redhat.com>
+# Published under the GNU LGPLv3 or later
+
+import sys
+import functools
+
+import ldb
+
+
+def _recursive_encode(obj):
+    if isinstance(obj, bytes):
+        return obj
+    elif isinstance(obj, str):
+        return obj.encode('utf-8')
+    else:
+        return [_recursive_encode(o) for o in obj]
+
+
+class _WrapBase(object):
+
+    @classmethod
+    def _wrap(cls, wrapped):
+        self = cls.__new__(cls)
+        self._wrapped = wrapped
+        return self
+
+    def __len__(self):
+        return len(self._wrapped)
+
+    def __eq__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped == other._wrapped
+        else:
+            return self._wrapped == other
+
+    def __ne__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped != other._wrapped
+        else:
+            return self._wrapped != other
+
+    def __lt__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped < other._wrapped
+        else:
+            return self._wrapped < other
+
+    def __le__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped >= other._wrapped
+        else:
+            return self._wrapped >= other
+
+    def __gt__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped > other._wrapped
+        else:
+            return self._wrapped > other
+
+    def __ge__(self, other):
+        if hasattr(other, '_wrapped'):
+            return self._wrapped >= other._wrapped
+        else:
+            return self._wrapped >= other
+
+    def __repr__(self):
+        return '%s.text' % repr(self._wrapped)
+
+
+class MessageElementTextWrapper(_WrapBase):
+
+    """Text interface for a LDB message element"""
+
+    def __iter__(self):
+        for item in self._wrapped:
+            yield item.decode('utf-8')
+
+    def __getitem__(self, key):
+        result = self._wrapped[key]
+        if result is None:
+            return None
+        else:
+            return result.decode('utf-8')
+
+    @property
+    def flags(self):
+        return self._wrapped.flags
+
+    @property
+    def set_flags(self):
+        return self._wrapped.set_flags
+
+_wrap_element = MessageElementTextWrapper._wrap
+
+
+class MessageTextWrapper(_WrapBase):
+
+    """Text interface for a LDB message"""
+
+    def __getitem__(self, key):
+        result = self._wrapped[key]
+        if result is None:
+            return None
+        else:
+            return _wrap_element(result)
+
+    def get(self, *args, **kwargs):
+        result = self._wrapped.get(*args, **kwargs)
+        if isinstance(result, ldb.MessageElement):
+            return _wrap_element(result)
+        elif isinstance(result, bytes):
+            return result.decode('utf-8')
+        else:
+            return result
+
+    def __setitem__(self, key, item):
+        self._wrapped[key] = _recursive_encode(item)
+
+    def __delitem__(self, key):
+        del self._wrapped[key]
+
+    def elements(self):
+        return [_wrap_element(el) for el in self._wrapped.elements()]
+
+    def items(self):
+        return [(attr, _wrap_element(el)) for attr, el in self._wrapped.items()]
+
+    @property
+    def keys(self):
+        return self._wrapped.keys
+
+    @property
+    def remove(self):
+        return self._wrapped.remove
+
+    @property
+    def add(self):
+        return self._wrapped.add
+
+    @property
+    def dn(self):
+        return self._wrapped.dn
+
+    @dn.setter
+    def dn(self, new_value):
+        self._wrapped.dn = new_value
diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index e279f97..8ad0f02 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -57,15 +57,34 @@ static struct ldb_message_element *PyObject_AsMessageElement(
 						      unsigned int flags,
 						      const char *attr_name);
 
-/* There's no Py_ssize_t in 2.4, apparently */
-#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 5
-typedef int Py_ssize_t;
-typedef inquiry lenfunc;
-typedef intargfunc ssizeargfunc;
-#endif
-
-
+#if PY_MAJOR_VERSION >= 3
+#define PyStr_Check PyUnicode_Check
+#define PyStr_FromString PyUnicode_FromString
+#define PyStr_FromStringAndSize PyUnicode_FromStringAndSize
+#define PyStr_FromFormat PyUnicode_FromFormat
+#define PyStr_FromFormatV PyUnicode_FromFormatV
+#define PyStr_AsUTF8 PyUnicode_AsUTF8
+#define PyStr_AsUTF8AndSize PyUnicode_AsUTF8AndSize
+#define PyInt_FromLong PyLong_FromLong
+#else
+#define PyStr_Check PyString_Check
+#define PyStr_FromString PyString_FromString
+#define PyStr_FromStringAndSize PyString_FromStringAndSize
+#define PyStr_FromFormat PyString_FromFormat
+#define PyStr_FromFormatV PyString_FromFormatV
+#define PyStr_AsUTF8 PyString_AsString
 
+const char *PyStr_AsUTF8AndSize(PyObject *pystr, Py_ssize_t *sizeptr);
+const char *
+PyStr_AsUTF8AndSize(PyObject *pystr, Py_ssize_t *sizeptr)
+{
+	const char * ret = PyString_AsString(pystr);
+	if (ret == NULL)
+		return NULL;
+	*sizeptr = PyString_Size(pystr);
+	return ret;
+}
+#endif
 
 static PyObject *richcmp(int cmp_val, int op)
 {
@@ -93,9 +112,9 @@ static PyObject *py_ldb_control_str(PyLdbControlObject *self)
 			PyErr_NoMemory();
 			return NULL;
 		}
-		return PyString_FromString(control);
+		return PyStr_FromString(control);
 	} else {
-		return PyString_FromFormat("ldb control");
+		return PyStr_FromString("ldb control");
 	}
 }
 
@@ -108,9 +127,32 @@ static void py_ldb_control_dealloc(PyLdbControlObject *self)
 	Py_TYPE(self)->tp_free(self);
 }
 
+/* Create a text (rather than bytes) interface for a LDB result object */
+static PyObject *wrap_text(const char *type, PyObject *wrapped)
+{
+	PyObject *mod, *cls, *constructor, *inst;
+	mod = PyImport_ImportModule("_ldb_text");
+	if (mod == NULL)
+		return NULL;
+	cls = PyObject_GetAttrString(mod, type);
+	Py_DECREF(mod);
+	if (cls == NULL) {
+		Py_DECREF(mod);
+		return NULL;
+	}
+	constructor = PyObject_GetAttrString(cls, "_wrap");
+	Py_DECREF(cls);
+	if (constructor == NULL) {
+		return NULL;
+	}
+	inst = PyObject_CallFunction(constructor, discard_const_p(char, "O"), wrapped);
+	Py_DECREF(constructor);
+	return inst;
+}
+
 static PyObject *py_ldb_control_get_oid(PyLdbControlObject *self)
 {
-	return PyString_FromString(self->data->oid);
+	return PyStr_FromString(self->data->oid);
 }
 
 static PyObject *py_ldb_control_get_critical(PyLdbControlObject *self)
@@ -208,7 +250,7 @@ static void PyErr_SetLdbError(PyObject *error, int ret, struct ldb_context *ldb_
 
 static PyObject *PyObject_FromLdbValue(const struct ldb_val *val)
 {
-	return PyString_FromStringAndSize((const char *)val->data, val->length);
+	return PyBytes_FromStringAndSize((const char *)val->data, val->length);
 }
 
 /**
@@ -334,7 +376,7 @@ static PyObject *PyLdbResult_FromResult(struct ldb_result *result)
 	}
 
 	for (i = 0;result->refs && result->refs[i]; i++) {
-		PyList_SetItem(referals, i, PyString_FromString(result->refs[i]));
+		PyList_SetItem(referals, i, PyStr_FromString(result->refs[i]));
 	}
 	ret->referals = referals;
 	return (PyObject *)ret;
@@ -392,22 +434,22 @@ static PyObject *py_ldb_dn_is_null(PyLdbDnObject *self)
  
 static PyObject *py_ldb_dn_get_casefold(PyLdbDnObject *self)
 {
-	return PyString_FromString(ldb_dn_get_casefold(self->dn));
+	return PyStr_FromString(ldb_dn_get_casefold(self->dn));
 }
 
 static PyObject *py_ldb_dn_get_linearized(PyLdbDnObject *self)
 {
-	return PyString_FromString(ldb_dn_get_linearized(self->dn));
+	return PyStr_FromString(ldb_dn_get_linearized(self->dn));
 }
 
 static PyObject *py_ldb_dn_canonical_str(PyLdbDnObject *self)
 {
-	return PyString_FromString(ldb_dn_canonical_string(self->dn, self->dn));
+	return PyStr_FromString(ldb_dn_canonical_string(self->dn, self->dn));
 }
 
 static PyObject *py_ldb_dn_canonical_ex_str(PyLdbDnObject *self)
 {
-	return PyString_FromString(ldb_dn_canonical_ex_string(self->dn, self->dn));
+	return PyStr_FromString(ldb_dn_canonical_ex_string(self->dn, self->dn));
 }
 
 static PyObject *py_ldb_dn_extended_str(PyLdbDnObject *self, PyObject *args, PyObject *kwargs)
@@ -418,7 +460,7 @@ static PyObject *py_ldb_dn_extended_str(PyLdbDnObject *self, PyObject *args, PyO
 					 discard_const_p(char *, kwnames),
 					 &mode))
 		return NULL;
-	return PyString_FromString(ldb_dn_get_extended_linearized(self->dn, self->dn, mode));
+	return PyStr_FromString(ldb_dn_get_extended_linearized(self->dn, self->dn, mode));
 }
 
 static PyObject *py_ldb_dn_get_extended_component(PyLdbDnObject *self, PyObject *args)
@@ -433,14 +475,15 @@ static PyObject *py_ldb_dn_get_extended_component(PyLdbDnObject *self, PyObject
 		Py_RETURN_NONE;
 	}
 
-	return PyString_FromStringAndSize((const char *)val->data, val->length);
+	return PyBytes_FromStringAndSize((const char *)val->data, val->length);
 }
 
 static PyObject *py_ldb_dn_set_extended_component(PyLdbDnObject *self, PyObject *args)
 {
 	char *name;
 	PyObject *value;
-	int err;
+	int err, result;
+	Py_ssize_t size;
 
 	if (!PyArg_ParseTuple(args, "sO", &name, &value))
 		return NULL;
@@ -449,12 +492,12 @@ static PyObject *py_ldb_dn_set_extended_component(PyLdbDnObject *self, PyObject
 		err = ldb_dn_set_extended_component(self->dn, name, NULL);
 	} else {
 		struct ldb_val val;
-		if (!PyString_Check(value)) {
-			PyErr_SetString(PyExc_TypeError, "Expected a string argument");
+		result = PyBytes_AsStringAndSize(value, (char **) &val.data, &size);
+		val.length = size;
+		if (result != 0) {
+			PyErr_SetString(PyExc_TypeError, "Expected a bytestring argument");
 			return NULL;
 		}
-		val.data = (uint8_t *)PyString_AsString(value);
-		val.length = PyString_Size(value);
 		err = ldb_dn_set_extended_component(self->dn, name, &val);
 	}
 
@@ -468,7 +511,19 @@ static PyObject *py_ldb_dn_set_extended_component(PyLdbDnObject *self, PyObject
 
 static PyObject *py_ldb_dn_repr(PyLdbDnObject *self)
 {
-	return PyString_FromFormat("Dn(%s)", PyObject_REPR(PyString_FromString(ldb_dn_get_linearized(self->dn))));
+	PyObject *str = PyStr_FromString(ldb_dn_get_linearized(self->dn));
+	PyObject *repr, *result;
+	if (str == NULL)
+		return NULL;
+	repr = PyObject_Repr(str);
+	if (repr == NULL) {
+		Py_DECREF(str);
+		return NULL;
+	}
+	result = PyStr_FromFormat("Dn(%s)", PyStr_AsUTF8(repr));
+	Py_DECREF(str);
+	Py_DECREF(repr);
+	return result;
 }
 
 static PyObject *py_ldb_dn_check_special(PyLdbDnObject *self, PyObject *args)
@@ -591,7 +646,7 @@ static PyObject *py_ldb_dn_get_component_name(PyLdbDnObject *self, PyObject *arg
 		Py_RETURN_NONE;
 	}
 
-	return PyString_FromString(name);
+	return PyStr_FromString(name);
 }
 
 static PyObject *py_ldb_dn_get_component_value(PyLdbDnObject *self, PyObject *args)
@@ -619,18 +674,19 @@ static PyObject *py_ldb_dn_set_component(PyLdbDnObject *self, PyObject *args)
 	char *name = NULL;
 	PyObject *value = Py_None;
 	struct ldb_val val = { NULL, };
-	int err;
+	int err, ret;
+	Py_ssize_t size;
 
 	if (!PyArg_ParseTuple(args, "IsO", &num, &name, &value))
 		return NULL;
 
 	if (value != Py_None) {
-		if (!PyString_Check(value)) {
-			PyErr_SetString(PyExc_TypeError, "Expected a string argument");
+		ret = PyBytes_AsStringAndSize(value, (char **) &val.data, &size);
+		if (ret != 0) {
+			PyErr_SetString(PyExc_TypeError, "Expected a bytestring argument");
 			return NULL;
 		}
-		val.data = (uint8_t *)PyString_AsString(value);
-		val.length = PyString_Size(value);
+		val.length = size;
 	}
 
 	err = ldb_dn_set_component(self->dn, num, name, val);
@@ -654,7 +710,7 @@ static PyObject *py_ldb_dn_get_rdn_name(PyLdbDnObject *self)
 		Py_RETURN_NONE;
 	}
 
-	return PyString_FromString(name);
+	return PyStr_FromString(name);
 }
 
 static PyObject *py_ldb_dn_get_rdn_value(PyLdbDnObject *self)
@@ -855,7 +911,7 @@ static void py_ldb_debug(void *context, enum ldb_debug_level level, const char *
 static void py_ldb_debug(void *context, enum ldb_debug_level level, const char *fmt, va_list ap)
 {
 	PyObject *fn = (PyObject *)context;
-	PyObject_CallFunction(fn, discard_const_p(char, "(i,O)"), level, PyString_FromFormatV(fmt, ap));
+	PyObject_CallFunction(fn, discard_const_p(char, "(i,O)"), level, PyStr_FromFormatV(fmt, ap));
 }
 
 static PyObject *py_ldb_debug_func;
@@ -952,7 +1008,7 @@ static PyObject *py_ldb_setup_wellknown_attributes(PyLdbObject *self)
 
 static PyObject *py_ldb_repr(PyLdbObject *self)
 {
-	return PyString_FromFormat("<ldb connection>");
+	return PyStr_FromString("<ldb connection>");
 }
 
 static PyObject *py_ldb_get_root_basedn(PyLdbObject *self)
@@ -988,10 +1044,12 @@ static PyObject *py_ldb_get_default_basedn(PyLdbObject *self)
 	return py_ldb_dn_copy(dn);
 }
 
-static const char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list, 
-					const char *paramname)
+static const char **PyList_AsStrList(TALLOC_CTX *mem_ctx, PyObject *list, 
+                    const char *paramname)
 {
 	const char **ret;
+	char *str;
+	Py_ssize_t size;
 	Py_ssize_t i;
 	if (!PyList_Check(list)) {
 		PyErr_Format(PyExc_TypeError, "%s is not a list", paramname);
@@ -1005,12 +1063,17 @@ static const char **PyList_AsStringList(TALLOC_CTX *mem_ctx, PyObject *list,
 
 	for (i = 0; i < PyList_Size(list); i++) {
 		PyObject *item = PyList_GetItem(list, i);
-		if (!PyString_Check(item)) {
+		if (!PyStr_Check(item)) {
 			PyErr_Format(PyExc_TypeError, "%s should be strings", paramname);
+			talloc_free(ret);
+			return NULL;
+		}
+		str = PyStr_AsUTF8AndSize(item, &size);
+		if (str == NULL) {
+			talloc_free(ret);
 			return NULL;
 		}
-		ret[i] = talloc_strndup(ret, PyString_AsString(item),
-					PyString_Size(item));
+		ret[i] = talloc_strndup(ret, str, size);
 	}
 	ret[i] = NULL;
 	return ret;
@@ -1036,7 +1099,7 @@ static int py_ldb_init(PyLdbObject *self, PyObject *args, PyObject *kwargs)
 	if (py_options == Py_None) {
 		options = NULL;
 	} else {
-		options = PyList_AsStringList(ldb, py_options, "options");
+		options = PyList_AsStrList(ldb, py_options, "options");
 		if (options == NULL)
 			return -1;
 	}
@@ -1092,7 +1155,7 @@ static PyObject *py_ldb_connect(PyLdbObject *self, PyObject *args, PyObject *kwa
 	if (py_options == Py_None) {
 		options = NULL;
 	} else {
-		options = PyList_AsStringList(NULL, py_options, "options");
+		options = PyList_AsStrList(NULL, py_options, "options");
 		if (options == NULL)
 			return NULL;
 	}
@@ -1134,7 +1197,7 @@ static PyObject *py_ldb_modify(PyLdbObject *self, PyObject *args, PyObject *kwar
 	if (py_controls == Py_None) {
 		parsed_controls = NULL;
 	} else {
-		const char **controls = PyList_AsStringList(mem_ctx, py_controls, "controls");
+		const char **controls = PyList_AsStrList(mem_ctx, py_controls, "controls");
 		if (controls == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -1161,7 +1224,7 @@ static PyObject *py_ldb_modify(PyLdbObject *self, PyObject *args, PyObject *kwar
 
 	ret = ldb_build_mod_req(&req, ldb_ctx, mem_ctx, msg, parsed_controls,
 				NULL, ldb_op_default_callback, NULL);
-        if (ret != LDB_SUCCESS) {
+	if (ret != LDB_SUCCESS) {
 		PyErr_SetString(PyExc_TypeError, "failed to build request");
 		talloc_free(mem_ctx);
 		return NULL;
@@ -1238,7 +1301,7 @@ static struct ldb_message *PyDict_AsMessage(TALLOC_CTX *mem_ctx,
 	}
 
 	while (PyDict_Next(py_obj, &dict_pos, &key, &value)) {
-		char *key_str = PyString_AsString(key);
+		char *key_str = PyStr_AsUTF8(key);
 		if (ldb_attr_cmp(key_str, "dn") != 0) {
 			msg_el = PyObject_AsMessageElement(msg->elements, value,
 							   mod_flags, key_str);
@@ -1283,7 +1346,7 @@ static PyObject *py_ldb_add(PyLdbObject *self, PyObject *args, PyObject *kwargs)
 	if (py_controls == Py_None) {
 		parsed_controls = NULL;
 	} else {
-		const char **controls = PyList_AsStringList(mem_ctx, py_controls, "controls");
+		const char **controls = PyList_AsStrList(mem_ctx, py_controls, "controls");
 		if (controls == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -1376,7 +1439,7 @@ static PyObject *py_ldb_delete(PyLdbObject *self, PyObject *args, PyObject *kwar
 	if (py_controls == Py_None) {
 		parsed_controls = NULL;
 	} else {
-		const char **controls = PyList_AsStringList(mem_ctx, py_controls, "controls");
+		const char **controls = PyList_AsStrList(mem_ctx, py_controls, "controls");
 		if (controls == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -1454,7 +1517,7 @@ static PyObject *py_ldb_rename(PyLdbObject *self, PyObject *args, PyObject *kwar
 	if (py_controls == Py_None) {
 		parsed_controls = NULL;
 	} else {
-		const char **controls = PyList_AsStringList(mem_ctx, py_controls, "controls");
+		const char **controls = PyList_AsStrList(mem_ctx, py_controls, "controls");
 		if (controls == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -1579,7 +1642,7 @@ static PyObject *py_ldb_write_ldif(PyLdbObject *self, PyObject *args)
 		return NULL;
 	}
 
-	ret = PyString_FromString(string);
+	ret = PyStr_FromString(string);
 
 	talloc_free(mem_ctx);
 
@@ -1668,14 +1731,16 @@ static PyObject *py_ldb_schema_format_value(PyLdbObject *self, PyObject *args)
 	PyObject *ret;
 	char *element_name;
 	PyObject *val;
+	Py_ssize_t size;
+	int result;
 
 	if (!PyArg_ParseTuple(args, "sO", &element_name, &val))
 		return NULL;
 
-	old_val.data = (uint8_t *)PyString_AsString(val);
-	old_val.length = PyString_Size(val);
+	result = PyBytes_AsStringAndSize(val, (char **)&old_val.data, &size);
+	old_val.length = size;
 
-	if (old_val.data == NULL) {
+	if (result != 0) {
 		PyErr_SetString(PyExc_RuntimeError, "Failed to convert passed value to String");
 		return NULL;
 	}
@@ -1697,7 +1762,7 @@ static PyObject *py_ldb_schema_format_value(PyLdbObject *self, PyObject *args)
 		Py_RETURN_NONE;
 	}
 
-	ret = PyString_FromStringAndSize((const char *)new_val.data, new_val.length);
+	ret = PyBytes_FromStringAndSize((const char *)new_val.data, new_val.length);
 
 	talloc_free(mem_ctx);
 
@@ -1739,7 +1804,7 @@ static PyObject *py_ldb_search(PyLdbObject *self, PyObject *args, PyObject *kwar
 	if (py_attrs == Py_None) {
 		attrs = NULL;
 	} else {
-		attrs = PyList_AsStringList(mem_ctx, py_attrs, "attrs");
+		attrs = PyList_AsStrList(mem_ctx, py_attrs, "attrs");
 		if (attrs == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -1758,7 +1823,7 @@ static PyObject *py_ldb_search(PyLdbObject *self, PyObject *args, PyObject *kwar
 	if (py_controls == Py_None) {
 		parsed_controls = NULL;
 	} else {
-		const char **controls = PyList_AsStringList(mem_ctx, py_controls, "controls");
+		const char **controls = PyList_AsStrList(mem_ctx, py_controls, "controls");
 		if (controls == NULL) {
 			talloc_free(mem_ctx);
 			return NULL;
@@ -2131,7 +2196,7 @@ static PySequenceMethods py_ldb_result_seq = {
 
 static PyObject *py_ldb_result_repr(PyLdbObject *self)
 {
-	return PyString_FromFormat("<ldb result>");
+	return PyStr_FromString("<ldb result>");
 }
 
 
@@ -2150,13 +2215,13 @@ static PyTypeObject PyLdbResult = {
 
 static PyObject *py_ldb_module_repr(PyLdbModuleObject *self)
 {
-	return PyString_FromFormat("<ldb module '%s'>",
+	return PyStr_FromFormat("<ldb module '%s'>",
 		pyldb_Module_AsModule(self)->ops->name);
 }
 
 static PyObject *py_ldb_module_str(PyLdbModuleObject *self)
 {
-	return PyString_FromString(pyldb_Module_AsModule(self)->ops->name);
+	return PyStr_FromString(pyldb_Module_AsModule(self)->ops->name);
 }
 
 static PyObject *py_ldb_module_start_transaction(PyLdbModuleObject *self)
@@ -2197,7 +2262,7 @@ static PyObject *py_ldb_module_search(PyLdbModuleObject *self, PyObject *args, P
 	if (py_attrs == Py_None) {
 		attrs = NULL;
 	} else {
-		attrs = PyList_AsStringList(NULL, py_attrs, "attrs");
+		attrs = PyList_AsStrList(NULL, py_attrs, "attrs");
 		if (attrs == NULL)
 			return NULL;
 	}
@@ -2361,6 +2426,9 @@ static struct ldb_message_element *PyObject_AsMessageElement(
 						      const char *attr_name)
 {
 	struct ldb_message_element *me;
+	char *msg;
+	Py_ssize_t size;
+	int result;
 
 	if (pyldb_MessageElement_Check(set_obj)) {
 		PyLdbMessageElementObject *set_obj_as_me = (PyLdbMessageElementObject *)set_obj;
@@ -2380,28 +2448,50 @@ static struct ldb_message_element *PyObject_AsMessageElement(
 
 	me->name = talloc_strdup(me, attr_name);
 	me->flags = flags;
-	if (PyString_Check(set_obj)) {
+	if (PyBytes_Check(set_obj) || PyStr_Check(set_obj)) {
 		me->num_values = 1;
 		me->values = talloc_array(me, struct ldb_val, me->num_values);
-		me->values[0].length = PyString_Size(set_obj);
-		me->values[0].data = talloc_memdup(me, 
-			(uint8_t *)PyString_AsString(set_obj), me->values[0].length+1);
+		if (PyBytes_Check(set_obj)) {
+			result = PyBytes_AsStringAndSize(set_obj, &msg, &size);
+			if (result != 0) {
+				talloc_free(me);
+				return NULL;
+			}
+		} else {
+			msg = PyStr_AsUTF8AndSize(set_obj, &size);
+			if (msg == NULL) {
+				talloc_free(me);
+				return NULL;
+			}
+		}
+		me->values[0].data = talloc_memdup(me, (uint8_t *)msg, size+1);
+		me->values[0].length = size;
 	} else if (PySequence_Check(set_obj)) {
 		Py_ssize_t i;
 		me->num_values = PySequence_Size(set_obj);
 		me->values = talloc_array(me, struct ldb_val, me->num_values);
 		for (i = 0; i < me->num_values; i++) {
 			PyObject *obj = PySequence_GetItem(set_obj, i);
-			if (!PyString_Check(obj)) {
+			if (PyBytes_Check(obj)) {
+				result = PyBytes_AsStringAndSize(obj, &msg, &size);
+				if (result != 0) {
+					talloc_free(me);
+					return NULL;
+				}
+			} else if (PyStr_Check(obj)) {
+				msg = PyStr_AsUTF8AndSize(obj, &size);
+				if (msg == NULL) {
+					talloc_free(me);
+					return NULL;
+				}
+			} else {
 				PyErr_Format(PyExc_TypeError,
 					     "Expected string as element %zd in list", i);
 				talloc_free(me);
 				return NULL;
 			}
-
-			me->values[i].length = PyString_Size(obj);
-			me->values[i].data = talloc_memdup(me, 
-				(uint8_t *)PyString_AsString(obj), me->values[i].length+1);
+			me->values[i].data = talloc_memdup(me, (uint8_t *)msg, size+1);
+			me->values[i].length = size;
 		}
 	} else {
 		PyErr_Format(PyExc_TypeError,
@@ -2479,7 +2569,7 @@ static PyObject *py_ldb_msg_element_find(PyLdbMessageElementObject *self, Py_ssi
 		PyErr_SetString(PyExc_IndexError, "Out of range");
 		return NULL;
 	}
-	return PyString_FromStringAndSize((char *)el->values[idx].data, el->values[idx].length);
+	return PyBytes_FromStringAndSize((char *)el->values[idx].data, el->values[idx].length);
 }
 
 static PySequenceMethods py_ldb_msg_element_seq = {
@@ -2534,6 +2624,9 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
 	const char * const kwnames[] = { "elements", "flags", "name", NULL };
 	PyLdbMessageElementObject *ret;
 	TALLOC_CTX *mem_ctx;
+	char *msg;
+	Py_ssize_t size;
+	int result;
 
 	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OIs",
 					 discard_const_p(char *, kwnames),
@@ -2555,7 +2648,7 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
 
 	if (py_elements != NULL) {
 		Py_ssize_t i;
-		if (PyString_Check(py_elements)) {
+		if (PyBytes_Check(py_elements)) {
 			el->num_values = 1;
 			el->values = talloc_array(el, struct ldb_val, 1);
 			if (el->values == NULL) {
@@ -2563,9 +2656,14 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
 				PyErr_NoMemory();
 				return NULL;
 			}
-			el->values[0].length = PyString_Size(py_elements);
+			result = PyBytes_AsStringAndSize(py_elements, &msg, &size);
+			if (result != 0) {
+				talloc_free(mem_ctx);
+				return NULL;
+			}
 			el->values[0].data = talloc_memdup(el->values, 
-				(uint8_t *)PyString_AsString(py_elements), el->values[0].length+1);
+				(uint8_t *)msg, size);
+			el->values[0].length = size;
 		} else if (PySequence_Check(py_elements)) {
 			el->num_values = PySequence_Size(py_elements);
 			el->values = talloc_array(el, struct ldb_val, el->num_values);
@@ -2580,15 +2678,23 @@ static PyObject *py_ldb_msg_element_new(PyTypeObject *type, PyObject *args, PyOb
 					talloc_free(mem_ctx);
 					return NULL;
 				}
-				if (!PyString_Check(item)) {
+				if (PyBytes_Check(item)) {
+					result = PyBytes_AsStringAndSize(item, &msg, &size);
+				} else if (PyStr_Check(item)) {
+					msg = PyStr_AsUTF8AndSize(item, &size);
+					result = (msg == NULL) ? -1 : 0;
+				} else {
 					PyErr_Format(PyExc_TypeError, 
 						     "Expected string as element %zd in list", i);
+					result = -1;
+				}
+				if (result != 0) {
 					talloc_free(mem_ctx);
 					return NULL;
 				}
-				el->values[i].length = PyString_Size(item);
 				el->values[i].data = talloc_memdup(el,
-					(uint8_t *)PyString_AsString(item), el->values[i].length+1);
+					(uint8_t *)msg, size+1);
+				el->values[i].length = size;
 			}
 		} else {
 			PyErr_SetString(PyExc_TypeError, 
@@ -2623,17 +2729,17 @@ static PyObject *py_ldb_msg_element_repr(PyLdbMessageElementObject *self)
 		PyObject *o = py_ldb_msg_element_find(self, i);
 		repr = PyObject_Repr(o);
 		if (element_str == NULL)
-			element_str = talloc_strdup(NULL, PyString_AsString(repr));
+			element_str = talloc_strdup(NULL, PyStr_AsUTF8(repr));
 		else
-			element_str = talloc_asprintf_append(element_str, ",%s", PyString_AsString(repr));
+			element_str = talloc_asprintf_append(element_str, ",%s", PyStr_AsUTF8(repr));
 		Py_DECREF(repr);
 	}
 
 	if (element_str != NULL) {
-		ret = PyString_FromFormat("MessageElement([%s])", element_str);
+		ret = PyStr_FromFormat("MessageElement([%s])", element_str);
 		talloc_free(element_str);
 	} else {
-		ret = PyString_FromString("MessageElement([])");
+		ret = PyStr_FromString("MessageElement([])");
 	}
 
 	return ret;
@@ -2644,7 +2750,7 @@ static PyObject *py_ldb_msg_element_str(PyLdbMessageElementObject *self)
 	struct ldb_message_element *el = pyldb_MessageElement_AsMessageElement(self);
 
 	if (el->num_values == 1)
-		return PyString_FromStringAndSize((char *)el->values[0].data, el->values[0].length);
+		return PyStr_FromStringAndSize((char *)el->values[0].data, el->values[0].length);
 	else
 		Py_RETURN_NONE;
 }
@@ -2655,6 +2761,16 @@ static void py_ldb_msg_element_dealloc(PyLdbMessageElementObject *self)
 	PyObject_Del(self);
 }
 
+static PyObject *py_ldb_msg_element_get_text(PyObject *self, void *closure)
+{
+	return wrap_text("MessageElementTextWrapper", self);
+}
+
+static PyGetSetDef py_ldb_msg_element_getset[] = {
+	{ discard_const_p(char, "text"), (getter)py_ldb_msg_element_get_text, NULL, NULL },
+	{ NULL }
+};
+
 static PyTypeObject PyLdbMessageElement = {
 	.tp_name = "ldb.MessageElement",
 	.tp_basicsize = sizeof(PyLdbMessageElementObject),
@@ -2662,6 +2778,7 @@ static PyTypeObject PyLdbMessageElement = {
 	.tp_repr = (reprfunc)py_ldb_msg_element_repr,
 	.tp_str = (reprfunc)py_ldb_msg_element_str,
 	.tp_methods = py_ldb_msg_element_methods,
+	.tp_getset = py_ldb_msg_element_getset,
 	.tp_richcompare = (richcmpfunc)py_ldb_msg_element_richcmp,
 	.tp_iter = (getiterfunc)py_ldb_msg_element_iter,
 	.tp_as_sequence = &py_ldb_msg_element_seq,
@@ -2731,11 +2848,11 @@ static PyObject *py_ldb_msg_keys(PyLdbMessageObject *self)
 	Py_ssize_t i, j = 0;
 	PyObject *obj = PyList_New(msg->num_elements+(msg->dn != NULL?1:0));
 	if (msg->dn != NULL) {
-		PyList_SetItem(obj, j, PyString_FromString("dn"));
+		PyList_SetItem(obj, j, PyStr_FromString("dn"));
 		j++;
 	}
 	for (i = 0; i < msg->num_elements; i++) {
-		PyList_SetItem(obj, j, PyString_FromString(msg->elements[i].name));
+		PyList_SetItem(obj, j, PyStr_FromString(msg->elements[i].name));
 		j++;
 	}
 	return obj;
@@ -2746,11 +2863,11 @@ static PyObject *py_ldb_msg_getitem_helper(PyLdbMessageObject *self, PyObject *p
 	struct ldb_message_element *el;
 	char *name;
 	struct ldb_message *msg = pyldb_Message_AsMessage(self);
-	if (!PyString_Check(py_name)) {
+	name = PyStr_AsUTF8(py_name);
+	if (name == NULL) {
 		PyErr_SetNone(PyExc_TypeError);
 		return NULL;
 	}
-	name = PyString_AsString(py_name);
 	if (!ldb_attr_cmp(name, "dn"))
 		return pyldb_Dn_FromDn(msg->dn);
 	el = ldb_msg_find_element(msg, name);
@@ -2912,12 +3029,12 @@ static int py_ldb_msg_setitem(PyLdbMessageObject *self, PyObject *name, PyObject
 {
 	char *attr_name;
 
-	if (!PyString_Check(name)) {
+	attr_name = PyStr_AsUTF8(name);
+	if (attr_name == NULL) {
 		PyErr_SetNone(PyExc_TypeError);
 		return -1;
 	}
 
-	attr_name = PyString_AsString(name);
 	if (value == NULL) {
 		/* delitem */
 		ldb_msg_remove_attr(self->msg, attr_name);
@@ -3028,8 +3145,14 @@ static int py_ldb_msg_set_dn(PyLdbMessageObject *self, PyObject *value, void *cl
 	return 0;
 }
 
+static PyObject *py_ldb_msg_get_text(PyObject *self, void *closure)
+{
+	return wrap_text("MessageTextWrapper", self);
+}
+
 static PyGetSetDef py_ldb_msg_getset[] = {
 	{ discard_const_p(char, "dn"), (getter)py_ldb_msg_get_dn, (setter)py_ldb_msg_set_dn, NULL },
+	{ discard_const_p(char, "text"), (getter)py_ldb_msg_get_text, NULL, NULL },
 	{ NULL }
 };
 
@@ -3043,7 +3166,7 @@ static PyObject *py_ldb_msg_repr(PyLdbMessageObject *self)
 		Py_DECREF(dict);
 		return NULL;
 	}
-	ret = PyString_FromFormat("Message(%s)", PyString_AsString(repr));
+	ret = PyStr_FromFormat("Message(%s)", PyStr_AsUTF8(repr));
 	Py_DECREF(repr);
 	Py_DECREF(dict);
 	return ret;
@@ -3166,7 +3289,7 @@ static int py_module_search(struct ldb_module *mod, struct ldb_request *req)
 		for (len = 0; req->op.search.attrs[len]; len++);
 		py_attrs = PyList_New(len);
 		for (i = 0; i < len; i++)
-			PyList_SetItem(py_attrs, i, PyString_FromString(req->op.search.attrs[i]));
+			PyList_SetItem(py_attrs, i, PyStr_FromString(req->op.search.attrs[i]));
 	}
 
 	py_result = PyObject_CallMethod(py_ldb, discard_const_p(char, "search"),
@@ -3424,7 +3547,7 @@ static PyObject *py_register_module(PyObject *module, PyObject *args)
 		return NULL;
 	}
 
-	ops->name = talloc_strdup(ops, PyString_AsString(PyObject_GetAttrString(input, discard_const_p(char, "name"))));
+	ops->name = talloc_strdup(ops, PyStr_AsUTF8(PyObject_GetAttrString(input, discard_const_p(char, "name"))));
 
 	Py_INCREF(input);
 	ops->private_data = input;
@@ -3457,7 +3580,7 @@ static PyObject *py_timestring(PyObject *module, PyObject *args)
 	if (!PyArg_ParseTuple(args, "l", &t_val))
 		return NULL;
 	tresult = ldb_timestring(NULL, (time_t) t_val);
-	ret = PyString_FromString(tresult);
+	ret = PyStr_FromString(tresult);
 	talloc_free(tresult);
 	return ret;
 }
@@ -3499,7 +3622,7 @@ static PyObject *py_binary_encode(PyObject *self, PyObject *args)
 		PyErr_SetString(PyExc_TypeError, "unable to encode binary string");
 		return NULL;
 	}
-	ret = PyString_FromString(encoded);
+	ret = PyStr_FromString(encoded);
 	talloc_free(encoded);
 	return ret;
 }
@@ -3521,7 +3644,7 @@ static PyObject *py_binary_decode(PyObject *self, PyObject *args)
 		PyErr_SetString(PyExc_TypeError, "unable to decode binary string");
 		return NULL;
 	}
-	ret = Py_BuildValue("s#", val.data, val.length);
+	ret = PyBytes_FromStringAndSize((const char*)val.data, val.length);
 	talloc_free(val.data);
 	return ret;
 }
diff --git a/lib/ldb/pyldb_util.c b/lib/ldb/pyldb_util.c
index 4be9126..26bc842 100644
--- a/lib/ldb/pyldb_util.c
+++ b/lib/ldb/pyldb_util.c
@@ -29,11 +29,12 @@
 
 static PyObject *ldb_module = NULL;
 
-/* There's no Py_ssize_t in 2.4, apparently */
-#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 5
-typedef int Py_ssize_t;
-typedef inquiry lenfunc;
-typedef intargfunc ssizeargfunc;
+#if PY_MAJOR_VERSION >= 3
+#define PyStr_Check PyUnicode_Check
+#define PyStr_AsUTF8 PyUnicode_AsUTF8
+#else
+#define PyStr_Check PyString_Check
+#define PyStr_AsUTF8 PyString_AsString
 #endif
 
 /**
@@ -69,11 +70,17 @@ bool pyldb_Object_AsDn(TALLOC_CTX *mem_ctx, PyObject *object,
 	struct ldb_dn *odn;
 	PyTypeObject *PyLdb_Dn_Type;
 
-	if (ldb_ctx != NULL && PyString_Check(object)) {
-		odn = ldb_dn_new(mem_ctx, ldb_ctx, PyString_AsString(object));
-		*dn = odn;
-		return true;
-	}
+    if (ldb_ctx != NULL && PyStr_Check(object)) {
+        odn = ldb_dn_new(mem_ctx, ldb_ctx, PyStr_AsUTF8(object));
+        *dn = odn;
+        return true;
+    }
+
+    if (ldb_ctx != NULL && PyBytes_Check(object)) {
+        odn = ldb_dn_new(mem_ctx, ldb_ctx, PyBytes_AsString(object));
+        *dn = odn;
+        return true;
+    }
 
 	PyLdb_Dn_Type = PyLdb_GetPyType("Dn");
 	if (PyLdb_Dn_Type == NULL) {
diff --git a/lib/ldb/wscript b/lib/ldb/wscript
index 5071dcd..ad56c7b 100755
--- a/lib/ldb/wscript
+++ b/lib/ldb/wscript
@@ -141,6 +141,14 @@ def build(bld):
                                  realname='ldb.so',
                                  cflags='-DPACKAGE_VERSION=\"%s\"' % VERSION)
 
+        if not bld.env.disable_python:
+            for env in bld.gen_python_environments(['PKGCONFIGDIR']):
+                bld.SAMBA_SCRIPT('_ldb_text.py',
+                                 pattern='_ldb_text.py',
+                                 installdir='python')
+
+                bld.INSTALL_FILES('${PYTHONARCHDIR}', '_ldb_text.py')
+
     if not bld.CONFIG_SET('USING_SYSTEM_LDB'):
         if Options.is_install:
             modules_dir = bld.EXPAND_VARIABLES('${LDB_MODULESDIR}')
-- 
2.1.0


From 1985f76c622e97c7f203493e10c6d78f9bb2e303 Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pviktori at redhat.com>
Date: Wed, 29 Jul 2015 12:45:43 +0200
Subject: [PATCH 9/9] pyldb: Adapt tests to Python 3

Signed-off-by: Petr Viktorin <pviktori at redhat.com>
---
 lib/ldb/tests/python/api.py | 405 ++++++++++++++++++++++++++++++++++++--------
 1 file changed, 330 insertions(+), 75 deletions(-)

diff --git a/lib/ldb/tests/python/api.py b/lib/ldb/tests/python/api.py
index e5f7ee4..fae04586 100755
--- a/lib/ldb/tests/python/api.py
+++ b/lib/ldb/tests/python/api.py
@@ -4,9 +4,12 @@
 
 import os
 from unittest import TestCase
+import sys
 
 import ldb
 
+PY3 = sys.version_info > (3, 0)
+
 
 def filename():
     import tempfile
@@ -32,9 +35,12 @@ class NoContextTests(TestCase):
         self.assertEqual(1195499412, ldb.string_to_time("20071119191012.0Z"))
 
     def test_binary_encode(self):
-        encoded = ldb.binary_encode('test\\x')
+        encoded = ldb.binary_encode(b'test\\x')
         decoded = ldb.binary_decode(encoded)
-        self.assertEqual(decoded, 'test\\x')
+        self.assertEqual(decoded, b'test\\x')
+
+        encoded2 = ldb.binary_encode('test\\x')
+        self.assertEqual(encoded2, encoded)
 
 class SimpleLdb(TestCase):
 
@@ -83,6 +89,7 @@ class SimpleLdb(TestCase):
     def test_search_attr_string(self):
         l = ldb.Ldb(filename())
         self.assertRaises(TypeError, l.search, attrs="dc")
+        self.assertRaises(TypeError, l.search, attrs=b"dc")
 
     def test_opaque(self):
         l = ldb.Ldb(filename())
@@ -103,7 +110,7 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo1")
-        m["b"] = ["a"]
+        m["b"] = [b"a"]
         l.add(m)
         self.assertRaises(ldb.LdbError, lambda: l.delete(m.dn, ["search_options:1:2"]))
         l.delete(m.dn)
@@ -142,6 +149,18 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo4")
+        m["bla"] = b"bla"
+        self.assertEqual(len(l.search()), 0)
+        l.add(m)
+        try:
+            self.assertEqual(len(l.search()), 1)
+        finally:
+            l.delete(ldb.Dn(l, "dc=foo4"))
+
+    def test_add_text(self):
+        l = ldb.Ldb(filename())
+        m = ldb.Message()
+        m.dn = ldb.Dn(l, "dc=foo4")
         m["bla"] = "bla"
         self.assertEqual(len(l.search()), 0)
         l.add(m)
@@ -154,13 +173,24 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo4")
-        m["bla"] = "bla"
+        m["bla"] = b"bla"
         self.assertEqual(len(l.search()), 0)
         self.assertRaises(ldb.LdbError, lambda: l.add(m,["search_options:1:2"]))
 
     def test_add_dict(self):
         l = ldb.Ldb(filename())
         m = {"dn": ldb.Dn(l, "dc=foo5"),
+             "bla": b"bla"}
+        self.assertEqual(len(l.search()), 0)
+        l.add(m)
+        try:
+            self.assertEqual(len(l.search()), 1)
+        finally:
+            l.delete(ldb.Dn(l, "dc=foo5"))
+
+    def test_add_dict_text(self):
+        l = ldb.Ldb(filename())
+        m = {"dn": ldb.Dn(l, "dc=foo5"),
              "bla": "bla"}
         self.assertEqual(len(l.search()), 0)
         l.add(m)
@@ -171,7 +201,17 @@ class SimpleLdb(TestCase):
 
     def test_add_dict_string_dn(self):
         l = ldb.Ldb(filename())
-        m = {"dn": "dc=foo6", "bla": "bla"}
+        m = {"dn": "dc=foo6", "bla": b"bla"}
+        self.assertEqual(len(l.search()), 0)
+        l.add(m)
+        try:
+            self.assertEqual(len(l.search()), 1)
+        finally:
+            l.delete(ldb.Dn(l, "dc=foo6"))
+
+    def test_add_dict_bytes_dn(self):
+        l = ldb.Ldb(filename())
+        m = {"dn": b"dc=foo6", "bla": b"bla"}
         self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
@@ -183,7 +223,7 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo7")
-        m["bla"] = "bla"
+        m["bla"] = b"bla"
         self.assertEqual(len(l.search()), 0)
         l.add(m)
         try:
@@ -196,7 +236,7 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=foo8")
-        m["bla"] = "bla"
+        m["bla"] = b"bla"
         self.assertEqual(len(l.search()), 0)
         l.add(m)
         self.assertEqual(len(l.search()), 1)
@@ -210,10 +250,31 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=modifydelete")
-        m["bla"] = ["1234"]
+        m["bla"] = [b"1234"]
+        l.add(m)
+        rm = l.search(m.dn)[0]
+        self.assertEqual([b"1234"], list(rm["bla"]))
+        try:
+            m = ldb.Message()
+            m.dn = ldb.Dn(l, "dc=modifydelete")
+            m["bla"] = ldb.MessageElement([], ldb.FLAG_MOD_DELETE, "bla")
+            self.assertEqual(ldb.FLAG_MOD_DELETE, m["bla"].flags())
+            l.modify(m)
+            rm = l.search(m.dn)[0]
+            self.assertEqual(1, len(rm))
+            rm = l.search(m.dn, attrs=["bla"])
+            self.assertEqual(0, len(rm))
+        finally:
+            l.delete(ldb.Dn(l, "dc=modifydelete"))
+
+    def test_modify_delete_text(self):
+        l = ldb.Ldb(filename())
+        m = ldb.Message()
+        m.dn = ldb.Dn(l, "dc=modifydelete")
+        m.text["bla"] = ["1234"]
         l.add(m)
         rm = l.search(m.dn)[0]
-        self.assertEqual(["1234"], list(rm["bla"]))
+        self.assertEqual(["1234"], list(rm.text["bla"]))
         try:
             m = ldb.Message()
             m.dn = ldb.Dn(l, "dc=modifydelete")
@@ -231,7 +292,25 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=add")
-        m["bla"] = ["1234"]
+        m["bla"] = [b"1234"]
+        l.add(m)
+        try:
+            m = ldb.Message()
+            m.dn = ldb.Dn(l, "dc=add")
+            m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+            self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+            l.modify(m)
+            rm = l.search(m.dn)[0]
+            self.assertEqual(2, len(rm))
+            self.assertEqual([b"1234", b"456"], list(rm["bla"]))
+        finally:
+            l.delete(ldb.Dn(l, "dc=add"))
+
+    def test_modify_add_text(self):
+        l = ldb.Ldb(filename())
+        m = ldb.Message()
+        m.dn = ldb.Dn(l, "dc=add")
+        m.text["bla"] = ["1234"]
         l.add(m)
         try:
             m = ldb.Message()
@@ -241,7 +320,7 @@ class SimpleLdb(TestCase):
             l.modify(m)
             rm = l.search(m.dn)[0]
             self.assertEqual(2, len(rm))
-            self.assertEqual(["1234", "456"], list(rm["bla"]))
+            self.assertEqual(["1234", "456"], list(rm.text["bla"]))
         finally:
             l.delete(ldb.Dn(l, "dc=add"))
 
@@ -249,7 +328,27 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=modify2")
-        m["bla"] = ["1234", "456"]
+        m["bla"] = [b"1234", b"456"]
+        l.add(m)
+        try:
+            m = ldb.Message()
+            m.dn = ldb.Dn(l, "dc=modify2")
+            m["bla"] = ldb.MessageElement([b"789"], ldb.FLAG_MOD_REPLACE, "bla")
+            self.assertEqual(ldb.FLAG_MOD_REPLACE, m["bla"].flags())
+            l.modify(m)
+            rm = l.search(m.dn)[0]
+            self.assertEqual(2, len(rm))
+            self.assertEqual([b"789"], list(rm["bla"]))
+            rm = l.search(m.dn, attrs=["bla"])[0]
+            self.assertEqual(1, len(rm))
+        finally:
+            l.delete(ldb.Dn(l, "dc=modify2"))
+
+    def test_modify_replace_text(self):
+        l = ldb.Ldb(filename())
+        m = ldb.Message()
+        m.dn = ldb.Dn(l, "dc=modify2")
+        m.text["bla"] = ["1234", "456"]
         l.add(m)
         try:
             m = ldb.Message()
@@ -259,7 +358,7 @@ class SimpleLdb(TestCase):
             l.modify(m)
             rm = l.search(m.dn)[0]
             self.assertEqual(2, len(rm))
-            self.assertEqual(["789"], list(rm["bla"]))
+            self.assertEqual(["789"], list(rm.text["bla"]))
             rm = l.search(m.dn, attrs=["bla"])[0]
             self.assertEqual(1, len(rm))
         finally:
@@ -269,7 +368,33 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         m = ldb.Message()
         m.dn = ldb.Dn(l, "dc=add")
-        m["bla"] = ["1234"]
+        m["bla"] = [b"1234"]
+        l.add(m)
+        try:
+            m = ldb.Message()
+            m.dn = ldb.Dn(l, "dc=add")
+            m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+            self.assertEqual(ldb.FLAG_MOD_ADD, m["bla"].flags())
+            l.modify(m)
+            rm = l.search(m.dn)[0]
+            self.assertEqual(2, len(rm))
+            self.assertEqual([b"1234", b"456"], list(rm["bla"]))
+
+            # Now create another modify, but switch the flags before we do it
+            m["bla"] = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+            m["bla"].set_flags(ldb.FLAG_MOD_DELETE)
+            l.modify(m)
+            rm = l.search(m.dn, attrs=["bla"])[0]
+            self.assertEqual(1, len(rm))
+            self.assertEqual([b"1234"], list(rm["bla"]))
+        finally:
+            l.delete(ldb.Dn(l, "dc=add"))
+
+    def test_modify_flags_change_text(self):
+        l = ldb.Ldb(filename())
+        m = ldb.Message()
+        m.dn = ldb.Dn(l, "dc=add")
+        m.text["bla"] = ["1234"]
         l.add(m)
         try:
             m = ldb.Message()
@@ -279,7 +404,7 @@ class SimpleLdb(TestCase):
             l.modify(m)
             rm = l.search(m.dn)[0]
             self.assertEqual(2, len(rm))
-            self.assertEqual(["1234", "456"], list(rm["bla"]))
+            self.assertEqual(["1234", "456"], list(rm.text["bla"]))
 
             # Now create another modify, but switch the flags before we do it
             m["bla"] = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
@@ -287,7 +412,7 @@ class SimpleLdb(TestCase):
             l.modify(m)
             rm = l.search(m.dn, attrs=["bla"])[0]
             self.assertEqual(1, len(rm))
-            self.assertEqual(["1234"], list(rm["bla"]))
+            self.assertEqual(["1234"], list(rm.text["bla"]))
         finally:
             l.delete(ldb.Dn(l, "dc=add"))
 
@@ -295,7 +420,7 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         l.transaction_start()
         m = ldb.Message(ldb.Dn(l, "dc=foo9"))
-        m["foo"] = ["bar"]
+        m["foo"] = [b"bar"]
         l.add(m)
         l.transaction_commit()
         l.delete(m.dn)
@@ -304,7 +429,7 @@ class SimpleLdb(TestCase):
         l = ldb.Ldb(filename())
         l.transaction_start()
         m = ldb.Message(ldb.Dn(l, "dc=foo10"))
-        m["foo"] = ["bar"]
+        m["foo"] = [b"bar"]
         l.add(m)
         l.transaction_cancel()
         self.assertEqual(0, len(l.search(ldb.Dn(l, "dc=foo10"))))
@@ -319,14 +444,14 @@ class SimpleLdb(TestCase):
         """Testing we do not get trapped in the \0 byte in a property string."""
         l = ldb.Ldb(filename())
         l.add({
-            "dn" : "dc=somedn",
-            "objectclass" : "user",
-            "cN" : "LDAPtestUSER",
-            "givenname" : "ldap",
-            "displayname" : "foo\0bar",
+            "dn" : b"dc=somedn",
+            "objectclass" : b"user",
+            "cN" : b"LDAPtestUSER",
+            "givenname" : b"ldap",
+            "displayname" : b"foo\0bar",
         })
         res = l.search(expression="(dn=dc=somedn)")
-        self.assertEqual("foo\0bar", res[0]["displayname"][0])
+        self.assertEqual(b"foo\0bar", res[0]["displayname"][0])
 
     def test_no_crash_broken_expr(self):
         l = ldb.Ldb(filename())
@@ -474,11 +599,23 @@ class LdbMsgTests(TestCase):
 
     def test_repr(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "dc=foo29")
-        self.msg["dc"] = "foo"
-        self.assertIn(repr(self.msg), [
-            "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement(['foo'])})",
-            "Message({'dc': MessageElement(['foo']), 'dn': Dn('dc=foo29')})",
-        ])
+        self.msg["dc"] = b"foo"
+        if PY3:
+            self.assertIn(repr(self.msg), [
+                "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])})",
+                "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')})",
+            ])
+            self.assertIn(repr(self.msg.text), [
+                "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement([b'foo'])}).text",
+                "Message({'dc': MessageElement([b'foo']), 'dn': Dn('dc=foo29')}).text",
+            ])
+        else:
+            self.assertEquals(
+                repr(self.msg),
+                "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement(['foo'])})")
+            self.assertEquals(
+                repr(self.msg.text),
+                "Message({'dn': Dn('dc=foo29'), 'dc': MessageElement(['foo'])}).text")
 
     def test_len(self):
         self.assertEqual(0, len(self.msg))
@@ -490,38 +627,65 @@ class LdbMsgTests(TestCase):
         del self.msg["foo"]
 
     def test_add(self):
+        self.msg.add(ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla"))
+
+    def test_add_text(self):
         self.msg.add(ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla"))
 
     def test_elements_empty(self):
         self.assertEqual([], self.msg.elements())
 
     def test_elements(self):
-        el = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
+        el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
         self.msg.add(el)
         self.assertEqual([el], self.msg.elements())
+        self.assertEqual([el.text], self.msg.text.elements())
 
     def test_add_value(self):
         self.assertEqual(0, len(self.msg))
+        self.msg["foo"] = [b"foo"]
+        self.assertEqual(1, len(self.msg))
+
+    def test_add_value_text(self):
+        self.assertEqual(0, len(self.msg))
         self.msg["foo"] = ["foo"]
         self.assertEqual(1, len(self.msg))
 
     def test_add_value_multiple(self):
         self.assertEqual(0, len(self.msg))
+        self.msg["foo"] = [b"foo", b"bla"]
+        self.assertEqual(1, len(self.msg))
+        self.assertEqual([b"foo", b"bla"], list(self.msg["foo"]))
+
+    def test_add_value_multiple_text(self):
+        self.assertEqual(0, len(self.msg))
         self.msg["foo"] = ["foo", "bla"]
         self.assertEqual(1, len(self.msg))
-        self.assertEqual(["foo", "bla"], list(self.msg["foo"]))
+        self.assertEqual(["foo", "bla"], list(self.msg.text["foo"]))
 
     def test_set_value(self):
+        self.msg["foo"] = [b"fool"]
+        self.assertEqual([b"fool"], list(self.msg["foo"]))
+        self.msg["foo"] = [b"bar"]
+        self.assertEqual([b"bar"], list(self.msg["foo"]))
+
+    def test_set_value_text(self):
         self.msg["foo"] = ["fool"]
-        self.assertEqual(["fool"], list(self.msg["foo"]))
+        self.assertEqual(["fool"], list(self.msg.text["foo"]))
         self.msg["foo"] = ["bar"]
-        self.assertEqual(["bar"], list(self.msg["foo"]))
+        self.assertEqual(["bar"], list(self.msg.text["foo"]))
 
     def test_keys(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
+        self.msg["foo"] = [b"bla"]
+        self.msg["bar"] = [b"bla"]
+        self.assertEqual(["dn", "foo", "bar"], self.msg.keys())
+
+    def test_keys_text(self):
+        self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
         self.msg["foo"] = ["bla"]
         self.msg["bar"] = ["bla"]
-        self.assertEqual(["dn", "foo", "bar"], self.msg.keys())
+        self.assertEqual(["dn", "foo", "bar"], self.msg.text.keys())
 
     def test_dn(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
@@ -531,14 +695,32 @@ class LdbMsgTests(TestCase):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
         self.assertEqual("@BASEINFO", self.msg.get("dn").__str__())
 
+    def test_dn_text(self):
+        self.msg.text.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
+        self.assertEqual("@BASEINFO", str(self.msg.dn))
+        self.assertEqual("@BASEINFO", str(self.msg.text.dn))
+
+    def test_get_dn_text(self):
+        self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
+        self.assertEqual("@BASEINFO", str(self.msg.get("dn")))
+        self.assertEqual("@BASEINFO", str(self.msg.text.get("dn")))
+
     def test_get_invalid(self):
         self.msg.dn = ldb.Dn(ldb.Ldb(filename()), "@BASEINFO")
         self.assertRaises(TypeError, self.msg.get, 42)
 
     def test_get_other(self):
+        self.msg["foo"] = [b"bar"]
+        self.assertEqual(b"bar", self.msg.get("foo")[0])
+        self.assertEqual(b"bar", self.msg.get("foo", idx=0))
+        self.assertEqual(None, self.msg.get("foo", idx=1))
+        self.assertEqual("", self.msg.get("foo", default='', idx=1))
+
+    def test_get_other_text(self):
         self.msg["foo"] = ["bar"]
-        self.assertEqual("bar", self.msg.get("foo")[0])
-        self.assertEqual("bar", self.msg.get("foo", idx=0))
+        self.assertEqual(["bar"], list(self.msg.text.get("foo")))
+        self.assertEqual("bar", self.msg.text.get("foo")[0])
+        self.assertEqual("bar", self.msg.text.get("foo", idx=0))
         self.assertEqual(None, self.msg.get("foo", idx=1))
         self.assertEqual("", self.msg.get("foo", default='', idx=1))
 
@@ -546,9 +728,16 @@ class LdbMsgTests(TestCase):
         self.assertEqual(None, self.msg.get("tatayoyo", idx=0))
         self.assertEqual("anniecordie", self.msg.get("tatayoyo", "anniecordie"))
 
+    def test_get_default_text(self):
+        self.assertEqual(None, self.msg.text.get("tatayoyo", idx=0))
+        self.assertEqual("anniecordie", self.msg.text.get("tatayoyo", "anniecordie"))
+
     def test_get_unknown(self):
         self.assertEqual(None, self.msg.get("lalalala"))
 
+    def test_get_unknown_text(self):
+        self.assertEqual(None, self.msg.text.get("lalalala"))
+
     def test_msg_diff(self):
         l = ldb.Ldb()
         msgs = l.parse_ldif("dn: foo=bar\nfoo: bar\nbaz: do\n\ndn: foo=bar\nfoo: bar\nbaz: dont\n")
@@ -571,16 +760,16 @@ class LdbMsgTests(TestCase):
         msg2 = ldb.Message()
         msg2.dn = ldb.Dn(db, "foo=bar")
         self.assertEqual(msg1, msg2)
-        msg1['foo'] = 'bar'
-        msg2['foo'] = 'bar'
+        msg1['foo'] = b'bar'
+        msg2['foo'] = b'bar'
         self.assertEqual(msg1, msg2)
-        msg2['foo'] = 'blie'
+        msg2['foo'] = b'blie'
         self.assertNotEqual(msg1, msg2)
-        msg2['foo'] = 'blie'
+        msg2['foo'] = b'blie'
 
     def test_from_dict(self):
         rec = {"dn": "dc=fromdict",
-               "a1": ["a1-val1", "a1-val1"]}
+               "a1": [b"a1-val1", b"a1-val1"]}
         l = ldb.Ldb()
         # check different types of input Flags
         for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]:
@@ -592,13 +781,30 @@ class LdbMsgTests(TestCase):
         self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE)
         self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0)
         # Message.from_dict expects dictionary with 'dn'
+        err_rec = {"a1": [b"a1-val1", b"a1-val1"]}
+        self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE)
+
+    def test_from_dict_text(self):
+        rec = {"dn": "dc=fromdict",
+               "a1": ["a1-val1", "a1-val1"]}
+        l = ldb.Ldb()
+        # check different types of input Flags
+        for flags in [ldb.FLAG_MOD_ADD, ldb.FLAG_MOD_REPLACE, ldb.FLAG_MOD_DELETE]:
+            m = ldb.Message.from_dict(l, rec, flags)
+            self.assertEqual(rec["a1"], list(m.text["a1"]))
+            self.assertEqual(flags, m.text["a1"].flags())
+        # check input params
+        self.assertRaises(TypeError, ldb.Message.from_dict, dict(), rec, ldb.FLAG_MOD_REPLACE)
+        self.assertRaises(TypeError, ldb.Message.from_dict, l, list(), ldb.FLAG_MOD_REPLACE)
+        self.assertRaises(ValueError, ldb.Message.from_dict, l, rec, 0)
+        # Message.from_dict expects dictionary with 'dn'
         err_rec = {"a1": ["a1-val1", "a1-val1"]}
         self.assertRaises(TypeError, ldb.Message.from_dict, l, err_rec, ldb.FLAG_MOD_REPLACE)
 
     def test_copy_add_message_element(self):
         m = ldb.Message()
-        m["1"] = ldb.MessageElement(["val 111"], ldb.FLAG_MOD_ADD, "1")
-        m["2"] = ldb.MessageElement(["val 222"], ldb.FLAG_MOD_ADD, "2")
+        m["1"] = ldb.MessageElement([b"val 111"], ldb.FLAG_MOD_ADD, "1")
+        m["2"] = ldb.MessageElement([b"val 222"], ldb.FLAG_MOD_ADD, "2")
         mto = ldb.Message()
         mto["1"] = m["1"]
         mto["2"] = m["2"]
@@ -610,50 +816,99 @@ class LdbMsgTests(TestCase):
         self.assertEqual(mto["1"], m["1"])
         self.assertEqual(mto["2"], m["2"])
 
+    def test_copy_add_message_element_text(self):
+        m = ldb.Message()
+        m["1"] = ldb.MessageElement(["val 111"], ldb.FLAG_MOD_ADD, "1")
+        m["2"] = ldb.MessageElement(["val 222"], ldb.FLAG_MOD_ADD, "2")
+        mto = ldb.Message()
+        mto["1"] = m["1"]
+        mto["2"] = m["2"]
+        self.assertEqual(mto["1"], m.text["1"])
+        self.assertEqual(mto["2"], m.text["2"])
+        mto = ldb.Message()
+        mto.add(m["1"])
+        mto.add(m["2"])
+        self.assertEqual(mto.text["1"], m.text["1"])
+        self.assertEqual(mto.text["2"], m.text["2"])
+        self.assertEqual(mto["1"], m["1"])
+        self.assertEqual(mto["2"], m["2"])
+
 
 class MessageElementTests(TestCase):
 
     def test_cmp_element(self):
-        x = ldb.MessageElement(["foo"])
-        y = ldb.MessageElement(["foo"])
-        z = ldb.MessageElement(["bzr"])
+        x = ldb.MessageElement([b"foo"])
+        y = ldb.MessageElement([b"foo"])
+        z = ldb.MessageElement([b"bzr"])
         self.assertEqual(x, y)
         self.assertNotEqual(x, z)
 
+    def test_cmp_element_text(self):
+        x = ldb.MessageElement([b"foo"])
+        y = ldb.MessageElement(["foo"])
+        self.assertEqual(x, y)
+
     def test_create_iterable(self):
-        x = ldb.MessageElement(["foo"])
-        self.assertEqual(["foo"], list(x))
+        x = ldb.MessageElement([b"foo"])
+        self.assertEqual([b"foo"], list(x))
+        self.assertEqual(["foo"], list(x.text))
 
     def test_repr(self):
-        x = ldb.MessageElement(["foo"])
-        self.assertEqual("MessageElement(['foo'])", repr(x))
-        x = ldb.MessageElement(["foo", "bla"])
+        x = ldb.MessageElement([b"foo"])
+        if PY3:
+            self.assertEqual("MessageElement([b'foo'])", repr(x))
+            self.assertEqual("MessageElement([b'foo']).text", repr(x.text))
+        else:
+            self.assertEqual("MessageElement(['foo'])", repr(x))
+            self.assertEqual("MessageElement(['foo']).text", repr(x.text))
+        x = ldb.MessageElement([b"foo", b"bla"])
         self.assertEqual(2, len(x))
-        self.assertEqual("MessageElement(['foo','bla'])", repr(x))
+        if PY3:
+            self.assertEqual("MessageElement([b'foo',b'bla'])", repr(x))
+            self.assertEqual("MessageElement([b'foo',b'bla']).text", repr(x.text))
+        else:
+            self.assertEqual("MessageElement(['foo','bla'])", repr(x))
+            self.assertEqual("MessageElement(['foo','bla']).text", repr(x.text))
 
     def test_get_item(self):
+        x = ldb.MessageElement([b"foo", b"bar"])
+        self.assertEqual(b"foo", x[0])
+        self.assertEqual(b"bar", x[1])
+        self.assertEqual(b"bar", x[-1])
+        self.assertRaises(IndexError, lambda: x[45])
+
+    def test_get_item_text(self):
         x = ldb.MessageElement(["foo", "bar"])
-        self.assertEqual("foo", x[0])
-        self.assertEqual("bar", x[1])
-        self.assertEqual("bar", x[-1])
+        self.assertEqual("foo", x.text[0])
+        self.assertEqual("bar", x.text[1])
+        self.assertEqual("bar", x.text[-1])
         self.assertRaises(IndexError, lambda: x[45])
 
     def test_len(self):
-        x = ldb.MessageElement(["foo", "bar"])
+        x = ldb.MessageElement([b"foo", b"bar"])
         self.assertEqual(2, len(x))
 
     def test_eq(self):
-        x = ldb.MessageElement(["foo", "bar"])
-        y = ldb.MessageElement(["foo", "bar"])
+        x = ldb.MessageElement([b"foo", b"bar"])
+        y = ldb.MessageElement([b"foo", b"bar"])
         self.assertEqual(y, x)
-        x = ldb.MessageElement(["foo"])
+        x = ldb.MessageElement([b"foo"])
         self.assertNotEqual(y, x)
-        y = ldb.MessageElement(["foo"])
+        y = ldb.MessageElement([b"foo"])
         self.assertEqual(y, x)
 
     def test_extended(self):
-        el = ldb.MessageElement(["456"], ldb.FLAG_MOD_ADD, "bla")
-        self.assertEqual("MessageElement(['456'])", repr(el))
+        el = ldb.MessageElement([b"456"], ldb.FLAG_MOD_ADD, "bla")
+        if PY3:
+            self.assertEqual("MessageElement([b'456'])", repr(el))
+            self.assertEqual("MessageElement([b'456']).text", repr(el.text))
+        else:
+            self.assertEqual("MessageElement(['456'])", repr(el))
+            self.assertEqual("MessageElement(['456']).text", repr(el.text))
+
+    def test_bad_text(self):
+        el = ldb.MessageElement(b'\xba\xdd')
+        self.assertRaises(UnicodeDecodeError, el.text.__getitem__, 0)
 
 
 class ModuleTests(TestCase):
@@ -697,19 +952,19 @@ class LdbResultTests(TestCase):
         if os.path.exists(name):
             os.unlink(name)
         self.l = ldb.Ldb(name)
-        self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": "samba.org"})
-        self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": "Admins"})
-        self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": "Users"})
-        self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": "OU #1"})
-        self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": "OU #2"})
-        self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": "OU #3"})
-        self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": "OU #4"})
-        self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": "OU #5"})
-        self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": "OU #6"})
-        self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": "OU #7"})
-        self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": "OU #8"})
-        self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": "OU #9"})
-        self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": "OU #10"})
+        self.l.add({"dn": "DC=SAMBA,DC=ORG", "name": b"samba.org"})
+        self.l.add({"dn": "OU=ADMIN,DC=SAMBA,DC=ORG", "name": b"Admins"})
+        self.l.add({"dn": "OU=USERS,DC=SAMBA,DC=ORG", "name": b"Users"})
+        self.l.add({"dn": "OU=OU1,DC=SAMBA,DC=ORG", "name": b"OU #1"})
+        self.l.add({"dn": "OU=OU2,DC=SAMBA,DC=ORG", "name": b"OU #2"})
+        self.l.add({"dn": "OU=OU3,DC=SAMBA,DC=ORG", "name": b"OU #3"})
+        self.l.add({"dn": "OU=OU4,DC=SAMBA,DC=ORG", "name": b"OU #4"})
+        self.l.add({"dn": "OU=OU5,DC=SAMBA,DC=ORG", "name": b"OU #5"})
+        self.l.add({"dn": "OU=OU6,DC=SAMBA,DC=ORG", "name": b"OU #6"})
+        self.l.add({"dn": "OU=OU7,DC=SAMBA,DC=ORG", "name": b"OU #7"})
+        self.l.add({"dn": "OU=OU8,DC=SAMBA,DC=ORG", "name": b"OU #8"})
+        self.l.add({"dn": "OU=OU9,DC=SAMBA,DC=ORG", "name": b"OU #9"})
+        self.l.add({"dn": "OU=OU10,DC=SAMBA,DC=ORG", "name": b"OU #10"})
 
     def tearDown(self):
         super(LdbResultTests, self).tearDown()
-- 
2.1.0



More information about the samba-technical mailing list