PipInterface/PipPackagesWidget.py

branch
maintenance
changeset 6826
c6dda2cbe081
parent 6806
d306647cb82d
child 6828
bb6667ea9ae7
equal deleted inserted replaced
6764:d14ddbfbbd36 6826:c6dda2cbe081
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the pip packages management widget.
8 """
9
10 from __future__ import unicode_literals
11
12 import textwrap
13 import os
14
15 from PyQt5.QtCore import pyqtSlot, Qt, QEventLoop, QRegExp
16 from PyQt5.QtGui import QCursor
17 from PyQt5.QtWidgets import QWidget, QToolButton, QApplication, QHeaderView, \
18 QTreeWidgetItem, QInputDialog, QMenu, QDialog
19
20 from E5Gui.E5Application import e5App
21 from E5Gui import E5MessageBox
22
23 from E5Network.E5XmlRpcClient import E5XmlRpcClient
24
25 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget
26
27 import UI.PixmapCache
28
29 from .Pip import Pip
30
31
32 class PipPackagesWidget(QWidget, Ui_PipPackagesWidget):
33 """
34 Class implementing the pip packages management widget.
35 """
36 ShowProcessGeneralMode = 0
37 ShowProcessClassifiersMode = 1
38 ShowProcessEntryPointsMode = 2
39 ShowProcessFilesListMode = 3
40
41 SearchStopwords = {
42 "a", "and", "are", "as", "at", "be", "but", "by",
43 "for", "if", "in", "into", "is", "it",
44 "no", "not", "of", "on", "or", "such",
45 "that", "the", "their", "then", "there", "these",
46 "they", "this", "to", "was", "will",
47 }
48 SearchVersionRole = Qt.UserRole + 1
49
50 def __init__(self, parent=None):
51 """
52 Constructor
53
54 @param parent reference to the parent widget
55 @type QWidget
56 """
57 super(PipPackagesWidget, self).__init__(parent)
58 self.setupUi(self)
59
60 self.pipMenuButton.setObjectName(
61 "navigation_supermenu_button")
62 self.pipMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu"))
63 self.pipMenuButton.setToolTip(self.tr("pip Menu"))
64 self.pipMenuButton.setPopupMode(QToolButton.InstantPopup)
65 self.pipMenuButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
66 self.pipMenuButton.setFocusPolicy(Qt.NoFocus)
67 self.pipMenuButton.setAutoRaise(True)
68 self.pipMenuButton.setShowMenuInside(True)
69
70 self.refreshButton.setIcon(UI.PixmapCache.getIcon("reload"))
71 self.upgradeButton.setIcon(UI.PixmapCache.getIcon("1uparrow"))
72 self.upgradeAllButton.setIcon(UI.PixmapCache.getIcon("2uparrow"))
73 self.uninstallButton.setIcon(UI.PixmapCache.getIcon("minus"))
74 self.showPackageDetailsButton.setIcon(UI.PixmapCache.getIcon("info"))
75 self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find"))
76 self.searchButton.setIcon(UI.PixmapCache.getIcon("findNext"))
77 self.installButton.setIcon(UI.PixmapCache.getIcon("plus"))
78 self.installUserSiteButton.setIcon(UI.PixmapCache.getIcon("addUser"))
79 self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info"))
80
81 self.__pip = Pip(self)
82 self.__client = E5XmlRpcClient(self.__pip.getIndexUrlXml(), self)
83
84 self.packagesList.header().setSortIndicator(0, Qt.AscendingOrder)
85
86 self.__infoLabels = {
87 "name": self.tr("Name:"),
88 "version": self.tr("Version:"),
89 "location": self.tr("Location:"),
90 "requires": self.tr("Requires:"),
91 "summary": self.tr("Summary:"),
92 "home-page": self.tr("Homepage:"),
93 "author": self.tr("Author:"),
94 "author-email": self.tr("Author Email:"),
95 "license": self.tr("License:"),
96 "metadata-version": self.tr("Metadata Version:"),
97 "installer": self.tr("Installer:"),
98 "classifiers": self.tr("Classifiers:"),
99 "entry-points": self.tr("Entry Points:"),
100 "files": self.tr("Files:"),
101 }
102 self.infoWidget.setHeaderLabels(["Key", "Value"])
103
104 venvManager = e5App().getObject("VirtualEnvManager")
105 venvManager.virtualEnvironmentAdded.connect(
106 self.on_refreshButton_clicked)
107 venvManager.virtualEnvironmentRemoved.connect(
108 self.on_refreshButton_clicked)
109
110 project = e5App().getObject("Project")
111 project.projectOpened.connect(
112 self.on_refreshButton_clicked)
113 project.projectClosed.connect(
114 self.on_refreshButton_clicked)
115
116 self.__initPipMenu()
117 self.__populateEnvironments()
118 self.__updateActionButtons()
119
120 self.statusLabel.hide()
121 self.searchWidget.hide()
122
123 self.__queryName = []
124 self.__querySummary = []
125
126 self.__packageDetailsDialog = None
127
128 def __populateEnvironments(self):
129 """
130 Private method to get a list of environments and populate the selector.
131 """
132 self.environmentsComboBox.addItem("")
133 projectVenv = self.__pip.getProjectEnvironmentString()
134 if projectVenv:
135 self.environmentsComboBox.addItem(projectVenv)
136 self.environmentsComboBox.addItems(self.__pip.getVirtualenvNames())
137
138 def __isPipAvailable(self):
139 """
140 Private method to check, if the pip package is available for the
141 selected environment.
142
143 @return flag indicating availability
144 @rtype bool
145 """
146 available = False
147
148 venvName = self.environmentsComboBox.currentText()
149 if venvName:
150 available = len(self.packagesList.findItems(
151 "pip", Qt.MatchExactly | Qt.MatchCaseSensitive)) == 1
152
153 return available
154
155 #######################################################################
156 ## Slots handling widget signals below
157 #######################################################################
158
159 def __selectedUpdateableItems(self):
160 """
161 Private method to get a list of selected items that can be updated.
162
163 @return list of selected items that can be updated
164 @rtype list of QTreeWidgetItem
165 """
166 return [
167 itm for itm in self.packagesList.selectedItems()
168 if bool(itm.text(2))
169 ]
170
171 def __allUpdateableItems(self):
172 """
173 Private method to get a list of all items that can be updated.
174
175 @return list of all items that can be updated
176 @rtype list of QTreeWidgetItem
177 """
178 updateableItems = []
179 for index in range(self.packagesList.topLevelItemCount()):
180 itm = self.packagesList.topLevelItem(index)
181 if itm.text(2):
182 updateableItems.append(itm)
183
184 return updateableItems
185
186 def __updateActionButtons(self):
187 """
188 Private method to set the state of the action buttons.
189 """
190 if self.__isPipAvailable():
191 self.upgradeButton.setEnabled(
192 bool(self.__selectedUpdateableItems()))
193 self.uninstallButton.setEnabled(
194 bool(self.packagesList.selectedItems()))
195 self.upgradeAllButton.setEnabled(
196 bool(self.__allUpdateableItems()))
197 self.showPackageDetailsButton.setEnabled(
198 len(self.packagesList.selectedItems()) == 1)
199 else:
200 self.upgradeButton.setEnabled(False)
201 self.uninstallButton.setEnabled(False)
202 self.upgradeAllButton.setEnabled(False)
203 self.showPackageDetailsButton.setEnabled(False)
204
205 def __refreshPackagesList(self):
206 """
207 Private method to referesh the packages list.
208 """
209 self.packagesList.clear()
210 venvName = self.environmentsComboBox.currentText()
211 if venvName:
212 interpreter = self.__pip.getVirtualenvInterpreter(venvName)
213 if interpreter:
214 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
215 self.statusLabel.show()
216 self.statusLabel.setText(
217 self.tr("Getting installed packages..."))
218 QApplication.processEvents()
219
220 # 1. populate with installed packages
221 self.packagesList.setUpdatesEnabled(False)
222 installedPackages = self.__pip.getInstalledPackages(
223 venvName,
224 localPackages=self.localCheckBox.isChecked(),
225 notRequired=self.notRequiredCheckBox.isChecked(),
226 usersite=self.userCheckBox.isChecked(),
227 )
228 for package, version in installedPackages:
229 QTreeWidgetItem(self.packagesList, [package, version])
230 self.packagesList.setUpdatesEnabled(True)
231 self.statusLabel.setText(
232 self.tr("Getting outdated packages..."))
233 QApplication.processEvents()
234
235 # 2. update with update information
236 self.packagesList.setUpdatesEnabled(False)
237 outdatedPackages = self.__pip.getOutdatedPackages(
238 venvName,
239 localPackages=self.localCheckBox.isChecked(),
240 notRequired=self.notRequiredCheckBox.isChecked(),
241 usersite=self.userCheckBox.isChecked(),
242 )
243 for package, _version, latest in outdatedPackages:
244 items = self.packagesList.findItems(
245 package, Qt.MatchExactly | Qt.MatchCaseSensitive)
246 if items:
247 itm = items[0]
248 itm.setText(2, latest)
249
250 self.packagesList.sortItems(0, Qt.AscendingOrder)
251 for col in range(self.packagesList.columnCount()):
252 self.packagesList.resizeColumnToContents(col)
253 self.packagesList.setUpdatesEnabled(True)
254 QApplication.restoreOverrideCursor()
255 self.statusLabel.hide()
256
257 self.__updateActionButtons()
258 self.__updateSearchActionButtons()
259 self.__updateSearchButton()
260
261 @pyqtSlot(int)
262 def on_environmentsComboBox_currentIndexChanged(self, index):
263 """
264 Private slot handling the selection of a conda environment.
265
266 @param index index of the selected conda environment
267 @type int
268 """
269 self.__refreshPackagesList()
270
271 @pyqtSlot(bool)
272 def on_localCheckBox_clicked(self, checked):
273 """
274 Private slot handling the switching of the local mode.
275
276 @param checked state of the local check box
277 @type bool
278 """
279 self.__refreshPackagesList()
280
281 @pyqtSlot(bool)
282 def on_notRequiredCheckBox_clicked(self, checked):
283 """
284 Private slot handling the switching of the 'not required' mode.
285
286 @param checked state of the 'not required' check box
287 @type bool
288 """
289 self.__refreshPackagesList()
290
291 @pyqtSlot(bool)
292 def on_userCheckBox_clicked(self, checked):
293 """
294 Private slot handling the switching of the 'user-site' mode.
295
296 @param checked state of the 'user-site' check box
297 @type bool
298 """
299 self.__refreshPackagesList()
300
301 @pyqtSlot()
302 def on_packagesList_itemSelectionChanged(self):
303 """
304 Private slot handling the selection of a package.
305 """
306 self.infoWidget.clear()
307
308 if len(self.packagesList.selectedItems()) == 1:
309 itm = self.packagesList.selectedItems()[0]
310
311 environment = self.environmentsComboBox.currentText()
312 interpreter = self.__pip.getVirtualenvInterpreter(environment)
313 if not interpreter:
314 return
315
316 QApplication.setOverrideCursor(Qt.WaitCursor)
317
318 args = ["-m", "pip", "show"]
319 if self.verboseCheckBox.isChecked():
320 args.append("--verbose")
321 if self.installedFilesCheckBox.isChecked():
322 args.append("--files")
323 args.append(itm.text(0))
324 success, output = self.__pip.runProcess(args, interpreter)
325
326 if success and output:
327 mode = self.ShowProcessGeneralMode
328 for line in output.splitlines():
329 line = line.rstrip()
330 if line != "---":
331 if mode != self.ShowProcessGeneralMode:
332 if line[0] == " ":
333 QTreeWidgetItem(
334 self.infoWidget,
335 [" ", line.strip()])
336 else:
337 mode = self.ShowProcessGeneralMode
338 if mode == self.ShowProcessGeneralMode:
339 try:
340 label, info = line.split(": ", 1)
341 except ValueError:
342 label = line[:-1]
343 info = ""
344 label = label.lower()
345 if label in self.__infoLabels:
346 QTreeWidgetItem(
347 self.infoWidget,
348 [self.__infoLabels[label], info])
349 if label == "files":
350 mode = self.ShowProcessFilesListMode
351 elif label == "classifiers":
352 mode = self.ShowProcessClassifiersMode
353 elif label == "entry-points":
354 mode = self.ShowProcessEntryPointsMode
355 self.infoWidget.scrollToTop()
356
357 header = self.infoWidget.header()
358 header.setStretchLastSection(False)
359 header.resizeSections(QHeaderView.ResizeToContents)
360 if header.sectionSize(0) + header.sectionSize(1) < header.width():
361 header.setStretchLastSection(True)
362
363 QApplication.restoreOverrideCursor()
364
365 self.__updateActionButtons()
366
367 @pyqtSlot(QTreeWidgetItem, int)
368 def on_packagesList_itemActivated(self, item, column):
369 """
370 Private slot reacting on a package item activation.
371
372 @param item reference to the activated item
373 @type QTreeWidgetItem
374 @param column activated column
375 @type int
376 """
377 packageName = item.text(0)
378 if column == 1:
379 # show details for installed version
380 packageVersion = item.text(1)
381 else:
382 # show details for available version or installed one
383 if item.text(2):
384 packageVersion = item.text(2)
385 else:
386 packageVersion = item.text(1)
387
388 self.__showPackageDetails(packageName, packageVersion)
389
390 @pyqtSlot(bool)
391 def on_verboseCheckBox_clicked(self, checked):
392 """
393 Private slot to handle a change of the verbose package information
394 checkbox.
395
396 @param checked state of the checkbox
397 @type bool
398 """
399 self.on_packagesList_itemSelectionChanged()
400
401 @pyqtSlot(bool)
402 def on_installedFilesCheckBox_clicked(self, checked):
403 """
404 Private slot to handle a change of the installed files information
405 checkbox.
406
407 @param checked state of the checkbox
408 @type bool
409 """
410 self.on_packagesList_itemSelectionChanged()
411
412 @pyqtSlot()
413 def on_refreshButton_clicked(self):
414 """
415 Private slot to refresh the display.
416 """
417 currentEnvironment = self.environmentsComboBox.currentText()
418 self.environmentsComboBox.clear()
419 self.packagesList.clear()
420
421 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
422 QApplication.processEvents()
423
424 self.__populateEnvironments()
425
426 index = self.environmentsComboBox.findText(
427 currentEnvironment, Qt.MatchExactly | Qt.MatchCaseSensitive)
428 if index != -1:
429 self.environmentsComboBox.setCurrentIndex(index)
430
431 QApplication.restoreOverrideCursor()
432 self.__updateActionButtons()
433
434 @pyqtSlot()
435 def on_upgradeButton_clicked(self):
436 """
437 Private slot to upgrade selected packages of the selected environment.
438 """
439 packages = [itm.text(0) for itm in self.__selectedUpdateableItems()]
440 if packages:
441 ok = self.__executeUpgradePackages(packages)
442 if ok:
443 self.on_refreshButton_clicked()
444
445 @pyqtSlot()
446 def on_upgradeAllButton_clicked(self):
447 """
448 Private slot to upgrade all packages of the selected environment.
449 """
450 packages = [itm.text(0) for itm in self.__allUpdateableItems()]
451 if packages:
452 ok = self.__executeUpgradePackages(packages)
453 if ok:
454 self.on_refreshButton_clicked()
455
456 @pyqtSlot()
457 def on_uninstallButton_clicked(self):
458 """
459 Private slot to remove selected packages of the selected environment.
460 """
461 packages = [itm.text(0) for itm in self.packagesList.selectedItems()]
462 if packages:
463 ok = self.__pip.uninstallPackages(
464 packages,
465 venvName=self.environmentsComboBox.currentText())
466 if ok:
467 self.on_refreshButton_clicked()
468
469 def __executeUpgradePackages(self, packages):
470 """
471 Private method to execute the pip upgrade command.
472
473 @param packages list of package names to be upgraded
474 @type list of str
475 @return flag indicating success
476 @rtype bool
477 """
478 ok = self.__pip.upgradePackages(
479 packages, venvName=self.environmentsComboBox.currentText(),
480 userSite=self.userCheckBox.isChecked())
481 return ok
482
483 @pyqtSlot()
484 def on_showPackageDetailsButton_clicked(self):
485 """
486 Private slot to show information for the selected package.
487 """
488 item = self.packagesList.selectedItems()[0]
489 if item:
490 packageName = item.text(0)
491 # show details for available version or installed one
492 if item.text(2):
493 packageVersion = item.text(2)
494 else:
495 packageVersion = item.text(1)
496
497 self.__showPackageDetails(packageName, packageVersion)
498
499 #######################################################################
500 ## Search widget related methods below
501 #######################################################################
502
503 def __updateSearchActionButtons(self):
504 """
505 Private method to update the action button states of the search widget.
506 """
507 installEnable = (
508 len(self.searchResultList.selectedItems()) > 0 and
509 self.environmentsComboBox.currentIndex() > 0 and
510 self.__isPipAvailable()
511 )
512 self.installButton.setEnabled(installEnable)
513 self.installUserSiteButton.setEnabled(installEnable)
514
515 self.showDetailsButton.setEnabled(
516 len(self.searchResultList.selectedItems()) == 1 and
517 self.__isPipAvailable()
518 )
519
520 def __updateSearchButton(self):
521 """
522 Private method to update the state of the search button.
523 """
524 self.searchButton.setEnabled(
525 (bool(self.searchEditName.text()) or
526 bool(self.searchEditSummary.text())) and
527 self.__isPipAvailable()
528 )
529
530 @pyqtSlot(bool)
531 def on_searchToggleButton_toggled(self, checked):
532 """
533 Private slot to togle the search widget.
534
535 @param checked state of the search widget button
536 @type bool
537 """
538 self.searchWidget.setVisible(checked)
539
540 if checked:
541 self.searchEditName.setFocus(Qt.OtherFocusReason)
542 self.searchEditName.selectAll()
543
544 self.__updateSearchActionButtons()
545 self.__updateSearchButton()
546
547 @pyqtSlot(str)
548 def on_searchEditName_textChanged(self, txt):
549 """
550 Private slot handling a change of the search term.
551
552 @param txt search term
553 @type str
554 """
555 self.__updateSearchButton()
556
557 @pyqtSlot()
558 def on_searchEditName_returnPressed(self):
559 """
560 Private slot initiating a search via a press of the Return key.
561 """
562 self.__search()
563
564 @pyqtSlot(str)
565 def on_searchEditSummary_textChanged(self, txt):
566 """
567 Private slot handling a change of the search term.
568
569 @param txt search term
570 @type str
571 """
572 self.__updateSearchButton()
573
574 @pyqtSlot()
575 def on_searchEditSummary_returnPressed(self):
576 """
577 Private slot initiating a search via a press of the Return key.
578 """
579 self.__search()
580
581 @pyqtSlot()
582 def on_searchButton_clicked(self):
583 """
584 Private slot handling a press of the search button.
585 """
586 self.__search()
587
588 @pyqtSlot()
589 def on_searchResultList_itemSelectionChanged(self):
590 """
591 Private slot handling changes of the search result selection.
592 """
593 self.__updateSearchActionButtons()
594
595 def __search(self):
596 """
597 Private method to perform the search.
598 """
599 self.searchResultList.clear()
600 self.searchInfoLabel.clear()
601
602 self.searchButton.setEnabled(False)
603 QApplication.setOverrideCursor(Qt.WaitCursor)
604 QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
605
606 self.__queryName = [
607 term for term in self.searchEditName.text().strip().split()
608 if term not in self.SearchStopwords
609 ]
610 self.__querySummary = [
611 term for term in self.searchEditSummary.text().strip().split()
612 if term not in self.SearchStopwords
613 ]
614 self.__client.call(
615 "search",
616 ({"name": self.__queryName,
617 "summary": self.__querySummary},
618 self.searchTermCombineComboBox.currentText()),
619 self.__processSearchResult,
620 self.__searchError
621 )
622
623 def __processSearchResult(self, data):
624 """
625 Private method to process the search result data from PyPI.
626
627 @param data result data with hits in the first element
628 @type tuple
629 """
630 if data:
631 packages = self.__transformHits(data[0])
632 if packages:
633 self.searchInfoLabel.setText(
634 self.tr("%n package(s) found.", "", len(packages)))
635 wrapper = textwrap.TextWrapper(width=80)
636 count = 0
637 total = 0
638 for package in packages:
639 itm = QTreeWidgetItem(
640 self.searchResultList, [
641 package['name'].strip(),
642 "{0:4d}".format(package['score']),
643 "\n".join([
644 wrapper.fill(line) for line in
645 package['summary'].strip().splitlines()
646 ])
647 ])
648 itm.setData(0, self.SearchVersionRole, package['version'])
649 count += 1
650 total += 1
651 if count == 100:
652 count = 0
653 QApplication.processEvents()
654 else:
655 QApplication.restoreOverrideCursor()
656 E5MessageBox.warning(
657 self,
658 self.tr("Search PyPI"),
659 self.tr("""<p>The package search did not return"""
660 """ anything.</p>"""))
661 self.searchInfoLabel.setText(
662 self.tr("""<p>The package search did not return"""
663 """ anything.</p>"""))
664 else:
665 QApplication.restoreOverrideCursor()
666 E5MessageBox.warning(
667 self,
668 self.tr("Search PyPI"),
669 self.tr("""<p>The package search did not return anything."""
670 """</p>"""))
671 self.searchInfoLabel.setText(
672 self.tr("""<p>The package search did not return anything."""
673 """</p>"""))
674
675 header = self.searchResultList.header()
676 self.searchResultList.sortItems(1, Qt.DescendingOrder)
677 header.setStretchLastSection(False)
678 header.resizeSections(QHeaderView.ResizeToContents)
679 headerSize = 0
680 for col in range(header.count()):
681 headerSize += header.sectionSize(col)
682 if headerSize < header.width():
683 header.setStretchLastSection(True)
684
685 self.__finishSearch()
686
687 def __finishSearch(self):
688 """
689 Private slot performing the search finishing actions.
690 """
691 QApplication.restoreOverrideCursor()
692
693 self.__updateSearchActionButtons()
694 self.__updateSearchButton()
695
696 self.searchEditName.setFocus(Qt.OtherFocusReason)
697
698 def __searchError(self, errorCode, errorString):
699 """
700 Private method handling a search error.
701
702 @param errorCode code of the error
703 @type int
704 @param errorString error message
705 @type str
706 """
707 self.__finish()
708 E5MessageBox.warning(
709 self,
710 self.tr("Search PyPI"),
711 self.tr("""<p>The package search failed.</p><p>Reason: {0}</p>""")
712 .format(errorString))
713 self.searchInfoLabel.setText(self.tr("Error: {0}").format(errorString))
714
715 def __transformHits(self, hits):
716 """
717 Private method to convert the list returned from pypi into a
718 packages list.
719
720 @param hits list returned from pypi
721 @type list of dict
722 @return list of packages
723 @rtype list of dict
724 """
725 # we only include the record with the highest score
726 packages = {}
727 for hit in hits:
728 name = hit['name'].strip()
729 summary = (hit['summary'] or "").strip()
730 version = hit['version'].strip()
731 score = self.__score(name, summary)
732 # cleanup the summary
733 if summary in ["UNKNOWN", "."]:
734 summary = ""
735
736 if name not in packages:
737 packages[name] = {
738 'name': name,
739 'summary': summary,
740 'version': [version.strip()],
741 'score': score}
742 else:
743 if score > packages[name]['score']:
744 packages[name]['score'] = score
745 packages[name]['summary'] = summary
746 packages[name]['version'].append(version.strip())
747
748 return list(packages.values())
749
750 def __score(self, name, summary):
751 """
752 Private method to calculate some score for a search result.
753
754 @param name name of the returned package
755 @type str
756 @param summary summary text for the package
757 @type str
758 @return score value
759 @rtype int
760 """
761 score = 0
762 for queryTerm in self.__queryName:
763 if queryTerm.lower() in name.lower():
764 score += 4
765 if queryTerm.lower() == name.lower():
766 score += 4
767
768 for queryTerm in self.__querySummary:
769 if queryTerm.lower() in summary.lower():
770 if QRegExp(r'\b{0}\b'.format(QRegExp.escape(queryTerm)),
771 Qt.CaseInsensitive).indexIn(summary) != -1:
772 # word match gets even higher score
773 score += 2
774 else:
775 score += 1
776
777 return score
778
779 @pyqtSlot()
780 def on_installButton_clicked(self):
781 """
782 Private slot to handle pressing the Install button..
783 """
784 self.__install()
785
786 @pyqtSlot()
787 def on_installUserSiteButton_clicked(self):
788 """
789 Private slot to handle pressing the Install to User-Site button..
790 """
791 self.__install(userSite=True)
792
793 def __install(self, userSite=False):
794 """
795 Private slot to install the selected packages.
796
797 @param userSite flag indicating to install to the user directory
798 @type bool
799 """
800 venvName = self.environmentsComboBox.currentText()
801 if venvName:
802 packages = []
803 for itm in self.searchResultList.selectedItems():
804 packages.append(itm.text(0).strip())
805 if packages:
806 self.__pip.installPackages(packages, venvName=venvName,
807 userSite=userSite)
808
809 @pyqtSlot()
810 def on_showDetailsButton_clicked(self):
811 """
812 Private slot to handle pressing the Show Details button.
813 """
814 self.__showSearchedDetails()
815
816 @pyqtSlot(QTreeWidgetItem, int)
817 def on_searchResultList_itemActivated(self, item, column):
818 """
819 Private slot reacting on an search result item activation.
820
821 @param item reference to the activated item
822 @type QTreeWidgetItem
823 @param column activated column
824 @type int
825 """
826 self.__showDetails(item)
827
828 def __showSearchedDetails(self, item=None):
829 """
830 Private slot to show details about the selected search result package.
831
832 @param item reference to the search result item to show details for
833 @type QTreeWidgetItem
834 """
835 self.showDetailsButton.setEnabled(False)
836
837 if not item:
838 item = self.searchResultList.selectedItems()[0]
839
840 packageVersions = item.data(0, self.SearchVersionRole)
841 if len(packageVersions) == 1:
842 packageVersion = packageVersions[0]
843 elif len(packageVersions) == 0:
844 packageVersion = ""
845 else:
846 packageVersion, ok = QInputDialog.getItem(
847 self,
848 self.tr("Show Package Details"),
849 self.tr("Select the package version:"),
850 packageVersions,
851 0, False)
852 if not ok:
853 return
854 packageName = item.text(0)
855
856 self.__showPackageDetails(packageName, packageVersion)
857
858 def __showPackageDetails(self, packageName, packageVersion):
859 """
860 Private method to populate the package details dialog.
861
862 @param packageName name of the package to show details for
863 @type str
864 @param packageVersion version of the package
865 @type str
866 """
867 QApplication.setOverrideCursor(Qt.WaitCursor)
868 QApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
869
870 packageData = self.__pip.getPackageDetails(packageName, packageVersion)
871
872 QApplication.restoreOverrideCursor()
873 if packageData:
874 from .PipPackageDetailsDialog import PipPackageDetailsDialog
875
876 self.showDetailsButton.setEnabled(True)
877
878 if self.__packageDetailsDialog is not None:
879 self.__packageDetailsDialog.close()
880
881 self.__packageDetailsDialog = \
882 PipPackageDetailsDialog(packageData, self)
883 self.__packageDetailsDialog.show()
884 else:
885 E5MessageBox.warning(
886 self,
887 self.tr("Search PyPI"),
888 self.tr("""<p>No package details info for <b>{0}</b>"""
889 """ available.</p>""").format(packageName))
890
891 #######################################################################
892 ## Menu related methods below
893 #######################################################################
894
895 def __initPipMenu(self):
896 """
897 Private method to create the super menu and attach it to the super
898 menu button.
899 """
900 self.__pipMenu = QMenu()
901 self.__installPipAct = self.__pipMenu.addAction(
902 self.tr("Install Pip"),
903 self.__installPip)
904 self.__installPipUserAct = self.__pipMenu.addAction(
905 self.tr("Install Pip to User-Site"),
906 self.__installPipUser)
907 self.__repairPipAct = self.__pipMenu.addAction(
908 self.tr("Repair Pip"),
909 self.__repairPip)
910 self.__pipMenu.addSeparator()
911 self.__installPackagesAct = self.__pipMenu.addAction(
912 self.tr("Install Packages"),
913 self.__installPackages)
914 self.__installLocalPackageAct = self.__pipMenu.addAction(
915 self.tr("Install Local Package"),
916 self.__installLocalPackage)
917 self.__pipMenu.addSeparator()
918 self.__installRequirementsAct = self.__pipMenu.addAction(
919 self.tr("Install Requirements"),
920 self.__installRequirements)
921 self.__uninstallRequirementsAct = self.__pipMenu.addAction(
922 self.tr("Uninstall Requirements"),
923 self.__uninstallRequirements)
924 self.__generateRequirementsAct = self.__pipMenu.addAction(
925 self.tr("Generate Requirements..."),
926 self.__generateRequirements)
927 self.__pipMenu.addSeparator()
928 # editUserConfigAct
929 self.__pipMenu.addAction(
930 self.tr("Edit User Configuration..."),
931 self.__editUserConfiguration)
932 self.__editVirtualenvConfigAct = self.__pipMenu.addAction(
933 self.tr("Edit Environment Configuration..."),
934 self.__editVirtualenvConfiguration)
935 self.__pipMenu.addSeparator()
936 # pipConfigAct
937 self.__pipMenu.addAction(
938 self.tr("Configure..."),
939 self.__pipConfigure)
940
941 self.__pipMenu.aboutToShow.connect(self.__aboutToShowPipMenu)
942
943 self.pipMenuButton.setMenu(self.__pipMenu)
944
945 def __aboutToShowPipMenu(self):
946 """
947 Private slot to set the action enabled status.
948 """
949 enable = bool(self.environmentsComboBox.currentText())
950 enablePip = self.__isPipAvailable()
951
952 self.__installPipAct.setEnabled(not enablePip)
953 self.__installPipUserAct.setEnabled(not enablePip)
954 self.__repairPipAct.setEnabled(enablePip)
955
956 self.__installPackagesAct.setEnabled(enablePip)
957 self.__installLocalPackageAct.setEnabled(enablePip)
958
959 self.__installRequirementsAct.setEnabled(enablePip)
960 self.__uninstallRequirementsAct.setEnabled(enablePip)
961 self.__generateRequirementsAct.setEnabled(enablePip)
962
963 self.__editVirtualenvConfigAct.setEnabled(enable)
964
965 @pyqtSlot()
966 def __installPip(self):
967 """
968 Private slot to install pip into the selected environment.
969 """
970 venvName = self.environmentsComboBox.currentText()
971 if venvName:
972 self.__pip.installPip(venvName)
973
974 @pyqtSlot()
975 def __installPipUser(self):
976 """
977 Private slot to install pip into the user site for the selected
978 environment.
979 """
980 venvName = self.environmentsComboBox.currentText()
981 if venvName:
982 self.__pip.installPip(venvName, userSite=True)
983
984 @pyqtSlot()
985 def __repairPip(self):
986 """
987 Private slot to repair the pip installation of the selected
988 environment.
989 """
990 venvName = self.environmentsComboBox.currentText()
991 if venvName:
992 self.__pip.repairPip(venvName)
993
994 @pyqtSlot()
995 def __installPackages(self):
996 """
997 Private slot to install packages to be given by the user.
998 """
999 venvName = self.environmentsComboBox.currentText()
1000 if venvName:
1001 from .PipPackagesInputDialog import PipPackagesInputDialog
1002 dlg = PipPackagesInputDialog(self, self.tr("Install Packages"))
1003 if dlg.exec_() == QDialog.Accepted:
1004 packages, user = dlg.getData()
1005 if packages:
1006 self.__pip.installPackages(packages, venvName=venvName,
1007 userSite=user)
1008
1009 @pyqtSlot()
1010 def __installLocalPackage(self):
1011 """
1012 Private slot to install a package available on local storage.
1013 """
1014 venvName = self.environmentsComboBox.currentText()
1015 if venvName:
1016 from .PipFileSelectionDialog import PipFileSelectionDialog
1017 dlg = PipFileSelectionDialog(self, "package")
1018 if dlg.exec_() == QDialog.Accepted:
1019 package, user = dlg.getData()
1020 if package and os.path.exists(package):
1021 self.__pip.installPackages([package], venvName=venvName,
1022 userSite=user)
1023
1024 @pyqtSlot()
1025 def __installRequirements(self):
1026 """
1027 Private slot to install packages as given in a requirements file.
1028 """
1029 venvName = self.environmentsComboBox.currentText()
1030 if venvName:
1031 self.__pip.installRequirements(venvName)
1032
1033 @pyqtSlot()
1034 def __uninstallRequirements(self):
1035 """
1036 Private slot to uninstall packages as given in a requirements file.
1037 """
1038 venvName = self.environmentsComboBox.currentText()
1039 if venvName:
1040 self.__pip.uninstallRequirements(venvName)
1041
1042 @pyqtSlot()
1043 def __generateRequirements(self):
1044 """
1045 Private slot to generate the contents for a requirements file.
1046 """
1047 venvName = self.environmentsComboBox.currentText()
1048 if venvName:
1049 from .PipFreezeDialog import PipFreezeDialog
1050 self.__freezeDialog = PipFreezeDialog(self.__pip, self)
1051 self.__freezeDialog.show()
1052 self.__freezeDialog.start(venvName)
1053
1054 @pyqtSlot()
1055 def __editUserConfiguration(self):
1056 """
1057 Private slot to edit the user configuration.
1058 """
1059 self.__editConfiguration()
1060
1061 @pyqtSlot()
1062 def __editVirtualenvConfiguration(self):
1063 """
1064 Private slot to edit the configuration of the selected environment.
1065 """
1066 venvName = self.environmentsComboBox.currentText()
1067 if venvName:
1068 self.__editConfiguration(venvName=venvName)
1069
1070 def __editConfiguration(self, venvName=""):
1071 """
1072 Private method to edit a configuration.
1073
1074 @param venvName name of the environment to act upon
1075 @type str
1076 """
1077 from QScintilla.MiniEditor import MiniEditor
1078 if venvName:
1079 cfgFile = self.__pip.getVirtualenvConfig(venvName)
1080 if not cfgFile:
1081 return
1082 else:
1083 cfgFile = self.__pip.getUserConfig()
1084 cfgDir = os.path.dirname(cfgFile)
1085 if not cfgDir:
1086 E5MessageBox.critical(
1087 None,
1088 self.tr("Edit Configuration"),
1089 self.tr("""No valid configuration path determined."""
1090 """ Aborting"""))
1091 return
1092
1093 try:
1094 if not os.path.isdir(cfgDir):
1095 os.makedirs(cfgDir)
1096 except OSError:
1097 E5MessageBox.critical(
1098 None,
1099 self.tr("Edit Configuration"),
1100 self.tr("""No valid configuration path determined."""
1101 """ Aborting"""))
1102 return
1103
1104 if not os.path.exists(cfgFile):
1105 try:
1106 f = open(cfgFile, "w")
1107 f.write("[global]\n")
1108 f.close()
1109 except (IOError, OSError):
1110 # ignore these
1111 pass
1112
1113 # check, if the destination is writeable
1114 if not os.access(cfgFile, os.W_OK):
1115 E5MessageBox.critical(
1116 None,
1117 self.tr("Edit Configuration"),
1118 self.tr("""No valid configuration path determined."""
1119 """ Aborting"""))
1120 return
1121
1122 self.__editor = MiniEditor(cfgFile, "Properties")
1123 self.__editor.show()
1124
1125 def __pipConfigure(self):
1126 """
1127 Private slot to open the configuration page.
1128 """
1129 e5App().getObject("UserInterface").showPreferences("pipPage")

eric ide

mercurial