Plugins/UiExtensionPlugins/PipInterface/PipSearchDialog.py

changeset 6805
10fefa6f331a
parent 6797
d9e56b0aa7ac
parent 6804
dc20bde4af90
child 6806
d306647cb82d
diff -r d9e56b0aa7ac -r 10fefa6f331a Plugins/UiExtensionPlugins/PipInterface/PipSearchDialog.py
--- a/Plugins/UiExtensionPlugins/PipInterface/PipSearchDialog.py	Sat Feb 23 13:05:18 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,465 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2015 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
-#
-
-"""
-Module implementing a dialog to search PyPI.
-"""
-
-from __future__ import unicode_literals
-
-import textwrap
-
-from PyQt5.QtCore import pyqtSlot, Qt, QEventLoop, QRegExp
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
-    QApplication, QTreeWidgetItem, QHeaderView, QInputDialog
-
-from E5Gui import E5MessageBox
-try:
-    from E5Network.E5XmlRpcClient import E5XmlRpcClient
-except ImportError:
-    from .E5XmlRpcClient import E5XmlRpcClient
-
-from .Ui_PipSearchDialog import Ui_PipSearchDialog
-
-
-class PipSearchDialog(QDialog, Ui_PipSearchDialog):
-    """
-    Class implementing a dialog to search PyPI.
-    """
-    VersionRole = Qt.UserRole + 1
-    
-    Stopwords = {
-        "a", "and", "are", "as", "at", "be", "but", "by",
-        "for", "if", "in", "into", "is", "it",
-        "no", "not", "of", "on", "or", "such",
-        "that", "the", "their", "then", "there", "these",
-        "they", "this", "to", "was", "will",
-    }
-    
-    def __init__(self, pip, indexUrl, parent=None):
-        """
-        Constructor
-        
-        @param pip reference to the master object
-        @type Pip
-        @param indexUrl URL of XML RPC interface to the pypi index
-        @type str
-        @param parent reference to the parent widget
-        @type QWidget
-        """
-        super(PipSearchDialog, self).__init__(parent)
-        self.setupUi(self)
-        self.setWindowFlags(Qt.Window)
-        
-        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
-        
-        self.__installButton = self.buttonBox.addButton(
-            self.tr("&Install"), QDialogButtonBox.ActionRole)
-        self.__installButton.setEnabled(False)
-        
-        self.__installUserButton = self.buttonBox.addButton(
-            self.tr("Install to &User-Site"), QDialogButtonBox.ActionRole)
-        self.__installUserButton.setEnabled(False)
-        
-        self.__showDetailsButton = self.buttonBox.addButton(
-            self.tr("&Show Details..."), QDialogButtonBox.ActionRole)
-        self.__showDetailsButton.setEnabled(False)
-        
-        self.__pip = pip
-        self.__client = E5XmlRpcClient(indexUrl, self)
-        
-        self.venvComboBox.addItem(self.__pip.getDefaultEnvironmentString())
-        projectVenv = self.__pip.getProjectEnvironmentString()
-        if projectVenv:
-            self.venvComboBox.addItem(projectVenv)
-        self.venvComboBox.addItems(self.__pip.getVirtualenvNames())
-        
-        self.searchEdit.setFocus(Qt.OtherFocusReason)
-        
-        self.__canceled = False
-        self.__detailsData = {}
-        self.__query = []
-        
-        self.__packageDetailsDialog = None
-    
-    def closeEvent(self, e):
-        """
-        Protected slot implementing a close event handler.
-        
-        @param e close event
-        @type QCloseEvent
-        """
-        QApplication.restoreOverrideCursor()
-        
-        if self.__packageDetailsDialog is not None:
-            self.__packageDetailsDialog.close()
-        
-        e.accept()
-    
-    @pyqtSlot(str)
-    def on_searchEdit_textChanged(self, txt):
-        """
-        Private slot handling a change of the search term.
-        
-        @param txt search term
-        @type str
-        """
-        self.searchButton.setEnabled(bool(txt))
-    
-    @pyqtSlot()
-    def on_searchButton_clicked(self):
-        """
-        Private slot handling a press of the search button.
-        """
-        self.__search()
-    
-    @pyqtSlot()
-    def on_resultList_itemSelectionChanged(self):
-        """
-        Private slot handling changes of the selection.
-        """
-        self.__installButton.setEnabled(
-            len(self.resultList.selectedItems()) > 0)
-        self.__installUserButton.setEnabled(
-            len(self.resultList.selectedItems()) > 0)
-        self.__showDetailsButton.setEnabled(
-            len(self.resultList.selectedItems()) == 1)
-    
-    @pyqtSlot(QAbstractButton)
-    def on_buttonBox_clicked(self, button):
-        """
-        Private slot called by a button of the button box clicked.
-        
-        @param button button that was clicked
-        @type QAbstractButton
-        """
-        if button == self.buttonBox.button(QDialogButtonBox.Close):
-            self.close()
-        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
-            self.__client.abort()
-            self.__canceled = True
-        elif button == self.__installButton:
-            self.__install()
-        elif button == self.__installUserButton:
-            self.__install(userSite=True)
-        elif button == self.__showDetailsButton:
-            self.__showDetails()
-    
-    def __search(self):
-        """
-        Private method to perform the search.
-        """
-        self.resultList.clear()
-        self.infoLabel.clear()
-        
-        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
-        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
-        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
-        self.searchButton.setEnabled(False)
-        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
-        
-        QApplication.setOverrideCursor(Qt.WaitCursor)
-        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
-        
-        self.__canceled = False
-        
-        self.__query = [term for term in self.searchEdit.text().strip().split()
-                        if term not in PipSearchDialog.Stopwords]
-        self.__client.call(
-            "search",
-            ({"name": self.__query, "summary": self.__query}, "or"),
-            self.__processSearchResult,
-            self.__searchError
-        )
-    
-    def __processSearchResult(self, data):
-        """
-        Private method to process the search result data from PyPI.
-        
-        @param data result data with hits in the first element
-        @type tuple
-        """
-        if data:
-            packages = self.__transformHits(data[0])
-            if packages:
-                self.infoLabel.setText(self.tr("%n package(s) found.", "",
-                                       len(packages)))
-                wrapper = textwrap.TextWrapper(width=80)
-                count = 0
-                total = 0
-                for package in packages:
-                    if self.__canceled:
-                        self.infoLabel.setText(
-                            self.tr("Canceled - only {0} out of %n package(s)"
-                                    " shown", "", len(packages)).format(total))
-                        break
-                    itm = QTreeWidgetItem(
-                        self.resultList, [
-                            package['name'].strip(),
-                            "{0:4d}".format(package['score']),
-                            "\n".join([
-                                wrapper.fill(line) for line in
-                                package['summary'].strip().splitlines()
-                            ])
-                        ])
-                    itm.setData(0, self.VersionRole, package['version'])
-                    count += 1
-                    total += 1
-                    if count == 100:
-                        count = 0
-                        QApplication.processEvents()
-            else:
-                QApplication.restoreOverrideCursor()
-                E5MessageBox.warning(
-                    self,
-                    self.tr("Search PyPI"),
-                    self.tr("""<p>The package search did not return"""
-                            """ anything.</p>"""))
-                self.infoLabel.setText(
-                    self.tr("""<p>The package search did not return"""
-                            """ anything.</p>"""))
-        else:
-            QApplication.restoreOverrideCursor()
-            E5MessageBox.warning(
-                self,
-                self.tr("Search PyPI"),
-                self.tr("""<p>The package search did not return anything."""
-                        """</p>"""))
-            self.infoLabel.setText(
-                self.tr("""<p>The package search did not return anything."""
-                        """</p>"""))
-        
-        header = self.resultList.header()
-        self.resultList.sortItems(1, Qt.DescendingOrder)
-        header.setStretchLastSection(False)
-        header.resizeSections(QHeaderView.ResizeToContents)
-        headerSize = 0
-        for col in range(header.count()):
-            headerSize += header.sectionSize(col)
-        if headerSize < header.width():
-            header.setStretchLastSection(True)
-        
-        self.__finish()
-    
-    def __finish(self):
-        """
-        Private slot performing the finishing actions.
-        """
-        QApplication.restoreOverrideCursor()
-        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
-        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
-        self.searchButton.setEnabled(True)
-        self.searchButton.setDefault(True)
-        self.searchEdit.setFocus(Qt.OtherFocusReason)
-    
-    def __searchError(self, errorCode, errorString):
-        """
-        Private method handling a search error.
-        
-        @param errorCode code of the error
-        @type int
-        @param errorString error message
-        @type str
-        """
-        self.__finish()
-        E5MessageBox.warning(
-            self,
-            self.tr("Search PyPI"),
-            self.tr("""<p>The package search failed.</p><p>Reason: {0}</p>""")
-            .format(errorString))
-        self.infoLabel.setText(self.tr("Error: {0}").format(errorString))
-    
-    def __transformHits(self, hits):
-        """
-        Private method to convert the list returned from pypi into a
-        packages list.
-        
-        @param hits list returned from pypi
-        @type list of dict
-        @return list of packages
-        @rtype list of dict
-        """
-        # we only include the record with the highest score
-        packages = {}
-        for hit in hits:
-            name = hit['name'].strip()
-            summary = (hit['summary'] or "").strip()
-            version = hit['version'].strip()
-            score = self.__score(name, summary)
-            # cleanup the summary
-            if summary in ["UNKNOWN", "."]:
-                summary = ""
-
-            if name not in packages:
-                packages[name] = {
-                    'name': name,
-                    'summary': summary,
-                    'version': [version.strip()],
-                    'score': score}
-            else:
-                if score > packages[name]['score']:
-                    packages[name]['score'] = score
-                    packages[name]['summary'] = summary
-                packages[name]['version'].append(version.strip())
-
-        return list(packages.values())
-    
-    def __score(self, name, summary):
-        """
-        Private method to calculate some score for a search result.
-        
-        @param name name of the returned package
-        @type str
-        @param summary summary text for the package
-        @type str
-        @return score value
-        @rtype int
-        """
-        score = 0
-        for queryTerm in self.__query:
-            if queryTerm.lower() in name.lower():
-                score += 4
-                if queryTerm.lower() == name.lower():
-                    score += 4
-            
-            if queryTerm.lower() in summary.lower():
-                if QRegExp(r'\b{0}\b'.format(QRegExp.escape(queryTerm)),
-                           Qt.CaseInsensitive).indexIn(summary) != -1:
-                    # word match gets even higher score
-                    score += 2
-                else:
-                    score += 1
-        
-        return score
-    
-    def __install(self, userSite=False):
-        """
-        Private slot to install the selected packages.
-        
-        @param userSite flag indicating to install to the user directory
-        @type bool
-        """
-        venvName = self.venvComboBox.currentText()
-        
-        packages = []
-        for itm in self.resultList.selectedItems():
-            packages.append(itm.text(0).strip())
-        if packages:
-            self.__pip.installPackages(packages, venvName=venvName,
-                                       userSite=userSite)
-    
-    def __showDetails(self):
-        """
-        Private slot to show details about the selected package.
-        """
-        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
-        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
-        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
-        self.__showDetailsButton.setEnabled(False)
-        QApplication.setOverrideCursor(Qt.WaitCursor)
-        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
-        
-        self.__detailsData = {}
-        
-        itm = self.resultList.selectedItems()[0]
-        packageVersions = itm.data(0, self.VersionRole)
-        if len(packageVersions) == 1:
-            packageVersion = packageVersions[0]
-        elif len(packageVersions) == 0:
-            packageVersion = ""
-        else:
-            packageVersion, ok = QInputDialog.getItem(
-                self,
-                self.tr("Show Package Details"),
-                self.tr("Select the package version:"),
-                packageVersions,
-                0, False)
-            if not ok:
-                return
-        
-        packageName = itm.text(0)
-        self.__client.call(
-            "release_data",
-            (packageName, packageVersion),
-            lambda d: self.__getPackageDownloadsData(packageVersion, d),
-            self.__detailsError
-        )
-    
-    def __getPackageDownloadsData(self, packageVersion, data):
-        """
-        Private method to store the details data and get downloads
-        information.
-        
-        @param packageVersion version info
-        @type str
-        @param data result data with package details in the first
-            element
-        @type tuple
-        """
-        if data and data[0]:
-            self.__detailsData = data[0]
-            itm = self.resultList.selectedItems()[0]
-            packageName = itm.text(0)
-            self.__client.call(
-                "release_urls",
-                (packageName, packageVersion),
-                self.__displayPackageDetails,
-                self.__detailsError
-            )
-        else:
-            self.__finish()
-            E5MessageBox.warning(
-                self,
-                self.tr("Search PyPI"),
-                self.tr("""<p>No package details info available.</p>"""))
-    
-    def __displayPackageDetails(self, data):
-        """
-        Private method to display the returned package details.
-        
-        @param data result data with downloads information in the first element
-        @type tuple
-        """
-        from .PipPackageDetailsDialog import PipPackageDetailsDialog
-        
-        self.__finish()
-        self.__showDetailsButton.setEnabled(True)
-        
-        if self.__packageDetailsDialog is not None:
-            self.__packageDetailsDialog.close()
-        
-        self.__packageDetailsDialog = \
-            PipPackageDetailsDialog(self.__detailsData, data[0], self)
-        self.__packageDetailsDialog.show()
-    
-    def __detailsError(self, errorCode, errorString):
-        """
-        Private method handling a details error.
-        
-        @param errorCode code of the error
-        @type int
-        @param errorString error message
-        @type str
-        """
-        self.__finish()
-        self.__showDetailsButton.setEnabled(True)
-        E5MessageBox.warning(
-            self,
-            self.tr("Search PyPI"),
-            self.tr("""<p>Package details info could not be retrieved.</p>"""
-                    """<p>Reason: {0}</p>""")
-            .format(errorString))
-    
-    @pyqtSlot(QTreeWidgetItem, int)
-    def on_resultList_itemActivated(self, item, column):
-        """
-        Private slot reacting on an item activation.
-        
-        @param item reference to the activated item
-        @type QTreeWidgetItem
-        @param column activated column
-        @type int
-        """
-        self.__showDetails()

eric ide

mercurial