Commit 2329f29e authored by Nikita Sirgienko's avatar Nikita Sirgienko
Browse files

[Python] Don't use Qt in pythonserver executable for avoding problems with PyQt5

BUG: 397264, 407362
FIXED-IN: 19.08
parent 59c6b85d
......@@ -19,23 +19,20 @@
*/
#include "pythonserver.h"
#include <vector>
#include <QFileInfo>
#include <QDir>
#include <Python.h>
PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr)
{
}
using namespace std;
namespace
{
QString pyObjectToQString(PyObject* obj)
string pyObjectToQString(PyObject* obj)
{
#if PY_MAJOR_VERSION == 3
return QString::fromUtf8(PyUnicode_AsUTF8(obj));
return string(PyUnicode_AsUTF8(obj));
#elif PY_MAJOR_VERSION == 2
return QString::fromLocal8Bit(PyString_AsString(obj));
return string(PyString_AsString(obj));
#else
#warning Unknown Python version
#endif
......@@ -47,7 +44,7 @@ void PythonServer::login()
Py_InspectFlag = 1;
Py_Initialize();
m_pModule = PyImport_AddModule("__main__");
filePath = QStringLiteral("python_cantor_worksheet");
filePath = "python_cantor_worksheet";
}
void PythonServer::interrupt()
......@@ -55,7 +52,7 @@ void PythonServer::interrupt()
PyErr_SetInterrupt();
}
void PythonServer::runPythonCommand(const QString& command) const
void PythonServer::runPythonCommand(const string& command) const
{
PyObject* py_dict = PyModule_GetDict(m_pModule);
......@@ -73,7 +70,7 @@ void PythonServer::runPythonCommand(const QString& command) const
PyRun_SimpleString(prepareCommand);
#if PY_MAJOR_VERSION == 3
PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input);
PyObject* compile = Py_CompileString(command.c_str(), filePath.c_str(), Py_single_input);
// There are two reasons for the error:
// 1) This code is not single expression, so we can't compile this with flag Py_single_input
// 2) There are errors in the code
......@@ -81,7 +78,7 @@ void PythonServer::runPythonCommand(const QString& command) const
{
PyErr_Clear();
// Try to recompile code as sequence of expressions
compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input);
compile = Py_CompileString(command.c_str(), filePath.c_str(), Py_file_input);
if (PyErr_Occurred())
{
// We now know, that we have a syntax error, so print the traceback and exit
......@@ -94,13 +91,13 @@ void PythonServer::runPythonCommand(const QString& command) const
// Python 2.X don't check, that input string contains only one expression.
// So for checking this, we compile string as file and as single expression and compare bytecode
// FIXME?
PyObject* codeFile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input);
PyObject* codeFile = Py_CompileString(command.c_str(), filePath.c_str(), Py_file_input);
if (PyErr_Occurred())
{
PyErr_PrintEx(0);
return;
}
PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input);
PyObject* codeSingle = Py_CompileString(command.c_str(), filePath.c_str(), Py_single_input);
if (PyErr_Occurred())
{
// We have error with Py_single_input, but haven't error with Py_file_input
......@@ -129,7 +126,7 @@ void PythonServer::runPythonCommand(const QString& command) const
PyErr_PrintEx(0);
}
QString PythonServer::getError() const
string PythonServer::getError() const
{
PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend");
PyObject *error = PyObject_GetAttrString(errorPython, "value");
......@@ -137,7 +134,7 @@ QString PythonServer::getError() const
return pyObjectToQString(error);
}
QString PythonServer::getOutput() const
string PythonServer::getOutput() const
{
PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend");
PyObject *output = PyObject_GetAttrString(outputPython, "value");
......@@ -145,23 +142,22 @@ QString PythonServer::getOutput() const
return pyObjectToQString(output);
}
void PythonServer::setFilePath(const QString& path)
void PythonServer::setFilePath(const string& path, const string& dir)
{
PyRun_SimpleString(("import sys; sys.argv = ['" + path.toStdString() + "']").c_str());
if (path.isEmpty()) // New session, not from file
PyRun_SimpleString(("import sys; sys.argv = ['" + path + "']").c_str());
if (path.length() == 0) // New session, not from file
{
PyRun_SimpleString("import sys; sys.path.insert(0, '')");
}
else
{
this->filePath = path;
QString dir = QFileInfo(path).absoluteDir().absolutePath();
PyRun_SimpleString(("import sys; sys.path.insert(0, '" + dir.toStdString() + "')").c_str());
PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str());
PyRun_SimpleString(("import sys; sys.path.insert(0, '" + dir + "')").c_str());
PyRun_SimpleString(("__file__ = '"+path+"'").c_str());
}
}
QString PythonServer::variables(bool parseValue) const
string PythonServer::variables(bool parseValue) const
{
PyRun_SimpleString(
"try: \n"
......@@ -181,15 +177,15 @@ QString PythonServer::variables(bool parseValue) const
PyObject *key, *value;
Py_ssize_t pos = 0;
QStringList vars;
vector<string> vars;
while (PyDict_Next(globals, &pos, &key, &value)) {
const QString& keyString = pyObjectToQString(key);
if (keyString.startsWith(QLatin1String("__")))
const string& keyString = pyObjectToQString(key);
if (keyString.substr(0, 2) == string("__"))
continue;
if (keyString == QLatin1String("CatchOutPythonBackend")
|| keyString == QLatin1String("errorPythonBackend")
|| keyString == QLatin1String("outputPythonBackend"))
if (keyString == string("CatchOutPythonBackend")
|| keyString == string("errorPythonBackend")
|| keyString == string("outputPythonBackend"))
continue;
if (PyModule_Check(value))
......@@ -201,12 +197,11 @@ QString PythonServer::variables(bool parseValue) const
if (PyType_Check(value))
continue;
QString valueString;
string valueString;
if (parseValue)
valueString = pyObjectToQString(PyObject_Repr(value));
vars.append(keyString + QChar(17) + valueString);
vars.push_back(keyString + char(17) + valueString);
}
PyRun_SimpleString(
......@@ -222,7 +217,12 @@ QString PythonServer::variables(bool parseValue) const
" pass \n"
);
return vars.join(QChar(18))+QChar(18);
string result;
for (const string& s : vars)
result += s + char(18);
result += char(18);
return result;
}
......@@ -20,30 +20,28 @@
#ifndef _PYTHONSERVER_H
#define _PYTHONSERVER_H
#include <QObject>
#include <QString>
#include <string>
struct _object;
using PyObject = _object;
class PythonServer : public QObject
class PythonServer
{
Q_OBJECT
public:
explicit PythonServer(QObject* parent = nullptr);
explicit PythonServer() = default;
public Q_SLOTS:
public:
void login();
void interrupt();
void setFilePath(const QString& path);
void runPythonCommand(const QString& command) const;
QString getOutput() const;
QString getError() const;
QString variables(bool parseValue) const;
void setFilePath(const std::string& path, const std::string& dir);
void runPythonCommand(const std::string& command) const;
std::string getOutput() const;
std::string getError() const;
std::string variables(bool parseValue) const;
private:
PyObject* m_pModule;
QString filePath;
PyObject* m_pModule{nullptr};
std::string filePath;
};
#endif
......@@ -20,102 +20,25 @@
#include <iostream>
#include <csignal>
#include <QApplication>
#include <QTimer>
#include <QChar>
#include <QByteArray>
#include <vector>
#include <cstring>
#include "pythonserver.h"
const QChar recordSep(30);
const QChar unitSep(31);
using namespace std;
const char messageEnd = 29;
const char recordSep = 30;
const char unitSep = 31;
PythonServer server;
bool isInterrupted = false;
QTimer inputTimer;
QMetaObject::Connection connection;
QString inputBuffer;
QLatin1String LOGIN("login");
QLatin1String EXIT("exit");
QLatin1String CODE("code");
QLatin1String FILEPATH("setFilePath");
QLatin1String MODEL("model");
void routeInput() {
QByteArray bytes;
char c;
while (std::cin.get(c))
{
if (messageEnd == c)
break;
else
bytes.append(c);
}
inputBuffer.append(QString::fromLocal8Bit(bytes));
if (inputBuffer.isEmpty())
return;
const QStringList& records = inputBuffer.split(recordSep);
inputBuffer.clear();
if (records.size() == 2)
{
if (records[0] == EXIT)
{
QObject::disconnect(connection);
QObject::connect(&inputTimer, &QTimer::timeout, QCoreApplication::instance(), &QCoreApplication::quit);
}
else if (records[0] == LOGIN)
{
server.login();
}
else if (records[0] == CODE)
{
server.runPythonCommand(records[1]);
if (!isInterrupted)
{
const QString& result =
server.getOutput()
+ unitSep
+ server.getError()
+ QLatin1Char(messageEnd);
const QByteArray bytes = result.toLocal8Bit();
std::cout << bytes.data();
}
else
{
// No replay when interrupted
isInterrupted = false;
}
}
else if (records[0] == FILEPATH)
{
server.setFilePath(records[1]);
}
else if (records[0] == MODEL)
{
bool ok;
bool val = records[1].toInt(&ok);
QString result;
if (ok)
result = server.variables(val) + unitSep;
else
result = unitSep + QLatin1String("Invalid argument %1 for 'model' command", val);
result += QLatin1Char(messageEnd);
const QByteArray bytes = result.toLocal8Bit();
std::cout << bytes.data();
}
std::cout.flush();
}
}
string LOGIN("login");
string EXIT("exit");
string CODE("code");
string FILEPATH("setFilePath");
string MODEL("model");
void signal_handler(int signal)
{
......@@ -126,17 +49,92 @@ void signal_handler(int signal)
}
}
vector<string> split(string s, char delimiter)
{
vector<string> results;
size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
token = s.substr(0, pos);
results.push_back(token);
s.erase(0, pos + 1);
}
results.push_back(s);
return results;
}
int main(int argc, char *argv[])
int main()
{
std::signal(SIGINT, signal_handler);
QCoreApplication app(argc, argv);
connection = QObject::connect(&inputTimer, &QTimer::timeout, routeInput);
inputTimer.setInterval(100);
inputTimer.start();
std::cout << "ready" << std::endl;
return app.exec();
std::string input;
while (getline(std::cin, input, messageEnd))
{
const vector<string>& records = split(input, recordSep);
if (records.size() == 2)
{
if (records[0] == EXIT)
{
//Exit from cycle and finish program
break;
}
else if (records[0] == LOGIN)
{
server.login();
}
if (records[0] == FILEPATH)
{
vector<string> args = split(records[1], unitSep);
if (args.size() == 2)
server.setFilePath(args[1], args[2]);
}
else if (records[0] == CODE)
{
server.runPythonCommand(records[1]);
if (!isInterrupted)
{
const string& result =
server.getOutput()
+ unitSep
+ server.getError()
+ messageEnd;
std::cout << result.c_str();
}
else
{
// No replay when interrupted
isInterrupted = false;
}
}
else if (records[0] == MODEL)
{
bool ok, val;
try {
val = (bool)stoi(records[1]);
ok = true;
} catch (std::invalid_argument e) {
ok = false;
};
string result;
if (ok)
result = server.variables(val) + unitSep;
else
result = unitSep + string("Invalid argument for 'model' command");
result += messageEnd;
std::cout << result.c_str();
}
std::cout.flush();
}
}
return 0;
}
......@@ -32,6 +32,7 @@
#include <QDir>
#include <QStandardPaths>
#include <QProcess>
#include <QFileInfo>
#include <KDirWatch>
#include <KLocalizedString>
......@@ -94,7 +95,10 @@ void PythonSession::login()
connect(m_process, &QProcess::errorOccurred, this, &PythonSession::reportServerProcessError);
sendCommand(QLatin1String("login"));
sendCommand(QLatin1String("setFilePath"), QStringList(worksheetPath));
QString dir;
if (!worksheetPath.isEmpty())
dir = QFileInfo(worksheetPath).absoluteDir().absolutePath();
sendCommand(QLatin1String("setFilePath"), QStringList() << worksheetPath << dir);
const QStringList& scripts = autorunScripts();
if(!scripts.isEmpty()){
......@@ -205,7 +209,15 @@ void PythonSession::sendCommand(const QString& command, const QStringList argume
void PythonSession::readOutput()
{
while (m_process->bytesAvailable() > 0)
m_output.append(QString::fromLocal8Bit(m_process->readAll()));
{
const QByteArray& bytes = m_process->readAll();
if (m_pythonVersion == 3)
m_output.append(QString::fromUtf8(bytes));
else if (m_pythonVersion == 2)
m_output.append(QString::fromLocal8Bit(bytes));
else
qCritical() << "Unsupported Python version" << m_pythonVersion;
}
qDebug() << "m_output: " << m_output;
......
......@@ -22,7 +22,7 @@ target_link_libraries(cantor_python2backend ${PYTHON_LIBRARIES} cantor_pythonbac
add_executable(cantor_python2server ${Python2Server_SRCS})
set_target_properties(cantor_python2server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false)
target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES} Qt5::Widgets)
target_link_libraries(cantor_python2server ${PYTHON_LIBRARIES})
if(BUILD_TESTING)
add_executable(testpython2 testpython2.cpp settings.cpp)
......
......@@ -15,7 +15,7 @@ target_link_libraries(cantor_python3backend cantor_pythonbackend)
add_executable(cantor_python3server ${Python3Server_SRCS})
set_target_properties(cantor_python3server PROPERTIES INSTALL_RPATH_USE_LINK_PATH false)
target_link_libraries(cantor_python3server ${PYTHONLIBS3_LIBRARIES} Qt5::Widgets)
target_link_libraries(cantor_python3server ${PYTHONLIBS3_LIBRARIES})
if(BUILD_TESTING)
add_executable(testpython3 testpython3.cpp settings.cpp)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment