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