PipInterface: continued with the pip interface widget. pypi

Wed, 20 Feb 2019 19:44:13 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 20 Feb 2019 19:44:13 +0100
branch
pypi
changeset 6793
cca6a35f3ad2
parent 6792
9dd854f05c83
child 6795
6e2ed2aac325

PipInterface: continued with the pip interface widget.

PipInterface/Pip.py file | annotate | diff | comparison | revisions
PipInterface/PipPackagesWidget.py file | annotate | diff | comparison | revisions
PipInterface/__init__.py file | annotate | diff | comparison | revisions
--- a/PipInterface/Pip.py	Tue Feb 19 19:56:24 2019 +0100
+++ b/PipInterface/Pip.py	Wed Feb 20 19:44:13 2019 +0100
@@ -25,7 +25,6 @@
 from E5Gui.E5Application import e5App
 
 from .PipDialog import PipDialog
-from . import DefaultIndexUrlXml
 
 import Preferences
 import Globals
@@ -37,6 +36,10 @@
     """
     Class implementing the pip GUI logic.
     """
+    DefaultPyPiUrl = "https://pypi.org"
+    DefaultIndexUrlXml = DefaultPyPiUrl + "/pypi"
+    DefaultIndexUrlPip = DefaultPyPiUrl + "/simple"
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -45,12 +48,12 @@
         @type QObject
         """
         super(Pip, self).__init__(parent)
-        
+##        
 ##        self.__virtualenvManager = e5App().getObject("VirtualEnvManager")
 ##        self.__project = e5App().getObject("Project")
-        
-        self.__menus = {}   # dictionary with references to menus
-        
+##        
+##        self.__menus = {}   # dictionary with references to menus
+##        
 ##        self.__plugin.currentEnvironmentChanged.connect(
 ##            self.__handleTearOffMenu)
     
@@ -60,21 +63,21 @@
         """
         self.actions = []
     
-        self.selectEnvironmentAct = E5Action(
-            self.tr('Virtual Environment for pip'),
-            self.tr('&Virtual Environment for pip'),
-            0, 0,
-            self, 'pip_select_environment')
-        self.selectEnvironmentAct.setStatusTip(self.tr(
-            'Selects the virtual environment to be used for pip'))
-        self.selectEnvironmentAct.setWhatsThis(self.tr(
-            """<b>Virtual Environment for pip</b>"""
-            """<p>This selects the virtual environment to be used for pip."""
-            """</p>"""
-        ))
-        self.selectEnvironmentAct.triggered.connect(self.__selectPipVirtualenv)
-        self.actions.append(self.selectEnvironmentAct)
-        
+##        self.selectEnvironmentAct = E5Action(
+##            self.tr('Virtual Environment for pip'),
+##            self.tr('&Virtual Environment for pip'),
+##            0, 0,
+##            self, 'pip_select_environment')
+##        self.selectEnvironmentAct.setStatusTip(self.tr(
+##            'Selects the virtual environment to be used for pip'))
+##        self.selectEnvironmentAct.setWhatsThis(self.tr(
+##            """<b>Virtual Environment for pip</b>"""
+##            """<p>This selects the virtual environment to be used for pip."""
+##            """</p>"""
+##        ))
+##        self.selectEnvironmentAct.triggered.connect(self.__selectPipVirtualenv)
+##        self.actions.append(self.selectEnvironmentAct)
+##        
 ##        ##############################################
 ##        ## Actions for listing packages
 ##        ##############################################
@@ -284,25 +287,25 @@
             self.__generateRequirements)
         self.actions.append(self.generateRequirementsAct)
         
-        ##############################################
-        ## Actions for generating requirements files
-        ##############################################
-        
-        self.searchPyPIAct = E5Action(
-            self.tr('Search PyPI'),
-            self.tr('&Search PyPI...'),
-            0, 0,
-            self, 'pip_search_pypi')
-        self.searchPyPIAct.setStatusTip(self.tr(
-            'Open a dialog to search the Python Package Index'))
-        self.searchPyPIAct.setWhatsThis(self.tr(
-            """<b>Search PyPI</b>"""
-            """<p>This opens a dialog to search the Python Package"""
-            """ Index.</p>"""
-        ))
-        self.searchPyPIAct.triggered.connect(self.__searchPyPI)
-        self.actions.append(self.searchPyPIAct)
-        
+##        ##############################################
+##        ## Actions for generating requirements files
+##        ##############################################
+##        
+##        self.searchPyPIAct = E5Action(
+##            self.tr('Search PyPI'),
+##            self.tr('&Search PyPI...'),
+##            0, 0,
+##            self, 'pip_search_pypi')
+##        self.searchPyPIAct.setStatusTip(self.tr(
+##            'Open a dialog to search the Python Package Index'))
+##        self.searchPyPIAct.setWhatsThis(self.tr(
+##            """<b>Search PyPI</b>"""
+##            """<p>This opens a dialog to search the Python Package"""
+##            """ Index.</p>"""
+##        ))
+##        self.searchPyPIAct.triggered.connect(self.__searchPyPI)
+##        self.actions.append(self.searchPyPIAct)
+##        
         ##############################################
         ## Actions for editing configuration files
         ##############################################
@@ -361,14 +364,14 @@
         @return the menu generated
         @rtype QMenu
         """
-        self.__menus = {}   # clear menus references
-        
+##        self.__menus = {}   # clear menus references
+##        
         menu = QMenu()
 ##        menu.setTearOffEnabled(True)
 ##        menu.setIcon(UI.PixmapCache.getIcon("pypi.png"))
-        
-        menu.addAction(self.selectEnvironmentAct)
-        menu.addSeparator()
+##        
+##        menu.addAction(self.selectEnvironmentAct)
+##        menu.addSeparator()
 ##        menu.addAction(self.listPackagesAct)
 ##        menu.addAction(self.listUptodatePackagesAct)
 ##        menu.addAction(self.listOutdatedPackagesAct)
@@ -396,8 +399,8 @@
         menu.addSeparator()
         menu.addAction(self.pipConfigAct)
         
-        self.__menus["main"] = menu
-        
+##        self.__menus["main"] = menu
+##        
         menu.aboutToShow.connect(self.__aboutToShowMenu)
         
         return menu
@@ -414,44 +417,44 @@
                            self.editVirtualenvConfigAct,
                            self.pipConfigAct]:
                 act.setEnabled(enable)
-    
-    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 __handleTearOffMenu(self, venvName):
-        """
-        Private slot to handle a change of the selected virtual environment.
-        
-        @param venvName logical name of the virtual environment
-        @type str
-        """
-        if self.__menus["main"].isTearOffMenuVisible():
-            # determine, if torn off menu needs to be refreshed
-            enabled = self.listPackagesAct.isEnabled()
-            if ((bool(venvName) and not enabled) or
-                    (not bool(venvName) and enabled)):
-                self.__menus["main"].hideTearOffMenu()
+##    
+##    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 __handleTearOffMenu(self, venvName):
+##        """
+##        Private slot to handle a change of the selected virtual environment.
+##        
+##        @param venvName logical name of the virtual environment
+##        @type str
+##        """
+##        if self.__menus["main"].isTearOffMenuVisible():
+##            # determine, if torn off menu needs to be refreshed
+##            enabled = self.listPackagesAct.isEnabled()
+##            if ((bool(venvName) and not enabled) or
+##                    (not bool(venvName) and enabled)):
+##                self.__menus["main"].hideTearOffMenu()
     
     ##########################################################################
     ## Methods below implement some utility functions
@@ -559,15 +562,15 @@
         
         return config
     
-    def getDefaultEnvironmentString(self):
-        """
-        Public method to get the string for the default environment.
-        
-        @return string for the default environment
-        @rtype str
-        """
-        return self.tr("<standard>")
-    
+##    def getDefaultEnvironmentString(self):
+##        """
+##        Public method to get the string for the default environment.
+##        
+##        @return string for the default environment
+##        @rtype str
+##        """
+##        return self.tr("<standard>")
+##    
     def getProjectEnvironmentString(self):
         """
         Public method to get the string for the project environment.
@@ -589,14 +592,16 @@
         @return interpreter path
         @rtype str
         """
-        if venvName == self.getDefaultEnvironmentString():
-            venvName = Preferences.getPip("CurrentEnvironment")
-        elif venvName == self.getProjectEnvironmentString():
+##        if venvName == self.getDefaultEnvironmentString():
+##            venvName = Preferences.getPip("CurrentEnvironment")
+        if venvName == self.getProjectEnvironmentString():
             venvName = \
                 e5App().getObject("Project").getDebugProperty("VIRTUALENV")
             if not venvName:
-                # fall back to standard if not defined
-                venvName = Preferences.getPip("CurrentEnvironment")
+                # fall back to interpreter used to run eric6
+                return sys.executable
+##                # fall back to standard if not defined
+##                venvName = Preferences.getPip("CurrentEnvironment")
         
         interpreter = \
             e5App().getObject("VirtualEnvManager").getVirtualenvInterpreter(
@@ -624,66 +629,65 @@
     ## Methods below implement the individual menu entries
     ##########################################################################
     
-    def __selectPipVirtualenv(self):
-        """
-        Private method to select the virtual environment to be used.
-        """
-        environments = self.getVirtualenvNames()
-        if environments:
-            currentEnvironment = Preferences.getPip("CurrentEnvironment")
-            try:
-                index = environments.index(currentEnvironment)
-            except ValueError:
-                index = 0
-            environment, ok = QInputDialog.getItem(
-                None,
-                self.tr("Virtual Environment for pip"),
-                self.tr("Select the virtual environment to be used:"),
-                environments, index, False)
-            
-            if ok and environment:
-                Preferences.getPip("CurrentEnvironment", environment)
-        else:
-            E5MessageBox.warning(
-                None,
-                self.tr("Virtual Environment for pip"),
-                self.tr("""No virtual environments have been configured yet."""
-                        """ Please use the Virtualenv Manager to do that."""))
-    
-    # TODO: move these three to the widget
-    def __listPackages(self):
-        """
-        Private slot to list all installed packages.
-        """
-        from .PipListDialog import PipListDialog
-        self.__listDialog = PipListDialog(
-            self, "list", Preferences.getPip("PipSearchIndex"),
-            self.tr("Installed Packages"))
-        self.__listDialog.show()
-        self.__listDialog.start()
-    
-    def __listUptodatePackages(self):
-        """
-        Private slot to list all installed, up-to-date packages.
-        """
-        from .PipListDialog import PipListDialog
-        self.__listUptodateDialog = PipListDialog(
-            self, "uptodate", Preferences.getPip("PipSearchIndex"),
-            self.tr("Up-to-date Packages"))
-        self.__listUptodateDialog.show()
-        self.__listUptodateDialog.start()
-    
-    def __listOutdatedPackages(self):
-        """
-        Private slot to list all installed, up-to-date packages.
-        """
-        from .PipListDialog import PipListDialog
-        self.__listOutdatedDialog = PipListDialog(
-            self, "outdated", Preferences.getPip("PipSearchIndex"),
-            self.tr("Outdated Packages"))
-        self.__listOutdatedDialog.show()
-        self.__listOutdatedDialog.start()
-    
+##    def __selectPipVirtualenv(self):
+##        """
+##        Private method to select the virtual environment to be used.
+##        """
+##        environments = self.getVirtualenvNames()
+##        if environments:
+##            currentEnvironment = Preferences.getPip("CurrentEnvironment")
+##            try:
+##                index = environments.index(currentEnvironment)
+##            except ValueError:
+##                index = 0
+##            environment, ok = QInputDialog.getItem(
+##                None,
+##                self.tr("Virtual Environment for pip"),
+##                self.tr("Select the virtual environment to be used:"),
+##                environments, index, False)
+##            
+##            if ok and environment:
+##                Preferences.getPip("CurrentEnvironment", environment)
+##        else:
+##            E5MessageBox.warning(
+##                None,
+##                self.tr("Virtual Environment for pip"),
+##                self.tr("""No virtual environments have been configured yet."""
+##                        """ Please use the Virtualenv Manager to do that."""))
+##    
+##    def __listPackages(self):
+##        """
+##        Private slot to list all installed packages.
+##        """
+##        from .PipListDialog import PipListDialog
+##        self.__listDialog = PipListDialog(
+##            self, "list", Preferences.getPip("PipSearchIndex"),
+##            self.tr("Installed Packages"))
+##        self.__listDialog.show()
+##        self.__listDialog.start()
+##    
+##    def __listUptodatePackages(self):
+##        """
+##        Private slot to list all installed, up-to-date packages.
+##        """
+##        from .PipListDialog import PipListDialog
+##        self.__listUptodateDialog = PipListDialog(
+##            self, "uptodate", Preferences.getPip("PipSearchIndex"),
+##            self.tr("Up-to-date Packages"))
+##        self.__listUptodateDialog.show()
+##        self.__listUptodateDialog.start()
+##    
+##    def __listOutdatedPackages(self):
+##        """
+##        Private slot to list all installed, up-to-date packages.
+##        """
+##        from .PipListDialog import PipListDialog
+##        self.__listOutdatedDialog = PipListDialog(
+##            self, "outdated", Preferences.getPip("PipSearchIndex"),
+##            self.tr("Outdated Packages"))
+##        self.__listOutdatedDialog.show()
+##        self.__listOutdatedDialog.start()
+##    
     def __editUserConfiguration(self):
         """
         Private slot to edit the user configuration.
@@ -1120,19 +1124,47 @@
         self.__freezeDialog.show()
         self.__freezeDialog.start()
     
-    def __searchPyPI(self):
+##    def __searchPyPI(self):
+##        """
+##        Private slot to search the Python Package Index.
+##        """
+##        from .PipSearchDialog import PipSearchDialog
+##        
+##        if Preferences.getPip("PipSearchIndex"):
+##            indexUrl = Preferences.getPip("PipSearchIndex") + "/pypi"
+##        else:
+##            indexUrl = DefaultIndexUrlXml
+##        
+##        self.__searchDialog = PipSearchDialog(self, indexUrl)
+##        self.__searchDialog.show()
+##    
+    def getIndexUrl(self):
         """
-        Private slot to search the Python Package Index.
+        Public method to get the index URL for PyPI.
+        
+        @return index URL for PyPI
+        @rtype str
         """
-        from .PipSearchDialog import PipSearchDialog
+        if Preferences.getPip("PipSearchIndex"):
+            indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
+        else:
+            indexUrl = Pip.DefaultIndexUrlPip
         
+        return indexUrl
+    
+    def getIndexUrlXml(self):
+        """
+        Public method to get the index URL for XML RPC calls.
+        
+        @return index URL for XML RPC calls
+        @rtype str
+        """
         if Preferences.getPip("PipSearchIndex"):
             indexUrl = Preferences.getPip("PipSearchIndex") + "/pypi"
         else:
-            indexUrl = DefaultIndexUrlXml
+            indexUrl = Pip.DefaultIndexUrlXml
         
-        self.__searchDialog = PipSearchDialog(self, indexUrl)
-        self.__searchDialog.show()
+        return indexUrl
     
     def getInstalledPackages(self, envName, localPackages=True,
                              notRequired=False, usersite=False):
--- a/PipInterface/PipPackagesWidget.py	Tue Feb 19 19:56:24 2019 +0100
+++ b/PipInterface/PipPackagesWidget.py	Wed Feb 20 19:44:13 2019 +0100
@@ -9,12 +9,17 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import pyqtSlot, Qt
+import textwrap
+
+from PyQt5.QtCore import pyqtSlot, Qt, QEventLoop, QRegExp
 from PyQt5.QtGui import QCursor
 from PyQt5.QtWidgets import QWidget, QToolButton, QApplication, QHeaderView, \
     QTreeWidgetItem
 
 from E5Gui.E5Application import e5App
+from E5Gui import E5MessageBox
+
+from E5Network.E5XmlRpcClient import E5XmlRpcClient
 
 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget
 
@@ -32,6 +37,15 @@
     ShowProcessEntryPointsMode = 2
     ShowProcessFilesListMode = 3
     
+    SearchStopwords = {
+        "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",
+    }
+    SearchVersionRole = Qt.UserRole + 1
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -55,6 +69,7 @@
         self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find"))
         
         self.__pip = Pip(self)
+        self.__client = E5XmlRpcClient(self.__pip.getIndexUrlXml(), self)
         
         self.packagesList.header().setSortIndicator(0, Qt.AscendingOrder)
         
@@ -105,6 +120,23 @@
             self.environmentsComboBox.addItem(projectVenv)
         self.environmentsComboBox.addItems(self.__pip.getVirtualenvNames())
     
+    def __isPipAvailable(self):
+        """
+        Private method to check, if the pip package is available for the
+        selected environment.
+        
+        @return flag indicating availability
+        @rtype bool
+        """
+        available = False
+        
+        venvName = self.environmentsComboBox.currentText()
+        if venvName:
+            available = len(self.packagesList.findItems(
+                "pip", Qt.MatchExactly | Qt.MatchCaseSensitive)) == 1
+        
+        return available
+    
     #######################################################################
     ## Slots handling widget signals below
     #######################################################################
@@ -140,12 +172,17 @@
         """
         Private method to set the state of the action buttons.
         """
-        self.upgradeButton.setEnabled(
-            bool(self.__selectedUpdateableItems()))
-        self.uninstallButton.setEnabled(
-            bool(self.packagesList.selectedItems()))
-        self.upgradeAllButton.setEnabled(
-            bool(self.__allUpdateableItems()))
+        if self.__isPipAvailable():
+            self.upgradeButton.setEnabled(
+                bool(self.__selectedUpdateableItems()))
+            self.uninstallButton.setEnabled(
+                bool(self.packagesList.selectedItems()))
+            self.upgradeAllButton.setEnabled(
+                bool(self.__allUpdateableItems()))
+        else:
+            self.upgradeButton.setEnabled(False)
+            self.uninstallButton.setEnabled(False)
+            self.upgradeAllButton.setEnabled(False)
     
     def __refreshPackagesList(self):
         """
@@ -201,6 +238,7 @@
         
         self.__updateActionButtons()
         self.__updateSearchActionButtons()
+        self.__updateSearchButton()
     
     @pyqtSlot(int)
     def on_environmentsComboBox_currentIndexChanged(self, index):
@@ -409,12 +447,27 @@
         """
         Private method to update the action button states of the search widget.
         """
-        # TODO: adjust this like search dialog
-        enable = len(self.searchResultList.selectedItems()) == 1
-        self.installButton.setEnabled(
-            enable and self.environmentsComboBox.currentIndex() > 0)
+        installEnable = (
+            len(self.searchResultList.selectedItems()) > 0 and
+            self.environmentsComboBox.currentIndex() > 0 and
+            self.__isPipAvailable()
+        )
+        self.installButton.setEnabled(installEnable)
+        self.installUserSiteButton.setEnabled(installEnable)
+        
         self.showDetailsButton.setEnabled(
-            enable and bool(self.searchResultList.selectedItems()[0].parent()))
+            len(self.searchResultList.selectedItems()) == 1 and
+            self.__isPipAvailable()
+        )
+    
+    def __updateSearchButton(self):
+        """
+        Private method to update the state of the search button.
+        """
+        self.searchButton.setEnabled(
+            bool(self.searchEdit.text()) and
+            self.__isPipAvailable()
+        )
     
     @pyqtSlot(bool)
     def on_searchToggleButton_toggled(self, checked):
@@ -431,6 +484,206 @@
             self.searchEdit.selectAll()
             
             self.__updateSearchActionButtons()
+            self.__updateSearchButton()
+    
+    @pyqtSlot(str)
+    def on_searchEdit_textChanged(self, txt):
+        """
+        Private slot handling a change of the search term.
+        
+        @param txt search term
+        @type str
+        """
+        self.__updateSearchButton()
+    
+    @pyqtSlot()
+    def on_searchButton_clicked(self):
+        """
+        Private slot handling a press of the search button.
+        """
+        self.__search()
+    
+    @pyqtSlot()
+    def on_searchResultList_itemSelectionChanged(self):
+        """
+        Private slot handling changes of the search result selection.
+        """
+        self.__updateSearchActionButtons()
+    
+    def __search(self):
+        """
+        Private method to perform the search.
+        """
+        self.searchResultList.clear()
+        self.searchInfoLabel.clear()
+        
+        self.searchButton.setEnabled(False)
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+        QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
+        
+        self.__query = [term for term in self.searchEdit.text().strip().split()
+                        if term not in self.SearchStopwords]
+        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.searchInfoLabel.setText(
+                    self.tr("%n package(s) found.", "", len(packages)))
+                wrapper = textwrap.TextWrapper(width=80)
+                count = 0
+                total = 0
+                for package in packages:
+                    itm = QTreeWidgetItem(
+                        self.searchResultList, [
+                            package['name'].strip(),
+                            "{0:4d}".format(package['score']),
+                            "\n".join([
+                                wrapper.fill(line) for line in
+                                package['summary'].strip().splitlines()
+                            ])
+                        ])
+                    itm.setData(0, self.SearchVersionRole, 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.searchInfoLabel.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.searchInfoLabel.setText(
+                self.tr("""<p>The package search did not return anything."""
+                        """</p>"""))
+        
+        header = self.searchResultList.header()
+        self.searchResultList.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.__finishSearch()
+    
+    def __finishSearch(self):
+        """
+        Private slot performing the search finishing actions.
+        """
+        QApplication.restoreOverrideCursor()
+        
+        self.__updateSearchActionButtons()
+        self.__updateSearchButton()
+        
+        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.searchInfoLabel.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
     
     #######################################################################
     ## Menu related methods below
--- a/PipInterface/__init__.py	Tue Feb 19 19:56:24 2019 +0100
+++ b/PipInterface/__init__.py	Wed Feb 20 19:44:13 2019 +0100
@@ -6,9 +6,3 @@
 """
 Package implementing the various pip dialogs and data.
 """
-
-from __future__ import unicode_literals
-
-DefaultPyPiUrl = "https://pypi.org"
-DefaultIndexUrlXml = DefaultPyPiUrl + "/pypi"
-DefaultIndexUrlPip = DefaultPyPiUrl + "/simple"

eric ide

mercurial