Sat, 23 Dec 2023 15:48:53 +0100
Updated copyright for 2024.
# -*- coding: utf-8 -*- # Copyright (c) 2012 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the Pyramid project support. """ import configparser import contextlib import glob import os import re import subprocess # secok import sys from PyQt6.QtCore import ( QFileInfo, QIODeviceBase, QObject, QProcess as QProcessPyQt, QTimer, QUrl, pyqtSlot, ) from PyQt6.QtGui import QDesktopServices from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu from eric7 import Preferences, Utilities from eric7.EricGui.EricAction import EricAction from eric7.EricWidgets import EricFileDialog, EricMessageBox from eric7.EricWidgets.EricApplication import ericApp 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 try: from eric7.EricGui import EricPixmapCache except ImportError: from UI import PixmapCache as EricPixmapCache from .PyramidDialog import PyramidDialog class PyramidNoProjectSelectedError(Exception): """ Exception thrown to signal, that there is no current Pyramid project. """ pass class QProcess(QProcessPyQt): """ Class transforming the call arguments in case of gnome-terminal. """ def start(self, cmd, args=None, mode=QIODeviceBase.OpenModeFlag.ReadWrite): """ Public method to start the given program (cmd) in a new process, if none is already running, passing the command line arguments in args. @param cmd start the given program cmd @type str @param args list of parameters @type list of str @param mode access mode @type QIODeviceBase.OpenMode """ if args is None: args = [] if ( cmd.endswith( ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal") ) and "-e" in args ): index = args.index("-e") + 1 cargs = " ".join(args[index:]) args[index:] = [cargs] super().start(cmd, args, mode) @staticmethod def startDetached(cmd, args=None, path=""): """ Public static method to start the given program (cmd) in a new process, if none is already running, passing the command line arguments in args. @param cmd start the given program cmd @type str @param args list of parameters @type list of str @param path new working directory @type str @return tuple of successful start and process id @rtype tuple of (bool, int) """ if args is None: args = [] if ( cmd.endswith( ("gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal") ) and "-e" in args ): index = args.index("-e") + 1 cargs = " ".join(args[index:]) args[index:] = [cargs] return QProcessPyQt.startDetached(cmd, args, path) class Project(QObject): """ Class implementing the Pyramid project support. """ def __init__(self, plugin, iconSuffix, parent=None): """ Constructor @param plugin reference to the plugin object @type ProjectPyramidPlugin @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.__hooksInstalled = False self.__menus = {} # dictionary with references to menus self.__serverProc = None self.__pyramidVersion = "" self.__migrationSummaryDialog = None def initActions(self): """ Public method to define the Pyramid actions. """ self.actions = [] self.selectProjectAct = EricAction( self.tr("Current Pyramid Project"), "", 0, 0, self, "pyramid_current_project", ) self.selectProjectAct.setStatusTip( self.tr("Selects the current Pyramid project") ) self.selectProjectAct.setWhatsThis( self.tr( """<b>Current Pyramid Project</b>""" """<p>Selects the Pyramid project. Used for multi-project """ """Pyramid projects to switch between the projects.</p>""" ) ) self.selectProjectAct.triggered.connect(self.__selectProject) self.actions.append(self.selectProjectAct) ############################### ## create actions below ## ############################### self.createProjectAct = EricAction( self.tr("Create Pyramid Project"), self.tr("Create Pyramid &Project"), 0, 0, self, "pyramid_create_project", ) self.createProjectAct.setStatusTip(self.tr("Creates a new Pyramid project")) self.createProjectAct.setWhatsThis( self.tr( """<b>Create Pyramid Project</b>""" """<p>Creates a new Pyramid project using "pcreate".</p>""" ) ) self.createProjectAct.triggered.connect(self.__createProject) self.actions.append(self.createProjectAct) ############################## ## run actions below ## ############################## self.runServerAct = EricAction( self.tr("Run Server"), self.tr("Run &Server"), 0, 0, self, "pyramid_run_server", ) self.runServerAct.setStatusTip(self.tr("Starts the Pyramid Web server")) self.runServerAct.setWhatsThis( self.tr( """<b>Run Server</b>""" """<p>Starts the Pyramid Web server using""" """ "pserve --reload development.ini".</p>""" ) ) self.runServerAct.triggered.connect(self.__runServer) self.actions.append(self.runServerAct) self.runBrowserAct = EricAction( self.tr("Run Web-Browser"), self.tr("Run &Web-Browser"), 0, 0, self, "pyramid_run_browser", ) self.runBrowserAct.setStatusTip( self.tr( "Starts the default Web-Browser with the URL of the Pyramid Web" " server" ) ) self.runBrowserAct.setWhatsThis( self.tr( """<b>Run Web-Browser</b>""" """<p>Starts the default Web-Browser with the URL of the """ """Pyramid Web server.</p>""" ) ) self.runBrowserAct.triggered.connect(self.__runBrowser) self.actions.append(self.runBrowserAct) self.runPythonShellAct = EricAction( self.tr("Start Pyramid Python Console"), self.tr("Start Pyramid &Python Console"), 0, 0, self, "pyramid_python_console", ) self.runPythonShellAct.setStatusTip( self.tr("Starts an interactive Python interpreter") ) self.runPythonShellAct.setWhatsThis( self.tr( """<b>Start Pyramid Python Console</b>""" """<p>Starts an interactive Python interpreter.</p>""" ) ) self.runPythonShellAct.triggered.connect(self.__runPythonShell) self.actions.append(self.runPythonShellAct) ############################### ## show actions below ## ############################### self.showViewsAct = EricAction( self.tr("Show Matching Views"), self.tr("Show Matching &Views"), 0, 0, self, "pyramid_show_views", ) self.showViewsAct.setStatusTip(self.tr("Show views matching a given URL")) self.showViewsAct.setWhatsThis( self.tr( """<b>Show Matching Views</b>""" """<p>Show views matching a given URL.</p>""" ) ) self.showViewsAct.triggered.connect(self.__showMatchingViews) self.actions.append(self.showViewsAct) self.showRoutesAct = EricAction( self.tr("Show Routes"), self.tr("Show &Routes"), 0, 0, self, "pyramid_show_routes", ) self.showRoutesAct.setStatusTip( self.tr("Show all URL dispatch routes used by a Pyramid application") ) self.showRoutesAct.setWhatsThis( self.tr( """<b>Show Routes</b>""" """<p>Show all URL dispatch routes used by a Pyramid application""" """ in the order in which they are evaluated.</p>""" ) ) self.showRoutesAct.triggered.connect(self.__showRoutes) self.actions.append(self.showRoutesAct) self.showTweensAct = EricAction( self.tr("Show Tween Objects"), self.tr("Show &Tween Objects"), 0, 0, self, "pyramid_show_routes", ) self.showTweensAct.setStatusTip( self.tr( "Show all implicit and explicit tween objects used by a Pyramid" " application" ) ) self.showTweensAct.setWhatsThis( self.tr( """<b>Show Tween Objects</b>""" """<p>Show all implicit and explicit tween objects used by a""" """ Pyramid application.</p>""" ) ) self.showTweensAct.triggered.connect(self.__showTweens) self.actions.append(self.showTweensAct) ################################## ## distribution actions below ## ################################## self.buildDistroAct = EricAction( self.tr("Build Distribution"), self.tr("Build &Distribution"), 0, 0, self, "pyramid_build_distribution", ) self.buildDistroAct.setStatusTip( self.tr("Builds a distribution file for the Pyramid project") ) self.buildDistroAct.setWhatsThis( self.tr( """<b>Build Distribution</b>""" """<p>Builds a distribution file for the Pyramid project using""" """ "python setup.py sdist".</p>""" ) ) self.buildDistroAct.triggered.connect(self.__buildDistribution) self.actions.append(self.buildDistroAct) ################################## ## documentation action below ## ################################## self.documentationAct = EricAction( self.tr("Documentation"), self.tr("D&ocumentation"), 0, 0, self, "pyramid_documentation", ) self.documentationAct.setStatusTip( self.tr("Shows the help viewer with the Pyramid documentation") ) self.documentationAct.setWhatsThis( self.tr( """<b>Documentation</b>""" """<p>Shows the help viewer with the Pyramid documentation.</p>""" ) ) self.documentationAct.triggered.connect(self.__showDocumentation) self.actions.append(self.documentationAct) ############################## ## about action below ## ############################## self.aboutPyramidAct = EricAction( self.tr("About Pyramid"), self.tr("About P&yramid"), 0, 0, self, "pyramid_about", ) self.aboutPyramidAct.setStatusTip( self.tr("Shows some information about Pyramid") ) self.aboutPyramidAct.setWhatsThis( self.tr( """<b>About Pyramid</b>""" """<p>Shows some information about Pyramid.</p>""" ) ) self.aboutPyramidAct.triggered.connect(self.__pyramidInfo) self.actions.append(self.aboutPyramidAct) self.__initDatabaseActions() self.__setCurrentProject(None) def __initDatabaseActions(self): """ Private method to initialize the database related actions. """ self.initializeDbAct = EricAction( self.tr("Initialize Database"), self.tr("Initialize &Database"), 0, 0, self, "pyramid_initialize_database", ) self.initializeDbAct.setStatusTip( self.tr( "Initializes (or re-initializes) the database of the current" " Pyramid project" ) ) self.initializeDbAct.setWhatsThis( self.tr( """<b>Initialize Database</b>""" """<p>Initializes (or re-initializes) the database of the""" """ current Pyramid project.</p>""" ) ) self.initializeDbAct.triggered.connect(self.__initializeDatabase) self.actions.append(self.initializeDbAct) ######################################################### ## action to create a new database migration ######################################################### self.migrateCreateAct = EricAction( self.tr("Create Migration"), self.tr("&Create Migration"), 0, 0, self, "flask_create_migration", ) self.migrateCreateAct.setStatusTip( self.tr("Create a new migration for the current database") ) self.migrateCreateAct.setWhatsThis( self.tr( """<b>Create Migration</b>""" """<p>Creates a new migration for the current database""" """ and stores it in the configured migrations directory.</p>""" ) ) self.migrateCreateAct.triggered.connect(self.__createMigration) self.actions.append(self.migrateCreateAct) ######################################################### ## action to up- and downgrade a databse ######################################################### self.upgradeDatabaseAct = EricAction( self.tr("Upgrade Database"), self.tr("&Upgrade Database"), 0, 0, self, "flask_upgrade_database", ) self.upgradeDatabaseAct.setStatusTip( self.tr("Upgrade the database to the current migration") ) self.upgradeDatabaseAct.setWhatsThis( self.tr( """<b>Upgrade Database</b>""" """<p>Upgrades the database to the current migration.</p>""" ) ) self.upgradeDatabaseAct.triggered.connect(self.upgradeDatabase) self.actions.append(self.upgradeDatabaseAct) self.downgradeDatabaseAct = EricAction( self.tr("Downgrade Database"), self.tr("&Downgrade Database"), 0, 0, self, "flask_downgrade_database", ) self.downgradeDatabaseAct.setStatusTip( self.tr("Downgrade the database to the previous version") ) self.downgradeDatabaseAct.setWhatsThis( self.tr( """<b>Downgrade Database</b>""" """<p>Downgrades the database to the previous version.</p>""" ) ) self.downgradeDatabaseAct.triggered.connect(self.downgradeDatabase) self.actions.append(self.downgradeDatabaseAct) ######################################################### ## actions to show migrations history information ######################################################### self.migrationSummaryAct = EricAction( self.tr("Show Migrations Summary"), self.tr("Show Migrations &Summary"), 0, 0, self, "flask_show_migrations_summary", ) self.migrationSummaryAct.setStatusTip( self.tr("Show a summary of the created database migrations") ) self.migrationSummaryAct.setWhatsThis( self.tr( """<b>Show Migrations Summary</b>""" """<p>Shows a summary list of the created database""" """ migrations.</p>""" ) ) self.migrationSummaryAct.triggered.connect(self.__showMigrationsSummary) self.actions.append(self.migrationSummaryAct) self.migrationHistoryAct = EricAction( self.tr("Show Migrations History"), self.tr("Show Migrations &History"), 0, 0, self, "flask_show_migrations_history", ) self.migrationHistoryAct.setStatusTip( self.tr("Show the full history of the created database migrations") ) self.migrationHistoryAct.setWhatsThis( self.tr( """<b>Show Migrations History</b>""" """<p>Shows the full history of the created database""" """ migrations.</p>""" ) ) self.migrationHistoryAct.triggered.connect(self.__showMigrationsHistory) self.actions.append(self.migrationHistoryAct) def initMenu(self): """ Public slot to initialize the Pyramid menu. @return the menu generated @rtype QMenu """ self.__menus = {} # clear menus references # Database menu dbMenu = QMenu(self.tr("Database")) dbMenu.setTearOffEnabled(True) dbMenu.addAction(self.initializeDbAct) dbMenu.addSeparator() dbMenu.addAction(self.migrateCreateAct) dbMenu.addSeparator() dbMenu.addAction(self.upgradeDatabaseAct) dbMenu.addAction(self.downgradeDatabaseAct) dbMenu.addSeparator() dbMenu.addAction(self.migrationSummaryAct) dbMenu.addAction(self.migrationHistoryAct) # main Pyramid menu menu = QMenu(self.tr("P&yramid"), self.__ui) menu.setTearOffEnabled(True) menu.addAction(self.selectProjectAct) menu.addSeparator() menu.addAction(self.runServerAct) menu.addAction(self.runBrowserAct) menu.addSeparator() menu.addAction(self.runPythonShellAct) menu.addSeparator() menu.addAction(self.createProjectAct) menu.addSeparator() menu.addMenu(dbMenu) menu.addSeparator() menu.addAction(self.showViewsAct) menu.addAction(self.showRoutesAct) menu.addAction(self.showTweensAct) menu.addSeparator() menu.addAction(self.buildDistroAct) menu.addSeparator() menu.addAction(self.documentationAct) menu.addSeparator() menu.addAction(self.aboutPyramidAct) self.__menus["main"] = menu self.__menus["database"] = dbMenu self.__setCurrentProject(None) 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 """ 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 registerOpenHook(self): """ Public method to register the open hook to open a translations file in a translations editor. """ if self.__hooksInstalled: editor = self.__plugin.getPreferences("TranslationsEditor") if editor: self.__translationsBrowser.addHookMethodAndMenuEntry( "open", self.openPOEditor, self.tr("Open with {0}").format(os.path.basename(editor)), ) else: self.__translationsBrowser.removeHookMethod("open") def projectOpenedHooks(self): """ Public method to add our hook methods. """ if self.__ericProject.getProjectType() == "Pyramid": self.__formsBrowser = ( ericApp().getObject("ProjectBrowser").getProjectBrowser("forms") ) self.__formsBrowser.addHookMethodAndMenuEntry( "newForm", self.newForm, self.tr("New template...") ) self.__ericProject.projectLanguageAddedByCode.connect( self.__projectLanguageAdded ) self.__translationsBrowser = ( ericApp().getObject("ProjectBrowser").getProjectBrowser("translations") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "extractMessages", self.extractMessages, self.tr("Extract Messages") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "releaseAll", self.compileCatalogs, self.tr("Compile All Catalogs") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "releaseSelected", self.compileSelectedCatalogs, self.tr("Compile Selected Catalogs"), ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateAll", self.updateCatalogs, self.tr("Update All Catalogs") ) self.__translationsBrowser.addHookMethodAndMenuEntry( "generateSelected", self.updateSelectedCatalogs, self.tr("Update Selected Catalogs"), ) self.__hooksInstalled = True self.registerOpenHook() def projectClosedHooks(self): """ Public method to remove our hook methods. """ if self.__hooksInstalled: self.__formsBrowser.removeHookMethod("newForm") self.__formsBrowser = None self.__ericProject.projectLanguageAddedByCode.disconnect( self.__projectLanguageAdded ) self.__translationsBrowser.removeHookMethod("extractMessages") self.__translationsBrowser.removeHookMethod("releaseAll") self.__translationsBrowser.removeHookMethod("releaseSelected") self.__translationsBrowser.removeHookMethod("generateAll") self.__translationsBrowser.removeHookMethod("generateSelected") self.__translationsBrowser.removeHookMethod("open") self.__translationsBrowser = None self.__hooksInstalled = False def newForm(self, path): """ Public method to create a new form. @param path 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( "Chameleon Templates (*.pt);;" "Chameleon Text Templates (*.txt);;" "Mako Templates (*.mako);;" "Mako Templates (*.mak);;" "HTML Files (*.html);;" "HTML Files (*.htm);;" "All Files (*)" ) fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( self.__ui, self.tr("New Form"), path, fileFilters, None, 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 e: EricMessageBox.critical( self.__ui, self.tr("New Form"), self.tr( "<p>The new form file <b>{0}</b> could" " not be created.<br/> Problem: {1}</p>" ).format(fname, e), ) return self.__ericProject.appendFile(fname) self.__formsBrowser.sourceFile.emit(fname) ################################################################## ## methods below implement general functionality ################################################################## def projectClosed(self): """ Public method to handle the closing of a project. """ if self.__serverProc is not None: self.__serverProcFinished() self.__setCurrentProject(None) for dlg in (self.__migrationSummaryDialog,): if dlg is not None: dlg.close() def __getExecutablePaths(self, file): """ Private method to build all full paths of an executable file from the environment. @param file filename of the executable @type str @return list of full executable names, if the executable file is accessible via the searchpath defined by the PATH environment variable, or an empty list otherwise. @rtype list of str """ paths = [] if os.path.isabs(file): if os.access(file, os.X_OK): return [file] else: return [] cur_path = os.path.join(os.curdir, file) if os.path.exists(cur_path) and os.access(cur_path, os.X_OK): paths.append(cur_path) path = os.getenv("PATH") # environment variable not defined if path is not None: dirs = path.split(os.pathsep) for directory in dirs: exe = os.path.join(directory, file) if os.access(exe, os.X_OK) and exe not in paths: paths.append(exe) return paths def supportedPythonVariants(self): """ Public method to get the supported Python variants. @return list of supported Python variants @rtype list of str """ variants = [] cmd = "cookiecutter" for variant in ["Python3"]: virtEnv = self.__getVirtualEnvironment(variant) if virtEnv: fullCmd = self.getPyramidCommand(cmd, variant) if fullCmd != cmd: variants.append(variant) else: fullCmd = self.getPyramidCommand(cmd, variant) if isWindowsPlatform(): if fullCmd != cmd: variants.append(variant) else: if cmd: try: fullCmds = getExecutablePaths(cmd) except AttributeError: fullCmds = self.__getExecutablePaths(cmd) for fullCmd in fullCmds: try: with open(fullCmd, "r", encoding="utf-8") as f: l0 = f.readline() except OSError: l0 = "" if self.__isSuitableForVariant(variant, l0): variants.append(variant) 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 command @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, language=""): """ Private method to get the path of the virtual environment. @param language Python variant to get the virtual environment for (one of '' or 'Python3') @type str @return path of the virtual environment @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() if self.__virtualEnvManager: if language == "Python3": venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3") else: venvName = "" if venvName: virtEnv = self.__virtualEnvManager.getVirtualenvDirectory(venvName) if not virtEnv: virtEnv = os.path.dirname( self.__virtualEnvManager.getVirtualenvInterpreter(venvName) ) if virtEnv.endswith(("Scripts", "bin")): virtEnv = os.path.dirname(virtEnv) else: virtEnv = "" if virtEnv and not os.path.exists(virtEnv): virtEnv = "" return virtEnv # __IGNORE_WARNING_M834__ def __getDebugEnvironment(self, language=""): """ Private method to get the path of the debugger environment. @param language Python variant to get the debugger environment for (one of '' or 'Python3') @type str @return path of the debugger environment @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() if self.__virtualEnvManager: debugEnv = self.__getVirtualEnvironment(language) if not debugEnv: if language == "Python3": venvName = Preferences.getDebugger("Python3VirtualEnv") else: venvName = "" if venvName: return self.__virtualEnvManager.getVirtualenvDirectory(venvName) return "" def getProjectVirtualEnvironment(self): """ Public method to generate the path of the project virtual environment. @return path of the Pyramid project virtual environment @rtype str """ return os.path.join(self.projectPath(), "env") def getPyramidCommand(self, cmd, language="", virtualEnv=""): """ Public method to build a Pyramid command. @param cmd command @type str @param language Python variant to get the virtual environment for (one of '' or 'Python3') @type str @param virtualEnv path of the project's Python virtual environment @type str @return full pyramid command @rtype str """ if not language: language = self.__ericProject.getProjectLanguage() if not virtualEnv: virtualEnv = self.__getVirtualEnvironment(language) if not virtualEnv: virtualEnv = self.__getDebugEnvironment(language) 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), getExecutablePath(cmd), cmd, # fall back to just cmd ] for cmd in fullCmds: if os.path.exists(cmd): break return cmd def __assemblePyramidCommand(self, cmd, virtualEnv): """ Private method to assemble the full pyramid command for a given virtual environment. @param cmd command @type str @param virtualEnv path of the project's Python virtual environment @type str @return assembled pyramid command @rtype str """ return ( os.path.join(virtualEnv, "Scripts", cmd + ".exe") if isWindowsPlatform() else os.path.join(virtualEnv, "bin", cmd) ) def getPythonCommand(self): """ Public method to build the Python command. @return python command @rtype str """ language = self.__ericProject.getProjectLanguage() if self.__virtualEnvManager: if language == "Python3": venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3") if not venvName: # if none configured, use the global one venvName = Preferences.getDebugger("Python3VirtualEnv") else: venvName = "" if venvName: return self.__virtualEnvManager.getVirtualenvInterpreter(venvName) return "" def __pyramidInfo(self): """ Private slot to show some info about Pyramid. """ try: version = self.getPyramidVersionString() except PyramidNoProjectSelectedError: version = self.tr("not available") url = "http://www.pylonsproject.org/projects/pyramid/about" msgBox = EricMessageBox.EricMessageBox( EricMessageBox.Question, self.tr("About Pyramid"), self.tr( "<p>Pyramid is a high-level Python Web framework that" " encourages rapid development and clean, pragmatic" " design.</p>" "<p><table>" "<tr><td>Version:</td><td>{0}</td></tr>" '<tr><td>URL:</td><td><a href="{1}">' "{1}</a></td></tr>" "</table></p>" ).format(version, url), modal=True, buttons=EricMessageBox.Ok, ) msgBox.setIconPixmap( EricPixmapCache.getPixmap( os.path.join( "ProjectPyramid", "icons", "pyramid64-{0}".format(self.__iconSuffix) ) ) ) msgBox.exec() def getPyramidVersionString(self): """ Public method to get the Pyramid version as a string. @return Pyramid version @rtype str """ if not self.__pyramidVersion: cmd = self.getPyramidCommand( "pdistreport", virtualEnv=self.getProjectVirtualEnvironment() ) try: output = subprocess.check_output([cmd]) # secok outputLines = output.decode().splitlines() for line in outputLines: if line.startswith("Pyramid version:"): self.__pyramidVersion = line.rsplit(None, 1)[1] break except (OSError, subprocess.CalledProcessError): self.__pyramidVersion = "" return self.__pyramidVersion def getPyramidVersion(self): """ Public method to get the Pyramid version as a tuple. @return Pyramid version @rtype tuple of int """ pyramidVersionStr = self.getPyramidVersionString() pyramidVersionList = [] if pyramidVersionStr: for part in pyramidVersionStr.split("."): try: pyramidVersionList.append(int(part)) except ValueError: pyramidVersionList.append(part) return tuple(pyramidVersionList) def isSpawningConsole(self, consoleCmd): """ Public method to check, if the given console is a spawning console. @param consoleCmd console command @type str @return tuple of two entries giving an indication, if the console is spawning (boolean) and the (possibly) cleaned console command @rtype str """ if consoleCmd and consoleCmd[0] == "@": return (True, consoleCmd[1:]) else: return (False, consoleCmd) def __adjustWorkingDirectory(self, args, wd): """ Private method to adjust the working directory in the arguments list. @param args list of arguments to be modified @type list of str @param wd working directory @type str """ if args[0].endswith("konsole") and "--workdir" in args: index = args.index("--workdir") args[index + 1] = wd elif args[0].endswith(("gnome-terminal", "mate-terminal")): for index in range(len(args)): if args[index].startswith("--working-directory="): args[index] = "--working-directory={0}".format(wd) break ################################################################## ## slots below implement creation functions ################################################################## def __createProject(self): """ Private slot to create a new Pyramid project. """ from .CreateParametersDialog import CreateParametersDialog dlg = CreateParametersDialog(self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: template, version, overwrite, contextData = dlg.getData() cmd = self.getPyramidCommand("cookiecutter") args = ["--no-input"] if overwrite: args.append("--overwrite-if-exists") if version: args += ["--checkout", version] args.append(template) for context, data in contextData.items(): args.append("{0}={1}".format(context, data)) dlg = PyramidDialog(self.tr("Create Pyramid Project"), parent=self.__ui) if dlg.startProcess(cmd, args, self.__ericProject.getProjectPath()): dlg.exec() if dlg.normalExit() and "repo_name" in contextData: # search for files created by cookiecutter and add them # to the project projectPath = os.path.join( self.__ericProject.getProjectPath(), contextData["repo_name"] ) for entry in os.walk(projectPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__ericProject.appendFile(fullName) # create the base directory for translations i18nPath = os.path.join( projectPath, contextData["repo_name"].lower(), "i18n" ) if not os.path.exists(i18nPath): os.makedirs(i18nPath) self.__ericProject.setDirty(True) combinedOutput = False argsLists = [] # 1. create a Python virtual environment for the project argsLists.append([sys.executable, "-m", "venv", "env"]) # 2. upgrade packaging tools python = self.__assemblePyramidCommand( "python", os.path.join(projectPath, "env") ) argsLists.append( [ python, "-m", "pip", "install", "--upgrade", "pip", "setuptools", ] ) # 3. install project in editable mode with testing argsLists.append( [python, "-m", "pip", "install", "-e", ".[testing]"] ) if ( "backend" in contextData and contextData["backend"] == "sqlalchemy" ): # only SQLAlchemy needs initialization of alembic combinedOutput = True # 4. initialize database alembic = self.__assemblePyramidCommand( "alembic", os.path.join(projectPath, "env") ) argsLists.append( [ alembic, "-c", "development.ini", "revision", "--autogenerate", "--message", "initialized database", ] ) # 5. upgrade database to initial version argsLists.append( [alembic, "-c", "development.ini", "upgrade", "head"] ) dlg = PyramidDialog( self.tr("Initializing Pyramid Project"), combinedOutput=combinedOutput, parent=self.__ui, ) if dlg.startBatchProcesses(argsLists, workingDir=projectPath): dlg.exec() self.__setCurrentProject(contextData["repo_name"]) if ( "backend" in contextData and contextData["backend"] == "sqlalchemy" ): # add the alembic files created above to the project migrationsPath = self.migrationsPath() for entry in os.walk(migrationsPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) self.__ericProject.appendFile(fullName) ################################################################## ## methods below implement site related functions ################################################################## def __findProjects(self): """ Private method to determine the relative path of all Pyramid projects (= top level dirs). @return list of projects @rtype list of str """ projects = [] ppath = self.__ericProject.getProjectPath() for entry in os.listdir(ppath): if entry[0] not in "._" and os.path.isdir(os.path.join(ppath, entry)): projects.append(entry) return sorted(projects) def __selectProject(self): """ Private method to select a Pyramid project to work with. """ projects = self.__findProjects() if len(projects) == 0: project = None elif len(projects) == 1: project = projects[0] else: if self.__currentProject is not None: try: cur = projects.index(self.__currentProject) except ValueError: cur = 0 else: cur = 0 project, ok = QInputDialog.getItem( self.__ui, self.tr("Select Pyramid Project"), self.tr("Select the Pyramid project to work with."), projects, cur, False, ) if not ok: projects = None self.__setCurrentProject(project) def projectPath(self): """ Public method to calculate the full path of the Pyramid project. @return path of the project @rtype str @exception PyramidNoProjectSelectedError raised, if no project is selected """ if self.__currentProject is None: self.__selectProject() if self.__currentProject is None: raise PyramidNoProjectSelectedError else: return os.path.join( self.__ericProject.getProjectPath(), self.__currentProject ) def __setCurrentProject(self, project): """ Private slot to set the current project. @param project name of the project @type str """ if project is not None and len(project) == 0: self.__currentProject = None else: self.__currentProject = project curProject = ( self.tr("None") if self.__currentProject is None else self.__currentProject ) self.selectProjectAct.setText( self.tr("&Current Pyramid Project ({0})").format(curProject) ) if self.__currentProject is None: self.__ericProject.setTranslationPattern("") else: lowerProject = self.__project().lower() config = configparser.ConfigParser() config.read(os.path.join(self.projectPath(), "setup.cfg")) try: outputDir = config.get("init_catalog", "output_dir") except (configparser.NoOptionError, configparser.NoSectionError): outputDir = "{0}/locale".format(lowerProject) try: domain = config.get("init_catalog", "domain") except (configparser.NoOptionError, configparser.NoSectionError): domain = lowerProject self.__ericProject.setTranslationPattern( os.path.join( project, outputDir, "%language%", "LC_MESSAGES", "{0}.po".format(domain), ) ) if self.__currentProject is None: self.initializeDbAct.setEnabled(False) with contextlib.suppress(KeyError): self.__menus["database"].setEnabled(False) else: initCmd = self.__getInitDbCommand() self.initializeDbAct.setEnabled(os.path.exists(initCmd)) alembicDir = os.path.join( self.projectPath(), self.__currentProject, "alembic", "versions" ) self.__menus["database"].setEnabled(os.path.exists(alembicDir)) self.runServerAct.setEnabled(project is not None) self.runBrowserAct.setEnabled(project is not None) self.runPythonShellAct.setEnabled(project is not None) self.showViewsAct.setEnabled(project is not None) self.showRoutesAct.setEnabled(project is not None) self.showTweensAct.setEnabled(project is not None) self.buildDistroAct.setEnabled(project is not None) def __project(self): """ Private method to get the name of the current Pyramid project. @return name of the project @rtype str @exception PyramidNoProjectSelectedError raised, if no project is selected """ if self.__currentProject is None: self.__selectProject() if self.__currentProject is None: raise PyramidNoProjectSelectedError else: return self.__currentProject ################################################################## ## slots below implement run functions ################################################################## def __runServer(self): """ Private slot to start the Pyramid Web server. """ consoleCmd = self.isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, self.tr("Run Server"), self.tr( "No current Pyramid project selected or no" " Pyramid project created yet. Aborting..." ), ) return args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append( self.getPyramidCommand( "pserve", virtualEnv=self.getProjectVirtualEnvironment() ) ) args.append("--reload") args.append(os.path.join(projectPath, "development.ini")) if isWindowsPlatform(): serverProcStarted, pid = QProcess.startDetached( args[0], args[1:], projectPath ) else: if self.__serverProc is not None: self.__serverProcFinished() self.__serverProc = QProcess() self.__serverProc.finished.connect(self.__serverProcFinished) self.__serverProc.setWorkingDirectory(projectPath) self.__serverProc.start(args[0], args[1:]) serverProcStarted = self.__serverProc.waitForStarted() if not serverProcStarted: EricMessageBox.critical( self.__ui, self.tr("Process Generation Error"), self.tr("The Pyramid server could not be started."), ) def __serverProcFinished(self): """ Private slot connected to the finished signal. """ if ( self.__serverProc is not None and self.__serverProc.state() != QProcess.ProcessState.NotRunning ): self.__serverProc.terminate() QTimer.singleShot(2000, self.__serverProc.kill) self.__serverProc.waitForFinished(3000) self.__serverProc = None def __runBrowser(self): """ Private slot to start the default web browser with the server URL. """ try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, self.tr("Run Web-Browser"), self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return config = configparser.ConfigParser() config.read(os.path.join(projectPath, "development.ini")) try: listen = config.get("server:main", "listen") except (configparser.NoOptionError, configparser.NoSectionError): listen = "localhost:6543" url = "http://{0}".format(listen) if self.__plugin.getPreferences("UseExternalBrowser"): res = QDesktopServices.openUrl(QUrl(url)) if not res: EricMessageBox.critical( self.__ui, self.tr("Run Web-Browser"), self.tr( "Could not start the web-browser for the URL" ' "{0}".' ).format(url.toString()), ) else: self.__ui.launchHelpViewer(url) def __runPythonShell(self): """ Private slot to start a Python console for a Pyramid project. """ consoleCmd = self.isSpawningConsole( self.__plugin.getPreferences("ConsoleCommand") )[1] if consoleCmd: try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, self.tr("Start Pyramid Python Console"), self.tr( "No current Pyramid project selected or no" " Pyramid project created yet. Aborting..." ), ) return args = Utilities.parseOptionString(consoleCmd) args[0] = getExecutablePath(args[0]) args.append( self.getPyramidCommand( "pshell", virtualEnv=self.getProjectVirtualEnvironment() ) ) consoleType = self.__plugin.getPreferences("Python3ConsoleType") args.append("--python-shell={0}".format(consoleType)) args.append(os.path.join(projectPath, "development.ini")) self.__adjustWorkingDirectory(args, projectPath) started, pid = QProcess.startDetached(args[0], args[1:], projectPath) if not started: EricMessageBox.critical( self.__ui, self.tr("Process Generation Error"), self.tr("The Pyramid Shell process could not be" " started."), ) ################################################################## ## slots below implement distribution functions ################################################################## def __buildDistribution(self): """ Private slot to build a distribution file for the current Pyramid project. """ title = self.tr("Build Distribution File") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return from .DistributionTypeSelectionDialog import DistributionTypeSelectionDialog dlg = DistributionTypeSelectionDialog(self, projectPath, self.__ui) if dlg.exec() == QDialog.DialogCode.Accepted: formats = dlg.getFormats() cmd = self.getPyramidCommand( "python", virtualEnv=self.getProjectVirtualEnvironment() ) args = [] args.append("setup.py") args.append("sdist") if formats: args.append("--formats={0}".format(",".join(formats))) dia = PyramidDialog( title, msgSuccess=self.tr("Python distribution file built" " successfully."), ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() ################################################################## ## slots below implement various debugging functions ################################################################## def __showMatchingViews(self): """ Private slot showing all views that would match a given URL. """ title = self.tr("Show Matching Views") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return url, ok = QInputDialog.getText( self.__ui, self.tr("Show Matching Views"), self.tr("Enter the URL to be matched:"), QLineEdit.EchoMode.Normal, "/", ) if not ok or url == "": return cmd = self.getPyramidCommand( "pviews", virtualEnv=self.getProjectVirtualEnvironment() ) args = [] args.append("development.ini") args.append(url) dia = PyramidDialog(title, fixed=True, linewrap=False) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() def __showRoutes(self): """ Private slot showing all URL dispatch routes. """ title = self.tr("Show Routes") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return from .PyramidRoutesDialog import PyramidRoutesDialog dia = PyramidRoutesDialog(self) res = dia.start(projectPath) if res: dia.exec() def __showTweens(self): """ Private slot showing all implicit and explicit tween objects. """ title = self.tr("Show Tween Objects") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return cmd = self.getPyramidCommand( "ptweens", virtualEnv=self.getProjectVirtualEnvironment() ) args = [] args.append("development.ini") dia = PyramidDialog(title, fixed=True, linewrap=False) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() ################################################################## ## slots below implement documentation functions ################################################################## def __showDocumentation(self): """ Private slot to show the helpviewer with the Pyramid documentation. """ page = self.__plugin.getPreferences("PyramidDocUrl") self.__ui.launchHelpViewer(page) ################################################################## ## slots below implement translation functions ################################################################## def __getLocale(self, filename): """ Private method to extract the locale out of a file name. @param filename name of the file used for extraction @type str @return extracted locale or None @rtype str """ if self.__ericProject.getTranslationPattern(): # On Windows, path typically contains backslashes. This leads # to an invalid search pattern '...\(' because the opening bracket # will be escaped. pattern = self.__ericProject.getTranslationPattern() pattern = os.path.normpath(pattern) pattern = pattern.replace("%language%", "(.*?)") pattern = pattern.replace("\\", "\\\\") match = re.search(pattern, filename) if match is not None: return match.group(1) return None def __normalizeList(self, filenames): """ Private method to normalize a list of file names. @param filenames list of file names to normalize @type list of str @return normalized file names @rtype list of str """ nfilenames = [] for filename in filenames: if filename.endswith(".mo"): filename = filename.replace(".mo", ".po") if filename not in nfilenames: nfilenames.append(filename) return nfilenames def __projectFilteredList(self, filenames): """ Private method to filter a list of file names by Pyramid project. @param filenames list of file names to be filtered @type list of str @return file names belonging to the current site @rtype list of str """ project = self.__project() nfilenames = [] for filename in filenames: if filename.startswith(project + os.sep): nfilenames.append(filename) return nfilenames def extractMessages(self): """ Public method to extract the messages catalog template file. """ title = self.tr("Extract messages") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return config = configparser.ConfigParser() config.read(os.path.join(projectPath, "setup.cfg")) try: potFile = config.get("extract_messages", "output_file") except configparser.NoSectionError: EricMessageBox.warning( self.__ui, title, self.tr( 'No setup.cfg found or no "extract_messages"' " section found in setup.cfg." ), ) return except configparser.NoOptionError: EricMessageBox.warning( self.__ui, title, self.tr('No "output_file" option found in setup.cfg.') ) return with contextlib.suppress(OSError): path = os.path.join(projectPath, os.path.dirname(potFile)) os.makedirs(path) cmd = self.getPythonCommand() args = [] args.append("setup.py") args.append("extract_messages") dia = PyramidDialog( title, msgSuccess=self.tr("\nMessages extracted successfully.") ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() self.__ericProject.appendFile(os.path.join(projectPath, potFile)) def __projectLanguageAdded(self, code): """ Private slot handling the addition of a new language. @param code language code of the new language @type str """ title = self.tr("Initializing message catalog for '{0}'").format(code) try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return cmd = self.getPythonCommand() args = [] args.append("setup.py") args.append("init_catalog") args.append("-l") args.append(code) dia = PyramidDialog( title, msgSuccess=self.tr("\nMessage catalog initialized" " successfully.") ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() langFile = self.__ericProject.getTranslationPattern().replace( "%language%", code ) self.__ericProject.appendFile(langFile) def compileCatalogs(self, filenames): """ Public method to compile the message catalogs. @param filenames list of filenames (not used) @type list of str """ title = self.tr("Compiling message catalogs") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return cmd = self.getPythonCommand() args = [] args.append("setup.py") args.append("compile_catalog") dia = PyramidDialog( title, msgSuccess=self.tr("\nMessage catalogs compiled" " successfully.") ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() for entry in os.walk(projectPath): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) if fullName.endswith(".mo"): self.__ericProject.appendFile(fullName) def compileSelectedCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of file names @type list of str """ title = self.tr("Compiling message catalogs") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return argsLists = [] for filename in self.__normalizeList(self.__projectFilteredList(filenames)): locale = self.__getLocale(filename) if locale: args = [] args.append(self.getPythonCommand()) args.append("setup.py") args.append("compile_catalog") args.append("-l") args.append(locale) argsLists.append(args) if len(argsLists) == 0: EricMessageBox.warning( self.__ui, title, self.tr("No locales detected. Aborting...") ) return dia = PyramidDialog( title, msgSuccess=self.tr("\nMessage catalogs compiled" " successfully.") ) res = dia.startBatchProcesses(argsLists, projectPath) if res: dia.exec() for entry in os.walk(self.__sitePath()): for fileName in entry[2]: fullName = os.path.join(entry[0], fileName) if fullName.endswith(".mo"): self.__ericProject.appendFile(fullName) def updateCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of filenames (not used) @type list of str """ title = self.tr("Updating message catalogs") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return cmd = self.getPythonCommand() args = [] args.append("setup.py") args.append("update_catalog") dia = PyramidDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() def updateSelectedCatalogs(self, filenames): """ Public method to update the message catalogs. @param filenames list of filenames @type list of str """ title = self.tr("Updating message catalogs") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return argsLists = [] for filename in self.__normalizeList(self.__projectFilteredList(filenames)): locale = self.__getLocale(filename) if locale: args = [] args.append(self.getPythonCommand()) args.append("setup.py") args.append("update_catalog") args.append("-l") args.append(locale) argsLists.append(args) if len(argsLists) == 0: EricMessageBox.warning( self.__ui, title, self.tr("No locales detected. Aborting...") ) return dia = PyramidDialog( title, msgSuccess=self.tr("\nMessage catalogs updated successfully.") ) res = dia.startBatchProcesses(argsLists, projectPath) if res: dia.exec() def openPOEditor(self, poFile): """ Public method to edit the given file in an external .po editor. @param poFile name of the .po file @type str """ editor = self.__plugin.getPreferences("TranslationsEditor") if poFile.endswith(".po") and editor: try: wd = self.projectPath() except PyramidNoProjectSelectedError: wd = "" started, pid = QProcess.startDetached(editor, [poFile], wd) if not started: EricMessageBox.critical( None, self.tr("Process Generation Error"), self.tr( "The translations editor process ({0}) could" " not be started." ).format(os.path.basename(editor)), ) ####################################################################### ## database related methods and slots below ####################################################################### def getAlembicCommand(self): """ Public method to get the path to the alembic executable of the current Pyramid project. @return path to the alembic executable @rtype str """ return self.getPyramidCommand( "alembic", virtualEnv=self.getProjectVirtualEnvironment() ) def migrationsPath(self): """ Public method to get the path to the migrations directory of the current Pyramid project. @return pathof the directory containing the migrations @rtype str """ return os.path.join( self.projectPath(), self.__currentProject, "alembic", "versions" ) def __getInitDbCommand(self): """ Private method to create the path to the initialization script. @return path to the initialization script @rtype str """ try: cmd = "initialize_{0}_db".format(self.__project()) return self.getPyramidCommand( cmd, virtualEnv=self.getProjectVirtualEnvironment() ) except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, self.tr("Initialize Database"), self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return "" @pyqtSlot() def __initializeDatabase(self): """ Private slot to initialize the database of the Pyramid project. """ title = self.tr("Initialize Database") try: projectPath = self.projectPath() except PyramidNoProjectSelectedError: EricMessageBox.warning( self.__ui, title, self.tr( "No current Pyramid project selected or no Pyramid" " project created yet. Aborting..." ), ) return cmd = self.__getInitDbCommand() args = [] args.append("development.ini") dia = PyramidDialog( title, msgSuccess=self.tr("Database initialized successfully.") ) res = dia.startProcess(cmd, args, projectPath) if res: dia.exec() @pyqtSlot() def __createMigration(self): """ Private slot to create a new database migration. """ title = self.tr("Create Migration") projectPath = self.projectPath() migrations = self.migrationsPath() message, ok = QInputDialog.getText( None, title, self.tr("Enter a short message for the migration:"), QLineEdit.EchoMode.Normal, ) if ok: args = ["-c", "development.ini", "revision", "--autogenerate"] if migrations: args += ["--version-path", migrations] if message: args += ["--message", message] dlg = PyramidDialog( title, msgSuccess=self.tr("\nMigration created successfully."), linewrap=False, combinedOutput=True, parent=self.__ui, ) if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath): dlg.exec() if dlg.normalExit(): versionsPattern = os.path.join(migrations, "*.py") for fileName in glob.iglob(versionsPattern): self.__ericProject.appendFile(fileName) @pyqtSlot() def upgradeDatabase(self, revision=None): """ Public slot to upgrade the database to the head or a given version. @param revision migration revision to upgrade to @type str """ title = self.tr("Upgrade Database") projectPath = self.projectPath() args = ["-c", "development.ini", "upgrade"] if revision: args.append(revision) else: args.append("head") dlg = PyramidDialog( title, msgSuccess=self.tr("\nDatabase upgraded successfully."), linewrap=False, combinedOutput=True, parent=self.__ui, ) if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath): dlg.exec() @pyqtSlot() def downgradeDatabase(self, revision=None): """ Public slot to downgrade the database to the previous or a given version. @param revision migration revision to downgrade to @type str """ title = self.tr("Downgrade Database") projectPath = self.projectPath() args = ["-c", "development.ini", "downgrade"] if revision: args.append(revision) else: args.append("-1") dlg = PyramidDialog( title, msgSuccess=self.tr("\nDatabase downgraded successfully."), linewrap=False, combinedOutput=True, parent=self.__ui, ) if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath): dlg.exec() @pyqtSlot() def __showMigrationsSummary(self): """ Private slot to show a migrations history summary. """ from .MigrateSummaryDialog import MigrateSummaryDialog if self.__migrationSummaryDialog is None: self.__migrationSummaryDialog = MigrateSummaryDialog(self, parent=self.__ui) self.__migrationSummaryDialog.showSummary() @pyqtSlot() def __showMigrationsHistory(self): """ Private slot to show the full migrations history. """ title = self.tr("Migrations History") projectPath = self.projectPath() args = ["-c", "development.ini", "history", "--indicate-current", "--verbose"] dlg = PyramidDialog( title, linewrap=False, combinedOutput=True, parent=self.__ui ) if dlg.startProcess(self.getAlembicCommand(), args, workingDir=projectPath): dlg.exec()