Kactus2
Kactus2 reference guide
Loading...
Searching...
No Matches
PythonInterpreter.cpp
Go to the documentation of this file.
1//-----------------------------------------------------------------------------
2// File: PythonInterpreter.cpp
3//-----------------------------------------------------------------------------
4// Project: Kactus2
5// Author: Esko Pekkarinen
6// Date: 03.02.2021
7//
8// Description:
9// Convenience class for accessing Python interpreter.
10//-----------------------------------------------------------------------------
11#define PY_SSIZE_T_CLEAN
12#include <Python.h>
13
14#include "PythonInterpreter.h"
15
16#include <PythonAPI/extensions/IOCatcher.h>
17#include <PythonAPI/PythonAPI.h>
18
19#include <QApplication>
20
21//-----------------------------------------------------------------------------
22// Function: PythonInterpreter::PythonInterpreter()
23//-----------------------------------------------------------------------------
25 bool printPromt, QObject* parent) :
26 QObject(parent),
28 printPrompt_(printPromt),
29 outputChannel_(outputChannel),
30 errorChannel_(errorChannel)
31{
32
33}
34
35//-----------------------------------------------------------------------------
36// Function: PythonInterpreter::initialize()
37//-----------------------------------------------------------------------------
38bool PythonInterpreter::initialize(bool interactive)
39{
40 PyImport_AppendInittab("IOCatcher", &PyInit_IOCatcher);
41
42 Py_InitializeEx(0); //<! Disable signals.
43
44 if (Py_IsInitialized() == false)
45 {
46 errorChannel_->write(QStringLiteral("Could not initialize Python interpreter."));
47 return false;
48 }
49
50 threadState_ = Py_NewInterpreter();
51
52 outputChannel_->write(QStringLiteral("Python ") + QString(Py_GetVersion()) + QString("\n"));
53
54 PyObject *pyModule = PyImport_ImportModule("__main__");
55 localContext_ = PyModule_GetDict(pyModule);
56 globalContext_ = localContext_;
57
58 if (redirectIO(interactive) == false)
59 {
60 return false;
61 }
62
63 setAPI();
64
65 PyEval_ReleaseThread(threadState_);
66
67 printPrompt();
68 return true;
69}
70
71//-----------------------------------------------------------------------------
72// Function: PythonInterpreter::finalize()
73//-----------------------------------------------------------------------------
75{
76 PyEval_AcquireThread(threadState_);
77 Py_FinalizeEx();
78}
79
80//-----------------------------------------------------------------------------
81// Function: PythonInterpreter::write()
82//-----------------------------------------------------------------------------
83void PythonInterpreter::write(QString const& command)
84{
85 execute(command.toStdString());
86}
87
88//-----------------------------------------------------------------------------
89// Function: PythonInterpreter::execute()
90//-----------------------------------------------------------------------------
91void PythonInterpreter::execute(std::string const& command)
92{
93 Q_ASSERT_X(Py_IsInitialized(), "Python interpreter", "Trying to execute without initializing.");
94
95 if (command.empty() && inputBuffer_.empty())
96 {
97 printPrompt();
98 return;
99 }
100
101 inputBuffer_.append(command);
102 inputBuffer_.push_back('\n');
103
104 auto inputSize = inputBuffer_.length();
105 bool endMultiline = runMultiline_ && inputSize >= 2 && inputBuffer_.at(inputSize - 2) == '\n';
106
107
108 PyEval_AcquireThread(threadState_);
109
110 PyObject* src = Py_CompileString(inputBuffer_.c_str(), "<stdin>", Py_single_input);
111
112 /* compiled just fine - */
113 if (src != nullptr)
114 {
115 /* ">>> " or "... " and double '\n' */
116 if (runMultiline_ == false || endMultiline)
117 { /* so execute it */
118 PyObject* dum = PyEval_EvalCode(src, globalContext_, localContext_);
119 Py_XDECREF(dum);
120 Py_XDECREF(src);
121
122 if (PyErr_Occurred())
123 {
124 PyErr_Print();
125 }
126
127 runMultiline_ = false;
128 inputBuffer_.clear();
129 }
130 else
131 {
132 runMultiline_ = true;
133 }
134 }
135 /* syntax error or E_EOF? */
136 else if (PyErr_ExceptionMatches(PyExc_SyntaxError))
137 {
138 PyObject* exc = nullptr;
139 PyObject* val = nullptr;
140 PyObject* trb = nullptr;
141 PyObject* obj = nullptr;
142
143 char *msg = nullptr;
144
145 /* clears exception! */
146 PyErr_Fetch(&exc, &val, &trb);
147
148 if (PyArg_ParseTuple(val, "sO", &msg, &obj) && !strcmp(msg, "unexpected EOF while parsing") &&
149 endMultiline == false) /* E_EOF */
150 {
151 Py_XDECREF(exc);
152 Py_XDECREF(val);
153 Py_XDECREF(trb);
154
155 runMultiline_ = true;
156 }
157 else /* some other syntax error */
158 {
159 PyErr_Restore(exc, val, trb);
160 PyErr_Print();
161
162 runMultiline_ = false;
163 inputBuffer_.clear();
164 }
165 }
166 else /* some non-syntax error */
167 {
168 PyErr_Print();
169 runMultiline_ = false;
170 inputBuffer_.clear();
171 }
172
173 PyEval_ReleaseThread(threadState_);
174
175 printPrompt();
176 emit executeDone();
177}
178
179//-----------------------------------------------------------------------------
180// Function: PythonInterpreter::executeString()
181//-----------------------------------------------------------------------------
182void PythonInterpreter::executeString(QString const& string)
183{
184 PyEval_AcquireThread(threadState_);
185 PyObject* result = PyRun_String(string.toStdString().c_str(), Py_file_input, globalContext_, localContext_);
186
187 if (result == nullptr)
188 {
189 PyErr_Print();
190 }
191 else
192 {
193 Py_DECREF(result);
194 }
195
196 PyEval_ReleaseThread(threadState_);
197
198 printPrompt();
199 emit executeDone();
200}
201
202//-----------------------------------------------------------------------------
203// Function: PythonInterpreter::runFile()
204//-----------------------------------------------------------------------------
205int PythonInterpreter::runFile(QString const& filePath)
206{
207 Q_ASSERT_X(Py_IsInitialized(), "Python interpreter", "Trying to execute file without initializing.");
208 int retVal = 1;
209 QFile scriptFile(filePath);
210 if (scriptFile.open(QIODevice::ReadOnly | QIODevice::Text))
211 {
212 int fd = scriptFile.handle();
213 FILE* f = fdopen(dup(fd), "rb");
214
215 PyEval_AcquireThread(threadState_);
216 retVal = PyRun_SimpleFile(f, scriptFile.fileName().toLocal8Bit());
217 PyEval_ReleaseThread(threadState_);
218
219 fclose(f);
220
221 scriptFile.close();
222
223 printPrompt();
224 }
225
226 emit executeDone();
227 return retVal;
228}
229
230//-----------------------------------------------------------------------------
231// Function: PythonInterpreter::setOutputChannels()
232//-----------------------------------------------------------------------------
233bool PythonInterpreter::redirectIO(bool interative)
234{
235 PyObject* IOCatcherName = PyUnicode_FromString("IOCatcher");
236 if (IOCatcherName == NULL)
237 {
238 return false;
239 }
240
241 PyObject* IOCatcherModule = PyImport_Import(IOCatcherName);
242 Py_DECREF(IOCatcherName);
243 if (IOCatcherModule == NULL)
244 {
245 return false;
246 }
247
248 PyObject* dict = PyModule_GetDict(IOCatcherModule);
249 Py_DECREF(IOCatcherModule);
250
251 if (dict == NULL)
252 {
253 PyErr_Print();
254 errorChannel_->write(QStringLiteral("Fails to get the output dictionary.\n"));
255 return false;
256 }
257
258 PyObject* python_class = PyDict_GetItemString(dict, "OutputCatcher");
259 if (python_class == NULL)
260 {
261 PyErr_Print();
262 errorChannel_->write(QStringLiteral("Fails to get the output Python class.\n"));
263 return false;
264 }
265
266 PyObject* outCatcher = nullptr;
267 PyObject* errCatcher = nullptr;
268
269 // Creates an instance of the class
270 if (PyCallable_Check(python_class))
271 {
272 outCatcher = PyObject_CallObject(python_class, nullptr);
273 errCatcher = PyObject_CallObject(python_class, nullptr);
274 }
275 else
276 {
277 outputChannel_->write(QStringLiteral("Cannot instantiate the Python class for output"));
278 return false;
279 }
280
281 ((OutputCatcherObject*)outCatcher)->channel = outputChannel_;
282 if (PySys_SetObject("stdout", outCatcher) < 0)
283 {
284 return false;
285 }
286
287 ((OutputCatcherObject*)errCatcher)->channel = errorChannel_;
288 if (PySys_SetObject("stderr", errCatcher) < 0)
289 {
290 return false;
291 }
292
293 if (interative == false)
294 {
295 PyObject* input_python_class = PyDict_GetItemString(dict, "InputCatcher");
296
297 if (input_python_class == NULL)
298 {
299 PyErr_Print();
300 errorChannel_->write(QStringLiteral("Fails to get the input Python class.\n"));
301 return false;
302 }
303
304 PyObject* inputBuffer;
305
306 // Creates an instance of the class
307 if (PyCallable_Check(input_python_class))
308 {
309 inputBuffer = PyObject_CallObject(input_python_class, nullptr);
310 }
311
312 if (PySys_SetObject("stdin", inputBuffer) < 0)
313 {
314 return false;
315 }
316 }
317
318 return true;
319}
320
321//-----------------------------------------------------------------------------
322// Function: PythonInterpreter::setAPI()
323//-----------------------------------------------------------------------------
324bool PythonInterpreter::setAPI()
325{
326 PyObject* emptyList = PyList_New(0);
327 PyObject* apiModule = PyImport_ImportModuleEx("pythonAPI", globalContext_, localContext_, emptyList);
328 Py_XDECREF(emptyList);
329
330 if (apiModule == NULL)
331 {
332 errorChannel_->write(QStringLiteral("Could not import Kactus2 PythonAPI.\n"));
333 PyErr_Print();
334 }
335 else
336 {
337 PyObject* dict = PyModule_GetDict(apiModule);
338
339 if (dict == NULL) {
340 PyErr_Print();
341 errorChannel_->write(QStringLiteral("Could not import Kactus2 PythonAPI.\n"));
342 return false;
343 }
344
345 PyObject* python_class = PyDict_GetItemString(dict, "PythonAPI");
346
347 if (python_class == NULL) {
348 PyErr_Print();
349 errorChannel_->write(QStringLiteral("Could not import Kactus2 PythonAPI.\n"));
350 return false;
351 }
352
353 // Creates an instance of the class
354 if (PyCallable_Check(python_class))
355 {
356 PyObject* apiObject = PyObject_CallObject(python_class, nullptr);
357 PyDict_SetItemString(localContext_, "kactus2", apiObject);
358 }
359 }
360
361 return true;
362}
363
364//-----------------------------------------------------------------------------
365// Function: PythonInterpreter::printPrompt()
366//-----------------------------------------------------------------------------
367void PythonInterpreter::printPrompt() const
368{
369 if (printPrompt_ == false)
370 {
371 return;
372 }
373
374 if (runMultiline_)
375 {
376 outputChannel_->write("... ");
377 }
378 else
379 {
380 outputChannel_->write(">>> ");
381 }
382}
PythonInterpreter(WriteChannel *outputChannel, WriteChannel *errorChannel, bool printPromt=true, QObject *parent=nullptr)
bool initialize(bool interactive=true)
int runFile(QString const &filePath)
void execute(std::string const &line)
void executeString(QString const &string)
virtual void write(QString const &command) override final
WriteChannel()=default
The constructor.