Plugins/UiExtensionPlugins/PipInterface/PipListDialog.py

branch
maintenance
changeset 6826
c6dda2cbe081
parent 6764
d14ddbfbbd36
parent 6825
e659bb96cdfa
child 6827
14680839ad7a
equal deleted inserted replaced
6764:d14ddbfbbd36 6826:c6dda2cbe081
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2015 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to list installed packages.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode # __IGNORE_EXCEPTION__
13 except NameError:
14 pass
15
16 import json
17
18 from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer
19 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
20 QApplication, QTreeWidgetItem, QHeaderView
21
22 from E5Gui import E5MessageBox
23
24 from .Ui_PipListDialog import Ui_PipListDialog
25
26 import Preferences
27
28
29 class PipListDialog(QDialog, Ui_PipListDialog):
30 """
31 Class implementing a dialog to list installed packages.
32 """
33 CommandArguments = {
34 "list": ["list", "--format=json"],
35 "uptodate": ["list", "--uptodate", "--format=json"],
36 "outdated": ["list", "--outdated", "--format=json"],
37 }
38
39 ShowProcessGeneralMode = 0
40 ShowProcessClassifiersMode = 1
41 ShowProcessEntryPointsMode = 2
42 ShowProcessFilesListMode = 3
43
44 def __init__(self, pip, mode, indexUrl, title, parent=None):
45 """
46 Constructor
47
48 @param pip reference to the master object
49 @type Pip
50 @param mode list command mode (one of 'list', 'uptodate', 'outdated')
51 @type str
52 @param indexUrl URL of the pypi index
53 @type str
54 @param title title of the dialog
55 @type str
56 @param parent reference to the parent widget
57 @type QWidget
58 """
59 assert mode in PipListDialog.CommandArguments
60
61 super(PipListDialog, self).__init__(parent)
62 self.setupUi(self)
63 self.setWindowFlags(Qt.Window)
64
65 self.setWindowTitle(title)
66
67 self.__refreshButton = self.buttonBox.addButton(
68 self.tr("&Refresh"), QDialogButtonBox.ActionRole)
69 self.__refreshButton.setEnabled(False)
70 if mode == "outdated":
71 self.__upgradeButton = self.buttonBox.addButton(
72 self.tr("Up&grade"), QDialogButtonBox.ActionRole)
73 self.__upgradeButton.setEnabled(False)
74 self.__upgradeAllButton = self.buttonBox.addButton(
75 self.tr("Upgrade &All"), QDialogButtonBox.ActionRole)
76 self.__upgradeAllButton.setEnabled(False)
77 else:
78 self.__upgradeButton = None
79 self.__upgradeAllButton = None
80 self.__uninstallButton = self.buttonBox.addButton(
81 self.tr("&Uninstall"), QDialogButtonBox.ActionRole)
82 self.__uninstallButton.setEnabled(False)
83
84 self.__pip = pip
85 self.__mode = mode
86 self.__ioEncoding = Preferences.getSystem("IOEncoding")
87 self.__indexUrl = indexUrl
88 self.__errors = ""
89 self.__output = []
90
91 self.__nothingStrings = {
92 "list": self.tr("Nothing to show"),
93 "uptodate": self.tr("All packages outdated"),
94 "outdated": self.tr("All packages up-to-date"),
95 }
96
97 self.venvComboBox.addItem(self.__pip.getDefaultEnvironmentString())
98 projectVenv = self.__pip.getProjectEnvironmentString()
99 if projectVenv:
100 self.venvComboBox.addItem(projectVenv)
101 self.venvComboBox.addItems(self.__pip.getVirtualenvNames())
102
103 if mode == "list":
104 self.infoLabel.setText(self.tr("Installed Packages:"))
105 self.packageList.setHeaderLabels([
106 self.tr("Package"),
107 self.tr("Version"),
108 ])
109 elif mode == "uptodate":
110 self.infoLabel.setText(self.tr("Up-to-date Packages:"))
111 self.packageList.setHeaderLabels([
112 self.tr("Package"),
113 self.tr("Version"),
114 ])
115 elif mode == "outdated":
116 self.infoLabel.setText(self.tr("Outdated Packages:"))
117 self.packageList.setHeaderLabels([
118 self.tr("Package"),
119 self.tr("Current Version"),
120 self.tr("Latest Version"),
121 self.tr("Package Type"),
122 ])
123
124 self.packageList.header().setSortIndicator(0, Qt.AscendingOrder)
125
126 self.__infoLabels = {
127 "name": self.tr("Name:"),
128 "version": self.tr("Version:"),
129 "location": self.tr("Location:"),
130 "requires": self.tr("Requires:"),
131 "summary": self.tr("Summary:"),
132 "home-page": self.tr("Homepage:"),
133 "author": self.tr("Author:"),
134 "author-email": self.tr("Author Email:"),
135 "license": self.tr("License:"),
136 "metadata-version": self.tr("Metadata Version:"),
137 "installer": self.tr("Installer:"),
138 "classifiers": self.tr("Classifiers:"),
139 "entry-points": self.tr("Entry Points:"),
140 "files": self.tr("Files:"),
141 }
142 self.infoWidget.setHeaderLabels(["Key", "Value"])
143
144 self.process = QProcess()
145 self.process.finished.connect(self.__procFinished)
146 self.process.readyReadStandardOutput.connect(self.__readStdout)
147 self.process.readyReadStandardError.connect(self.__readStderr)
148
149 self.show()
150 QApplication.processEvents()
151
152 def __stopProcess(self):
153 """
154 Private slot to stop the running process.
155 """
156 if self.process.state() != QProcess.NotRunning:
157 self.process.terminate()
158 QTimer.singleShot(2000, self.process.kill)
159 self.process.waitForFinished(3000)
160
161 QApplication.restoreOverrideCursor()
162
163 def closeEvent(self, e):
164 """
165 Protected slot implementing a close event handler.
166
167 @param e close event
168 @type QCloseEvent
169 """
170 self.__stopProcess()
171 e.accept()
172
173 def __finish(self):
174 """
175 Private slot called when the process finished or the user pressed
176 the cancel button.
177 """
178 self.__stopProcess()
179
180 self.__processOutput()
181
182 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
183 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
184 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
185 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
186 Qt.OtherFocusReason)
187 self.__refreshButton.setEnabled(True)
188
189 if self.packageList.topLevelItemCount() == 0:
190 QTreeWidgetItem(self.packageList,
191 [self.__nothingStrings[self.__mode]])
192 if self.__errors and not self.__errors.startswith("DEPRECATION"):
193 E5MessageBox.critical(
194 self,
195 self.windowTitle(),
196 self.tr("""<p>The command failed.</p>"""
197 """<p>Reason: {0}</p>""").format(
198 self.__errors.replace("\r\n", "<br/>")
199 .replace("\n", "<br/>").replace("\r", "<br/>")
200 .replace(" ", "&nbsp;")))
201 if self.__upgradeAllButton is not None:
202 self.__upgradeAllButton.setEnabled(False)
203 else:
204 if self.__upgradeAllButton is not None:
205 self.__upgradeAllButton.setEnabled(True)
206
207 self.packageList.sortItems(
208 0,
209 self.packageList.header().sortIndicatorOrder())
210 self.packageList.header().resizeSections(
211 QHeaderView.ResizeToContents)
212 self.packageList.header().setStretchLastSection(True)
213
214 @pyqtSlot(QAbstractButton)
215 def on_buttonBox_clicked(self, button):
216 """
217 Private slot called by a button of the button box clicked.
218
219 @param button button that was clicked
220 @type QAbstractButton
221 """
222 if button == self.buttonBox.button(QDialogButtonBox.Close):
223 self.close()
224 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
225 self.__finish()
226 elif button == self.__refreshButton:
227 self.__refresh()
228 elif button == self.__upgradeButton:
229 self.__upgradePackages()
230 elif button == self.__upgradeAllButton:
231 self.__upgradeAllPackages()
232 elif button == self.__uninstallButton:
233 self.__uninstallPackages()
234
235 def __procFinished(self, exitCode, exitStatus):
236 """
237 Private slot connected to the finished signal.
238
239 @param exitCode exit code of the process
240 @type int
241 @param exitStatus exit status of the process
242 @type QProcess.ExitStatus
243 """
244 self.__finish()
245
246 def __refresh(self):
247 """
248 Private slot to refresh the displayed list.
249 """
250 self.__stopProcess()
251 self.start()
252
253 def start(self):
254 """
255 Public method to start the command.
256 """
257 self.packageList.clear()
258 self.__errors = ""
259 self.__output = []
260
261 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
262 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
263 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
264 self.__refreshButton.setEnabled(False)
265 if self.__upgradeAllButton is not None:
266 self.__upgradeAllButton.setEnabled(False)
267 QApplication.processEvents()
268
269 QApplication.setOverrideCursor(Qt.WaitCursor)
270 QApplication.processEvents()
271
272 venvName = self.venvComboBox.currentText()
273 interpreter = self.__pip.getVirtualenvInterpreter(venvName)
274 if not interpreter:
275 return
276
277 args = ["-m", "pip"] + PipListDialog.CommandArguments[self.__mode]
278 if self.localCheckBox.isChecked():
279 args.append("--local")
280 if self.notRequiredCheckBox.isChecked():
281 args.append("--not-required")
282 if self.userCheckBox.isChecked():
283 args.append("--user")
284
285 if self.__indexUrl:
286 args.append("--index-url")
287 args.append(self.__indexUrl + "/simple")
288
289 self.process.start(interpreter, args)
290 procStarted = self.process.waitForStarted(5000)
291 if not procStarted:
292 self.buttonBox.setFocus()
293 self.__stopProcess()
294 E5MessageBox.critical(
295 self,
296 self.tr('Process Generation Error'),
297 self.tr(
298 'The process {0} could not be started.'
299 ).format(interpreter))
300 self.__finish()
301
302 def __processOutput(self):
303 """
304 Private method to process the captured output.
305 """
306 if self.__output:
307 try:
308 packageData = json.loads("\n".join(self.__output))
309 for package in packageData:
310 data = [
311 package["name"],
312 package["version"],
313 ]
314 if self.__mode == "outdated":
315 data.extend([
316 package["latest_version"],
317 package["latest_filetype"],
318 ])
319 QTreeWidgetItem(self.packageList, data)
320 except ValueError as err:
321 self.__errors += str(err) + "\n"
322 self.__errors += "received output:\n"
323 self.__errors += "\n".join(self.__output)
324
325 def __readStdout(self):
326 """
327 Private slot to handle the readyReadStandardOutput signal.
328
329 It reads the output of the process, formats it and inserts it into
330 the contents pane.
331 """
332 self.process.setReadChannel(QProcess.StandardOutput)
333
334 while self.process.canReadLine():
335 line = str(self.process.readLine(), self.__ioEncoding,
336 'replace').strip()
337 self.__output.append(line)
338
339 def __readStderr(self):
340 """
341 Private slot to handle the readyReadStandardError signal.
342
343 It reads the error output of the process and inserts it into the
344 error pane.
345 """
346 self.__errors += str(self.process.readAllStandardError(),
347 self.__ioEncoding, 'replace')
348
349 @pyqtSlot(str)
350 def on_venvComboBox_activated(self, txt):
351 """
352 Private slot handling the selection of a virtual environment.
353
354 @param txt virtual environment
355 @type str
356 """
357 self.__refresh()
358
359 @pyqtSlot(bool)
360 def on_localCheckBox_clicked(self, checked):
361 """
362 Private slot handling the switching of the local mode.
363
364 @param checked state of the local check box
365 @type bool
366 """
367 self.__refresh()
368
369 @pyqtSlot(bool)
370 def on_notRequiredCheckBox_clicked(self, checked):
371 """
372 Private slot handling the switching of the 'not required' mode.
373
374 @param checked state of the 'not required' check box
375 @type bool
376 """
377 self.__refresh()
378
379 @pyqtSlot(bool)
380 def on_userCheckBox_clicked(self, checked):
381 """
382 Private slot handling the switching of the 'user-site' mode.
383
384 @param checked state of the 'user-site' check box
385 @type bool
386 """
387 self.__refresh()
388
389 @pyqtSlot()
390 def on_packageList_itemSelectionChanged(self):
391 """
392 Private slot handling the selection of a package.
393 """
394 self.infoWidget.clear()
395
396 if len(self.packageList.selectedItems()) == 1:
397 itm = self.packageList.selectedItems()[0]
398
399 environment = self.venvComboBox.currentText()
400 interpreter = self.__pip.getVirtualenvInterpreter(environment)
401 if not interpreter:
402 return
403
404 QApplication.setOverrideCursor(Qt.WaitCursor)
405
406 args = ["-m", "pip", "show"]
407 if self.verboseCheckBox.isChecked():
408 args.append("--verbose")
409 if self.installedFilesCheckBox.isChecked():
410 args.append("--files")
411 args.append(itm.text(0))
412 success, output = self.__pip.runProcess(args, interpreter)
413
414 if success and output:
415 mode = PipListDialog.ShowProcessGeneralMode
416 for line in output.splitlines():
417 line = line.rstrip()
418 if line != "---":
419 if mode != PipListDialog.ShowProcessGeneralMode:
420 if line[0] == " ":
421 QTreeWidgetItem(
422 self.infoWidget,
423 [" ", line.strip()])
424 else:
425 mode = PipListDialog.ShowProcessGeneralMode
426 if mode == PipListDialog.ShowProcessGeneralMode:
427 try:
428 label, info = line.split(": ", 1)
429 except ValueError:
430 label = line[:-1]
431 info = ""
432 label = label.lower()
433 if label in self.__infoLabels:
434 QTreeWidgetItem(
435 self.infoWidget,
436 [self.__infoLabels[label], info])
437 if label == "files":
438 mode = PipListDialog.ShowProcessFilesListMode
439 elif label == "classifiers":
440 mode = PipListDialog.ShowProcessClassifiersMode
441 elif label == "entry-points":
442 mode = PipListDialog.ShowProcessEntryPointsMode
443 self.infoWidget.scrollToTop()
444
445 header = self.infoWidget.header()
446 header.setStretchLastSection(False)
447 header.resizeSections(QHeaderView.ResizeToContents)
448 if header.sectionSize(0) + header.sectionSize(1) < header.width():
449 header.setStretchLastSection(True)
450
451 QApplication.restoreOverrideCursor()
452
453 enable = (len(self.packageList.selectedItems()) > 1 or
454 (len(self.packageList.selectedItems()) == 1 and
455 self.packageList.selectedItems()[0].text(0) not in
456 self.__nothingStrings.values()))
457 self.__upgradeButton and self.__upgradeButton.setEnabled(enable)
458 self.__uninstallButton.setEnabled(enable)
459
460 @pyqtSlot(bool)
461 def on_verboseCheckBox_clicked(self, checked):
462 """
463 Private slot to handle a change of the verbose package information
464 checkbox.
465
466 @param checked state of the checkbox
467 @type bool
468 """
469 self.on_packageList_itemSelectionChanged()
470
471 @pyqtSlot(bool)
472 def on_installedFilesCheckBox_clicked(self, checked):
473 """
474 Private slot to handle a change of the installed files information
475 checkbox.
476
477 @param checked state of the checkbox
478 @type bool
479 """
480 self.on_packageList_itemSelectionChanged()
481
482 def __upgradePackages(self):
483 """
484 Private slot to upgrade the selected packages.
485 """
486 packages = []
487 for itm in self.packageList.selectedItems():
488 packages.append(itm.text(0))
489
490 if packages:
491 if "pip" in packages:
492 self.__upgradePip()
493 else:
494 self.__executeUpgradePackages(packages)
495
496 def __upgradeAllPackages(self):
497 """
498 Private slot to upgrade all listed packages.
499 """
500 packages = []
501 for index in range(self.packageList.topLevelItemCount()):
502 itm = self.packageList.topLevelItem(index)
503 packages.append(itm.text(0))
504
505 if packages:
506 if "pip" in packages:
507 self.__upgradePip()
508 else:
509 self.__executeUpgradePackages(packages)
510
511 def __upgradePip(self):
512 """
513 Private slot to upgrade pip itself.
514 """
515 res = self.__pip.upgradePip(
516 venvName=self.venvComboBox.currentText(),
517 userSite=self.userCheckBox.isChecked())
518 if res:
519 self.__refresh()
520
521 def __executeUpgradePackages(self, packages):
522 """
523 Private method to execute the pip upgrade command.
524
525 @param packages list of package names to be upgraded
526 @type list of str
527 """
528 res = self.__pip.upgradePackages(
529 packages, venvName=self.venvComboBox.currentText(),
530 userSite=self.userCheckBox.isChecked())
531 if res:
532 self.activateWindow()
533 self.raise_()
534 self.__refresh()
535
536 def __uninstallPackages(self):
537 """
538 Private slot to uninstall the selected packages.
539 """
540 packages = []
541 for itm in self.packageList.selectedItems():
542 packages.append(itm.text(0))
543
544 if packages:
545 res = self.__pip.uninstallPackages(
546 packages,
547 venvName=self.venvComboBox.currentText())
548 if res:
549 self.activateWindow()
550 self.raise_()
551 self.__refresh()

eric ide

mercurial