ProjectFlask/Project.py

Wed, 21 Dec 2022 09:59:34 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 21 Dec 2022 09:59:34 +0100
branch
eric7
changeset 80
662e5eb1ba8b
parent 75
7a30d96ea9f6
child 82
bb14c648099b
permissions
-rw-r--r--

Adapted some import statements to eric 23.1 and newer.

# -*- coding: utf-8 -*-

# Copyright (c) 2020 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the Flask project support.
"""

import os

from PyQt6.QtCore import (
    QFileInfo,
    QObject,
    QProcess,
    QProcessEnvironment,
    QTimer,
    pyqtSlot,
)
from PyQt6.QtWidgets import QDialog, QMenu

from eric7 import Utilities
from eric7.EricGui.EricAction import EricAction
from eric7.EricWidgets import EricFileDialog, EricMessageBox
from eric7.EricWidgets.EricApplication import ericApp

try:
    from eric7.EricGui import EricPixmapCache
except ImportError:
    from UI import PixmapCache as EricPixmapCache

try:
    from eric7.SystemUtilities.FileSystemUtilities import (
        getExecutablePath,
        getExecutablePaths,
    )
except ImportError:
    # imports for eric < 23.1
    from eric7.Utilities import getExecutablePath, getExecutablePaths
try:
    from eric7.SystemUtilities.OSUtilities import isWindowsPlatform
except ImportError:
    # imports for eric < 23.1
    from eric7.Globals import isWindowsPlatform

from .FlaskBabelExtension.PyBabelProjectExtension import PyBabelProject
from .FlaskMigrateExtension.MigrateProjectExtension import MigrateProject


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().__init__(parent)

        self.__plugin = plugin
        self.__iconSuffix = iconSuffix
        self.__ui = parent

        self.__ericProject = ericApp().getObject("Project")
        self.__virtualEnvManager = ericApp().getObject("VirtualEnvManager")

        self.__menus = {}  # dictionary with references to menus
        self.__formsBrowser = None
        self.__hooksInstalled = False

        self.__serverDialog = None
        self.__routesDialog = None
        self.__shellProcess = None

        self.__projectData = {
            "flask": {},
            "flask-babel": {},
            "flask-migrate": {},
        }

        self.__flaskVersions = {
            "python": "",
            "flask": "",
            "werkzeug": "",
        }

        self.__capabilities = {}

        self.__pybabelProject = PyBabelProject(self.__plugin, self, self)
        self.__migrateProject = MigrateProject(self.__plugin, self, self)

    def initActions(self):
        """
        Public method to define the Flask actions.
        """
        self.actions = []

        ##############################
        ## run actions below        ##
        ##############################

        self.runServerAct = EricAction(
            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)

        self.runDevServerAct = EricAction(
            self.tr("Run Development Server"),
            self.tr("Run &Development Server"),
            0,
            0,
            self,
            "flask_run_dev_server",
        )
        self.runDevServerAct.setStatusTip(
            self.tr("Starts the Flask Web server in development mode")
        )
        self.runDevServerAct.setWhatsThis(
            self.tr(
                """<b>Run Development Server</b>"""
                """<p>Starts the Flask Web server in development mode.</p>"""
            )
        )
        self.runDevServerAct.triggered.connect(self.__runDevelopmentServer)
        self.actions.append(self.runDevServerAct)

        self.askForServerOptionsAct = EricAction(
            self.tr("Ask for Server Start Options"),
            self.tr("Ask for Server Start Options"),
            0,
            0,
            self,
            "flask_ask_server_options",
        )
        self.askForServerOptionsAct.setStatusTip(
            self.tr("Ask for server start options")
        )
        self.askForServerOptionsAct.setWhatsThis(
            self.tr(
                """<b>Ask for Server Start Options</b>"""
                """<p>Asks for server start options before the Flask Web server"""
                """ is started. If this is unchecked, the server is started with"""
                """ default parameters.</p>"""
            )
        )
        self.askForServerOptionsAct.setCheckable(True)
        self.actions.append(self.askForServerOptionsAct)

        ###############################
        ## shell action below        ##
        ###############################

        self.runPythonShellAct = EricAction(
            self.tr("Start Flask Python Console"),
            self.tr("Start Flask &Python Console"),
            0,
            0,
            self,
            "flask_python_console",
        )
        self.runPythonShellAct.setStatusTip(
            self.tr("Starts an interactive Python interpreter")
        )
        self.runPythonShellAct.setWhatsThis(
            self.tr(
                """<b>Start Flask Python Console</b>"""
                """<p>Starts an interactive Python interpreter.</p>"""
            )
        )
        self.runPythonShellAct.triggered.connect(self.__runPythonShell)
        self.actions.append(self.runPythonShellAct)

        ################################
        ## routes action below        ##
        ################################

        self.showRoutesAct = EricAction(
            self.tr("Show Routes"),
            self.tr("Show &Routes"),
            0,
            0,
            self,
            "flask_show_routes",
        )
        self.showRoutesAct.setStatusTip(
            self.tr("Shows a dialog with the routes of the flask app")
        )
        self.showRoutesAct.setWhatsThis(
            self.tr(
                """<b>Show Routes</b>"""
                """<p>Shows a dialog with the routes of the flask app.</p>"""
            )
        )
        self.showRoutesAct.triggered.connect(self.__showRoutes)
        self.actions.append(self.showRoutesAct)

        ##################################
        ## documentation action below   ##
        ##################################

        self.documentationAct = EricAction(
            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 = EricAction(
            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)

        self.__pybabelProject.initActions()
        self.__migrateProject.initActions()

        ######################################
        ## configuration action below       ##
        ######################################

        self.flaskConfigAct = EricAction(
            self.tr("Configure Flask for Project"),
            self.tr("Configure Flask for &Project"),
            0,
            0,
            self,
            "flask_config_for_project",
        )
        self.flaskConfigAct.setStatusTip(
            self.tr("Shows a dialog to edit the project specific flask configuration")
        )
        self.flaskConfigAct.setWhatsThis(
            self.tr(
                """<b>Configure Flask for Project</b>"""
                """<p>Shows a dialog to edit the project specific flask"""
                """ configuration.</p>"""
            )
        )
        self.flaskConfigAct.triggered.connect(self.__configureFlaskForProject)
        self.actions.append(self.flaskConfigAct)

    def initMenu(self):
        """
        Public method to initialize the Flask menu.

        @return the menu generated
        @rtype QMenu
        """
        self.__menus = {}  # clear menus references

        self.__menus["flask-babel"] = self.__pybabelProject.initMenu()
        self.__menus["flask-migrate"] = self.__migrateProject.initMenu()

        menu = QMenu(self.tr("&Flask"), self.__ui)
        menu.setTearOffEnabled(True)

        menu.addAction(self.flaskConfigAct)
        menu.addSeparator()
        menu.addAction(self.runServerAct)
        menu.addAction(self.runDevServerAct)
        menu.addAction(self.askForServerOptionsAct)
        menu.addSeparator()
        menu.addAction(self.runPythonShellAct)
        menu.addSeparator()
        menu.addAction(self.showRoutesAct)
        menu.addSeparator()
        menu.addMenu(self.__menus["flask-migrate"])
        menu.addSeparator()
        menu.addMenu(self.__menus["flask-babel"])
        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())

    def projectOpenedHooks(self):
        """
        Public method to add our hook methods.
        """
        if self.__ericProject.getProjectType() == "Flask":
            self.__formsBrowser = (
                ericApp().getObject("ProjectBrowser").getProjectBrowser("forms")
            )
            self.__formsBrowser.addHookMethodAndMenuEntry(
                "newForm", self.newForm, self.tr("New template...")
            )

            self.__determineCapabilities()
            self.__setDebugEnvironment()

            self.__pybabelProject.projectOpenedHooks()

            self.__hooksInstalled = True

    def projectClosedHooks(self):
        """
        Public method to remove our hook methods.
        """
        self.__pybabelProject.projectClosedHooks()

        if self.__hooksInstalled:
            self.__formsBrowser.removeHookMethod("newForm")
            self.__formsBrowser = None

        self.__hooksInstalled = False

    def newForm(self, dirPath):
        """
        Public method to create a new form.

        @param dirPath full directory path for the new form file
        @type str
        """
        from .FormSelectionDialog import FormSelectionDialog

        dlg = FormSelectionDialog()
        if dlg.exec() == QDialog.DialogCode.Accepted:
            template = dlg.getTemplateText()

            fileFilters = self.tr(
                "HTML Files (*.html);;" "HTML Files (*.htm);;" "All Files (*)"
            )
            fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
                self.__ui,
                self.tr("New Form"),
                dirPath,
                fileFilters,
                None,
                EricFileDialog.Options(EricFileDialog.DontConfirmOverwrite),
            )
            if fname:
                ext = QFileInfo(fname).suffix()
                if not ext:
                    ex = selectedFilter.split("(*")[1].split(")")[0]
                    if ex:
                        fname += ex

                if os.path.exists(fname):
                    res = EricMessageBox.yesNo(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr("""The file already exists! Overwrite""" """ it?"""),
                        icon=EricMessageBox.Warning,
                    )
                    if not res:
                        # user selected to not overwrite
                        return

                try:
                    with open(fname, "w", encoding="utf-8") as f:
                        f.write(template)
                except OSError as err:
                    EricMessageBox.critical(
                        self.__ui,
                        self.tr("New Form"),
                        self.tr(
                            "<p>The new form file <b>{0}</b> could"
                            " not be created.</p><p>Problem: {1}</p>"
                        ).format(fname, str(err)),
                    )
                    return

                self.__ericProject.appendFile(fname)
                self.__formsBrowser.sourceFile.emit(fname)

    ##################################################################
    ## methods below implement virtual environment handling
    ##################################################################

    def getVirtualEnvironment(self):
        """
        Public method to get the path of the virtual environment.

        @return path of the virtual environment
        @rtype str
        """
        language = self.__ericProject.getProjectLanguage()
        if language == "Python3":
            # get project specific virtual environment name
            venvName = self.getData("flask", "virtual_environment_name")
            if not venvName:
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
        else:
            venvName = ""
        virtEnv = (
            self.__virtualEnvManager.getVirtualenvDirectory(venvName)
            if venvName
            else ""
        )

        if virtEnv and not os.path.exists(virtEnv):
            virtEnv = ""

        return virtEnv  # __IGNORE_WARNING_M834__

    def getVirtualenvInterpreter(self):
        """
        Public method to get the path of the Python interpreter to be used
        with the current project.

        @return path of the Python interpreter
        @rtype str
        """
        return self.getFullCommand("python")

    def getFullCommand(self, command, virtualEnvPath=None):
        """
        Public method to get the full command for a given command name.

        @param command command name
        @type str
        @param virtualEnvPath path of the virtual environment
        @type str
        @return full command
        @rtype str
        """
        virtualEnv = virtualEnvPath or self.getVirtualEnvironment()
        fullCmds = (
            [
                os.path.join(virtualEnv, "Scripts", command + ".exe"),
                os.path.join(virtualEnv, "bin", command + ".exe"),
                command,
            ]  # fall back to just cmd
            if isWindowsPlatform()
            else [
                os.path.join(virtualEnv, "bin", command),
                os.path.join(virtualEnv, "local", "bin", command),
                getExecutablePath(command),
                command,
            ]  # fall back to just cmd
        )
        for command in fullCmds:
            if os.path.exists(command):
                break
        return command

    ##################################################################
    ## methods below implement general functionality
    ##################################################################

    def projectClosed(self):
        """
        Public method to handle the closing of a project.
        """
        for dlg in (self.__serverDialog, self.__routesDialog):
            if dlg is not None:
                dlg.close()

        self.__migrateProject.projectClosed()

    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 = getExecutablePaths("flask")
                for fullCmd in fullCmds:
                    try:
                        with open(fullCmd, "r", encoding="utf-8") as f:
                            l0 = f.readline()
                    except 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 getFlaskCommand(self):
        """
        Public method to build the Flask command.

        @return full flask command
        @rtype str
        """
        return self.getFullCommand("flask")

    @pyqtSlot()
    def __flaskInfo(self):
        """
        Private slot to show some info about Flask.
        """
        versions = self.getFlaskVersionStrings()
        url = "https://palletsprojects.com/p/flask/"

        msgBox = EricMessageBox.EricMessageBox(
            EricMessageBox.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>",
                "Do not translate the program names.",
            ).format(versions["flask"], versions["werkzeug"], versions["python"], url),
            modal=True,
            buttons=EricMessageBox.Ok,
        )
        msgBox.setIconPixmap(
            EricPixmapCache.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)
        """
        workdir, app = self.getApplication()
        env = QProcessEnvironment.systemEnvironment()
        env.insert("FLASK_APP", app)
        if development:
            env.insert("FLASK_ENV", "development")

        return workdir, env

    def getApplication(self):
        """
        Public method to determine the application name and the respective
        working directory.

        @return tuple containing the working directory and the application name
        @rtype tuple of (str, str)
        """
        mainScript = self.__ericProject.getMainScript(normalized=True)
        if not mainScript:
            EricMessageBox.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
        return workdir, app

    def getData(self, category, key):
        """
        Public method to get data stored in the project store.

        @param category data category
        @type str
        @param key data key
        @type str
        @return referenced data
        @rtype any
        """
        if category not in self.__projectData:
            self.__projectData[category] = {}

        if not self.__projectData[category]:
            data = self.__ericProject.getData("PROJECTTYPESPECIFICDATA", category)
            if data is not None:
                self.__projectData[category] = data

        data = self.__projectData[category]
        if not key:
            # return complete category dictionary
            return data
        elif key in data:
            # return individual entry
            return data[key]
        else:
            # failure
            return None

    def setData(self, category, key, value):
        """
        Public method to store data in the project store.

        @param category data category
        @type str
        @param key data key
        @type str
        @param value data to be stored
        @type any (serializable type)
        """
        if category not in self.__projectData:
            self.__projectData[category] = {}

        if not self.__projectData[category]:
            data = self.__ericProject.getData("PROJECTTYPESPECIFICDATA", category)
            if data is not None:
                self.__projectData[category] = data

        if not key:
            # update the complete category
            self.__projectData[category] = value
        else:
            # update individual entry
            self.__projectData[category][key] = value

        self.__ericProject.setData(
            "PROJECTTYPESPECIFICDATA", category, self.__projectData[category]
        )

    def __determineCapabilities(self):
        """
        Private method to determine capabilities provided by supported
        extensions.
        """
        # 1. support for flask-babel (i.e. pybabel)
        self.__pybabelProject.determineCapability()

        # 2. support for flask-migrate
        self.__migrateProject.determineCapability()

    def hasCapability(self, key):
        """
        Public method to check, if a capability is available.

        @param key key of the capability to check
        @type str
        @return flag indicating the availability of the capability
        @rtype bool
        """
        try:
            return self.__capabilities[key]
        except KeyError:
            return False

    def setCapability(self, key, available):
        """
        Public method to set the availability status of a capability.

        @param key key of the capability to set
        @type str
        @param available flag indicating the availability of the capability
        @type bool
        """
        self.__capabilities[key] = available

    ##################################################################
    ## slots below implements project specific flask configuration
    ##################################################################

    @pyqtSlot()
    def __configureFlaskForProject(self):
        """
        Private slot to configure the project specific flask parameters.
        """
        from .FlaskConfigDialog import FlaskConfigDialog

        config = self.getData("flask", "")
        dlg = FlaskConfigDialog(config, self)
        if dlg.exec() == QDialog.DialogCode.Accepted:
            config = dlg.getConfiguration()
            self.setData("flask", "", config)
            self.__setIgnoreVirtualEnvironment()
            self.__setDebugEnvironment()

            self.__migrateProject.determineCapability()

            self.__pybabelProject.determineCapability()
            self.projectClosedHooks()
            self.projectOpenedHooks()

    def __setIgnoreVirtualEnvironment(self):
        """
        Private method to add an embedded project specific virtual environment
        to the list of ignore files/directories.
        """
        virtenvName = self.getData("flask", "virtual_environment_name")
        if virtenvName:
            virtenvPath = self.getVirtualEnvironment()
            if self.__ericProject.startswithProjectPath(virtenvPath):
                relVirtenvPath = self.__ericProject.getRelativeUniversalPath(
                    virtenvPath
                )
                try:
                    # code path for eric 22.12 and above
                    fileTypes = self.__ericProject.getProjectData(dataKey="FILETYPES")
                    fileTypes[relVirtenvPath] = "__IGNORE__"
                    self.__ericProject.setProjectData(fileTypes, dataKey="FILETYPES")
                except AttributeError:
                    # older versions access pdata directly
                    if relVirtenvPath not in self.__ericProject.pdata["FILETYPES"]:
                        self.__ericProject.pdata["FILETYPES"][
                            relVirtenvPath
                        ] = "__IGNORE__"
                        self.__ericProject.setDirty(True)

    def __setDebugEnvironment(self):
        """
        Private method to set the virtual environment as the selected debug
        environment.
        """
        language = self.__ericProject.getProjectLanguage()
        if language == "Python3":
            # get project specific virtual environment name
            venvName = self.getData("flask", "virtual_environment_name")
            if not venvName:
                venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
            if venvName:
                self.__ericProject.debugProperties["VIRTUALENV"] = venvName

    ##################################################################
    ## slot below implements documentation function
    ##################################################################

    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 for the server
    ##################################################################

    @pyqtSlot()
    def __runServer(self, development=False):
        """
        Private slot to start the Flask Web server.

        @param development flag indicating development mode
        @type bool
        """
        from .RunServerDialog import RunServerDialog

        if self.__serverDialog is not None:
            self.__serverDialog.close()

        askForOptions = self.askForServerOptionsAct.isChecked()
        dlg = RunServerDialog(self.__plugin, self)
        if dlg.startServer(development=development, askForOptions=askForOptions):
            dlg.show()
            self.__serverDialog = dlg

    @pyqtSlot()
    def __runDevelopmentServer(self):
        """
        Private slot to start the Flask Web server in development mode.
        """
        self.__runServer(development=True)

    ##################################################################
    ## slots below implement functions for the flask console
    ##################################################################

    @pyqtSlot()
    def __runPythonShell(self):
        """
        Private slot to start a Python console in the app context.
        """
        workdir, env = self.prepareRuntimeEnvironment()
        if env is not None:
            command = self.getFlaskCommand()

            consoleCmd = self.__plugin.getPreferences("ConsoleCommand")
            if consoleCmd:
                self.__terminatePythonShell()

                args = Utilities.parseOptionString(consoleCmd)
                args[0] = getExecutablePath(args[0])
                args += [command, "shell"]

                self.__shellProcess = QProcess()
                self.__shellProcess.setProcessEnvironment(env)
                self.__shellProcess.setWorkingDirectory(workdir)
                self.__shellProcess.finished.connect(self.__shellProcessFinished)

                self.__shellProcess.start(args[0], args[1:])
                self.__shellProcess.waitForStarted(10000)

    @pyqtSlot()
    def __shellProcessFinished(self):
        """
        Private slot connected to the finished signal.
        """
        self.__shellProcess = None

    def __terminatePythonShell(self):
        """
        Private method to terminate the current Python console.
        """
        if (
            self.__shellProcess is not None
            and self.__shellProcess.state() != QProcess.ProcessState.NotRunning
        ):
            self.__shellProcess.terminate()
            QTimer.singleShot(2000, self.__shellProcess.kill)
            self.__shellProcess.waitForFinished(3000)

    ##################################################################
    ## slots below implement various debugging functions
    ##################################################################

    @pyqtSlot()
    def __showRoutes(self):
        """
        Private slot showing all URL dispatch routes.
        """
        from .RoutesDialog import RoutesDialog

        if self.__routesDialog is not None:
            self.__routesDialog.close()

        dlg = RoutesDialog(self)
        if dlg.showRoutes():
            dlg.show()
            self.__routesDialog = dlg

eric ide

mercurial