From 926a9ffd1efbd1dd60b5864ac109cc6807176d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Thu, 22 Feb 2018 22:08:06 +0100 Subject: [PATCH] Prepare IPyKernel communication --- src/cutter.pro | 6 ++- src/python/cutter_ipykernel.py | 3 ++ src/python/cutter_jupyter.py | 10 ++++- src/utils/JupyterConnection.cpp | 24 ++++++++++- src/utils/JupyterConnection.h | 8 ++++ src/utils/NestedIPyKernel.cpp | 70 +++++++++++++++++++++++++++++++++ src/utils/NestedIPyKernel.h | 29 ++++++++++++++ src/utils/PythonAPI.cpp | 43 +++++++------------- 8 files changed, 159 insertions(+), 34 deletions(-) create mode 100644 src/utils/NestedIPyKernel.cpp create mode 100644 src/utils/NestedIPyKernel.h diff --git a/src/cutter.pro b/src/cutter.pro index dac431f0..37beb0ca 100644 --- a/src/cutter.pro +++ b/src/cutter.pro @@ -102,7 +102,8 @@ SOURCES += \ CutterApplication.cpp \ utils/JupyterConnection.cpp \ widgets/JupyterWidget.cpp \ - utils/PythonAPI.cpp + utils/PythonAPI.cpp \ + utils/NestedIPyKernel.cpp HEADERS += \ cutter.h \ @@ -166,7 +167,8 @@ HEADERS += \ widgets/VTablesWidget.h \ utils/JupyterConnection.h \ widgets/JupyterWidget.h \ - utils/PythonAPI.h + utils/PythonAPI.h \ + utils/NestedIPyKernel.h FORMS += \ dialogs/AboutDialog.ui \ diff --git a/src/python/cutter_ipykernel.py b/src/python/cutter_ipykernel.py index 1ebcaad4..9dcf0ad0 100644 --- a/src/python/cutter_ipykernel.py +++ b/src/python/cutter_ipykernel.py @@ -7,6 +7,9 @@ from ipykernel.ipkernel import IPythonKernel # TODO: Make this behave like a Popen instance and pipe it to IPyKernelInterfaceJupyter! class IPyKernelInterfaceKernel: + def kill(self): + print("No!! Not into the pit! It burns!!!") + def poll(self): return None diff --git a/src/python/cutter_jupyter.py b/src/python/cutter_jupyter.py index 5bd01647..f261b436 100644 --- a/src/python/cutter_jupyter.py +++ b/src/python/cutter_jupyter.py @@ -5,6 +5,12 @@ from notebook.notebookapp import * # TODO: this must behave like a Popen instance and pipe to IPyKernelInterfaceKernel! class IPyKernelInterfaceJupyter: + def __init__(self, id): + self._id = id + + def kill(self): + print("DIE!! " + str(self._id)) + def poll(self): return None @@ -45,8 +51,8 @@ class CutterInternalIPyKernelManager(IOLoopKernelManager): # TODO: kernel_cmd including python executable and so on is currently used for argv. Make a clean version! import cutter_internal - cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw) - self.kernel = IPyKernelInterfaceJupyter() + id = cutter_internal.launch_ipykernel(kernel_cmd, env=env, **kw) + self.kernel = IPyKernelInterfaceJupyter(id) # self._launch_kernel(kernel_cmd, env=env, # **kw) diff --git a/src/utils/JupyterConnection.cpp b/src/utils/JupyterConnection.cpp index a8c2e36c..e727b5dc 100644 --- a/src/utils/JupyterConnection.cpp +++ b/src/utils/JupyterConnection.cpp @@ -9,6 +9,7 @@ #include #include "JupyterConnection.h" +#include "NestedIPyKernel.h" #include "PythonAPI.h" Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance) @@ -123,4 +124,25 @@ QString JupyterConnection::getUrl() pyThreadState = PyEval_SaveThread(); return urlWithTokenString; -} \ No newline at end of file +} + +long JupyterConnection::startNestedIPyKernel(const QStringList &argv) +{ + NestedIPyKernel *kernel = NestedIPyKernel::start(argv); + + if (!kernel) + { + qWarning() << "Could not start nested IPyKernel."; + return 0; + } + + long id = nextKernelId++; + kernels.insert(id, kernel); + + return id; +} + +NestedIPyKernel *JupyterConnection::getNestedIPyKernel(long id) +{ + return kernels[id]; +} diff --git a/src/utils/JupyterConnection.h b/src/utils/JupyterConnection.h index e956cc7c..c67727ce 100644 --- a/src/utils/JupyterConnection.h +++ b/src/utils/JupyterConnection.h @@ -3,6 +3,8 @@ #include +class NestedIPyKernel; + struct _object; typedef _object PyObject; @@ -22,6 +24,9 @@ public: void start(); QString getUrl(); + long startNestedIPyKernel(const QStringList &argv); + NestedIPyKernel *getNestedIPyKernel(long id); + signals: void urlReceived(const QString &url); void creationFailed(); @@ -32,6 +37,9 @@ private: PyThreadState *pyThreadState = nullptr; + QMap kernels; + long nextKernelId = 1; + void initPython(); void createCutterJupyterModule(); }; diff --git a/src/utils/NestedIPyKernel.cpp b/src/utils/NestedIPyKernel.cpp new file mode 100644 index 00000000..cb97ce00 --- /dev/null +++ b/src/utils/NestedIPyKernel.cpp @@ -0,0 +1,70 @@ + +#include + +#include + +#include "cutter.h" +#include "NestedIPyKernel.h" + +NestedIPyKernel *NestedIPyKernel::start(const QStringList &argv) +{ + PyThreadState *parentThreadState = PyThreadState_Get(); + + PyThreadState *threadState = Py_NewInterpreter(); + if (!threadState) + { + qWarning() << "Could not create subinterpreter."; + return nullptr; + } + + QFile moduleFile(":/python/cutter_ipykernel.py"); + moduleFile.open(QIODevice::ReadOnly); + QByteArray moduleCode = moduleFile.readAll(); + moduleFile.close(); + + auto moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_ipykernel.py", Py_file_input); + if (!moduleCodeObject) + { + qWarning() << "Could not compile cutter_ipykernel."; + return nullptr; + } + auto cutterIPykernelModule = PyImport_ExecCodeModule("cutter_ipykernel", moduleCodeObject); + Py_DECREF(moduleCodeObject); + if (!cutterIPykernelModule) + { + qWarning() << "Could not import cutter_ipykernel."; + return nullptr; + } + + auto kernel = new NestedIPyKernel(cutterIPykernelModule, argv); + + PyThreadState_Swap(parentThreadState); + + return kernel; +} + +NestedIPyKernel::NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv) +{ + auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel"); + + PyObject *argvListObject = PyList_New(argv.size()); + for (int i = 0; i < argv.size(); i++) + { + QString s = argv[i]; + PyList_SetItem(argvListObject, i, PyUnicode_DecodeUTF8(s.toUtf8().constData(), s.length(), nullptr)); + } + + kernel = PyObject_CallFunction(launchFunc, "O", argvListObject); +} + +NestedIPyKernel::~NestedIPyKernel() +{ + +} + +void NestedIPyKernel::kill() +{ + auto parentThreadState = PyThreadState_Swap(threadState); + PyObject_CallMethod(kernel, "kill", nullptr); + PyThreadState_Swap(parentThreadState); +} diff --git a/src/utils/NestedIPyKernel.h b/src/utils/NestedIPyKernel.h new file mode 100644 index 00000000..316b6bad --- /dev/null +++ b/src/utils/NestedIPyKernel.h @@ -0,0 +1,29 @@ + + +#ifndef NESTEDIPYKERNEL_H +#define NESTEDIPYKERNEL_H + +#include + +struct _object; +typedef _object PyObject; + +struct _ts; +typedef _ts PyThreadState; + +class NestedIPyKernel +{ +public: + static NestedIPyKernel *start(const QStringList &argv); + ~NestedIPyKernel(); + + void kill(); + +private: + NestedIPyKernel(PyObject *cutterIPykernelModule, const QStringList &argv); + + PyThreadState *threadState; + PyObject *kernel; +}; + +#endif //NESTEDIPYKERNEL_H diff --git a/src/utils/PythonAPI.cpp b/src/utils/PythonAPI.cpp index 157a8dfb..3e1cd7c5 100644 --- a/src/utils/PythonAPI.cpp +++ b/src/utils/PythonAPI.cpp @@ -1,6 +1,8 @@ #include "PythonAPI.h" #include "cutter.h" +#include "JupyterConnection.h" +#include "NestedIPyKernel.h" #include @@ -67,47 +69,30 @@ PyObject *api_internal_launch_ipykernel(PyObject *self, PyObject *args, PyObject argv.append(s); } - PyThreadState *parentThreadState = PyThreadState_Get(); + long id = Jupyter()->startNestedIPyKernel(argv); - QFile moduleFile(":/python/cutter_ipykernel.py"); - moduleFile.open(QIODevice::ReadOnly); - QByteArray moduleCode = moduleFile.readAll(); - moduleFile.close(); + return PyLong_FromLong(id); +} - auto moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_ipykernel.py", Py_file_input); - if (!moduleCodeObject) +PyObject *api_internal_kernel_interface_kill(PyObject *, PyObject *args) +{ + long id; + + if (!PyArg_ParseTuple(args, "l", &id)) { - qWarning() << "Could not compile cutter_ipykernel."; - return nullptr; - } - auto cutterIPykernelModule = PyImport_ExecCodeModule("cutter_ipykernel", moduleCodeObject); - Py_DECREF(moduleCodeObject); - if (!cutterIPykernelModule) - { - qWarning() << "Could not import cutter_ipykernel."; + qWarning() << "Invalid args passed to api_internal_kernel_interface_kill()."; return nullptr; } - auto launchFunc = PyObject_GetAttrString(cutterIPykernelModule, "launch_ipykernel"); + Jupyter()->getNestedIPyKernel(id)->kill(); - argvListObject = PyList_New(argv.size()); - for (int i = 0; i < argv.size(); i++) - { - QString s = argv[i]; - PyList_SetItem(argvListObject, i, PyUnicode_DecodeUTF8(s.toUtf8().constData(), s.length(), nullptr)); - } - - auto ipyKernel = PyObject_CallFunction(launchFunc, "O", argvListObject); - Q_UNUSED(ipyKernel); - - PyThreadState_Swap(parentThreadState); - - return PyLong_FromLong(42); + Py_RETURN_NONE; } PyMethodDef CutterInternalMethods[] = { {"launch_ipykernel", (PyCFunction)api_internal_launch_ipykernel, METH_VARARGS | METH_KEYWORDS, "Launch an IPython Kernel in a subinterpreter"}, + {"kernel_interface_kill", (PyCFunction)api_internal_kernel_interface_kill, METH_VARARGS, ""}, {NULL, NULL, 0, NULL} };