pythonserver.cpp 6.46 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA  02110-1301, USA.

    ---
    Copyright (C) 2015 Minh Ngo <minh@fedoraproject.org>
 */

21
#include "pythonserver.h"
22
23
24

#include <Python.h>

25
PythonServer::PythonServer(QObject* parent) : QObject(parent), m_pModule(nullptr)
26
27
28
{
}

29
30
31
32
namespace
{
    QString pyObjectToQString(PyObject* obj)
    {
33
#if PY_MAJOR_VERSION == 3
34
        return QString::fromUtf8(PyUnicode_AsUTF8(obj));
35
36
37
38
39
#elif PY_MAJOR_VERSION == 2
        return QString::fromLocal8Bit(PyString_AsString(obj));
#else
    #warning Unknown Python version
#endif
40
41
42
    }
}

43
void PythonServer::login()
44
{
45
    Py_InspectFlag = 1;
46
47
    Py_Initialize();
    m_pModule = PyImport_AddModule("__main__");
Laurent Montel's avatar
Laurent Montel committed
48
    filePath = QStringLiteral("python_cantor_worksheet");
49
50
}

51
void PythonServer::runPythonCommand(const QString& command) const
52
{
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    PyObject* py_dict = PyModule_GetDict(m_pModule);

    const char* prepareCommand =
        "import sys;\n"\
        "class CatchOutPythonBackend:\n"\
        "  def __init__(self):\n"\
        "    self.value = ''\n"\
        "  def write(self, txt):\n"\
        "    self.value += txt\n"\
        "outputPythonBackend = CatchOutPythonBackend()\n"\
        "errorPythonBackend  = CatchOutPythonBackend()\n"\
        "sys.stdout = outputPythonBackend\n"\
        "sys.stderr = errorPythonBackend\n";
    PyRun_SimpleString(prepareCommand);

68
#if PY_MAJOR_VERSION == 3
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    PyObject* compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().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
    if (PyErr_Occurred())
    {
        PyErr_Clear();
        // Try to recompile code as sequence of expressions
        compile = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_file_input);
        if (PyErr_Occurred())
        {
            // We now know, that we have a syntax error, so print the traceback and exit
            PyErr_PrintEx(0);
            return;
        }
    }
    PyEval_EvalCode(compile, py_dict, py_dict);
#elif PY_MAJOR_VERSION == 2
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    // 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);
    if (PyErr_Occurred())
    {
        PyErr_PrintEx(0);
        return;
    }
    PyObject* codeSingle = Py_CompileString(command.toStdString().c_str(), filePath.toStdString().c_str(), Py_single_input);
    if (PyErr_Occurred())
    {
        // We have error with Py_single_input, but haven't error with Py_file_input
        // So, the code can't be compiled as singel input -> use file input right away
        PyErr_Clear();
        PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict);
    }
    else
    {
        PyObject* bytecode1 = ((PyCodeObject*)codeSingle)->co_code;
        PyObject* bytecode2 = ((PyCodeObject*)codeFile)->co_code;

        if (PyObject_Length(bytecode1) >= PyObject_Length(bytecode2))
        {
            PyEval_EvalCode((PyCodeObject*)codeSingle, py_dict, py_dict);
        }
        else
        {
            PyEval_EvalCode((PyCodeObject*)codeFile, py_dict, py_dict);
        }
    }
118
119
120
121
122
#else
    #warning Unknown Python version
#endif
    if (PyErr_Occurred())
        PyErr_PrintEx(0);
123
124
}

125
QString PythonServer::getError() const
126
{
127
128
    PyObject *errorPython = PyObject_GetAttrString(m_pModule, "errorPythonBackend");
    PyObject *error = PyObject_GetAttrString(errorPython, "value");
129

130
    return pyObjectToQString(error);
131
132
}

133
QString PythonServer::getOutput() const
134
{
135
136
    PyObject *outputPython = PyObject_GetAttrString(m_pModule, "outputPythonBackend");
    PyObject *output = PyObject_GetAttrString(outputPython, "value");
137

138
    return pyObjectToQString(output);
139
140
}

141
142
143
144
145
void PythonServer::setFilePath(const QString& path)
{
    this->filePath = path;
    PyRun_SimpleString(("__file__ = '"+path.toStdString()+"'").c_str());
}
146

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
QString PythonServer::variables() const
{
    // FIXME: This code allows get full form of numpy array, but for big arrays it's could cause performonce problems
    // especially for displaying in variables panel
    // So, uncomment this, when fix this problem
    /*
        "try: \n"
        "   import numpy \n"
        "   __cantor_numpy_internal__ = numpy.get_printoptions()['threshold'] \n"
        "   numpy.set_printoptions(threshold=100000000) \n"
        "except ModuleNotFoundError: \n"
        "   pass \n"

        "try: \n"
        "   import numpy \n"
        "   numpy.set_printoptions(threshold=__cantor_numpy_internal__) \n"
        "   del __cantor_numpy_internal__ \n"
        "except ModuleNotFoundError: \n"
        "   pass \n"
    */

    PyRun_SimpleString("__tmp_globals__ = globals()");
    PyObject* globals = PyObject_GetAttrString(m_pModule,"__tmp_globals__");
    PyObject *key, *value;
    Py_ssize_t pos = 0;

    QStringList vars;
    const QChar sep(30); // INFORMATION SEPARATOR TWO
    while (PyDict_Next(globals, &pos, &key, &value)) {
        const QString& keyString = pyObjectToQString(key);
        if (keyString.startsWith(QLatin1String("__")))
            continue;

        if (keyString == QLatin1String("CatchOutPythonBackend")
            || keyString == QLatin1String("errorPythonBackend")
            || keyString == QLatin1String("outputPythonBackend"))
            continue;

        if (PyModule_Check(value))
            continue;

        if (PyFunction_Check(value))
            continue;

        if (PyType_Check(value))
            continue;

        const QString& valueString = pyObjectToQString(PyObject_Repr(value));

        vars.append(keyString + QChar(31) + valueString);
    }

    return vars.join(sep);
}

202