From 4264cd49d662290cbbb144e6c70c798288745917 Mon Sep 17 00:00:00 2001
From: Stefan Wunsch <stefan.wunsch@cern.ch>
Date: Fri, 12 Apr 2019 06:45:46 +0200
Subject: [PATCH] [PyROOT exp] Make DeclareCppCallable py3 compliant

[PyROOT exp] long does not exist anymore in Python3, map to int

[PyROOT exp] Clear error indicator for fallback (enforced since py3.6)

[PyROOT] Use Python/C API utility for warnings

[PyROOT exp] Dont set error indicator during fetching keyword
---
 .../PyROOT/src/CppCallablePyz.cxx             | 61 ++++++-------------
 .../PyROOT/test/cppcallable.py                |  4 ++
 2 files changed, 22 insertions(+), 43 deletions(-)

diff --git a/bindings/pyroot_experimental/PyROOT/src/CppCallablePyz.cxx b/bindings/pyroot_experimental/PyROOT/src/CppCallablePyz.cxx
index b3078fb75c4..83dd45223d2 100644
--- a/bindings/pyroot_experimental/PyROOT/src/CppCallablePyz.cxx
+++ b/bindings/pyroot_experimental/PyROOT/src/CppCallablePyz.cxx
@@ -634,9 +634,9 @@ PyObject* NumbaCallableImpl_call(PyObject * /*self*/, PyObject *args)
 
 bool GetKeyword(PyObject* obj, const char* name, bool defaultVal)
 {
-   auto attr = PyObject_GetAttrString(obj, name);
    bool prop = defaultVal;
-   if (attr != NULL) {
+   if (PyObject_HasAttrString(obj, name)) {
+      auto attr = PyObject_GetAttrString(obj, name);
       prop = PyObject_IsTrue(attr);
       Py_DECREF(attr);
    }
@@ -644,41 +644,6 @@ bool GetKeyword(PyObject* obj, const char* name, bool defaultVal)
 }
 
 
-// Emit a RuntimeWarning using the warnings module
-// Note that this function returns silently if something goes wrong.
-void EmitRuntimeWarning(const std::string message)
-{
-   // Load warnings.warn
-   auto warnings = PyImport_ImportModule("warnings");
-   if (warnings == NULL) return;
-   auto warn = PyObject_GetAttrString(warnings, "warn");
-   Py_DECREF(warnings);
-   if (warn == NULL) return;
-
-   // Load RuntimeWarning class
-#if PY_MAJOR_VERSION < 3
-   auto builtins = PyImport_ImportModule("__builtin__");
-#else
-   auto builtins = PyImport_ImportModule("builtins");
-#endif
-   if (builtins == NULL) {
-      Py_DECREF(warn);
-      return;
-   }
-   auto runtimeWarning = PyObject_GetAttrString(builtins, "RuntimeWarning");
-   Py_DECREF(builtins);
-   if (runtimeWarning == NULL) {
-      Py_DECREF(warn);
-      return;
-   }
-
-   // Call warn(message, RuntimeWarning)
-   PyObject_CallFunction(warn, "(sO)", message.c_str(), runtimeWarning);
-   Py_DECREF(warn);
-   Py_DECREF(runtimeWarning);
-}
-
-
 // Call method of class used as decorator to create either generic or numba C++ wrapper.
 // The call method creates the C++ wrapper class for the Python callable and
 // passes through the actual callable.
@@ -711,8 +676,10 @@ PyObject* ProxyCallableImpl_call(PyObject * /*self*/, PyObject *args)
       if (pyfunc) {
          return pyfunc;
       } else {
+         PyErr_Clear();
          if (verbose) {
-            EmitRuntimeWarning("Failed to compile Python callable using numba, fall back to generic implementation. Note that the generic implementation is potentially slow.");
+            PyErr_WarnEx(PyExc_RuntimeWarning,
+                    "Failed to compile Python callable using numba, fall back to generic implementation. Note that the generic implementation is potentially slow and does not allow multi-threading.", 1);
          }
          return GenericCallableImpl_call(NULL, args);
       }
@@ -739,18 +706,26 @@ PyObject *PyROOT::GetCppCallableClass(PyObject * /*self*/, PyObject * args) {
 
    // Create wrapper class for decorator
    auto classDict = PyDict_New();
-   auto className = PyString_FromString("CppCallableImpl");
-   auto callableClass = PyClass_New(NULL, classDict, className);
-   Py_DECREF(className);
+   auto className = CPyCppyy_PyUnicode_FromString("CppCallableImpl");
+   auto classBases = PyTuple_New(0);
 
    // Add methods
    for (auto def = CallableImplMethods; def->ml_name != NULL; def++) {
-      PyObject *func = PyCFunction_New(def, NULL);
-      PyObject *method = PyMethod_New(func, NULL, callableClass);
+      auto func = PyCFunction_New(def, NULL);
+#if PY_VERSION_HEX < 0x03000000
+      auto method = PyMethod_New(func, NULL, NULL);
+#else
+      auto method = PyInstanceMethod_New(func);
+#endif
 	  PyDict_SetItemString(classDict, def->ml_name, method);
 	  Py_DECREF(func);
 	  Py_DECREF(method);
    }
+
+   auto callableClass = PyObject_CallFunctionObjArgs(
+           (PyObject*)&PyType_Type, className, classBases, classDict, NULL);
+   Py_DECREF(className);
+   Py_DECREF(classBases);
    Py_DECREF(classDict);
 
    // Return implementation class
diff --git a/bindings/pyroot_experimental/PyROOT/test/cppcallable.py b/bindings/pyroot_experimental/PyROOT/test/cppcallable.py
index cd6aa913f31..2751036b4de 100644
--- a/bindings/pyroot_experimental/PyROOT/test/cppcallable.py
+++ b/bindings/pyroot_experimental/PyROOT/test/cppcallable.py
@@ -2,6 +2,10 @@ import unittest
 import ROOT
 import sys
 
+# long does not exist anymore on Python 3, map it to int
+if sys.version_info[0] > 2:
+    long = int
+
 
 default_test_inputs = [-1.0, 0.0, 100.0]
 
-- 
GitLab