src/eric7/PipInterface/Pip.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9121
6ac528d4f318
child 9218
71cf3979a6c9
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2015 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Package implementing the pip GUI logic.
8 """
9
10 import os
11 import sys
12 import json
13 import contextlib
14
15 from PyQt6.QtCore import (
16 pyqtSlot, QObject, QProcess, QUrl, QCoreApplication, QThread
17 )
18 from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit
19 from PyQt6.QtNetwork import (
20 QNetworkAccessManager, QNetworkRequest, QNetworkReply
21 )
22
23 from EricWidgets import EricMessageBox
24 from EricWidgets.EricApplication import ericApp
25
26 from EricNetwork.EricNetworkProxyFactory import proxyAuthenticationRequired
27 try:
28 from EricNetwork.EricSslErrorHandler import EricSslErrorHandler
29 SSL_AVAILABLE = True
30 except ImportError:
31 SSL_AVAILABLE = False
32
33 from .PipDialog import PipDialog
34 from .PipVulnerabilityChecker import PipVulnerabilityChecker
35
36 import Preferences
37 import Globals
38
39
40 class Pip(QObject):
41 """
42 Class implementing the pip GUI logic.
43 """
44 DefaultPyPiUrl = "https://pypi.org"
45 DefaultIndexUrlPypi = DefaultPyPiUrl + "/pypi"
46 DefaultIndexUrlSimple = DefaultPyPiUrl + "/simple"
47 DefaultIndexUrlSearch = DefaultPyPiUrl + "/search/"
48
49 def __init__(self, parent=None):
50 """
51 Constructor
52
53 @param parent reference to the user interface object
54 @type QObject
55 """
56 super().__init__(parent)
57
58 self.__ui = parent
59
60 # attributes for the network objects
61 self.__networkManager = QNetworkAccessManager(self)
62 self.__networkManager.proxyAuthenticationRequired.connect(
63 proxyAuthenticationRequired)
64 if SSL_AVAILABLE:
65 self.__sslErrorHandler = EricSslErrorHandler(self)
66 self.__networkManager.sslErrors.connect(
67 self.__sslErrorHandler.sslErrorsReply)
68 self.__replies = []
69
70 self.__vulnerabilityChecker = PipVulnerabilityChecker(self, self)
71
72 def getNetworkAccessManager(self):
73 """
74 Public method to get a reference to the network access manager object.
75
76 @return reference to the network access manager object
77 @rtype QNetworkAccessManager
78 """
79 return self.__networkManager
80
81 def getVulnerabilityChecker(self):
82 """
83 Public method to get a reference to the vulnerability checker object.
84
85 @return reference to the vulnerability checker object
86 @rtype PipVulnerabilityChecker
87 """
88 return self.__vulnerabilityChecker
89
90 ##########################################################################
91 ## Methods below implement some utility functions
92 ##########################################################################
93
94 def runProcess(self, args, interpreter):
95 """
96 Public method to execute the current pip with the given arguments.
97
98 The selected pip executable is called with the given arguments and
99 waited for its end.
100
101 @param args list of command line arguments
102 @type list of str
103 @param interpreter path of the Python interpreter to be used
104 @type str
105 @return tuple containing a flag indicating success and the output
106 of the process
107 @rtype tuple of (bool, str)
108 """
109 ioEncoding = Preferences.getSystem("IOEncoding")
110
111 process = QProcess()
112 process.start(interpreter, args)
113 procStarted = process.waitForStarted()
114 if procStarted:
115 finished = process.waitForFinished(30000)
116 if finished:
117 if process.exitCode() == 0:
118 output = str(process.readAllStandardOutput(), ioEncoding,
119 'replace')
120 return True, output
121 else:
122 return (False,
123 self.tr("python exited with an error ({0}).")
124 .format(process.exitCode()))
125 else:
126 process.terminate()
127 process.waitForFinished(2000)
128 process.kill()
129 process.waitForFinished(3000)
130 return False, self.tr("python did not finish within"
131 " 30 seconds.")
132
133 return False, self.tr("python could not be started.")
134
135 def getUserConfig(self):
136 """
137 Public method to get the name of the user configuration file.
138
139 @return path of the user configuration file
140 @rtype str
141 """
142 # Unix: ~/.config/pip/pip.conf
143 # OS X: ~/Library/Application Support/pip/pip.conf
144 # Windows: %APPDATA%\pip\pip.ini
145 # Environment: $PIP_CONFIG_FILE
146
147 with contextlib.suppress(KeyError):
148 return os.environ["PIP_CONFIG_FILE"]
149
150 if Globals.isWindowsPlatform():
151 config = os.path.join(os.environ["APPDATA"], "pip", "pip.ini")
152 elif Globals.isMacPlatform():
153 config = os.path.expanduser(
154 "~/Library/Application Support/pip/pip.conf")
155 else:
156 config = os.path.expanduser("~/.config/pip/pip.conf")
157
158 return config
159
160 def getVirtualenvConfig(self, venvName):
161 """
162 Public method to get the name of the virtualenv configuration file.
163
164 @param venvName name of the environment to get config file path for
165 @type str
166 @return path of the virtualenv configuration file
167 @rtype str
168 """
169 # Unix, OS X: $VIRTUAL_ENV/pip.conf
170 # Windows: %VIRTUAL_ENV%\pip.ini
171
172 pip = "pip.ini" if Globals.isWindowsPlatform() else "pip.conf"
173
174 venvManager = ericApp().getObject("VirtualEnvManager")
175 venvDirectory = (
176 os.path.dirname(self.getUserConfig())
177 if venvManager.isGlobalEnvironment(venvName) else
178 venvManager.getVirtualenvDirectory(venvName)
179 )
180
181 config = os.path.join(venvDirectory, pip) if venvDirectory else ""
182
183 return config
184
185 def getProjectEnvironmentString(self):
186 """
187 Public method to get the string for the project environment.
188
189 @return string for the project environment
190 @rtype str
191 """
192 if ericApp().getObject("Project").isOpen():
193 return self.tr("<project>")
194 else:
195 return ""
196
197 def getVirtualenvInterpreter(self, venvName):
198 """
199 Public method to get the interpreter for a virtual environment.
200
201 @param venvName logical name for the virtual environment
202 @type str
203 @return interpreter path
204 @rtype str
205 """
206 interpreter = (
207 ericApp().getObject("Project").getProjectInterpreter()
208 if venvName == self.getProjectEnvironmentString() else
209 ericApp().getObject("VirtualEnvManager")
210 .getVirtualenvInterpreter(venvName)
211 )
212 if not interpreter:
213 EricMessageBox.critical(
214 None,
215 self.tr("Interpreter for Virtual Environment"),
216 self.tr("""No interpreter configured for the selected"""
217 """ virtual environment."""))
218
219 return interpreter
220
221 def getVirtualenvNames(self, noRemote=False, noConda=False):
222 """
223 Public method to get a sorted list of virtual environment names.
224
225 @param noRemote flag indicating to exclude environments for remote
226 debugging
227 @type bool
228 @param noConda flag indicating to exclude Conda environments
229 @type bool
230 @return sorted list of virtual environment names
231 @rtype list of str
232 """
233 return sorted(
234 ericApp().getObject("VirtualEnvManager").getVirtualenvNames(
235 noRemote=noRemote, noConda=noConda))
236
237 def installPip(self, venvName, userSite=False):
238 """
239 Public method to install pip.
240
241 @param venvName name of the environment to install pip into
242 @type str
243 @param userSite flag indicating an install to the user install
244 directory
245 @type bool
246 """
247 interpreter = self.getVirtualenvInterpreter(venvName)
248 if not interpreter:
249 return
250
251 dia = PipDialog(self.tr('Install PIP'))
252 commands = (
253 [(interpreter, ["-m", "ensurepip", "--user"])]
254 if userSite else
255 [(interpreter, ["-m", "ensurepip"])]
256 )
257 if Preferences.getPip("PipSearchIndex"):
258 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
259 args = ["-m", "pip", "install", "--index-url", indexUrl,
260 "--upgrade"]
261 else:
262 args = ["-m", "pip", "install", "--upgrade"]
263 if userSite:
264 args.append("--user")
265 args.append("pip")
266 commands.append((interpreter, args[:]))
267
268 res = dia.startProcesses(commands)
269 if res:
270 dia.exec()
271
272 @pyqtSlot()
273 def repairPip(self, venvName):
274 """
275 Public method to repair the pip installation.
276
277 @param venvName name of the environment to install pip into
278 @type str
279 """
280 interpreter = self.getVirtualenvInterpreter(venvName)
281 if not interpreter:
282 return
283
284 # python -m pip install --ignore-installed pip
285 if Preferences.getPip("PipSearchIndex"):
286 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
287 args = ["-m", "pip", "install", "--index-url", indexUrl,
288 "--ignore-installed"]
289 else:
290 args = ["-m", "pip", "install", "--ignore-installed"]
291 args.append("pip")
292
293 dia = PipDialog(self.tr('Repair PIP'))
294 res = dia.startProcess(interpreter, args)
295 if res:
296 dia.exec()
297
298 def __checkUpgradePyQt(self, packages):
299 """
300 Private method to check, if an upgrade of PyQt packages is attempted.
301
302 @param packages list of packages to upgrade
303 @type list of str
304 @return flag indicating a PyQt upgrade
305 @rtype bool
306 """
307 pyqtPackages = [
308 p for p in packages if p.lower() in [
309 "pyqt6", "pyqt6-sip", "pyqt6-webengine", "pyqt6-charts",
310 "pyqt6-qscintilla", "pyqt6-qt6", "pyqt6-webengine-qt6",
311 "pyqt6-charts-qt6"
312 ]
313 ]
314 return bool(pyqtPackages)
315
316 def __checkUpgradeEric(self, packages):
317 """
318 Private method to check, if an upgrade of the eric-ide package is
319 attempted.
320
321 @param packages list of packages to upgrade
322 @type list of str
323 @return flag indicating an eric-ide upgrade
324 @rtype bool
325 """
326 ericPackages = [
327 p for p in packages if p.lower() == "eric-ide"
328 ]
329 return bool(ericPackages)
330
331 def upgradePackages(self, packages, venvName, userSite=False):
332 """
333 Public method to upgrade the given list of packages.
334
335 @param packages list of packages to upgrade
336 @type list of str
337 @param venvName name of the virtual environment to be used
338 @type str
339 @param userSite flag indicating an install to the user install
340 directory
341 @type bool
342 @return flag indicating a successful execution
343 @rtype bool
344 """
345 if not venvName:
346 return False
347
348 if self.getVirtualenvInterpreter(venvName) in (
349 sys.executable, Globals.getPythonExecutable()
350 ):
351 upgradePyQt = self.__checkUpgradePyQt(packages)
352 upgradeEric = self.__checkUpgradeEric(packages)
353 if upgradeEric or upgradePyQt:
354 try:
355 if upgradeEric and upgradePyQt:
356 self.__ui.upgradeEricPyQt()
357 elif upgradeEric:
358 self.__ui.upgradeEric()
359 elif upgradePyQt:
360 self.__ui.upgradePyQt()
361 return None # should not be reached; play it safe
362 except AttributeError:
363 return False
364
365 interpreter = self.getVirtualenvInterpreter(venvName)
366 if not interpreter:
367 return False
368
369 if Preferences.getPip("PipSearchIndex"):
370 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
371 args = ["-m", "pip", "install", "--index-url", indexUrl,
372 "--upgrade"]
373 else:
374 args = ["-m", "pip", "install", "--upgrade"]
375 if userSite:
376 args.append("--user")
377 args += packages
378 dia = PipDialog(self.tr('Upgrade Packages'))
379 res = dia.startProcess(interpreter, args)
380 if res:
381 dia.exec()
382 return res
383
384 def installPackages(self, packages, venvName="", userSite=False,
385 interpreter="", forceReinstall=False):
386 """
387 Public method to install the given list of packages.
388
389 @param packages list of packages to install
390 @type list of str
391 @param venvName name of the virtual environment to be used
392 @type str
393 @param userSite flag indicating an install to the user install
394 directory
395 @type bool
396 @param interpreter interpreter to be used for execution
397 @type str
398 @param forceReinstall flag indicating to force a reinstall of
399 the packages
400 @type bool
401 """
402 if venvName:
403 interpreter = self.getVirtualenvInterpreter(venvName)
404 if not interpreter:
405 return
406
407 if interpreter:
408 if Preferences.getPip("PipSearchIndex"):
409 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
410 args = ["-m", "pip", "install", "--index-url", indexUrl]
411 else:
412 args = ["-m", "pip", "install"]
413 if userSite:
414 args.append("--user")
415 if forceReinstall:
416 args.append("--force-reinstall")
417 args += packages
418 dia = PipDialog(self.tr('Install Packages'))
419 res = dia.startProcess(interpreter, args)
420 if res:
421 dia.exec()
422
423 def installRequirements(self, venvName):
424 """
425 Public method to install packages as given in a requirements file.
426
427 @param venvName name of the virtual environment to be used
428 @type str
429 """
430 from .PipFileSelectionDialog import PipFileSelectionDialog
431 dlg = PipFileSelectionDialog(self, "requirements")
432 if dlg.exec() == QDialog.DialogCode.Accepted:
433 requirements, user = dlg.getData()
434 if requirements and os.path.exists(requirements):
435 interpreter = self.getVirtualenvInterpreter(venvName)
436 if not interpreter:
437 return
438
439 if Preferences.getPip("PipSearchIndex"):
440 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
441 args = ["-m", "pip", "install", "--index-url", indexUrl]
442 else:
443 args = ["-m", "pip", "install"]
444 if user:
445 args.append("--user")
446 args += ["--requirement", requirements]
447 dia = PipDialog(self.tr('Install Packages from Requirements'))
448 res = dia.startProcess(interpreter, args)
449 if res:
450 dia.exec()
451
452 def uninstallPackages(self, packages, venvName):
453 """
454 Public method to uninstall the given list of packages.
455
456 @param packages list of packages to uninstall
457 @type list of str
458 @param venvName name of the virtual environment to be used
459 @type str
460 @return flag indicating a successful execution
461 @rtype bool
462 """
463 res = False
464 if packages and venvName:
465 from UI.DeleteFilesConfirmationDialog import (
466 DeleteFilesConfirmationDialog
467 )
468 dlg = DeleteFilesConfirmationDialog(
469 self.parent(),
470 self.tr("Uninstall Packages"),
471 self.tr(
472 "Do you really want to uninstall these packages?"),
473 packages)
474 if dlg.exec() == QDialog.DialogCode.Accepted:
475 interpreter = self.getVirtualenvInterpreter(venvName)
476 if not interpreter:
477 return False
478 args = ["-m", "pip", "uninstall", "--yes"] + packages
479 dia = PipDialog(self.tr('Uninstall Packages'))
480 res = dia.startProcess(interpreter, args)
481 if res:
482 dia.exec()
483 return res
484
485 def uninstallRequirements(self, venvName):
486 """
487 Public method to uninstall packages as given in a requirements file.
488
489 @param venvName name of the virtual environment to be used
490 @type str
491 """
492 if venvName:
493 from .PipFileSelectionDialog import PipFileSelectionDialog
494 dlg = PipFileSelectionDialog(self, "requirements",
495 install=False)
496 if dlg.exec() == QDialog.DialogCode.Accepted:
497 requirements, _user = dlg.getData()
498 if requirements and os.path.exists(requirements):
499 try:
500 with open(requirements, "r") as f:
501 reqs = f.read().splitlines()
502 except OSError:
503 return
504
505 from UI.DeleteFilesConfirmationDialog import (
506 DeleteFilesConfirmationDialog
507 )
508 dlg = DeleteFilesConfirmationDialog(
509 self.parent(),
510 self.tr("Uninstall Packages"),
511 self.tr(
512 "Do you really want to uninstall these packages?"),
513 reqs)
514 if dlg.exec() == QDialog.DialogCode.Accepted:
515 interpreter = self.getVirtualenvInterpreter(venvName)
516 if not interpreter:
517 return
518
519 args = ["-m", "pip", "uninstall", "--requirement",
520 requirements]
521 dia = PipDialog(
522 self.tr('Uninstall Packages from Requirements'))
523 res = dia.startProcess(interpreter, args)
524 if res:
525 dia.exec()
526
527 def getIndexUrl(self):
528 """
529 Public method to get the index URL for PyPI.
530
531 @return index URL for PyPI
532 @rtype str
533 """
534 indexUrl = (
535 Preferences.getPip("PipSearchIndex") + "/simple"
536 if Preferences.getPip("PipSearchIndex") else
537 Pip.DefaultIndexUrlSimple
538 )
539
540 return indexUrl
541
542 def getIndexUrlPypi(self):
543 """
544 Public method to get the index URL for PyPI API calls.
545
546 @return index URL for XML RPC calls
547 @rtype str
548 """
549 indexUrl = (
550 Preferences.getPip("PipSearchIndex") + "/pypi"
551 if Preferences.getPip("PipSearchIndex") else
552 Pip.DefaultIndexUrlPypi
553 )
554
555 return indexUrl
556
557 def getIndexUrlSearch(self):
558 """
559 Public method to get the index URL for PyPI API calls.
560
561 @return index URL for XML RPC calls
562 @rtype str
563 """
564 indexUrl = (
565 Preferences.getPip("PipSearchIndex") + "/search/"
566 if Preferences.getPip("PipSearchIndex") else
567 Pip.DefaultIndexUrlSearch
568 )
569
570 return indexUrl
571
572 def getInstalledPackages(self, envName, localPackages=True,
573 notRequired=False, usersite=False):
574 """
575 Public method to get the list of installed packages.
576
577 @param envName name of the environment to get the packages for
578 @type str
579 @param localPackages flag indicating to get local packages only
580 @type bool
581 @param notRequired flag indicating to list packages that are not
582 dependencies of installed packages as well
583 @type bool
584 @param usersite flag indicating to only list packages installed
585 in user-site
586 @type bool
587 @return list of tuples containing the package name and version
588 @rtype list of tuple of (str, str)
589 """
590 packages = []
591
592 if envName:
593 interpreter = self.getVirtualenvInterpreter(envName)
594 if interpreter:
595 args = [
596 "-m", "pip",
597 "list",
598 "--format=json",
599 ]
600 if localPackages:
601 args.append("--local")
602 if notRequired:
603 args.append("--not-required")
604 if usersite:
605 args.append("--user")
606
607 if Preferences.getPip("PipSearchIndex"):
608 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
609 args += ["--index-url", indexUrl]
610
611 proc = QProcess()
612 proc.start(interpreter, args)
613 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
614 output = str(proc.readAllStandardOutput(),
615 Preferences.getSystem("IOEncoding"),
616 'replace').strip()
617 try:
618 jsonList = json.loads(output)
619 except Exception:
620 jsonList = []
621
622 for package in jsonList:
623 if isinstance(package, dict):
624 packages.append((
625 package["name"],
626 package["version"],
627 ))
628
629 return packages
630
631 def getOutdatedPackages(self, envName, localPackages=True,
632 notRequired=False, usersite=False):
633 """
634 Public method to get the list of outdated packages.
635
636 @param envName name of the environment to get the packages for
637 @type str
638 @param localPackages flag indicating to get local packages only
639 @type bool
640 @param notRequired flag indicating to list packages that are not
641 dependencies of installed packages as well
642 @type bool
643 @param usersite flag indicating to only list packages installed
644 in user-site
645 @type bool
646 @return list of tuples containing the package name, installed version
647 and available version
648 @rtype list of tuple of (str, str, str)
649 """
650 packages = []
651
652 if envName:
653 interpreter = self.getVirtualenvInterpreter(envName)
654 if interpreter:
655 args = [
656 "-m", "pip",
657 "list",
658 "--outdated",
659 "--format=json",
660 ]
661 if localPackages:
662 args.append("--local")
663 if notRequired:
664 args.append("--not-required")
665 if usersite:
666 args.append("--user")
667
668 if Preferences.getPip("PipSearchIndex"):
669 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple"
670 args += ["--index-url", indexUrl]
671
672 proc = QProcess()
673 proc.start(interpreter, args)
674 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
675 output = str(proc.readAllStandardOutput(),
676 Preferences.getSystem("IOEncoding"),
677 'replace').strip()
678 try:
679 jsonList = json.loads(output)
680 except Exception:
681 jsonList = []
682
683 for package in jsonList:
684 if isinstance(package, dict):
685 packages.append((
686 package["name"],
687 package["version"],
688 package["latest_version"],
689 ))
690
691 return packages
692
693 def checkPackageOutdated(self, packageStart, envName):
694 """
695 Public method to check, if a group of packages is outdated.
696
697 @param packageStart start string for package names to be checked
698 (case insensitive)
699 @type str
700 @param envName name of the environment to get the packages for
701 @type str
702 @return tuple containing a flag indicating outdated packages and the
703 list of tuples containing the package name, installed version
704 and available version
705 @rtype tuple of (bool, (str, str, str))
706 """
707 filteredPackages = []
708
709 if bool(envName) and bool(packageStart):
710 packages = self.getOutdatedPackages(envName)
711 filterStr = packageStart.lower()
712 filteredPackages = [
713 p for p in packages
714 if p[0].lower().startswith(filterStr)]
715
716 return bool(filteredPackages), filteredPackages
717
718 def getPackageDetails(self, name, version):
719 """
720 Public method to get package details using the PyPI JSON interface.
721
722 @param name package name
723 @type str
724 @param version package version
725 @type str
726 @return dictionary containing PyPI package data
727 @rtype dict
728 """
729 result = {}
730
731 if name and version:
732 url = "{0}/{1}/{2}/json".format(
733 self.getIndexUrlPypi(), name, version)
734 request = QNetworkRequest(QUrl(url))
735 reply = self.__networkManager.get(request)
736 while not reply.isFinished():
737 QCoreApplication.processEvents()
738 QThread.msleep(100)
739
740 reply.deleteLater()
741 if reply.error() == QNetworkReply.NetworkError.NoError:
742 data = str(reply.readAll(),
743 Preferences.getSystem("IOEncoding"),
744 'replace')
745 with contextlib.suppress(Exception):
746 result = json.loads(data)
747
748 return result
749
750 def getPackageVersions(self, name):
751 """
752 Public method to get a list of versions available for the given
753 package.
754
755 @param name package name
756 @type str
757 @return list of available versions
758 @rtype list of str
759 """
760 result = []
761
762 if name:
763 url = "{0}/{1}/json".format(self.getIndexUrlPypi(), name)
764 request = QNetworkRequest(QUrl(url))
765 reply = self.__networkManager.get(request)
766 while not reply.isFinished():
767 QCoreApplication.processEvents()
768 QThread.msleep(100)
769
770 reply.deleteLater()
771 if reply.error() == QNetworkReply.NetworkError.NoError:
772 dataStr = str(reply.readAll(),
773 Preferences.getSystem("IOEncoding"),
774 'replace')
775 with contextlib.suppress(Exception):
776 data = json.loads(dataStr)
777 result = list(data["releases"].keys())
778
779 return result
780
781 def getFrozenPackages(self, envName, localPackages=True, usersite=False,
782 requirement=None):
783 """
784 Public method to get the list of package specifiers to freeze them.
785
786 @param envName name of the environment to get the package specifiers
787 for
788 @type str
789 @param localPackages flag indicating to get package specifiers for
790 local packages only
791 @type bool
792 @param usersite flag indicating to get package specifiers for packages
793 installed in user-site only
794 @type bool
795 @param requirement name of a requirements file
796 @type str
797 @return list of package specifiers
798 @rtype list of str
799 """
800 specifiers = []
801
802 if envName:
803 interpreter = self.getVirtualenvInterpreter(envName)
804 if interpreter:
805 args = [
806 "-m", "pip",
807 "freeze",
808 ]
809 if localPackages:
810 args.append("--local")
811 if usersite:
812 args.append("--user")
813 if requirement and os.path.exists(requirement):
814 args.append("--requirement")
815 args.append(requirement)
816
817 success, output = self.runProcess(args, interpreter)
818 if success and output:
819 specifiers = [spec.strip() for spec in output.splitlines()
820 if spec.strip()]
821
822 return specifiers
823
824 #######################################################################
825 ## Cache handling methods below
826 #######################################################################
827
828 def showCacheInfo(self, venvName):
829 """
830 Public method to show some information about the pip cache.
831
832 @param venvName name of the virtual environment to be used
833 @type str
834 """
835 if venvName:
836 interpreter = self.getVirtualenvInterpreter(venvName)
837 if interpreter:
838 args = ["-m", "pip", "cache", "info"]
839 dia = PipDialog(self.tr("Cache Info"))
840 res = dia.startProcess(interpreter, args, showArgs=False)
841 if res:
842 dia.exec()
843
844 def cacheList(self, venvName):
845 """
846 Public method to list files contained in the pip cache.
847
848 @param venvName name of the virtual environment to be used
849 @type str
850 """
851 if venvName:
852 interpreter = self.getVirtualenvInterpreter(venvName)
853 if interpreter:
854 pattern, ok = QInputDialog.getText(
855 None,
856 self.tr("List Cached Files"),
857 self.tr("Enter a file pattern (empty for all):"),
858 QLineEdit.EchoMode.Normal)
859
860 if ok:
861 args = ["-m", "pip", "cache", "list"]
862 if pattern.strip():
863 args.append(pattern.strip())
864 dia = PipDialog(self.tr("List Cached Files"))
865 res = dia.startProcess(interpreter, args,
866 showArgs=False)
867 if res:
868 dia.exec()
869
870 def cacheRemove(self, venvName):
871 """
872 Public method to remove files from the pip cache.
873
874 @param venvName name of the virtual environment to be used
875 @type str
876 """
877 if venvName:
878 interpreter = self.getVirtualenvInterpreter(venvName)
879 if interpreter:
880 pattern, ok = QInputDialog.getText(
881 None,
882 self.tr("Remove Cached Files"),
883 self.tr("Enter a file pattern:"),
884 QLineEdit.EchoMode.Normal)
885
886 if ok and pattern.strip():
887 args = ["-m", "pip", "cache", "remove", pattern.strip()]
888 dia = PipDialog(self.tr("Remove Cached Files"))
889 res = dia.startProcess(interpreter, args,
890 showArgs=False)
891 if res:
892 dia.exec()
893
894 def cachePurge(self, venvName):
895 """
896 Public method to remove all files from the pip cache.
897
898 @param venvName name of the virtual environment to be used
899 @type str
900 """
901 if venvName:
902 interpreter = self.getVirtualenvInterpreter(venvName)
903 if interpreter:
904 ok = EricMessageBox.yesNo(
905 None,
906 self.tr("Purge Cache"),
907 self.tr("Do you really want to purge the pip cache? All"
908 " files need to be downloaded again."))
909 if ok:
910 args = ["-m", "pip", "cache", "purge"]
911 dia = PipDialog(self.tr("Purge Cache"))
912 res = dia.startProcess(interpreter, args,
913 showArgs=False)
914 if res:
915 dia.exec()
916
917 #######################################################################
918 ## Dependency tree handling methods below
919 #######################################################################
920
921 def getDependencyTree(self, envName, localPackages=True, usersite=False,
922 reverse=False):
923 """
924 Public method to get the dependency tree of installed packages.
925
926 @param envName name of the environment to get the packages for
927 @type str
928 @param localPackages flag indicating to get the tree for local
929 packages only
930 @type bool
931 @param usersite flag indicating to get the tree for packages
932 installed in user-site directory only
933 @type bool
934 @param reverse flag indicating to get the dependency tree in
935 reverse order (i.e. list packages needed by other)
936 @type bool
937 @return list of nested dictionaries resembling the requested
938 dependency tree
939 @rtype list of dict
940 """
941 dependencies = []
942
943 if envName:
944 interpreter = self.getVirtualenvInterpreter(envName)
945 if interpreter:
946 args = [
947 "-m", "pipdeptree",
948 "--json-tree",
949 "--python", interpreter,
950 ]
951 if localPackages:
952 args.append("--local-only")
953 if usersite:
954 args.append("--user-only")
955 if reverse:
956 args.append("--reverse")
957
958 proc = QProcess()
959 proc.start(Globals.getPythonExecutable(), args)
960 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
961 output = str(proc.readAllStandardOutput(),
962 Preferences.getSystem("IOEncoding"),
963 'replace').strip()
964 with contextlib.suppress(json.JSONDecodeError):
965 dependencies = json.loads(output)
966
967 return dependencies
968
969 #######################################################################
970 ## License handling methods below
971 #######################################################################
972
973 def getLicenses(self, envName, localPackages=True, usersite=False,
974 summary=False):
975 """
976 Public method to get the licenses per package for a given environment.
977
978 @param envName name of the environment to get the licenses for
979 @type str
980 @param localPackages flag indicating to get the licenses for local
981 packages only
982 @type bool
983 @param usersite flag indicating to get the licenses for packages
984 installed in user-site directory only
985 @type bool
986 @param summary flag indicating to get a summary listing (defaults to
987 False)
988 @type bool (optional)
989 @return list of dictionaries containing the license and version per
990 package
991 @rtype dict
992 """
993 licenses = []
994
995 if envName:
996 interpreter = self.getVirtualenvInterpreter(envName)
997 if interpreter:
998 from . import piplicenses
999 with open(piplicenses.__file__, "r") as f:
1000 content = f.read()
1001 args = [
1002 "-c",
1003 content,
1004 "--from",
1005 "mixed",
1006 "--with-system",
1007 "--with-authors",
1008 "--with-urls",
1009 "--with-description",
1010 ]
1011 if localPackages:
1012 args.append("--local-only")
1013 if usersite:
1014 args.append("--user-only")
1015 if summary:
1016 args.append("--summary")
1017
1018 proc = QProcess()
1019 proc.start(interpreter, args)
1020 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
1021 output = str(proc.readAllStandardOutput(),
1022 Preferences.getSystem("IOEncoding"),
1023 'replace').strip()
1024 with contextlib.suppress(json.JSONDecodeError):
1025 licenses = json.loads(output)
1026
1027 return licenses
1028
1029 def getLicensesSummary(self, envName, localPackages=True, usersite=False):
1030 """
1031 Public method to get a summary of licenses found in a given
1032 environment.
1033
1034 @param envName name of the environment to get the licenses summary for
1035 @type str
1036 @param localPackages flag indicating to get the licenses summary for
1037 local packages only
1038 @type bool
1039 @param usersite flag indicating to get the licenses summary for
1040 packages installed in user-site directory only
1041 @type bool
1042 @return list of dictionaries containing the license and the count of
1043 packages
1044 @rtype dict
1045 """
1046 return self.getLicenses(envName, localPackages=localPackages,
1047 usersite=usersite, summary=True)

eric ide

mercurial