11 import os |
11 import os |
12 import html.parser |
12 import html.parser |
13 import contextlib |
13 import contextlib |
14 |
14 |
15 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery |
15 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery |
|
16 from PyQt6.QtGui import QIcon |
16 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest |
17 from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest |
17 from PyQt6.QtWidgets import ( |
18 from PyQt6.QtWidgets import ( |
18 QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, |
19 QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, |
19 QMenu, QDialog |
20 QMenu, QDialog |
20 ) |
21 ) |
146 ShowProcessClassifiersMode = 1 |
147 ShowProcessClassifiersMode = 1 |
147 ShowProcessEntryPointsMode = 2 |
148 ShowProcessEntryPointsMode = 2 |
148 ShowProcessFilesListMode = 3 |
149 ShowProcessFilesListMode = 3 |
149 |
150 |
150 SearchVersionRole = Qt.ItemDataRole.UserRole + 1 |
151 SearchVersionRole = Qt.ItemDataRole.UserRole + 1 |
|
152 VulnerabilityRole = Qt.ItemDataRole.UserRole + 2 |
|
153 |
|
154 PackageColumn = 0 |
|
155 InstalledVersionColumn = 1 |
|
156 AvailableVersionColumn = 2 |
|
157 VulnerabilityColumn = 3 |
151 |
158 |
152 def __init__(self, pip, parent=None): |
159 def __init__(self, pip, parent=None): |
153 """ |
160 """ |
154 Constructor |
161 Constructor |
155 |
162 |
187 self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) |
194 self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) |
188 |
195 |
189 self.__pip = pip |
196 self.__pip = pip |
190 |
197 |
191 self.packagesList.header().setSortIndicator( |
198 self.packagesList.header().setSortIndicator( |
192 0, Qt.SortOrder.AscendingOrder) |
199 PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder) |
193 |
200 |
194 self.__infoLabels = { |
201 self.__infoLabels = { |
195 "name": self.tr("Name:"), |
202 "name": self.tr("Name:"), |
196 "version": self.tr("Version:"), |
203 "version": self.tr("Version:"), |
197 "location": self.tr("Location:"), |
204 "location": self.tr("Location:"), |
322 @return list of selected items that can be updated |
329 @return list of selected items that can be updated |
323 @rtype list of QTreeWidgetItem |
330 @rtype list of QTreeWidgetItem |
324 """ |
331 """ |
325 return [ |
332 return [ |
326 itm for itm in self.packagesList.selectedItems() |
333 itm for itm in self.packagesList.selectedItems() |
327 if bool(itm.text(2)) |
334 if bool(itm.text(PipPackagesWidget.AvailableVersionColumn)) |
328 ] |
335 ] |
329 |
336 |
330 def __allUpdateableItems(self): |
337 def __allUpdateableItems(self): |
331 """ |
338 """ |
332 Private method to get a list of all items that can be updated. |
339 Private method to get a list of all items that can be updated. |
335 @rtype list of QTreeWidgetItem |
342 @rtype list of QTreeWidgetItem |
336 """ |
343 """ |
337 updateableItems = [] |
344 updateableItems = [] |
338 for index in range(self.packagesList.topLevelItemCount()): |
345 for index in range(self.packagesList.topLevelItemCount()): |
339 itm = self.packagesList.topLevelItem(index) |
346 itm = self.packagesList.topLevelItem(index) |
340 if itm.text(2): |
347 if itm.text(PipPackagesWidget.AvailableVersionColumn): |
341 updateableItems.append(itm) |
348 updateableItems.append(itm) |
342 |
349 |
343 return updateableItems |
350 return updateableItems |
344 |
351 |
345 def __updateActionButtons(self): |
352 def __updateActionButtons(self): |
382 localPackages=self.localCheckBox.isChecked(), |
389 localPackages=self.localCheckBox.isChecked(), |
383 notRequired=self.notRequiredCheckBox.isChecked(), |
390 notRequired=self.notRequiredCheckBox.isChecked(), |
384 usersite=self.userCheckBox.isChecked(), |
391 usersite=self.userCheckBox.isChecked(), |
385 ) |
392 ) |
386 for package, version in installedPackages: |
393 for package, version in installedPackages: |
387 QTreeWidgetItem(self.packagesList, [package, version]) |
394 QTreeWidgetItem(self.packagesList, |
|
395 [package, version, "", ""]) |
388 self.packagesList.setUpdatesEnabled(True) |
396 self.packagesList.setUpdatesEnabled(True) |
389 self.statusLabel.setText( |
397 self.statusLabel.setText( |
390 self.tr("Getting outdated packages...")) |
398 self.tr("Getting outdated packages...")) |
391 QApplication.processEvents() |
399 QApplication.processEvents() |
392 |
400 |
404 Qt.MatchFlag.MatchExactly | |
412 Qt.MatchFlag.MatchExactly | |
405 Qt.MatchFlag.MatchCaseSensitive |
413 Qt.MatchFlag.MatchCaseSensitive |
406 ) |
414 ) |
407 if items: |
415 if items: |
408 itm = items[0] |
416 itm = items[0] |
409 itm.setText(2, latest) |
417 itm.setText( |
|
418 PipPackagesWidget.AvailableVersionColumn, |
|
419 latest) |
410 |
420 |
411 self.packagesList.sortItems(0, Qt.SortOrder.AscendingOrder) |
421 self.packagesList.sortItems( |
|
422 PipPackagesWidget.PackageColumn, |
|
423 Qt.SortOrder.AscendingOrder) |
412 for col in range(self.packagesList.columnCount()): |
424 for col in range(self.packagesList.columnCount()): |
413 self.packagesList.resizeColumnToContents(col) |
425 self.packagesList.resizeColumnToContents(col) |
414 self.packagesList.setUpdatesEnabled(True) |
426 self.packagesList.setUpdatesEnabled(True) |
|
427 |
|
428 # 3. update with vulnerability information |
|
429 if self.vulnerabilityCheckBox.isChecked(): |
|
430 self.__updateVulnerabilityData() |
415 self.statusLabel.hide() |
431 self.statusLabel.hide() |
416 |
432 |
417 self.__updateActionButtons() |
433 self.__updateActionButtons() |
418 self.__updateSearchActionButtons() |
434 self.__updateSearchActionButtons() |
419 self.__updateSearchButton() |
435 self.__updateSearchButton() |
426 @param index index of the selected Python environment |
442 @param index index of the selected Python environment |
427 @type int |
443 @type int |
428 """ |
444 """ |
429 self.__refreshPackagesList() |
445 self.__refreshPackagesList() |
430 |
446 |
431 @pyqtSlot(bool) |
447 @pyqtSlot() |
432 def on_localCheckBox_clicked(self, checked): |
448 def on_localCheckBox_clicked(self): |
433 """ |
449 """ |
434 Private slot handling the switching of the local mode. |
450 Private slot handling the switching of the local mode. |
435 |
|
436 @param checked state of the local check box |
|
437 @type bool |
|
438 """ |
451 """ |
439 self.__refreshPackagesList() |
452 self.__refreshPackagesList() |
440 |
453 |
441 @pyqtSlot(bool) |
454 @pyqtSlot() |
442 def on_notRequiredCheckBox_clicked(self, checked): |
455 def on_notRequiredCheckBox_clicked(self): |
443 """ |
456 """ |
444 Private slot handling the switching of the 'not required' mode. |
457 Private slot handling the switching of the 'not required' mode. |
445 |
|
446 @param checked state of the 'not required' check box |
|
447 @type bool |
|
448 """ |
458 """ |
449 self.__refreshPackagesList() |
459 self.__refreshPackagesList() |
450 |
460 |
451 @pyqtSlot(bool) |
461 @pyqtSlot() |
452 def on_userCheckBox_clicked(self, checked): |
462 def on_userCheckBox_clicked(self): |
453 """ |
463 """ |
454 Private slot handling the switching of the 'user-site' mode. |
464 Private slot handling the switching of the 'user-site' mode. |
455 |
|
456 @param checked state of the 'user-site' check box |
|
457 @type bool |
|
458 """ |
465 """ |
459 self.__refreshPackagesList() |
466 self.__refreshPackagesList() |
460 |
467 |
461 @pyqtSlot() |
468 @pyqtSlot() |
462 def on_packagesList_itemSelectionChanged(self): |
469 def on_packagesList_itemSelectionChanged(self): |
614 @pyqtSlot() |
621 @pyqtSlot() |
615 def on_uninstallButton_clicked(self): |
622 def on_uninstallButton_clicked(self): |
616 """ |
623 """ |
617 Private slot to remove selected packages of the selected environment. |
624 Private slot to remove selected packages of the selected environment. |
618 """ |
625 """ |
619 packages = [itm.text(0) for itm in self.packagesList.selectedItems()] |
626 packages = [itm.text(PipPackagesWidget.PackageColumn) |
|
627 for itm in self.packagesList.selectedItems()] |
620 self.executeUninstallPackages(packages) |
628 self.executeUninstallPackages(packages) |
621 |
629 |
622 def executeUninstallPackages(self, packages): |
630 def executeUninstallPackages(self, packages): |
623 """ |
631 """ |
624 Public method to uninstall the given list of packages. |
632 Public method to uninstall the given list of packages. |
651 """ |
659 """ |
652 Private slot to show information for the selected package. |
660 Private slot to show information for the selected package. |
653 """ |
661 """ |
654 item = self.packagesList.selectedItems()[0] |
662 item = self.packagesList.selectedItems()[0] |
655 if item: |
663 if item: |
656 packageName = item.text(0) |
664 packageName = item.text(PipPackagesWidget.PackageColumn) |
657 upgradable = bool(item.text(2)) |
665 upgradable = bool(item.text(2)) |
658 # show details for available version or installed one |
666 # show details for available version or installed one |
659 if item.text(2): |
667 if item.text(2): |
660 packageVersion = item.text(2) |
668 packageVersion = item.text(2) |
661 else: |
669 else: |
1135 @pyqtSlot() |
1143 @pyqtSlot() |
1136 def __reinstallPackages(self): |
1144 def __reinstallPackages(self): |
1137 """ |
1145 """ |
1138 Private slot to force a re-installation of the selected packages. |
1146 Private slot to force a re-installation of the selected packages. |
1139 """ |
1147 """ |
1140 packages = [itm.text(0) for itm in self.packagesList.selectedItems()] |
1148 packages = [itm.text(PipPackagesWidget.PackageColumn) |
|
1149 for itm in self.packagesList.selectedItems()] |
1141 venvName = self.environmentsComboBox.currentText() |
1150 venvName = self.environmentsComboBox.currentText() |
1142 if venvName and packages: |
1151 if venvName and packages: |
1143 self.__pip.installPackages(packages, venvName=venvName, |
1152 self.__pip.installPackages(packages, venvName=venvName, |
1144 forceReinstall=True) |
1153 forceReinstall=True) |
1145 self.on_refreshButton_clicked() |
1154 self.on_refreshButton_clicked() |
1281 Private slot to empty the pip cache. |
1290 Private slot to empty the pip cache. |
1282 """ |
1291 """ |
1283 venvName = self.environmentsComboBox.currentText() |
1292 venvName = self.environmentsComboBox.currentText() |
1284 if venvName: |
1293 if venvName: |
1285 self.__pip.cachePurge(venvName) |
1294 self.__pip.cachePurge(venvName) |
|
1295 |
|
1296 ################################################################## |
|
1297 ## Interface to the vulnerability checks below |
|
1298 ################################################################## |
|
1299 |
|
1300 @pyqtSlot(bool) |
|
1301 def on_vulnerabilityCheckBox_clicked(self, checked): |
|
1302 """ |
|
1303 Private slot handling a change of the automatic vulnerability checks. |
|
1304 |
|
1305 @param checked flag indicating the state of the check box |
|
1306 @type bool |
|
1307 """ |
|
1308 if checked: |
|
1309 self.__updateVulnerabilityData(clearFirst=True) |
|
1310 |
|
1311 @pyqtSlot() |
|
1312 def __clearVulnerabilityInfo(self): |
|
1313 """ |
|
1314 Private slot to clear the vulnerability info. |
|
1315 """ |
|
1316 for row in range(self.packagesList.topLevelItemCount()): |
|
1317 itm = self.packagesList.topLevelItem(row) |
|
1318 itm.setText(PipPackagesWidget.VulnerabilityColumn, "") |
|
1319 itm.setToolTip(PipPackagesWidget.VulnerabilityColumn, "") |
|
1320 itm.setIcon(PipPackagesWidget.VulnerabilityColumn, QIcon()) |
|
1321 itm.setData(PipPackagesWidget.VulnerabilityColumn, |
|
1322 PipPackagesWidget.VulnerabilityRole, |
|
1323 None) |
|
1324 |
|
1325 @pyqtSlot() |
|
1326 def __updateVulnerabilityData(self, clearFirst=True): |
|
1327 """ |
|
1328 Private slot to update the shown vulnerability info. |
|
1329 |
|
1330 @param clearFirst flag indicating to clear the vulnerability info first |
|
1331 (defaults to True) |
|
1332 @type bool (optional) |
|
1333 """ |
|
1334 if clearFirst: |
|
1335 self.__clearVulnerabilityInfo() |
|
1336 |
|
1337 packages = [] # TODO: fill this list with real data |
|
1338 |
|
1339 error, vulnerabilities = ( |
|
1340 self.__pip.getVulnerabilityChecker().check(packages) |
|
1341 ) |