Mon, 09 Nov 2020 20:00:56 +0100
Started implementing the "Run Server" function.
# -*- 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, QProcessEnvironment, 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 from .RunServerDialog import RunServerDialog 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.__serverDialog = None self.__flaskVersions = { "python": "", "flask": "", "werkzeug": "", } def initActions(self): """ Public method to define the Flask actions. """ self.actions = [] ############################## ## run actions below ## ############################## self.runServerAct = E5Action( self.tr('Run Server'), self.tr('Run &Server'), 0, 0, self, 'flask_run_server') self.runServerAct.setStatusTip(self.tr( 'Starts the Flask Web server')) self.runServerAct.setWhatsThis(self.tr( """<b>Run Server</b>""" """<p>Starts the Flask Web server.</p>""" )) self.runServerAct.triggered.connect(self.__runServer) self.actions.append(self.runServerAct) ################################## ## documentation action below ## ################################## self.documentationAct = E5Action( self.tr('Documentation'), self.tr('D&ocumentation'), 0, 0, self, 'flask_documentation') self.documentationAct.setStatusTip(self.tr( 'Shows the help viewer with the Flask documentation')) self.documentationAct.setWhatsThis(self.tr( """<b>Documentation</b>""" """<p>Shows the help viewer with the Flask documentation.</p>""" )) self.documentationAct.triggered.connect(self.__showDocumentation) self.actions.append(self.documentationAct) ############################## ## 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.runServerAct) menu.addSeparator() menu.addAction(self.documentationAct) menu.addSeparator() 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() 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://palletsprojects.com/p/flask/" 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}\">" "The Pallets Projects - Flask</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 def prepareRuntimeEnvironment(self, development=False): """ Public method to prepare a QProcessEnvironment object and determine the appropriate working directory. @param development flag indicating development mode @type bool @return tuple containing the working directory and a prepared environment object to be used with QProcess @rtype tuple of (str, QProcessEnvironment) """ mainScript = self.__e5project.getMainScript(normalized=True) if not mainScript: E5MessageBox.critical( self.__ui, self.tr("Prepare Environment"), self.tr("""The project has no configured main script""" """ (= Flask application). Aborting...""")) return "", None scriptPath, scriptName = os.path.split(mainScript) if scriptName == "__init__.py": workdir, app = os.path.split(scriptPath) else: workdir, app = scriptPath, scriptName env = QProcessEnvironment.systemEnvironment() env.insert("FLASK_APP", app) if development: env.insert("FLASK_ENV", "development") return workdir, env ################################################################## ## slots below implement documentation functions ################################################################## def __showDocumentation(self): """ Private slot to show the helpviewer with the Flask documentation. """ page = self.__plugin.getPreferences("FlaskDocUrl") self.__ui.launchHelpViewer(page) ################################################################## ## slots below implement run functions ################################################################## # TODO: does the flask server support logging? def __runServer(self, logging=False): """ Private slot to start the Flask Web server. @param logging flag indicating to enable logging @type bool """ # TODO: implement this (flask run) workdir, env = self.prepareRuntimeEnvironment() if env is not None: cmd = self.getFlaskCommand() dlg = RunServerDialog() if dlg.startServer(cmd, workdir, env): dlg.show() self.__serverDialog = dlg def __runDevelopmentServer(self, logging=False): """ Private slot to start the Flask Web server in development mode. @param logging flag indicating to enable logging @type bool """ # TODO: implement this (flask run with FLASK_ENV=development) ## 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 def __runPythonShell(self): """ Private slot to start a Python console in the app context. """ # TODO: implement this (flask shell) ################################################################## ## slots below implement various debugging functions ################################################################## def __showRoutes(self): """ Private slot showing all URL dispatch routes. """ # TODO: implement this (flask routes)