itom  4.1.0
pythonJediRunner.h
1 /* ********************************************************************
2  itom software
3  URL: http://www.uni-stuttgart.de/ito
4  Copyright (C) 2020, Institut fuer Technische Optik (ITO),
5  Universitaet Stuttgart, Germany
6 
7  This file is part of itom.
8 
9  itom is free software; you can redistribute it and/or modify it
10  under the terms of the GNU Library General Public Licence as published by
11  the Free Software Foundation; either version 2 of the Licence, or (at
12  your option) any later version.
13 
14  itom is distributed in the hope that it will be useful, but
15  WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
17  General Public Licence for more details.
18 
19  You should have received a copy of the GNU Library General Public License
20  along with itom. If not, see <http://www.gnu.org/licenses/>.
21 *********************************************************************** */
22 
23 #pragma once
24 
25 /* includes */
26 #ifndef Q_MOC_RUN
27  #define PY_ARRAY_UNIQUE_SYMBOL itom_ARRAY_API //see numpy help ::array api :: Miscellaneous :: Importing the api (this line must bebefore include global.h)
28  #define NO_IMPORT_ARRAY
29 
30  //python
31  // see http://vtk.org/gitweb?p=VTK.git;a=commitdiff;h=7f3f750596a105d48ea84ebfe1b1c4ca03e0bab3
32  #if (defined _DEBUG) && (defined WIN32)
33  #undef _DEBUG
34  #include "python/pythonWrapper.h"
35  #define _DEBUG
36  #else
37  #include "python/pythonWrapper.h"
38  #endif
39 #endif
40 
41 #include <qobject.h>
42 #include <qthreadpool.h>
43 #include <qscopedpointer.h>
44 #include <qqueue.h>
45 #include <QRunnable>
46 #include <qmutex.h>
47 
48 #include "pythonJedi.h"
49 
50 
51 namespace ito
52 {
53 
54 //-------------------------------------------------------------------------------------
56 /*
57  Every instance of a derived class of JediRunnable has a run() method,
58  that is called by the thread pool (with one thread only) of the PythonJediRunner
59  class. By this run method, a specific request to the Python library Jedi is
60  executed. Since the thread pool only has one thread, Jedi is never called
61  in parallel.
62 
63  However, the thread can be run in parallel to any other Python code execution.
64  Therefore, every run method of a runnable has to lock the GIL before starting
65  the request and release it if done.
66 
67  It might be, that the script editors enqueue more requests of the same type
68  than can be currently handled. Therefore, the run method skips the call
69  if there is a newer runnable in the queue.
70 */
71 class JediRunnable : public QRunnable
72 {
73 public:
74  enum Type
75  {
76  RunnableCalltip,
77  RunnableCompletion,
78  RunnableGoToAssignment,
79  RunnableGetHelp
80  };
81 
83  const Type &type,
84  PyObject *pPyModJedi,
85  const QString &additionalImportString
86  ) :
87  m_type(type),
88  m_pPyModJedi(pPyModJedi),
89  m_additionalImportString(additionalImportString)
90  {
91  setAutoDelete(true);
92  };
93 
94  virtual ~JediRunnable() {};
95 
96  virtual unsigned char getCurrentId() const
97  {
98  return m_currentId;
99  }
100 
101  virtual unsigned char getMostRecentId() const = 0;
102 
103 protected:
104  bool isOutdated() const;
105  void startRun();
106  void endRun();
107 
108  Type m_type;
109  QString m_additionalImportString;
110  PyObject *m_pPyModJedi;
111  unsigned char m_currentId;
112 
113  static QMutex m_mutex;
114 };
115 
116 //-------------------------------------------------------------------------------------
119 {
120 public:
122  const QString &additionalImportString,
123  PyObject *pPyModJedi,
124  const JediCompletionRequest &request
125  ) :
126  JediRunnable(JediRunnable::RunnableCompletion, pPyModJedi, additionalImportString),
127  m_request(request)
128  {
129  m_mutex.lock();
130 
131  if (mostRecentId < 255)
132  {
133  m_currentId = ++mostRecentId;
134  }
135  else
136  {
137  m_currentId = 0;
138  mostRecentId = 0;
139  }
140 
141  m_mutex.unlock();
142  };
143 
144 
145  virtual ~CompletionRunnable() {};
146 
147  void run();
148 
149  virtual unsigned char getMostRecentId() const
150  {
151  return CompletionRunnable::mostRecentId;
152  }
153 
154 private:
155  JediCompletionRequest m_request;
156 
157  static unsigned char mostRecentId;
158 };
159 
160 //-------------------------------------------------------------------------------------
163 {
164 public:
166  const QString &additionalImportString,
167  PyObject *pPyModJedi,
168  const JediAssignmentRequest &request
169  ) :
170  JediRunnable(JediRunnable::RunnableGoToAssignment, pPyModJedi, additionalImportString),
171  m_request(request)
172  {
173  m_mutex.lock();
174 
175  if (mostRecentId < 255)
176  {
177  m_currentId = ++mostRecentId;
178  }
179  else
180  {
181  m_currentId = 0;
182  mostRecentId = 0;
183  }
184 
185  m_mutex.unlock();
186  };
187 
188  virtual ~GoToAssignmentRunnable() {};
189 
190  void run();
191 
192  virtual unsigned char getMostRecentId() const
193  {
194  return GoToAssignmentRunnable::mostRecentId;
195  }
196 
197 private:
198  JediAssignmentRequest m_request;
199 
200  static unsigned char mostRecentId;
201 };
202 
203 //-------------------------------------------------------------------------------------
206 {
207 public:
209  const QString &additionalImportString,
210  PyObject *pPyModJedi,
211  const JediCalltipRequest &request
212  ) :
213  JediRunnable(JediRunnable::RunnableCalltip, pPyModJedi, additionalImportString) ,
214  m_request(request)
215  {
216  m_mutex.lock();
217 
218  if (mostRecentId < 255)
219  {
220  m_currentId = ++mostRecentId;
221  }
222  else
223  {
224  m_currentId = 0;
225  mostRecentId = 0;
226  }
227 
228  m_mutex.unlock();
229  };
230 
231  virtual ~CalltipRunnable() {};
232 
233  void run();
234 
235  virtual unsigned char getMostRecentId() const
236  {
237  return CalltipRunnable::mostRecentId;
238  }
239 
240 private:
241  JediCalltipRequest m_request;
242 
243  static unsigned char mostRecentId;
244 };
245 
246 //-------------------------------------------------------------------------------------
249 {
250 public:
252  const QString &additionalImportString,
253  PyObject *pPyModJedi,
254  const JediGetHelpRequest &request
255  ) :
256  JediRunnable(JediRunnable::RunnableGetHelp, pPyModJedi, additionalImportString),
257  m_request(request)
258  {
259  m_mutex.lock();
260 
261  if (mostRecentId < 255)
262  {
263  m_currentId = ++mostRecentId;
264  }
265  else
266  {
267  m_currentId = 0;
268  mostRecentId = 0;
269  }
270 
271  m_mutex.unlock();
272  };
273 
274  virtual ~GetHelpRunnable() {};
275 
276  void run();
277 
278  virtual unsigned char getMostRecentId() const
279  {
280  return GetHelpRunnable::mostRecentId;
281  }
282 
283 private:
284  JediGetHelpRequest m_request;
285 
286  static unsigned char mostRecentId;
287 };
288 
289 //-------------------------------------------------------------------------------------
291 /* This class is initialized by the PythonEngine as singleton and opened in pythonStartup()
292 and closed in pythonShutdown().
293 
294 Its methods are thread-safe and are usually called via wrapper methods in Python engine.
295 The idea is, that jedi is always triggered via the Python C-API from another thread,
296 using the Python GIL, such that other Python commands can be executed in parallel.
297 
298 This is for instance important if somebody enters "import numpy" in the command line
299 and presses enter: Since numpy is an intense package, Jedi requires a little bit of time
300 when numpy is analyzed for the first time, such that the code execution would have to wait
301 for a couple of seconds. Using the thread-pool approach of this class, the execution is
302 started much faster.
303 
304 Hint: The thread pool of this class is limited to one thread, such that no parallel
305 requests to jedi can be executed (desired behaviour).
306 */
307 class PythonJediRunner : public QObject
308 {
309  Q_OBJECT
310 
311 public:
312  PythonJediRunner(const QString &includeItomImportString);
314 
316  /*
317  If the module and package is already imported, true is directly returned.
318  This method is thread-safe.
319  */
320  bool tryToLoadJediIfNotYetDone();
321 
323  {
325  }
326 
328  void addCalltipRequest(const JediCalltipRequest &request);
329 
331  void addCompletionRequest(const JediCompletionRequest &request);
332 
334  void addGoToAssignmentRequest(const JediAssignmentRequest &request);
335 
337  void addGetHelpRequest(const JediGetHelpRequest &request);
338 
339 private:
340  QString additionalImportString() const {
341  return
343  m_includeItomImportString : "");
344  }
345 
346  QScopedPointer<QThreadPool> m_threadPool;
347 
349  PyObject *m_pyModJedi;
350 
353 
356 
358  QString m_includeItomImportString;
359 };
360 
361 }; //end namespace ito
void setIncludeItomImportBeforeCodeAnalysis(bool include)
Adds a new calltip request. Thread-safe.
Definition: pythonJediRunner.h:322
< runnable that executes a completion call to Jedi by the thread pool of Python Jedi Runner...
Definition: pythonJediRunner.h:118
bool m_pyModJediChecked
decides if itom is automatically included in every source file before it is handed to the syntax chec...
Definition: pythonJediRunner.h:352
void addCompletionRequest(const JediCompletionRequest &request)
Adds a new goto assignment / definition request. Thread-safe.
Definition: pythonJediRunner.cpp:144
< runnable that executes a calltip call to Jedi by the thread pool of Python Jedi Runner...
Definition: pythonJediRunner.h:205
bool m_includeItomImportBeforeCodeAnalysis
string that is prepended to each script before syntax check (if m_includeItomImportBeforeCodeAnalysis...
Definition: pythonJediRunner.h:355
Definition: pythonJedi.h:94
< Thread-safe helper class for PythonEngine to manage calls to the Python Jedi package.
Definition: pythonJediRunner.h:307
< runnable that executes a calltip call to Jedi by the thread pool of Python Jedi Runner...
Definition: pythonJediRunner.h:248
Definition: apiFunctionsGraph.cpp:39
PyObject * m_pyModJedi
defines, if it is already checked if Jedi could be loaded on this computer.
Definition: pythonJediRunner.h:349
~PythonJediRunner()
Tries to import itomJediLib (and the jedi package) and returns true if successful, else false.
Definition: pythonJediRunner.cpp:61
QScopedPointer< QThreadPool > m_threadPool
Python package Jedi for auto completion and calltips (Jedi is tried to be loaded as late as possible)...
Definition: pythonJediRunner.h:346
void addGoToAssignmentRequest(const JediAssignmentRequest &request)
Adds a new get-help request. Thread-safe.
Definition: pythonJediRunner.cpp:164
< runnable that executes a goto definition / assignment call to Jedi by the thread pool of Python Jed...
Definition: pythonJediRunner.h:162
Definition: pythonJedi.h:123
Definition: pythonJedi.h:64
Definition: pythonJedi.h:34
< base class for all runnables, that are executed with PythonJediRunner
Definition: pythonJediRunner.h:71
void addCalltipRequest(const JediCalltipRequest &request)
Adds a new completion request. Thread-safe.
Definition: pythonJediRunner.cpp:124