Switch to side-by-side view

--- a/src/python/recoll/pyrecoll.cpp
+++ b/src/python/recoll/pyrecoll.cpp
@@ -47,6 +47,10 @@
 
 static RclConfig *rclconfig;
 
+#if PY_MAJOR_VERSION >=3
+#  define Py_TPFLAGS_HAVE_ITER 0
+#endif
+
 //////////////////////////////////////////////////////////////////////
 /// SEARCHDATA SearchData code
 typedef struct {
@@ -59,7 +63,7 @@
 SearchData_dealloc(recoll_SearchDataObject *self)
 {
     LOGDEB(("SearchData_dealloc\n"));
-    self->ob_type->tp_free((PyObject*)self);
+    Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
 static PyObject *
@@ -130,8 +134,7 @@
 };
 
 static PyTypeObject recoll_SearchDataType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "recoll.SearchData",             /*tp_name*/
     sizeof(recoll_SearchDataObject), /*tp_basicsize*/
     0,                         /*tp_itemsize*/
@@ -293,7 +296,7 @@
     if (self->doc)
 	the_docs.erase(self->doc);
     deleteZ(self->doc);
-    self->ob_type->tp_free((PyObject*)self);
+    Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
 static PyObject *
@@ -463,22 +466,41 @@
     {NULL}  /* Sentinel */
 };
 
-static PyObject *
-Doc_getattr(recoll_DocObject *self, char *name)
-{
-    LOGDEB1(("Doc_getattr: name [%s]\n", name));
-    if (self->doc == 0 || 
-	the_docs.find(self->doc) == the_docs.end()) {
+// Note that this returns None if the attribute is not found instead of raising
+// an exception as would be standard. We don't change it to keep existing code
+// working.
+static PyObject *
+Doc_getattro(recoll_DocObject *self, PyObject *nameobj)
+{
+    if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
         PyErr_SetString(PyExc_AttributeError, "doc");
 	return 0;
     }
-    string key = rclconfig->fieldCanon(string(name));
-
-    // Handle special cases, then check this is not a method then
-    // try retrieving key value from the meta array
-
+
+    bool found = false;
     string value;
-    bool found = false;
+    string key;
+    char *name = 0;
+    PyObject* utf8o = 0;
+
+    if (PyUnicode_Check(nameobj)) {
+	utf8o = PyUnicode_AsUTF8String(nameobj);
+	if (utf8o == 0) {
+	    LOGERR(("Doc_getattro: encoding name to utf8 failed\n"));
+	    PyErr_SetString(PyExc_AttributeError, "name??");
+	    Py_RETURN_NONE;
+	}
+	name = PyBytes_AsString(utf8o);
+	Py_DECREF(utf8o);
+    }  else if (PyBytes_Check(nameobj)) {
+	name = PyBytes_AsString(nameobj);
+    } else {
+	PyErr_SetString(PyExc_AttributeError, "name not unicode nor string??");
+	Py_RETURN_NONE;
+    }
+
+    key = rclconfig->fieldCanon(string(name));
+
     switch (key.at(0)) {
     case 'u':
 	if (!key.compare(Rcl::Doc::keyurl)) {
@@ -533,48 +555,46 @@
     }
 
     if (!found) {
-	PyObject *meth = Py_FindMethod(Doc_methods, (PyObject*)self, 
-				       key.c_str());
+	// This will look up a method name (we have no other standard
+	// attributes)
+	PyObject *meth = PyObject_GenericGetAttr((PyObject*)self, nameobj);
 	if (meth) {
 	    return meth;
-	} else {
-	    PyErr_Clear();
-	}
-
+	}
+	PyErr_Clear();
+	// Else look for another attribute
 	if (self->doc->getmeta(key, 0)) {
 	    value = self->doc->meta[key];
 	    found = true;
 	}
     }
 
-    if (!found) {
-	LOGDEB(("Doc_getattr: name [%s] key [%s] Not found\n",
-		name, key.c_str()));
-	Py_RETURN_NONE;
-    }
-
-    LOGDEB1(("Doc_getattr: [%s] (%s) -> [%s]\n",
-	    name, key.c_str(), value.c_str()));
-    // Return a python unicode object
-    PyObject* res = PyUnicode_Decode(value.c_str(), value.size(), "utf-8",
-				     "replace");
-    return res;
+    if (found) {
+	LOGDEB1(("Doc_getattro: [%s] -> [%s]\n", key.c_str(), value.c_str()));
+	// Return a python unicode object
+	return PyUnicode_Decode(value.c_str(), value.size(), "utf-8",
+				"replace");
+    }
+    
+    Py_RETURN_NONE;
 }
 
 static int
 Doc_setattr(recoll_DocObject *self, char *name, PyObject *value)
 {
-    if (self->doc == 0 || 
-	the_docs.find(self->doc) == the_docs.end()) {
+    if (self->doc == 0 || the_docs.find(self->doc) == the_docs.end()) {
         PyErr_SetString(PyExc_AttributeError, "doc??");
 	return -1;
     }
     LOGDEB1(("Doc_setmeta: doc %p\n", self->doc));
+
+#if PY_MAJOR_VERSION < 3
     if (PyString_Check(value)) {
 	value = PyUnicode_FromObject(value);
 	if (value == 0) 
 	    return -1;
     } 
+#endif
 
     if (!PyUnicode_Check(value)) {
 	PyErr_SetString(PyExc_AttributeError, "value not str/unicode??");
@@ -591,8 +611,8 @@
 	PyErr_SetString(PyExc_AttributeError, "value??");
 	return -1;
     }
-
-    char* uvalue = PyString_AsString(putf8);
+    char* uvalue = PyBytes_AsString(putf8);
+    Py_DECREF(putf8);
     string key = rclconfig->fieldCanon(string(name));
 
     LOGDEB0(("Doc_setattr: [%s] (%s) -> [%s]\n", key.c_str(), name, uvalue));
@@ -690,14 +710,13 @@
 " keywords (both)\n"
 );
 static PyTypeObject recoll_DocType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "recoll.Doc",             /*tp_name*/
     sizeof(recoll_DocObject), /*tp_basicsize*/
     0,                         /*tp_itemsize*/
     (destructor)Doc_dealloc,    /*tp_dealloc*/
     0,                         /*tp_print*/
-    (getattrfunc)Doc_getattr,  /*tp_getattr*/
+    0,  /*tp_getattr*/
     (setattrfunc)Doc_setattr, /*tp_setattr*/
     0,                         /*tp_compare*/
     0,                         /*tp_repr*/
@@ -707,7 +726,7 @@
     0,                         /*tp_hash */
     0,                         /*tp_call*/
     0,                         /*tp_str*/
-    0,                         /*tp_getattro*/
+    (getattrofunc)Doc_getattro,/*tp_getattro*/
     0,                         /*tp_setattro*/
     0,                         /*tp_as_buffer*/
     Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,        /*tp_flags*/
@@ -768,7 +787,7 @@
 {
     LOGDEB(("Query_dealloc\n"));
     Query_close(self);
-    self->ob_type->tp_free((PyObject*)self);
+    Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
 static PyObject *
@@ -1118,7 +1137,7 @@
 	PyObject *res1 = res;
 	if (PyUnicode_Check(res))
 	    res1 = PyUnicode_AsUTF8String(res);
-	return PyString_AsString(res1);
+	return PyBytes_AsString(res1);
     } 
 
     virtual string endMatch() 
@@ -1131,7 +1150,7 @@
 	PyObject *res1 = res;
 	if (PyUnicode_Check(res))
 	    res1 = PyUnicode_AsUTF8String(res);
-	return PyString_AsString(res1);
+	return PyBytes_AsString(res1);
     }
 
     PyObject *m_methods;
@@ -1393,8 +1412,7 @@
 "They must be created by the Db.query() method.\n"
 	     );
 static PyTypeObject recoll_QueryType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "recoll.Query",             /*tp_name*/
     sizeof(recoll_QueryObject), /*tp_basicsize*/
     0,                         /*tp_itemsize*/
@@ -1458,7 +1476,7 @@
 {
     LOGDEB(("Db_dealloc\n"));
     Db_close(self);
-    self->ob_type->tp_free((PyObject*)self);
+    Py_TYPE(self)->tp_free((PyObject*)self);
 }
 
 static PyObject *
@@ -1530,7 +1548,7 @@
 	}
 	for (int i = 0; i < dbcnt; i++) {
 	    PyObject *item = PySequence_GetItem(extradbs, i);
-	    char *s = PyString_AsString(item);
+	    char *s = PyBytes_AsString(item);
 	    Py_DECREF(item);
 	    if (!s) {
 		PyErr_SetString(PyExc_TypeError,
@@ -1857,8 +1875,7 @@
 "writable decides if we can index new data through this connection\n"
 );
 static PyTypeObject recoll_DbType = {
-    PyObject_HEAD_INIT(NULL)
-    0,                         /*ob_size*/
+    PyVarObject_HEAD_INIT(NULL, 0)
     "recoll.Db",             /*tp_name*/
     sizeof(recoll_DbObject), /*tp_basicsize*/
     0,                         /*tp_itemsize*/
@@ -1920,7 +1937,7 @@
 "writable decides if we can index new data through this connection\n"
 );
 
-static PyMethodDef recollMethods[] = {
+static PyMethodDef recoll_methods[] = {
     {"connect",  (PyCFunction)recoll_connect, METH_VARARGS|METH_KEYWORDS, 
      doc_connect},
 
@@ -1931,50 +1948,110 @@
 PyDoc_STRVAR(pyrecoll_doc_string,
 "This is an interface to the Recoll full text indexer.");
 
-#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
-#define PyMODINIT_FUNC void
+struct module_state {
+    PyObject *error;
+};
+
+#if PY_MAJOR_VERSION >= 3
+#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
+#else
+#define GETSTATE(m) (&_state)
+static struct module_state _state;
 #endif
+
+#if PY_MAJOR_VERSION >= 3
+static int recoll_traverse(PyObject *m, visitproc visit, void *arg) {
+    Py_VISIT(GETSTATE(m)->error);
+    return 0;
+}
+
+static int recoll_clear(PyObject *m) {
+    Py_CLEAR(GETSTATE(m)->error);
+    return 0;
+}
+
+static struct PyModuleDef moduledef = {
+        PyModuleDef_HEAD_INIT,
+        "recoll",
+        NULL,
+        sizeof(struct module_state),
+        recoll_methods,
+        NULL,
+        recoll_traverse,
+        recoll_clear,
+        NULL
+};
+
+#define INITERROR return NULL
+
+extern "C" PyObject *
+PyInit_recoll(void)
+
+#else
+#define INITERROR return
+
 PyMODINIT_FUNC
 initrecoll(void)
+#endif
 {
     // Note: we can't call recollinit here, because the confdir is only really
     // known when the first db object is created (it is an optional parameter).
     // Using a default here may end up with variables such as stripchars being
     // wrong
 
-    PyObject* m;
-    m = Py_InitModule3("recoll", recollMethods, "Recoll extension module.");
+#if PY_MAJOR_VERSION >= 3
+    PyObject *module = PyModule_Create(&moduledef);
+#else
+    PyObject *module = Py_InitModule("recoll", recoll_methods);
+#endif
+    if (module == NULL)
+        INITERROR;
+
+    struct module_state *st = GETSTATE(module);
+    // The first parameter is a char *. Hopefully we don't initialize
+    // modules too often...
+    st->error = PyErr_NewException(strdup("recoll.Error"), NULL, NULL);
+    if (st->error == NULL) {
+        Py_DECREF(module);
+        INITERROR;
+    }
     
     if (PyType_Ready(&recoll_DbType) < 0)
-        return;
+        INITERROR;
     Py_INCREF((PyObject*)&recoll_DbType);
-    PyModule_AddObject(m, "Db", (PyObject *)&recoll_DbType);
+    PyModule_AddObject(module, "Db", (PyObject *)&recoll_DbType);
 
     if (PyType_Ready(&recoll_QueryType) < 0)
-        return;
+        INITERROR;
     Py_INCREF((PyObject*)&recoll_QueryType);
-    PyModule_AddObject(m, "Query", (PyObject *)&recoll_QueryType);
+    PyModule_AddObject(module, "Query", (PyObject *)&recoll_QueryType);
 
     if (PyType_Ready(&recoll_DocType) < 0)
-        return;
+        INITERROR;
     Py_INCREF((PyObject*)&recoll_DocType);
-    PyModule_AddObject(m, "Doc", (PyObject *)&recoll_DocType);
+    PyModule_AddObject(module, "Doc", (PyObject *)&recoll_DocType);
 
     if (PyType_Ready(&recoll_SearchDataType) < 0)
-        return;
+        INITERROR;
     Py_INCREF((PyObject*)&recoll_SearchDataType);
-    PyModule_AddObject(m, "SearchData", (PyObject *)&recoll_SearchDataType);
-    PyModule_AddStringConstant(m, "__doc__",
+    PyModule_AddObject(module, "SearchData", 
+		       (PyObject *)&recoll_SearchDataType);
+    PyModule_AddStringConstant(module, "__doc__",
                                pyrecoll_doc_string);
 
     PyObject *doctypecobject;
 
-#if PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >=	7
+#if PY_MAJOR_VERSION >= 3 || (PY_MAJOR_VERSION >= 2 && PY_MINOR_VERSION >=  7)
     // Export a few pointers for the benefit of other recoll python modules
     doctypecobject= 
 	PyCapsule_New(&recoll_DocType, PYRECOLL_PACKAGE "recoll.doctypeptr", 0);
 #else
     doctypecobject = PyCObject_FromVoidPtr(&recoll_DocType, NULL);
 #endif
-    PyModule_AddObject(m, "doctypeptr", doctypecobject);
-}
+
+    PyModule_AddObject(module, "doctypeptr", doctypecobject);
+
+#if PY_MAJOR_VERSION >= 3
+    return module;
+#endif
+}