--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ProjectFlask/Project.py Sun Nov 08 17:54:22 2020 +0100 @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Flask project support. +""" + +import os + +from PyQt5.QtCore import pyqtSlot, QObject, QProcess, QTimer +from PyQt5.QtWidgets import QMenu + +from E5Gui import E5MessageBox +from E5Gui.E5Action import E5Action +from E5Gui.E5Application import e5App + +from Globals import isWindowsPlatform + +import UI.PixmapCache +import Utilities + + +class Project(QObject): + """ + Class implementing the Flask project support. + """ + def __init__(self, plugin, iconSuffix, parent=None): + """ + Constructor + + @param plugin reference to the plugin object + @type ProjectFlaskPlugin + @param iconSuffix suffix for the icons + @type str + @param parent parent + @type QObject + """ + super(Project, self).__init__(parent) + + self.__plugin = plugin + self.__iconSuffix = iconSuffix + self.__ui = parent + + self.__e5project = e5App().getObject("Project") + self.__virtualEnvManager = e5App().getObject("VirtualEnvManager") + + self.__menus = {} # dictionary with references to menus + + self.__serverProc = None + + self.__flaskVersions = { + "python": "", + "flask": "", + "werkzeug": "", + } + + def initActions(self): + """ + Public method to define the Flask actions. + """ + self.actions = [] + + ############################## + ## about action below ## + ############################## + + self.aboutFlaskAct = E5Action( + self.tr('About Flask'), + self.tr('About &Flask'), + 0, 0, + self, 'flask_about') + self.aboutFlaskAct.setStatusTip(self.tr( + 'Shows some information about Flask')) + self.aboutFlaskAct.setWhatsThis(self.tr( + """<b>About Flask</b>""" + """<p>Shows some information about Flask.</p>""" + )) + self.aboutFlaskAct.triggered.connect(self.__flaskInfo) + self.actions.append(self.aboutFlaskAct) + + def initMenu(self): + """ + Public method to initialize the Flask menu. + + @return the menu generated + @rtype QMenu + """ + self.__menus = {} # clear menus references + + menu = QMenu(self.tr('&Flask'), self.__ui) + menu.setTearOffEnabled(True) + + menu.addAction(self.aboutFlaskAct) + + self.__menus["main"] = menu + + return menu + + def getMenu(self, name): + """ + Public method to get a reference to the requested menu. + + @param name name of the menu + @type str + @return reference to the menu or None, if no menu with the given + name exists + @rtype QMenu or None + """ + if name in self.__menus: + return self.__menus[name] + else: + return None + + def getMenuNames(self): + """ + Public method to get the names of all menus. + + @return menu names + @rtype list of str + """ + return list(self.__menus.keys()) + + ################################################################## + ## slots below implement general functionality + ################################################################## + + def projectClosed(self): + """ + Public method to handle the closing of a project. + """ + if self.__serverProc is not None: + self.__serverProcFinished() + + # TODO: implement this correctly + def supportedPythonVariants(self): + """ + Public method to get the supported Python variants. + + @return list of supported Python variants + @rtype list of str + """ + variants = [] + + virtEnv = self.__getVirtualEnvironment() + if virtEnv: + fullCmd = self.getFlaskCommand() + if fullCmd: + variants.append("Python3") + else: + fullCmd = self.getFlaskCommand() + if isWindowsPlatform(): + if fullCmd: + variants.append("Python3") + else: + fullCmds = Utilities.getExecutablePaths("flask") + for fullCmd in fullCmds: + try: + with open(fullCmd, 'r', encoding='utf-8') as f: + l0 = f.readline() + except (IOError, OSError): + l0 = "" + if self.__isSuitableForVariant("Python3", l0): + variants.append("Python3") + break + + return variants + + def __isSuitableForVariant(self, variant, line0): + """ + Private method to test, if a detected command file is suitable for the + given Python variant. + + @param variant Python variant to test for + @type str + @param line0 first line of the executable + @type str + @return flag indicating a suitable file was found + @rtype bool + """ + l0 = line0.lower() + ok = (variant.lower() in l0 or + "{0}.".format(variant[-1]) in l0) + ok |= "pypy3" in l0 + + return ok + + def __getVirtualEnvironment(self): + """ + Private method to get the path of the virtual environment. + + @return path of the virtual environment + @rtype str + """ + language = self.__e5project.getProjectLanguage() + if language == "Python3": + venvName = self.__plugin.getPreferences( + "VirtualEnvironmentNamePy3") + else: + venvName = "" + if venvName: + virtEnv = self.__virtualEnvManager.getVirtualenvDirectory( + venvName) + else: + virtEnv = "" + + if virtEnv and not os.path.exists(virtEnv): + virtEnv = "" + + return virtEnv # __IGNORE_WARNING_M834__ + + def getFlaskCommand(self): + """ + Public method to build the Flask command. + + @return full flask command + @rtype str + """ + cmd = "flask" + + virtualEnv = self.__getVirtualEnvironment() + if isWindowsPlatform(): + fullCmds = [ + os.path.join(virtualEnv, "Scripts", cmd + '.exe'), + os.path.join(virtualEnv, "bin", cmd + '.exe'), + cmd # fall back to just cmd + ] + for cmd in fullCmds: + if os.path.exists(cmd): + break + else: + fullCmds = [ + os.path.join(virtualEnv, "bin", cmd), + os.path.join(virtualEnv, "local", "bin", cmd), + Utilities.getExecutablePath(cmd), + cmd # fall back to just cmd + ] + for cmd in fullCmds: + if os.path.exists(cmd): + break + return cmd + + @pyqtSlot() + def __flaskInfo(self): + """ + Private slot to show some info about Flask. + """ + versions = self.getFlaskVersionStrings() + url = "https://flask.palletsprojects.com" + + msgBox = E5MessageBox.E5MessageBox( + E5MessageBox.Question, + self.tr("About Flask"), + self.tr( + "<p>Flask is a lightweight WSGI web application framework." + " It is designed to make getting started quick and easy," + " with the ability to scale up to complex applications.</p>" + "<p><table>" + "<tr><td>Flask Version:</td><td>{0}</td></tr>" + "<tr><td>Werkzeug Version:</td><td>{1}</td></tr>" + "<tr><td>Python Version:</td><td>{2}</td></tr>" + "<tr><td>Flask URL:</td><td><a href=\"{3}\">" + "{3}</a></td></tr>" + "</table></p>" + ).format(versions["flask"], versions["werkzeug"], + versions["python"], url), + modal=True, + buttons=E5MessageBox.Ok) + msgBox.setIconPixmap(UI.PixmapCache.getPixmap( + os.path.join("ProjectFlask", "icons", + "flask64-{0}".format(self.__iconSuffix)))) + msgBox.exec() + + def getFlaskVersionStrings(self): + """ + Public method to get the Flask, Werkzeug and Python versions as a + string. + + @return dictionary containing the Flask, Werkzeug and Python versions + @rtype dict + """ + if not self.__flaskVersions["flask"]: + cmd = self.getFlaskCommand() + proc = QProcess() + proc.start(cmd, ["--version"]) + if proc.waitForFinished(10000): + output = str(proc.readAllStandardOutput(), "utf-8") + for line in output.lower().splitlines(): + key, version = line.strip().split(None, 1) + self.__flaskVersions[key] = version + + return self.__flaskVersions + + ################################################################## + ## slots below implement run functions + ################################################################## + + def __runServer(self, logging=False): + """ + Private slot to start the Pyramid Web server. + + @param logging flag indicating to enable logging + @type bool + """ + # TODO: implement this + + def __serverProcFinished(self): + """ + Private slot connected to the finished signal. + """ + if ( + self.__serverProc is not None and + self.__serverProc.state() != QProcess.NotRunning + ): + self.__serverProc.terminate() + QTimer.singleShot(2000, self.__serverProc.kill) + self.__serverProc.waitForFinished(3000) + self.__serverProc = None