|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the Flask project support. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 from PyQt5.QtCore import pyqtSlot, QObject, QProcess, QTimer |
|
13 from PyQt5.QtWidgets import QMenu |
|
14 |
|
15 from E5Gui import E5MessageBox |
|
16 from E5Gui.E5Action import E5Action |
|
17 from E5Gui.E5Application import e5App |
|
18 |
|
19 from Globals import isWindowsPlatform |
|
20 |
|
21 import UI.PixmapCache |
|
22 import Utilities |
|
23 |
|
24 |
|
25 class Project(QObject): |
|
26 """ |
|
27 Class implementing the Flask project support. |
|
28 """ |
|
29 def __init__(self, plugin, iconSuffix, parent=None): |
|
30 """ |
|
31 Constructor |
|
32 |
|
33 @param plugin reference to the plugin object |
|
34 @type ProjectFlaskPlugin |
|
35 @param iconSuffix suffix for the icons |
|
36 @type str |
|
37 @param parent parent |
|
38 @type QObject |
|
39 """ |
|
40 super(Project, self).__init__(parent) |
|
41 |
|
42 self.__plugin = plugin |
|
43 self.__iconSuffix = iconSuffix |
|
44 self.__ui = parent |
|
45 |
|
46 self.__e5project = e5App().getObject("Project") |
|
47 self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") |
|
48 |
|
49 self.__menus = {} # dictionary with references to menus |
|
50 |
|
51 self.__serverProc = None |
|
52 |
|
53 self.__flaskVersions = { |
|
54 "python": "", |
|
55 "flask": "", |
|
56 "werkzeug": "", |
|
57 } |
|
58 |
|
59 def initActions(self): |
|
60 """ |
|
61 Public method to define the Flask actions. |
|
62 """ |
|
63 self.actions = [] |
|
64 |
|
65 ############################## |
|
66 ## about action below ## |
|
67 ############################## |
|
68 |
|
69 self.aboutFlaskAct = E5Action( |
|
70 self.tr('About Flask'), |
|
71 self.tr('About &Flask'), |
|
72 0, 0, |
|
73 self, 'flask_about') |
|
74 self.aboutFlaskAct.setStatusTip(self.tr( |
|
75 'Shows some information about Flask')) |
|
76 self.aboutFlaskAct.setWhatsThis(self.tr( |
|
77 """<b>About Flask</b>""" |
|
78 """<p>Shows some information about Flask.</p>""" |
|
79 )) |
|
80 self.aboutFlaskAct.triggered.connect(self.__flaskInfo) |
|
81 self.actions.append(self.aboutFlaskAct) |
|
82 |
|
83 def initMenu(self): |
|
84 """ |
|
85 Public method to initialize the Flask menu. |
|
86 |
|
87 @return the menu generated |
|
88 @rtype QMenu |
|
89 """ |
|
90 self.__menus = {} # clear menus references |
|
91 |
|
92 menu = QMenu(self.tr('&Flask'), self.__ui) |
|
93 menu.setTearOffEnabled(True) |
|
94 |
|
95 menu.addAction(self.aboutFlaskAct) |
|
96 |
|
97 self.__menus["main"] = menu |
|
98 |
|
99 return menu |
|
100 |
|
101 def getMenu(self, name): |
|
102 """ |
|
103 Public method to get a reference to the requested menu. |
|
104 |
|
105 @param name name of the menu |
|
106 @type str |
|
107 @return reference to the menu or None, if no menu with the given |
|
108 name exists |
|
109 @rtype QMenu or None |
|
110 """ |
|
111 if name in self.__menus: |
|
112 return self.__menus[name] |
|
113 else: |
|
114 return None |
|
115 |
|
116 def getMenuNames(self): |
|
117 """ |
|
118 Public method to get the names of all menus. |
|
119 |
|
120 @return menu names |
|
121 @rtype list of str |
|
122 """ |
|
123 return list(self.__menus.keys()) |
|
124 |
|
125 ################################################################## |
|
126 ## slots below implement general functionality |
|
127 ################################################################## |
|
128 |
|
129 def projectClosed(self): |
|
130 """ |
|
131 Public method to handle the closing of a project. |
|
132 """ |
|
133 if self.__serverProc is not None: |
|
134 self.__serverProcFinished() |
|
135 |
|
136 # TODO: implement this correctly |
|
137 def supportedPythonVariants(self): |
|
138 """ |
|
139 Public method to get the supported Python variants. |
|
140 |
|
141 @return list of supported Python variants |
|
142 @rtype list of str |
|
143 """ |
|
144 variants = [] |
|
145 |
|
146 virtEnv = self.__getVirtualEnvironment() |
|
147 if virtEnv: |
|
148 fullCmd = self.getFlaskCommand() |
|
149 if fullCmd: |
|
150 variants.append("Python3") |
|
151 else: |
|
152 fullCmd = self.getFlaskCommand() |
|
153 if isWindowsPlatform(): |
|
154 if fullCmd: |
|
155 variants.append("Python3") |
|
156 else: |
|
157 fullCmds = Utilities.getExecutablePaths("flask") |
|
158 for fullCmd in fullCmds: |
|
159 try: |
|
160 with open(fullCmd, 'r', encoding='utf-8') as f: |
|
161 l0 = f.readline() |
|
162 except (IOError, OSError): |
|
163 l0 = "" |
|
164 if self.__isSuitableForVariant("Python3", l0): |
|
165 variants.append("Python3") |
|
166 break |
|
167 |
|
168 return variants |
|
169 |
|
170 def __isSuitableForVariant(self, variant, line0): |
|
171 """ |
|
172 Private method to test, if a detected command file is suitable for the |
|
173 given Python variant. |
|
174 |
|
175 @param variant Python variant to test for |
|
176 @type str |
|
177 @param line0 first line of the executable |
|
178 @type str |
|
179 @return flag indicating a suitable file was found |
|
180 @rtype bool |
|
181 """ |
|
182 l0 = line0.lower() |
|
183 ok = (variant.lower() in l0 or |
|
184 "{0}.".format(variant[-1]) in l0) |
|
185 ok |= "pypy3" in l0 |
|
186 |
|
187 return ok |
|
188 |
|
189 def __getVirtualEnvironment(self): |
|
190 """ |
|
191 Private method to get the path of the virtual environment. |
|
192 |
|
193 @return path of the virtual environment |
|
194 @rtype str |
|
195 """ |
|
196 language = self.__e5project.getProjectLanguage() |
|
197 if language == "Python3": |
|
198 venvName = self.__plugin.getPreferences( |
|
199 "VirtualEnvironmentNamePy3") |
|
200 else: |
|
201 venvName = "" |
|
202 if venvName: |
|
203 virtEnv = self.__virtualEnvManager.getVirtualenvDirectory( |
|
204 venvName) |
|
205 else: |
|
206 virtEnv = "" |
|
207 |
|
208 if virtEnv and not os.path.exists(virtEnv): |
|
209 virtEnv = "" |
|
210 |
|
211 return virtEnv # __IGNORE_WARNING_M834__ |
|
212 |
|
213 def getFlaskCommand(self): |
|
214 """ |
|
215 Public method to build the Flask command. |
|
216 |
|
217 @return full flask command |
|
218 @rtype str |
|
219 """ |
|
220 cmd = "flask" |
|
221 |
|
222 virtualEnv = self.__getVirtualEnvironment() |
|
223 if isWindowsPlatform(): |
|
224 fullCmds = [ |
|
225 os.path.join(virtualEnv, "Scripts", cmd + '.exe'), |
|
226 os.path.join(virtualEnv, "bin", cmd + '.exe'), |
|
227 cmd # fall back to just cmd |
|
228 ] |
|
229 for cmd in fullCmds: |
|
230 if os.path.exists(cmd): |
|
231 break |
|
232 else: |
|
233 fullCmds = [ |
|
234 os.path.join(virtualEnv, "bin", cmd), |
|
235 os.path.join(virtualEnv, "local", "bin", cmd), |
|
236 Utilities.getExecutablePath(cmd), |
|
237 cmd # fall back to just cmd |
|
238 ] |
|
239 for cmd in fullCmds: |
|
240 if os.path.exists(cmd): |
|
241 break |
|
242 return cmd |
|
243 |
|
244 @pyqtSlot() |
|
245 def __flaskInfo(self): |
|
246 """ |
|
247 Private slot to show some info about Flask. |
|
248 """ |
|
249 versions = self.getFlaskVersionStrings() |
|
250 url = "https://flask.palletsprojects.com" |
|
251 |
|
252 msgBox = E5MessageBox.E5MessageBox( |
|
253 E5MessageBox.Question, |
|
254 self.tr("About Flask"), |
|
255 self.tr( |
|
256 "<p>Flask is a lightweight WSGI web application framework." |
|
257 " It is designed to make getting started quick and easy," |
|
258 " with the ability to scale up to complex applications.</p>" |
|
259 "<p><table>" |
|
260 "<tr><td>Flask Version:</td><td>{0}</td></tr>" |
|
261 "<tr><td>Werkzeug Version:</td><td>{1}</td></tr>" |
|
262 "<tr><td>Python Version:</td><td>{2}</td></tr>" |
|
263 "<tr><td>Flask URL:</td><td><a href=\"{3}\">" |
|
264 "{3}</a></td></tr>" |
|
265 "</table></p>" |
|
266 ).format(versions["flask"], versions["werkzeug"], |
|
267 versions["python"], url), |
|
268 modal=True, |
|
269 buttons=E5MessageBox.Ok) |
|
270 msgBox.setIconPixmap(UI.PixmapCache.getPixmap( |
|
271 os.path.join("ProjectFlask", "icons", |
|
272 "flask64-{0}".format(self.__iconSuffix)))) |
|
273 msgBox.exec() |
|
274 |
|
275 def getFlaskVersionStrings(self): |
|
276 """ |
|
277 Public method to get the Flask, Werkzeug and Python versions as a |
|
278 string. |
|
279 |
|
280 @return dictionary containing the Flask, Werkzeug and Python versions |
|
281 @rtype dict |
|
282 """ |
|
283 if not self.__flaskVersions["flask"]: |
|
284 cmd = self.getFlaskCommand() |
|
285 proc = QProcess() |
|
286 proc.start(cmd, ["--version"]) |
|
287 if proc.waitForFinished(10000): |
|
288 output = str(proc.readAllStandardOutput(), "utf-8") |
|
289 for line in output.lower().splitlines(): |
|
290 key, version = line.strip().split(None, 1) |
|
291 self.__flaskVersions[key] = version |
|
292 |
|
293 return self.__flaskVersions |
|
294 |
|
295 ################################################################## |
|
296 ## slots below implement run functions |
|
297 ################################################################## |
|
298 |
|
299 def __runServer(self, logging=False): |
|
300 """ |
|
301 Private slot to start the Pyramid Web server. |
|
302 |
|
303 @param logging flag indicating to enable logging |
|
304 @type bool |
|
305 """ |
|
306 # TODO: implement this |
|
307 |
|
308 def __serverProcFinished(self): |
|
309 """ |
|
310 Private slot connected to the finished signal. |
|
311 """ |
|
312 if ( |
|
313 self.__serverProc is not None and |
|
314 self.__serverProc.state() != QProcess.NotRunning |
|
315 ): |
|
316 self.__serverProc.terminate() |
|
317 QTimer.singleShot(2000, self.__serverProc.kill) |
|
318 self.__serverProc.waitForFinished(3000) |
|
319 self.__serverProc = None |