cutter/src/common/JupyterConnection.cpp

227 lines
5.9 KiB
C++
Raw Normal View History

2018-03-02 13:15:53 +00:00
#ifdef CUTTER_ENABLE_JUPYTER
2017-12-13 17:36:00 +00:00
2018-02-09 15:48:02 +00:00
#include <Python.h>
#include <marshal.h>
2018-02-09 15:48:02 +00:00
2017-12-13 17:36:00 +00:00
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDebug>
#include <QThread>
#include <QFile>
#include <QCoreApplication>
#include <QDir>
2017-12-13 17:36:00 +00:00
#include "JupyterConnection.h"
2018-02-22 21:08:06 +00:00
#include "NestedIPyKernel.h"
2018-02-11 17:59:23 +00:00
#include "PythonAPI.h"
2017-12-13 17:36:00 +00:00
2018-02-22 19:56:15 +00:00
Q_GLOBAL_STATIC(JupyterConnection, uniqueInstance)
JupyterConnection *JupyterConnection::getInstance()
{
return uniqueInstance;
}
2017-12-13 17:36:00 +00:00
JupyterConnection::JupyterConnection(QObject *parent) : QObject(parent)
{
/* Will be removed/reworked with python plugins PR */
initPythonHome();
initPython();
qDebug() << "Python init";
2017-12-13 17:36:00 +00:00
}
JupyterConnection::~JupyterConnection()
{
2018-03-21 20:32:32 +00:00
if (pyThreadState) {
2018-02-09 15:48:02 +00:00
PyEval_RestoreThread(pyThreadState);
2018-03-21 20:32:32 +00:00
if (cutterNotebookAppInstance) {
auto stopFunc = PyObject_GetAttrString(cutterNotebookAppInstance, "stop");
PyObject_CallObject(stopFunc, nullptr);
Py_DECREF(cutterNotebookAppInstance);
}
2018-02-09 15:48:02 +00:00
Py_Finalize();
2018-02-09 15:48:02 +00:00
}
2018-03-21 20:32:32 +00:00
if (pythonHome) {
PyMem_RawFree(pythonHome);
}
2017-12-13 17:36:00 +00:00
}
void JupyterConnection::initPythonHome()
2017-12-13 17:36:00 +00:00
{
2018-03-05 20:39:36 +00:00
#if defined(APPIMAGE) || defined(MACOS_PYTHON_FRAMEWORK_BUNDLED)
2018-03-21 20:32:32 +00:00
if (customPythonHome.isNull()) {
2018-03-06 17:21:42 +00:00
auto pythonHomeDir = QDir(QCoreApplication::applicationDirPath());
2018-09-05 05:46:40 +00:00
#ifdef APPIMAGE
2018-03-21 20:32:32 +00:00
// Executable is in appdir/bin
pythonHomeDir.cdUp();
qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() << " for AppImage.";
2018-09-05 05:46:40 +00:00
#else // MACOS_PYTHON_FRAMEWORK_BUNDLED
2018-03-21 20:32:32 +00:00
// @executable_path/../Frameworks/Python.framework/Versions/Current
pythonHomeDir.cd("../Frameworks/Python.framework/Versions/Current");
qInfo() << "Setting PYTHONHOME =" << pythonHomeDir.absolutePath() <<
" for macOS Application Bundle.";
2018-09-05 05:46:40 +00:00
#endif
2018-03-06 17:21:42 +00:00
customPythonHome = pythonHomeDir.absolutePath();
}
#endif
2018-03-21 20:32:32 +00:00
if (!customPythonHome.isNull()) {
2018-03-06 17:21:42 +00:00
qInfo() << "PYTHONHOME =" << customPythonHome;
pythonHome = Py_DecodeLocale(customPythonHome.toLocal8Bit().constData(), nullptr);
Py_SetPythonHome(pythonHome);
}
}
void JupyterConnection::initPython()
{
2018-02-11 17:59:23 +00:00
PyImport_AppendInittab("cutter", &PyInit_api);
PyImport_AppendInittab("cutter_internal", &PyInit_api_internal);
2018-02-09 15:48:02 +00:00
Py_Initialize();
PyEval_InitThreads();
2018-02-22 19:56:15 +00:00
pyThreadState = PyEval_SaveThread();
}
void JupyterConnection::createCutterJupyterModule()
{
2018-03-25 11:21:07 +00:00
if (pyThreadState) {
PyEval_RestoreThread(pyThreadState);
}
2018-02-22 19:56:15 +00:00
QFile moduleFile(":/python/cutter_jupyter.pyc");
bool isBytecode = moduleFile.exists();
if (!isBytecode) {
moduleFile.setFileName(":/python/cutter_jupyter.py");
}
moduleFile.open(QIODevice::ReadOnly);
QByteArray moduleCode = moduleFile.readAll();
moduleFile.close();
PyObject *moduleCodeObject;
if (isBytecode) {
moduleCodeObject = PyMarshal_ReadObjectFromString(moduleCode.constData() + 12,
moduleCode.size() - 12);
} else {
moduleCodeObject = Py_CompileString(moduleCode.constData(), "cutter_jupyter.py",
Py_file_input);
}
2018-03-21 20:32:32 +00:00
if (!moduleCodeObject) {
PyErr_Print();
qWarning() << "Could not compile cutter_jupyter.";
2018-02-22 18:39:20 +00:00
emit creationFailed();
2018-02-22 19:56:15 +00:00
pyThreadState = PyEval_SaveThread();
return;
}
cutterJupyterModule = PyImport_ExecCodeModule("cutter_jupyter", moduleCodeObject);
2018-03-21 20:32:32 +00:00
if (!cutterJupyterModule) {
PyErr_Print();
2018-02-11 11:40:50 +00:00
qWarning() << "Could not import cutter_jupyter.";
2018-02-22 18:39:20 +00:00
emit creationFailed();
2018-02-22 19:56:15 +00:00
pyThreadState = PyEval_SaveThread();
2018-02-11 11:40:50 +00:00
return;
}
Py_DECREF(moduleCodeObject);
2018-02-22 19:56:15 +00:00
pyThreadState = PyEval_SaveThread();
}
void JupyterConnection::start()
{
2018-03-21 20:32:32 +00:00
if (cutterNotebookAppInstance) {
2018-02-22 19:56:15 +00:00
return;
}
2018-03-21 20:32:32 +00:00
if (!Py_IsInitialized()) {
2018-02-22 19:56:15 +00:00
initPython();
}
2018-03-21 20:32:32 +00:00
if (!cutterJupyterModule) {
2018-02-22 19:56:15 +00:00
createCutterJupyterModule();
2018-02-23 15:24:19 +00:00
2018-03-21 20:32:32 +00:00
if (!cutterJupyterModule) {
2018-02-23 15:24:19 +00:00
return;
}
2018-02-22 19:56:15 +00:00
}
PyEval_RestoreThread(pyThreadState);
2018-02-09 15:48:02 +00:00
auto startFunc = PyObject_GetAttrString(cutterJupyterModule, "start_jupyter");
cutterNotebookAppInstance = PyObject_CallObject(startFunc, nullptr);
2018-02-22 19:56:15 +00:00
pyThreadState = PyEval_SaveThread();
emit urlReceived(getUrl());
}
QString JupyterConnection::getUrl()
{
2018-03-21 20:32:32 +00:00
if (!cutterNotebookAppInstance) {
2018-02-22 19:56:15 +00:00
return nullptr;
}
PyEval_RestoreThread(pyThreadState);
2018-02-09 15:48:02 +00:00
auto urlWithToken = PyObject_GetAttrString(cutterNotebookAppInstance, "url_with_token");
auto asciiBytes = PyUnicode_AsASCIIString(urlWithToken);
2018-02-22 19:56:15 +00:00
auto urlWithTokenString = QString::fromUtf8(PyBytes_AsString(asciiBytes));
2018-02-09 15:48:02 +00:00
Py_DECREF(asciiBytes);
Py_DECREF(urlWithToken);
pyThreadState = PyEval_SaveThread();
2018-02-22 19:56:15 +00:00
return urlWithTokenString;
2018-02-22 21:08:06 +00:00
}
long JupyterConnection::startNestedIPyKernel(const QStringList &argv)
{
NestedIPyKernel *kernel = NestedIPyKernel::start(argv);
2018-03-21 20:32:32 +00:00
if (!kernel) {
2018-02-22 21:08:06 +00:00
qWarning() << "Could not start nested IPyKernel.";
return 0;
}
long id = nextKernelId++;
kernels.insert(id, kernel);
return id;
}
NestedIPyKernel *JupyterConnection::getNestedIPyKernel(long id)
{
2018-02-23 12:04:53 +00:00
auto it = kernels.find(id);
2018-03-21 20:32:32 +00:00
if (it == kernels.end()) {
2018-02-23 12:04:53 +00:00
return nullptr;
}
return *it;
2018-02-22 21:08:06 +00:00
}
2018-02-23 16:42:54 +00:00
QVariant JupyterConnection::pollNestedIPyKernel(long id)
{
auto it = kernels.find(id);
2018-03-21 20:32:32 +00:00
if (it == kernels.end()) {
2018-02-23 16:42:54 +00:00
return QVariant(0);
}
NestedIPyKernel *kernel = *it;
QVariant v = kernel->poll();
2018-03-21 20:32:32 +00:00
if (!v.isNull()) {
2018-02-23 16:42:54 +00:00
// if poll of kernel returns anything but None, it has already quit and should be cleaned up
PyThreadState *subinterpreterState = kernel->getThreadState();
delete kernel;
kernels.erase(it);
PyThreadState *parentThreadState = PyThreadState_Swap(subinterpreterState);
Py_EndInterpreter(subinterpreterState);
PyThreadState_Swap(parentThreadState);
}
return v;
}
2018-03-02 13:15:53 +00:00
#endif