src/eric7/PipInterface/PipPackagesWidget.py

branch
eric7-maintenance
changeset 11118
967a88a16a21
parent 11063
bb05d1db9286
parent 11113
2e03383143e3
child 11155
e1843b6efa73
equal deleted inserted replaced
11064:c2cb561a39b0 11118:967a88a16a21
1 # -*- coding: utf-8 -*- 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (c) 2019 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> 3 # Copyright (c) 2019 - 2025 Detlev Offenbach <detlev@die-offenbachs.de>
4 # 4 #
5 5
6 """ 6 """
7 Module implementing the pip packages management widget. 7 Module implementing the pip packages management widget.
8 """ 8 """
9 9
10 import contextlib 10 import contextlib
11 import enum 11 import enum
12 import html.parser
13 import os 12 import os
14 import textwrap
15 13
16 from packaging.specifiers import InvalidSpecifier, SpecifierSet 14 from packaging.specifiers import InvalidSpecifier, SpecifierSet
17 from PyQt6.QtCore import Qt, QUrl, QUrlQuery, pyqtSlot 15 from PyQt6.QtCore import Qt, QUrl, QUrlQuery, pyqtSlot
18 from PyQt6.QtGui import QIcon 16 from PyQt6.QtGui import QDesktopServices, QIcon
19 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest
20 from PyQt6.QtWidgets import ( 17 from PyQt6.QtWidgets import (
21 QAbstractItemView,
22 QDialog, 18 QDialog,
23 QHeaderView, 19 QHeaderView,
24 QMenu, 20 QMenu,
25 QToolButton, 21 QToolButton,
26 QTreeWidgetItem, 22 QTreeWidgetItem,
35 31
36 from .PipVulnerabilityChecker import Package, VulnerabilityCheckError 32 from .PipVulnerabilityChecker import Package, VulnerabilityCheckError
37 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget 33 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget
38 34
39 35
40 class PypiSearchResultsParser(html.parser.HTMLParser):
41 """
42 Class implementing the parser for the PyPI search result page.
43 """
44
45 ClassPrefix = "package-snippet__"
46
47 def __init__(self, data):
48 """
49 Constructor
50
51 @param data data to be parsed
52 @type str
53 """
54 super().__init__()
55 self.__results = []
56 self.__activeClass = None
57 self.feed(data)
58
59 def __getClass(self, attrs):
60 """
61 Private method to extract the class attribute out of the list of
62 attributes.
63
64 @param attrs list of tag attributes as (name, value) tuples
65 @type list of tuple of (str, str)
66 @return value of the 'class' attribute or None
67 @rtype str
68 """
69 for name, value in attrs:
70 if name == "class":
71 return value
72
73 return None
74
75 def __getDate(self, attrs):
76 """
77 Private method to extract the datetime attribute out of the list of
78 attributes and process it.
79
80 @param attrs list of tag attributes as (name, value) tuples
81 @type list of tuple of (str, str)
82 @return value of the 'class' attribute or None
83 @rtype str
84 """
85 for name, value in attrs:
86 if name == "datetime":
87 return value.split("T")[0]
88
89 return None
90
91 def handle_starttag(self, tag, attrs):
92 """
93 Public method to process the start tag.
94
95 @param tag tag name (all lowercase)
96 @type str
97 @param attrs list of tag attributes as (name, value) tuples
98 @type list of tuple of (str, str)
99 """
100 if tag == "a" and self.__getClass(attrs) == "package-snippet":
101 self.__results.append({})
102
103 if tag in ("span", "p"):
104 tagClass = self.__getClass(attrs)
105 if tagClass in (
106 "package-snippet__name",
107 "package-snippet__description",
108 "package-snippet__version",
109 "package-snippet__released",
110 "package-snippet__created",
111 ):
112 self.__activeClass = tagClass
113 else:
114 self.__activeClass = None
115 elif tag == "time":
116 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
117 self.__results[-1][attributeName] = self.__getDate(attrs)
118 self.__activeClass = None
119 else:
120 self.__activeClass = None
121
122 def handle_data(self, data):
123 """
124 Public method process arbitrary data.
125
126 @param data data to be processed
127 @type str
128 """
129 if self.__activeClass is not None:
130 attributeName = self.__activeClass.replace(self.ClassPrefix, "")
131 self.__results[-1][attributeName] = data
132
133 def handle_endtag(self, _tag):
134 """
135 Public method to process the end tag.
136
137 @param _tag tag name (all lowercase) (unused)
138 @type str
139 """
140 self.__activeClass = None
141
142 def getResults(self):
143 """
144 Public method to get the extracted search results.
145
146 @return extracted result data
147 @rtype list of dict
148 """
149 return self.__results
150
151
152 class PipPackageInformationMode(enum.Enum): 36 class PipPackageInformationMode(enum.Enum):
153 """ 37 """
154 Class defining the show information process modes. 38 Class defining the show information process modes.
155 """ 39 """
156 40
201 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) 85 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
202 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) 86 self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus)
203 self.pipMenuButton.setShowMenuInside(True) 87 self.pipMenuButton.setShowMenuInside(True)
204 88
205 self.refreshButton.setIcon(EricPixmapCache.getIcon("reload")) 89 self.refreshButton.setIcon(EricPixmapCache.getIcon("reload"))
90 self.installButton.setIcon(EricPixmapCache.getIcon("plus"))
206 self.upgradeButton.setIcon(EricPixmapCache.getIcon("1uparrow")) 91 self.upgradeButton.setIcon(EricPixmapCache.getIcon("1uparrow"))
207 self.upgradeAllButton.setIcon(EricPixmapCache.getIcon("2uparrow")) 92 self.upgradeAllButton.setIcon(EricPixmapCache.getIcon("2uparrow"))
208 self.uninstallButton.setIcon(EricPixmapCache.getIcon("minus")) 93 self.uninstallButton.setIcon(EricPixmapCache.getIcon("minus"))
209 self.showPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info")) 94 self.showPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
210 self.searchToggleButton_1.setIcon(EricPixmapCache.getIcon("find")) 95 self.searchButton.setIcon(EricPixmapCache.getIcon("find"))
211 self.searchToggleButton_2.setIcon(EricPixmapCache.getIcon("find")) 96 self.cleanupButton.setIcon(EricPixmapCache.getIcon("clear"))
212 self.searchButton.setIcon(EricPixmapCache.getIcon("findNext"))
213 self.searchMoreButton.setIcon(EricPixmapCache.getIcon("plus"))
214 self.installButton.setIcon(EricPixmapCache.getIcon("plus"))
215 self.installUserSiteButton.setIcon(EricPixmapCache.getIcon("addUser"))
216 self.showDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
217 97
218 self.refreshDependenciesButton.setIcon(EricPixmapCache.getIcon("reload")) 98 self.refreshDependenciesButton.setIcon(EricPixmapCache.getIcon("reload"))
219 self.showDepPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info")) 99 self.showDepPackageDetailsButton.setIcon(EricPixmapCache.getIcon("info"))
220 self.dependencyRepairButton.setIcon(EricPixmapCache.getIcon("repair")) 100 self.dependencyRepairButton.setIcon(EricPixmapCache.getIcon("repair"))
221 self.dependencyRepairAllButton.setIcon(EricPixmapCache.getIcon("repairAll")) 101 self.dependencyRepairAllButton.setIcon(EricPixmapCache.getIcon("repairAll"))
260 project.projectOpened.connect(self.__projectOpened) 140 project.projectOpened.connect(self.__projectOpened)
261 project.projectClosed.connect(self.__projectClosed) 141 project.projectClosed.connect(self.__projectClosed)
262 142
263 self.__packageDetailsDialog = None 143 self.__packageDetailsDialog = None
264 144
145 self.installButton.clicked.connect(self.__installPackages)
146
265 self.__initPipMenu() 147 self.__initPipMenu()
266 self.__populateEnvironments() 148 self.__populateEnvironments()
267 self.__updateActionButtons() 149 self.__updateActionButtons()
268 self.__updateDepActionButtons() 150 self.__updateDepActionButtons()
269 151
270 self.statusLabel.hide() 152 self.statusLabel.hide()
271 self.searchWidget.hide()
272 self.__lastSearchPage = 0 153 self.__lastSearchPage = 0
273 154
274 self.__queryName = [] 155 self.__queryName = []
275 self.__querySummary = [] 156 self.__querySummary = []
276 157
423 def __updateActionButtons(self): 304 def __updateActionButtons(self):
424 """ 305 """
425 Private method to set the state of the action buttons. 306 Private method to set the state of the action buttons.
426 """ 307 """
427 if self.__isPipAvailable(): 308 if self.__isPipAvailable():
309 self.installButton.setEnabled(True)
428 self.upgradeButton.setEnabled(bool(self.__selectedUpdateableItems())) 310 self.upgradeButton.setEnabled(bool(self.__selectedUpdateableItems()))
429 self.uninstallButton.setEnabled(bool(self.packagesList.selectedItems())) 311 self.uninstallButton.setEnabled(bool(self.packagesList.selectedItems()))
430 self.upgradeAllButton.setEnabled(bool(self.__allUpdateableItems())) 312 self.upgradeAllButton.setEnabled(bool(self.__allUpdateableItems()))
431 self.showPackageDetailsButton.setEnabled( 313 self.showPackageDetailsButton.setEnabled(
432 len(self.packagesList.selectedItems()) == 1 314 len(self.packagesList.selectedItems()) == 1
433 ) 315 )
316 self.cleanupButton.setEnabled(True)
434 else: 317 else:
318 self.installButton.setEnabled(False)
435 self.upgradeButton.setEnabled(False) 319 self.upgradeButton.setEnabled(False)
436 self.uninstallButton.setEnabled(False) 320 self.uninstallButton.setEnabled(False)
437 self.upgradeAllButton.setEnabled(False) 321 self.upgradeAllButton.setEnabled(False)
438 self.showPackageDetailsButton.setEnabled(False) 322 self.showPackageDetailsButton.setEnabled(False)
323 self.cleanupButton.setEnabled(False)
439 324
440 def __refreshPackagesList(self): 325 def __refreshPackagesList(self):
441 """ 326 """
442 Private method to refresh the packages list. 327 Private method to refresh the packages list.
443 """ 328 """
486 callback=self.__updateOutdatedInfo, 371 callback=self.__updateOutdatedInfo,
487 ) 372 )
488 373
489 else: 374 else:
490 self.__updateActionButtons() 375 self.__updateActionButtons()
491 self.__updateSearchActionButtons()
492 self.__updateSearchButton()
493 self.__updateSearchMoreButton(False)
494 376
495 def __updateOutdatedInfo(self, outdatedPackages): 377 def __updateOutdatedInfo(self, outdatedPackages):
496 """ 378 """
497 Private method to process the list of outdated packages. 379 Private method to process the list of outdated packages.
498 380
510 self.packagesList.resizeColumnToContents( 392 self.packagesList.resizeColumnToContents(
511 PipPackagesWidget.AvailableVersionColumn 393 PipPackagesWidget.AvailableVersionColumn
512 ) 394 )
513 395
514 self.__updateActionButtons() 396 self.__updateActionButtons()
515 self.__updateSearchActionButtons()
516 self.__updateSearchButton()
517 self.__updateSearchMoreButton(False)
518 397
519 self.statusLabel.hide() 398 self.statusLabel.hide()
520 399
521 @pyqtSlot(str) 400 @pyqtSlot(str)
522 def on_environmentsComboBox_currentTextChanged(self, name): 401 def on_environmentsComboBox_currentTextChanged(self, name):
529 if name != self.__selectedEnvironment: 408 if name != self.__selectedEnvironment:
530 if name: 409 if name:
531 self.environmentPathLabel.setPath( 410 self.environmentPathLabel.setPath(
532 self.__pip.getVirtualenvInterpreter(name) 411 self.__pip.getVirtualenvInterpreter(name)
533 ) 412 )
534 self.searchNameEdit.setEnabled(True)
535 else: 413 else:
536 self.environmentPathLabel.setPath("") 414 self.environmentPathLabel.setPath("")
537 self.searchNameEdit.clear()
538 self.searchNameEdit.setEnabled(False)
539 self.searchResultList.clear()
540 if self.__packageDetailsDialog is not None: 415 if self.__packageDetailsDialog is not None:
541 self.__packageDetailsDialog.close() 416 self.__packageDetailsDialog.close()
542 417
543 if self.viewToggleButton.isChecked(): 418 if self.viewToggleButton.isChecked():
544 self.__refreshDependencyTree() 419 self.__refreshDependencyTree()
545 else: 420 else:
546 self.__refreshPackagesList() 421 self.__refreshPackagesList()
547 self.__selectedEnvironment = name 422 self.__selectedEnvironment = name
423
424 self.__updateActionButtons()
548 425
549 @pyqtSlot() 426 @pyqtSlot()
550 def on_localCheckBox_clicked(self): 427 def on_localCheckBox_clicked(self):
551 """ 428 """
552 Private slot handling the switching of the local mode. 429 Private slot handling the switching of the local mode.
835 packageVersion, 712 packageVersion,
836 vulnerabilities=vulnerabilities, 713 vulnerabilities=vulnerabilities,
837 upgradable=upgradable, 714 upgradable=upgradable,
838 ) 715 )
839 716
840 ####################################################################### 717 @pyqtSlot()
841 ## Search widget related methods below 718 def on_cleanupButton_clicked(self):
842 ####################################################################### 719 """
843 720 Private slot to cleanup the site-packages directory of the selected
844 def __updateSearchActionButtons(self): 721 environment.
845 """ 722 """
846 Private method to update the action button states of the search widget. 723 envName = self.environmentsComboBox.currentText()
847 """ 724 if envName:
848 installEnable = ( 725 ok = self.__pip.runCleanup(envName=envName)
849 len(self.searchResultList.selectedItems()) > 0 726 if ok:
850 and self.environmentsComboBox.currentIndex() > 0 727 EricMessageBox.information(
851 and self.__isPipAvailable()
852 )
853 self.installButton.setEnabled(installEnable)
854 self.installUserSiteButton.setEnabled(installEnable)
855
856 self.showDetailsButton.setEnabled(
857 len(self.searchResultList.selectedItems()) == 1 and self.__isPipAvailable()
858 )
859
860 def __updateSearchButton(self):
861 """
862 Private method to update the state of the search button.
863 """
864 self.searchButton.setEnabled(
865 bool(self.searchNameEdit.text()) and self.__isPipAvailable()
866 )
867
868 def __updateSearchMoreButton(self, enable):
869 """
870 Private method to update the state of the search more button.
871
872 @param enable flag indicating the desired enable state
873 @type bool
874 """
875 self.searchMoreButton.setEnabled(
876 enable and bool(self.searchNameEdit.text()) and self.__isPipAvailable()
877 )
878
879 @pyqtSlot(bool)
880 def on_searchToggleButton_1_toggled(self, checked):
881 """
882 Private slot to toggle the search widget.
883
884 @param checked state of the search widget button
885 @type bool
886 """
887 self.searchWidget.setVisible(checked)
888 self.searchToggleButton_2.setChecked(checked)
889
890 if checked:
891 self.searchNameEdit.setFocus(Qt.FocusReason.OtherFocusReason)
892 self.searchNameEdit.selectAll()
893
894 self.__updateSearchActionButtons()
895 self.__updateSearchButton()
896 self.__updateSearchMoreButton(False)
897
898 @pyqtSlot(bool)
899 def on_searchToggleButton_2_toggled(self, checked):
900 """
901 Private slot to toggle the search widget.
902
903 @param checked state of the search widget button
904 @type bool
905 """
906 self.searchToggleButton_1.setChecked(checked)
907
908 @pyqtSlot(str)
909 def on_searchNameEdit_textChanged(self, _txt):
910 """
911 Private slot handling a change of the search term.
912
913 @param _txt search term (unused)
914 @type str
915 """
916 self.__updateSearchButton()
917
918 @pyqtSlot()
919 def on_searchNameEdit_returnPressed(self):
920 """
921 Private slot initiating a search via a press of the Return key.
922 """
923 if bool(self.searchNameEdit.text()) and self.__isPipAvailable():
924 self.__searchFirst()
925
926 @pyqtSlot()
927 def on_searchButton_clicked(self):
928 """
929 Private slot handling a press of the search button.
930 """
931 self.__searchFirst()
932
933 @pyqtSlot()
934 def on_searchMoreButton_clicked(self):
935 """
936 Private slot handling a press of the search more button.
937 """
938 self.__search(self.__lastSearchPage + 1)
939
940 @pyqtSlot()
941 def on_searchResultList_itemSelectionChanged(self):
942 """
943 Private slot handling changes of the search result selection.
944 """
945 self.__updateSearchActionButtons()
946
947 def __searchFirst(self):
948 """
949 Private method to perform the search for packages.
950 """
951 self.searchResultList.clear()
952 self.searchInfoLabel.clear()
953
954 self.__updateSearchMoreButton(False)
955
956 self.__search()
957
958 def __search(self, page=1):
959 """
960 Private method to perform the search by calling the PyPI search URL.
961
962 @param page search page to retrieve (defaults to 1)
963 @type int (optional)
964 """
965 self.__lastSearchPage = page
966
967 self.searchButton.setEnabled(False)
968
969 searchTerm = self.searchNameEdit.text().strip()
970 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode()
971 urlQuery = QUrlQuery()
972 urlQuery.addQueryItem("q", searchTerm)
973 urlQuery.addQueryItem("page", str(page))
974 url = QUrl(self.__pip.getIndexUrlSearch())
975 url.setQuery(urlQuery)
976
977 request = QNetworkRequest(QUrl(url))
978 request.setAttribute(
979 QNetworkRequest.Attribute.CacheLoadControlAttribute,
980 QNetworkRequest.CacheLoadControl.AlwaysNetwork,
981 )
982 reply = self.__pip.getNetworkAccessManager().get(request)
983 reply.finished.connect(lambda: self.__searchResponse(reply))
984 self.__replies.append(reply)
985
986 def __searchResponse(self, reply):
987 """
988 Private method to extract the search result data from the response.
989
990 @param reply reference to the reply object containing the data
991 @type QNetworkReply
992 """
993 if reply in self.__replies:
994 self.__replies.remove(reply)
995
996 urlQuery = QUrlQuery(reply.url())
997 searchTerm = urlQuery.queryItemValue("q")
998
999 if reply.error() != QNetworkReply.NetworkError.NoError:
1000 EricMessageBox.warning(
1001 None,
1002 self.tr("Search PyPI"),
1003 self.tr(
1004 "<p>Received an error while searching for <b>{0}</b>.</p>"
1005 "<p>Error: {1}</p>"
1006 ).format(searchTerm, reply.errorString()),
1007 )
1008 reply.deleteLater()
1009 return
1010
1011 data = bytes(reply.readAll()).decode()
1012 reply.deleteLater()
1013
1014 results = PypiSearchResultsParser(data).getResults()
1015 if results:
1016 # PyPI returns max. 20 entries per page
1017 if len(results) < 20:
1018 msg = self.tr(
1019 "%n package(s) found.",
1020 "",
1021 (self.__lastSearchPage - 1) * 20 + len(results),
1022 )
1023 self.__updateSearchMoreButton(False)
1024 else:
1025 msg = self.tr("Showing first {0} packages found.").format(
1026 self.__lastSearchPage * 20
1027 )
1028 self.__updateSearchMoreButton(True)
1029 self.searchInfoLabel.setText(msg)
1030 lastItem = self.searchResultList.topLevelItem(
1031 self.searchResultList.topLevelItemCount() - 1
1032 )
1033 else:
1034 self.__updateSearchMoreButton(False)
1035 if self.__lastSearchPage == 1:
1036 EricMessageBox.warning(
1037 self, 728 self,
1038 self.tr("Search PyPI"), 729 self.tr("Cleanup Environment"),
1039 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format( 730 self.tr("The environment cleanup was successful."),
1040 searchTerm
1041 ),
1042 )
1043 self.searchInfoLabel.setText(
1044 self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format(
1045 searchTerm
1046 )
1047 ) 731 )
1048 else: 732 else:
1049 EricMessageBox.warning( 733 EricMessageBox.warning(
1050 self, 734 self,
1051 self.tr("Search PyPI"), 735 self.tr("Cleanup Environment"),
1052 self.tr( 736 self.tr(
1053 """<p>There were no more results for <b>{0}</b>.</p>""" 737 "Some leftover package directories could not been removed."
1054 ).format(searchTerm), 738 " Delete them manually."
739 ),
1055 ) 740 )
1056 lastItem = None 741
1057 742 @pyqtSlot()
1058 wrapper = textwrap.TextWrapper(width=80) 743 def on_searchButton_clicked(self):
1059 for result in results: 744 """
1060 try: 745 Private slot to open a web browser for package searching.
1061 description = "\n".join( 746 """
1062 [ 747 url = QUrl(self.__pip.getIndexUrlSearch())
1063 wrapper.fill(line) 748
1064 for line in result["description"].strip().splitlines() 749 searchTerm = self.searchEdit.text().strip()
1065 ] 750 if searchTerm:
1066 ) 751 searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode()
1067 except KeyError: 752 urlQuery = QUrlQuery()
1068 description = "" 753 urlQuery.addQueryItem("q", searchTerm)
1069 date = result["released"] if "released" in result else result["created"] 754 url.setQuery(urlQuery)
1070 itm = QTreeWidgetItem( 755
1071 self.searchResultList, 756 QDesktopServices.openUrl(url)
1072 [ 757
1073 result["name"].strip(), 758 @pyqtSlot()
1074 result["version"], 759 def on_searchEdit_returnPressed(self):
1075 date.strip(), 760 """
1076 description, 761 Private slot to handle the press of the Return key in the search line edit.
1077 ], 762 """
1078 ) 763 self.on_searchButton_clicked()
1079 itm.setData(0, self.SearchVersionRole, result["version"])
1080
1081 if lastItem:
1082 self.searchResultList.scrollToItem(
1083 lastItem, QAbstractItemView.ScrollHint.PositionAtTop
1084 )
1085
1086 header = self.searchResultList.header()
1087 header.setStretchLastSection(False)
1088 header.resizeSections(QHeaderView.ResizeMode.ResizeToContents)
1089 headerSize = 0
1090 for col in range(header.count()):
1091 headerSize += header.sectionSize(col)
1092 if headerSize < header.width():
1093 header.setStretchLastSection(True)
1094
1095 self.__finishSearch()
1096
1097 def __finishSearch(self):
1098 """
1099 Private slot performing the search finishing actions.
1100 """
1101 self.__updateSearchActionButtons()
1102 self.__updateSearchButton()
1103
1104 self.searchNameEdit.setFocus(Qt.FocusReason.OtherFocusReason)
1105
1106 @pyqtSlot()
1107 def on_installButton_clicked(self):
1108 """
1109 Private slot to handle pressing the Install button..
1110 """
1111 packages = [
1112 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1113 ]
1114 self.executeInstallPackages(packages)
1115
1116 @pyqtSlot()
1117 def on_installUserSiteButton_clicked(self):
1118 """
1119 Private slot to handle pressing the Install to User-Site button..
1120 """
1121 packages = [
1122 itm.text(0).strip() for itm in self.searchResultList.selectedItems()
1123 ]
1124 self.executeInstallPackages(packages, userSite=True)
1125 764
1126 def executeInstallPackages(self, packages, userSite=False): 765 def executeInstallPackages(self, packages, userSite=False):
1127 """ 766 """
1128 Public method to install the given list of packages. 767 Public method to install the given list of packages.
1129 768
1134 """ 773 """
1135 venvName = self.environmentsComboBox.currentText() 774 venvName = self.environmentsComboBox.currentText()
1136 if venvName and packages: 775 if venvName and packages:
1137 self.__pip.installPackages(packages, venvName=venvName, userSite=userSite) 776 self.__pip.installPackages(packages, venvName=venvName, userSite=userSite)
1138 self.on_refreshButton_clicked() 777 self.on_refreshButton_clicked()
1139
1140 @pyqtSlot()
1141 def on_showDetailsButton_clicked(self):
1142 """
1143 Private slot to handle pressing the Show Details button.
1144 """
1145 self.__showSearchedDetails()
1146
1147 @pyqtSlot(QTreeWidgetItem, int)
1148 def on_searchResultList_itemActivated(self, item, column):
1149 """
1150 Private slot reacting on an search result item activation.
1151
1152 @param item reference to the activated item
1153 @type QTreeWidgetItem
1154 @param column activated column
1155 @type int
1156 """
1157 self.__showSearchedDetails(item)
1158
1159 def __showSearchedDetails(self, item=None):
1160 """
1161 Private slot to show details about the selected search result package.
1162
1163 @param item reference to the search result item to show details for
1164 @type QTreeWidgetItem
1165 """
1166 self.showDetailsButton.setEnabled(False)
1167
1168 if not item:
1169 item = self.searchResultList.selectedItems()[0]
1170
1171 packageVersion = item.data(0, self.SearchVersionRole)
1172 packageName = item.text(0)
1173
1174 self.__showPackageDetails(packageName, packageVersion, installable=True)
1175 778
1176 def __showPackageDetails( 779 def __showPackageDetails(
1177 self, 780 self,
1178 packageName, 781 packageName,
1179 packageVersion, 782 packageVersion,
1201 804
1202 with EricOverrideCursor(): 805 with EricOverrideCursor():
1203 packageData = self.__pip.getPackageDetails(packageName, packageVersion) 806 packageData = self.__pip.getPackageDetails(packageName, packageVersion)
1204 807
1205 if packageData: 808 if packageData:
1206 self.showDetailsButton.setEnabled(True)
1207
1208 if installable: 809 if installable:
1209 buttonsMode = PipPackageDetailsDialog.ButtonInstall 810 buttonsMode = PipPackageDetailsDialog.ButtonInstall
1210 elif upgradable: 811 elif upgradable:
1211 buttonsMode = ( 812 buttonsMode = (
1212 PipPackageDetailsDialog.ButtonRemove 813 PipPackageDetailsDialog.ButtonRemove

eric ide

mercurial