src/eric7/PipInterface/Pip.py

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

eric ide

mercurial