|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2015 - 2021 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 PyQt5.QtCore import pyqtSlot, QObject, QProcess, QUrl, QCoreApplication |
|
16 from PyQt5.QtWidgets import QDialog, QInputDialog, QLineEdit |
|
17 from PyQt5.QtNetwork import ( |
|
18 QNetworkAccessManager, QNetworkRequest, QNetworkReply |
|
19 ) |
|
20 |
|
21 from E5Gui import E5MessageBox |
|
22 from E5Gui.E5Application import e5App |
|
23 |
|
24 from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired |
|
25 try: |
|
26 from E5Network.E5SslErrorHandler import E5SslErrorHandler |
|
27 SSL_AVAILABLE = True |
|
28 except ImportError: |
|
29 SSL_AVAILABLE = False |
|
30 |
|
31 from .PipDialog import PipDialog |
|
32 |
|
33 import Preferences |
|
34 import Globals |
|
35 |
|
36 |
|
37 class Pip(QObject): |
|
38 """ |
|
39 Class implementing the pip GUI logic. |
|
40 """ |
|
41 DefaultPyPiUrl = "https://pypi.org" |
|
42 DefaultIndexUrlPypi = DefaultPyPiUrl + "/pypi" |
|
43 DefaultIndexUrlSimple = DefaultPyPiUrl + "/simple" |
|
44 DefaultIndexUrlSearch = DefaultPyPiUrl + "/search/" |
|
45 |
|
46 def __init__(self, parent=None): |
|
47 """ |
|
48 Constructor |
|
49 |
|
50 @param parent parent |
|
51 @type QObject |
|
52 """ |
|
53 super().__init__(parent) |
|
54 |
|
55 # attributes for the network objects |
|
56 self.__networkManager = QNetworkAccessManager(self) |
|
57 self.__networkManager.proxyAuthenticationRequired.connect( |
|
58 proxyAuthenticationRequired) |
|
59 if SSL_AVAILABLE: |
|
60 self.__sslErrorHandler = E5SslErrorHandler(self) |
|
61 self.__networkManager.sslErrors.connect( |
|
62 self.__sslErrorHandler.sslErrorsReply) |
|
63 self.__replies = [] |
|
64 |
|
65 def getNetworkAccessManager(self): |
|
66 """ |
|
67 Public method to get a reference to the network access manager object. |
|
68 |
|
69 @return reference to the network access manager object |
|
70 @rtype QNetworkAccessManager |
|
71 """ |
|
72 return self.__networkManager |
|
73 |
|
74 ########################################################################## |
|
75 ## Methods below implement some utility functions |
|
76 ########################################################################## |
|
77 |
|
78 def runProcess(self, args, interpreter): |
|
79 """ |
|
80 Public method to execute the current pip with the given arguments. |
|
81 |
|
82 The selected pip executable is called with the given arguments and |
|
83 waited for its end. |
|
84 |
|
85 @param args list of command line arguments |
|
86 @type list of str |
|
87 @param interpreter path of the Python interpreter to be used |
|
88 @type str |
|
89 @return tuple containing a flag indicating success and the output |
|
90 of the process |
|
91 @rtype tuple of (bool, str) |
|
92 """ |
|
93 ioEncoding = Preferences.getSystem("IOEncoding") |
|
94 |
|
95 process = QProcess() |
|
96 process.start(interpreter, args) |
|
97 procStarted = process.waitForStarted() |
|
98 if procStarted: |
|
99 finished = process.waitForFinished(30000) |
|
100 if finished: |
|
101 if process.exitCode() == 0: |
|
102 output = str(process.readAllStandardOutput(), ioEncoding, |
|
103 'replace') |
|
104 return True, output |
|
105 else: |
|
106 return (False, |
|
107 self.tr("python exited with an error ({0}).") |
|
108 .format(process.exitCode())) |
|
109 else: |
|
110 process.terminate() |
|
111 process.waitForFinished(2000) |
|
112 process.kill() |
|
113 process.waitForFinished(3000) |
|
114 return False, self.tr("python did not finish within" |
|
115 " 30 seconds.") |
|
116 |
|
117 return False, self.tr("python could not be started.") |
|
118 |
|
119 def getUserConfig(self): |
|
120 """ |
|
121 Public method to get the name of the user configuration file. |
|
122 |
|
123 @return path of the user configuration file |
|
124 @rtype str |
|
125 """ |
|
126 # Unix: ~/.config/pip/pip.conf |
|
127 # OS X: ~/Library/Application Support/pip/pip.conf |
|
128 # Windows: %APPDATA%\pip\pip.ini |
|
129 # Environment: $PIP_CONFIG_FILE |
|
130 |
|
131 with contextlib.suppress(KeyError): |
|
132 return os.environ["PIP_CONFIG_FILE"] |
|
133 |
|
134 if Globals.isWindowsPlatform(): |
|
135 config = os.path.join(os.environ["APPDATA"], "pip", "pip.ini") |
|
136 elif Globals.isMacPlatform(): |
|
137 config = os.path.expanduser( |
|
138 "~/Library/Application Support/pip/pip.conf") |
|
139 else: |
|
140 config = os.path.expanduser("~/.config/pip/pip.conf") |
|
141 |
|
142 return config |
|
143 |
|
144 def getVirtualenvConfig(self, venvName): |
|
145 """ |
|
146 Public method to get the name of the virtualenv configuration file. |
|
147 |
|
148 @param venvName name of the environment to get config file path for |
|
149 @type str |
|
150 @return path of the virtualenv configuration file |
|
151 @rtype str |
|
152 """ |
|
153 # Unix, OS X: $VIRTUAL_ENV/pip.conf |
|
154 # Windows: %VIRTUAL_ENV%\pip.ini |
|
155 |
|
156 pip = "pip.ini" if Globals.isWindowsPlatform() else "pip.conf" |
|
157 |
|
158 venvManager = e5App().getObject("VirtualEnvManager") |
|
159 venvDirectory = ( |
|
160 os.path.dirname(self.getUserConfig()) |
|
161 if venvManager.isGlobalEnvironment(venvName) else |
|
162 venvManager.getVirtualenvDirectory(venvName) |
|
163 ) |
|
164 |
|
165 config = os.path.join(venvDirectory, pip) if venvDirectory else "" |
|
166 |
|
167 return config |
|
168 |
|
169 def getProjectEnvironmentString(self): |
|
170 """ |
|
171 Public method to get the string for the project environment. |
|
172 |
|
173 @return string for the project environment |
|
174 @rtype str |
|
175 """ |
|
176 if e5App().getObject("Project").isOpen(): |
|
177 return self.tr("<project>") |
|
178 else: |
|
179 return "" |
|
180 |
|
181 def getVirtualenvInterpreter(self, venvName): |
|
182 """ |
|
183 Public method to get the interpreter for a virtual environment. |
|
184 |
|
185 @param venvName logical name for the virtual environment |
|
186 @type str |
|
187 @return interpreter path |
|
188 @rtype str |
|
189 """ |
|
190 if venvName == self.getProjectEnvironmentString(): |
|
191 venvName = ( |
|
192 e5App().getObject("Project") |
|
193 .getDebugProperty("VIRTUALENV") |
|
194 ) |
|
195 if not venvName: |
|
196 # fall back to interpreter used to run eric6 |
|
197 return sys.executable |
|
198 |
|
199 interpreter = ( |
|
200 e5App().getObject("VirtualEnvManager") |
|
201 .getVirtualenvInterpreter(venvName) |
|
202 ) |
|
203 if not interpreter: |
|
204 E5MessageBox.critical( |
|
205 None, |
|
206 self.tr("Interpreter for Virtual Environment"), |
|
207 self.tr("""No interpreter configured for the selected""" |
|
208 """ virtual environment.""")) |
|
209 |
|
210 return interpreter |
|
211 |
|
212 def getVirtualenvNames(self, noRemote=False, noConda=False): |
|
213 """ |
|
214 Public method to get a sorted list of virtual environment names. |
|
215 |
|
216 @param noRemote flag indicating to exclude environments for remote |
|
217 debugging |
|
218 @type bool |
|
219 @param noConda flag indicating to exclude Conda environments |
|
220 @type bool |
|
221 @return sorted list of virtual environment names |
|
222 @rtype list of str |
|
223 """ |
|
224 return sorted( |
|
225 e5App().getObject("VirtualEnvManager").getVirtualenvNames( |
|
226 noRemote=noRemote, noConda=noConda)) |
|
227 |
|
228 def installPip(self, venvName, userSite=False): |
|
229 """ |
|
230 Public method to install pip. |
|
231 |
|
232 @param venvName name of the environment to install pip into |
|
233 @type str |
|
234 @param userSite flag indicating an install to the user install |
|
235 directory |
|
236 @type bool |
|
237 """ |
|
238 interpreter = self.getVirtualenvInterpreter(venvName) |
|
239 if not interpreter: |
|
240 return |
|
241 |
|
242 dia = PipDialog(self.tr('Install PIP')) |
|
243 commands = ( |
|
244 [(interpreter, ["-m", "ensurepip", "--user"])] |
|
245 if userSite else |
|
246 [(interpreter, ["-m", "ensurepip"])] |
|
247 ) |
|
248 if Preferences.getPip("PipSearchIndex"): |
|
249 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
250 args = ["-m", "pip", "install", "--index-url", indexUrl, |
|
251 "--upgrade"] |
|
252 else: |
|
253 args = ["-m", "pip", "install", "--upgrade"] |
|
254 if userSite: |
|
255 args.append("--user") |
|
256 args.append("pip") |
|
257 commands.append((interpreter, args[:])) |
|
258 |
|
259 res = dia.startProcesses(commands) |
|
260 if res: |
|
261 dia.exec() |
|
262 |
|
263 @pyqtSlot() |
|
264 def repairPip(self, venvName): |
|
265 """ |
|
266 Public method to repair the pip installation. |
|
267 |
|
268 @param venvName name of the environment to install pip into |
|
269 @type str |
|
270 """ |
|
271 interpreter = self.getVirtualenvInterpreter(venvName) |
|
272 if not interpreter: |
|
273 return |
|
274 |
|
275 # python -m pip install --ignore-installed pip |
|
276 if Preferences.getPip("PipSearchIndex"): |
|
277 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
278 args = ["-m", "pip", "install", "--index-url", indexUrl, |
|
279 "--ignore-installed"] |
|
280 else: |
|
281 args = ["-m", "pip", "install", "--ignore-installed"] |
|
282 args.append("pip") |
|
283 |
|
284 dia = PipDialog(self.tr('Repair PIP')) |
|
285 res = dia.startProcess(interpreter, args) |
|
286 if res: |
|
287 dia.exec() |
|
288 |
|
289 def __checkUpgradePyQt(self, packages): |
|
290 """ |
|
291 Private method to check, if an upgrade of PyQt packages is attempted. |
|
292 |
|
293 @param packages list of packages to upgrade |
|
294 @type list of str |
|
295 @return flag indicating to abort the upgrade attempt |
|
296 @rtype bool |
|
297 """ |
|
298 pyqtPackages = [p for p in packages |
|
299 if p.lower() in ["pyqt5", "pyqt5-sip", "pyqtwebengine", |
|
300 "qscintilla", "sip"]] |
|
301 |
|
302 abort = ( |
|
303 not E5MessageBox.yesNo( |
|
304 None, |
|
305 self.tr("Upgrade Packages"), |
|
306 self.tr( |
|
307 """You are trying to upgrade PyQt packages. This might""" |
|
308 """ not work for the current instance of Python ({0}).""" |
|
309 """ Do you want to continue?""").format(sys.executable), |
|
310 icon=E5MessageBox.Critical) |
|
311 if bool(pyqtPackages) else |
|
312 False |
|
313 ) |
|
314 |
|
315 return abort |
|
316 |
|
317 def upgradePackages(self, packages, venvName, userSite=False): |
|
318 """ |
|
319 Public method to upgrade the given list of packages. |
|
320 |
|
321 @param packages list of packages to upgrade |
|
322 @type list of str |
|
323 @param venvName name of the virtual environment to be used |
|
324 @type str |
|
325 @param userSite flag indicating an install to the user install |
|
326 directory |
|
327 @type bool |
|
328 @return flag indicating a successful execution |
|
329 @rtype bool |
|
330 """ |
|
331 if self.__checkUpgradePyQt(packages): |
|
332 return False |
|
333 |
|
334 if not venvName: |
|
335 return False |
|
336 |
|
337 interpreter = self.getVirtualenvInterpreter(venvName) |
|
338 if not interpreter: |
|
339 return False |
|
340 |
|
341 if Preferences.getPip("PipSearchIndex"): |
|
342 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
343 args = ["-m", "pip", "install", "--index-url", indexUrl, |
|
344 "--upgrade"] |
|
345 else: |
|
346 args = ["-m", "pip", "install", "--upgrade"] |
|
347 if userSite: |
|
348 args.append("--user") |
|
349 args += packages |
|
350 dia = PipDialog(self.tr('Upgrade Packages')) |
|
351 res = dia.startProcess(interpreter, args) |
|
352 if res: |
|
353 dia.exec() |
|
354 return res |
|
355 |
|
356 def installPackages(self, packages, venvName="", userSite=False, |
|
357 interpreter="", forceReinstall=False): |
|
358 """ |
|
359 Public method to install the given list of packages. |
|
360 |
|
361 @param packages list of packages to install |
|
362 @type list of str |
|
363 @param venvName name of the virtual environment to be used |
|
364 @type str |
|
365 @param userSite flag indicating an install to the user install |
|
366 directory |
|
367 @type bool |
|
368 @param interpreter interpreter to be used for execution |
|
369 @type str |
|
370 @param forceReinstall flag indicating to force a reinstall of |
|
371 the packages |
|
372 @type bool |
|
373 """ |
|
374 if venvName: |
|
375 interpreter = self.getVirtualenvInterpreter(venvName) |
|
376 if not interpreter: |
|
377 return |
|
378 |
|
379 if interpreter: |
|
380 if Preferences.getPip("PipSearchIndex"): |
|
381 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
382 args = ["-m", "pip", "install", "--index-url", indexUrl] |
|
383 else: |
|
384 args = ["-m", "pip", "install"] |
|
385 if userSite: |
|
386 args.append("--user") |
|
387 if forceReinstall: |
|
388 args.append("--force-reinstall") |
|
389 args += packages |
|
390 dia = PipDialog(self.tr('Install Packages')) |
|
391 res = dia.startProcess(interpreter, args) |
|
392 if res: |
|
393 dia.exec() |
|
394 |
|
395 def installRequirements(self, venvName): |
|
396 """ |
|
397 Public method to install packages as given in a requirements file. |
|
398 |
|
399 @param venvName name of the virtual environment to be used |
|
400 @type str |
|
401 """ |
|
402 from .PipFileSelectionDialog import PipFileSelectionDialog |
|
403 dlg = PipFileSelectionDialog(self, "requirements") |
|
404 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
405 requirements, user = dlg.getData() |
|
406 if requirements and os.path.exists(requirements): |
|
407 interpreter = self.getVirtualenvInterpreter(venvName) |
|
408 if not interpreter: |
|
409 return |
|
410 |
|
411 if Preferences.getPip("PipSearchIndex"): |
|
412 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
413 args = ["-m", "pip", "install", "--index-url", indexUrl] |
|
414 else: |
|
415 args = ["-m", "pip", "install"] |
|
416 if user: |
|
417 args.append("--user") |
|
418 args += ["--requirement", requirements] |
|
419 dia = PipDialog(self.tr('Install Packages from Requirements')) |
|
420 res = dia.startProcess(interpreter, args) |
|
421 if res: |
|
422 dia.exec() |
|
423 |
|
424 def uninstallPackages(self, packages, venvName): |
|
425 """ |
|
426 Public method to uninstall the given list of packages. |
|
427 |
|
428 @param packages list of packages to uninstall |
|
429 @type list of str |
|
430 @param venvName name of the virtual environment to be used |
|
431 @type str |
|
432 @return flag indicating a successful execution |
|
433 @rtype bool |
|
434 """ |
|
435 res = False |
|
436 if packages and venvName: |
|
437 from UI.DeleteFilesConfirmationDialog import ( |
|
438 DeleteFilesConfirmationDialog |
|
439 ) |
|
440 dlg = DeleteFilesConfirmationDialog( |
|
441 self.parent(), |
|
442 self.tr("Uninstall Packages"), |
|
443 self.tr( |
|
444 "Do you really want to uninstall these packages?"), |
|
445 packages) |
|
446 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
447 interpreter = self.getVirtualenvInterpreter(venvName) |
|
448 if not interpreter: |
|
449 return False |
|
450 args = ["-m", "pip", "uninstall", "--yes"] + packages |
|
451 dia = PipDialog(self.tr('Uninstall Packages')) |
|
452 res = dia.startProcess(interpreter, args) |
|
453 if res: |
|
454 dia.exec() |
|
455 return res |
|
456 |
|
457 def uninstallRequirements(self, venvName): |
|
458 """ |
|
459 Public method to uninstall packages as given in a requirements file. |
|
460 |
|
461 @param venvName name of the virtual environment to be used |
|
462 @type str |
|
463 """ |
|
464 if venvName: |
|
465 from .PipFileSelectionDialog import PipFileSelectionDialog |
|
466 dlg = PipFileSelectionDialog(self, "requirements", |
|
467 install=False) |
|
468 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
469 requirements, _user = dlg.getData() |
|
470 if requirements and os.path.exists(requirements): |
|
471 try: |
|
472 with open(requirements, "r") as f: |
|
473 reqs = f.read().splitlines() |
|
474 except OSError: |
|
475 return |
|
476 |
|
477 from UI.DeleteFilesConfirmationDialog import ( |
|
478 DeleteFilesConfirmationDialog |
|
479 ) |
|
480 dlg = DeleteFilesConfirmationDialog( |
|
481 self.parent(), |
|
482 self.tr("Uninstall Packages"), |
|
483 self.tr( |
|
484 "Do you really want to uninstall these packages?"), |
|
485 reqs) |
|
486 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
487 interpreter = self.getVirtualenvInterpreter(venvName) |
|
488 if not interpreter: |
|
489 return |
|
490 |
|
491 args = ["-m", "pip", "uninstall", "--requirement", |
|
492 requirements] |
|
493 dia = PipDialog( |
|
494 self.tr('Uninstall Packages from Requirements')) |
|
495 res = dia.startProcess(interpreter, args) |
|
496 if res: |
|
497 dia.exec() |
|
498 |
|
499 def getIndexUrl(self): |
|
500 """ |
|
501 Public method to get the index URL for PyPI. |
|
502 |
|
503 @return index URL for PyPI |
|
504 @rtype str |
|
505 """ |
|
506 indexUrl = ( |
|
507 Preferences.getPip("PipSearchIndex") + "/simple" |
|
508 if Preferences.getPip("PipSearchIndex") else |
|
509 Pip.DefaultIndexUrlSimple |
|
510 ) |
|
511 |
|
512 return indexUrl |
|
513 |
|
514 def getIndexUrlPypi(self): |
|
515 """ |
|
516 Public method to get the index URL for PyPI API calls. |
|
517 |
|
518 @return index URL for XML RPC calls |
|
519 @rtype str |
|
520 """ |
|
521 indexUrl = ( |
|
522 Preferences.getPip("PipSearchIndex") + "/pypi" |
|
523 if Preferences.getPip("PipSearchIndex") else |
|
524 Pip.DefaultIndexUrlPypi |
|
525 ) |
|
526 |
|
527 return indexUrl |
|
528 |
|
529 def getIndexUrlSearch(self): |
|
530 """ |
|
531 Public method to get the index URL for PyPI API calls. |
|
532 |
|
533 @return index URL for XML RPC calls |
|
534 @rtype str |
|
535 """ |
|
536 indexUrl = ( |
|
537 Preferences.getPip("PipSearchIndex") + "/search/" |
|
538 if Preferences.getPip("PipSearchIndex") else |
|
539 Pip.DefaultIndexUrlSearch |
|
540 ) |
|
541 |
|
542 return indexUrl |
|
543 |
|
544 def getInstalledPackages(self, envName, localPackages=True, |
|
545 notRequired=False, usersite=False): |
|
546 """ |
|
547 Public method to get the list of installed packages. |
|
548 |
|
549 @param envName name of the environment to get the packages for |
|
550 @type str |
|
551 @param localPackages flag indicating to get local packages only |
|
552 @type bool |
|
553 @param notRequired flag indicating to list packages that are not |
|
554 dependencies of installed packages as well |
|
555 @type bool |
|
556 @param usersite flag indicating to only list packages installed |
|
557 in user-site |
|
558 @type bool |
|
559 @return list of tuples containing the package name and version |
|
560 @rtype list of tuple of (str, str) |
|
561 """ |
|
562 packages = [] |
|
563 |
|
564 if envName: |
|
565 interpreter = self.getVirtualenvInterpreter(envName) |
|
566 if interpreter: |
|
567 args = [ |
|
568 "-m", "pip", |
|
569 "list", |
|
570 "--format=json", |
|
571 ] |
|
572 if localPackages: |
|
573 args.append("--local") |
|
574 if notRequired: |
|
575 args.append("--not-required") |
|
576 if usersite: |
|
577 args.append("--user") |
|
578 |
|
579 if Preferences.getPip("PipSearchIndex"): |
|
580 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
581 args += ["--index-url", indexUrl] |
|
582 |
|
583 proc = QProcess() |
|
584 proc.start(interpreter, args) |
|
585 if proc.waitForStarted(15000) and proc.waitForFinished(30000): |
|
586 output = str(proc.readAllStandardOutput(), |
|
587 Preferences.getSystem("IOEncoding"), |
|
588 'replace').strip() |
|
589 try: |
|
590 jsonList = json.loads(output) |
|
591 except Exception: |
|
592 jsonList = [] |
|
593 |
|
594 for package in jsonList: |
|
595 if isinstance(package, dict): |
|
596 packages.append(( |
|
597 package["name"], |
|
598 package["version"], |
|
599 )) |
|
600 |
|
601 return packages |
|
602 |
|
603 def getOutdatedPackages(self, envName, localPackages=True, |
|
604 notRequired=False, usersite=False): |
|
605 """ |
|
606 Public method to get the list of outdated packages. |
|
607 |
|
608 @param envName name of the environment to get the packages for |
|
609 @type str |
|
610 @param localPackages flag indicating to get local packages only |
|
611 @type bool |
|
612 @param notRequired flag indicating to list packages that are not |
|
613 dependencies of installed packages as well |
|
614 @type bool |
|
615 @param usersite flag indicating to only list packages installed |
|
616 in user-site |
|
617 @type bool |
|
618 @return list of tuples containing the package name, installed version |
|
619 and available version |
|
620 @rtype list of tuple of (str, str, str) |
|
621 """ |
|
622 packages = [] |
|
623 |
|
624 if envName: |
|
625 interpreter = self.getVirtualenvInterpreter(envName) |
|
626 if interpreter: |
|
627 args = [ |
|
628 "-m", "pip", |
|
629 "list", |
|
630 "--outdated", |
|
631 "--format=json", |
|
632 ] |
|
633 if localPackages: |
|
634 args.append("--local") |
|
635 if notRequired: |
|
636 args.append("--not-required") |
|
637 if usersite: |
|
638 args.append("--user") |
|
639 |
|
640 if Preferences.getPip("PipSearchIndex"): |
|
641 indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" |
|
642 args += ["--index-url", indexUrl] |
|
643 |
|
644 proc = QProcess() |
|
645 proc.start(interpreter, args) |
|
646 if proc.waitForStarted(15000) and proc.waitForFinished(30000): |
|
647 output = str(proc.readAllStandardOutput(), |
|
648 Preferences.getSystem("IOEncoding"), |
|
649 'replace').strip() |
|
650 try: |
|
651 jsonList = json.loads(output) |
|
652 except Exception: |
|
653 jsonList = [] |
|
654 |
|
655 for package in jsonList: |
|
656 if isinstance(package, dict): |
|
657 packages.append(( |
|
658 package["name"], |
|
659 package["version"], |
|
660 package["latest_version"], |
|
661 )) |
|
662 |
|
663 return packages |
|
664 |
|
665 def getPackageDetails(self, name, version): |
|
666 """ |
|
667 Public method to get package details using the PyPI JSON interface. |
|
668 |
|
669 @param name package name |
|
670 @type str |
|
671 @param version package version |
|
672 @type str |
|
673 @return dictionary containing PyPI package data |
|
674 @rtype dict |
|
675 """ |
|
676 result = {} |
|
677 |
|
678 if name and version: |
|
679 url = "{0}/{1}/{2}/json".format( |
|
680 self.getIndexUrlPypi(), name, version) |
|
681 request = QNetworkRequest(QUrl(url)) |
|
682 reply = self.__networkManager.get(request) |
|
683 while not reply.isFinished(): |
|
684 QCoreApplication.processEvents() |
|
685 |
|
686 reply.deleteLater() |
|
687 if reply.error() == QNetworkReply.NetworkError.NoError: |
|
688 data = str(reply.readAll(), |
|
689 Preferences.getSystem("IOEncoding"), |
|
690 'replace') |
|
691 with contextlib.suppress(Exception): |
|
692 result = json.loads(data) |
|
693 |
|
694 return result |
|
695 |
|
696 ####################################################################### |
|
697 ## Cache handling methods below |
|
698 ####################################################################### |
|
699 |
|
700 def showCacheInfo(self, venvName): |
|
701 """ |
|
702 Public method to show some information about the pip cache. |
|
703 |
|
704 @param venvName name of the virtual environment to be used |
|
705 @type str |
|
706 """ |
|
707 if venvName: |
|
708 interpreter = self.getVirtualenvInterpreter(venvName) |
|
709 if interpreter: |
|
710 args = ["-m", "pip", "cache", "info"] |
|
711 dia = PipDialog(self.tr("Cache Info")) |
|
712 res = dia.startProcess(interpreter, args, showArgs=False) |
|
713 if res: |
|
714 dia.exec() |
|
715 |
|
716 def cacheList(self, venvName): |
|
717 """ |
|
718 Public method to list files contained in the pip cache. |
|
719 |
|
720 @param venvName name of the virtual environment to be used |
|
721 @type str |
|
722 """ |
|
723 if venvName: |
|
724 interpreter = self.getVirtualenvInterpreter(venvName) |
|
725 if interpreter: |
|
726 pattern, ok = QInputDialog.getText( |
|
727 None, |
|
728 self.tr("List Cached Files"), |
|
729 self.tr("Enter a file pattern (empty for all):"), |
|
730 QLineEdit.EchoMode.Normal) |
|
731 |
|
732 if ok: |
|
733 args = ["-m", "pip", "cache", "list"] |
|
734 if pattern.strip(): |
|
735 args.append(pattern.strip()) |
|
736 dia = PipDialog(self.tr("List Cached Files")) |
|
737 res = dia.startProcess(interpreter, args, |
|
738 showArgs=False) |
|
739 if res: |
|
740 dia.exec() |
|
741 |
|
742 def cacheRemove(self, venvName): |
|
743 """ |
|
744 Public method to remove files from the pip cache. |
|
745 |
|
746 @param venvName name of the virtual environment to be used |
|
747 @type str |
|
748 """ |
|
749 if venvName: |
|
750 interpreter = self.getVirtualenvInterpreter(venvName) |
|
751 if interpreter: |
|
752 pattern, ok = QInputDialog.getText( |
|
753 None, |
|
754 self.tr("Remove Cached Files"), |
|
755 self.tr("Enter a file pattern:"), |
|
756 QLineEdit.EchoMode.Normal) |
|
757 |
|
758 if ok and pattern.strip(): |
|
759 args = ["-m", "pip", "cache", "remove", pattern.strip()] |
|
760 dia = PipDialog(self.tr("Remove Cached Files")) |
|
761 res = dia.startProcess(interpreter, args, |
|
762 showArgs=False) |
|
763 if res: |
|
764 dia.exec() |
|
765 |
|
766 def cachePurge(self, venvName): |
|
767 """ |
|
768 Public method to remove all files from the pip cache. |
|
769 |
|
770 @param venvName name of the virtual environment to be used |
|
771 @type str |
|
772 """ |
|
773 if venvName: |
|
774 interpreter = self.getVirtualenvInterpreter(venvName) |
|
775 if interpreter: |
|
776 ok = E5MessageBox.yesNo( |
|
777 None, |
|
778 self.tr("Purge Cache"), |
|
779 self.tr("Do you really want to purge the pip cache? All" |
|
780 " files need to be downloaded again.")) |
|
781 if ok: |
|
782 args = ["-m", "pip", "cache", "purge"] |
|
783 dia = PipDialog(self.tr("Purge Cache")) |
|
784 res = dia.startProcess(interpreter, args, |
|
785 showArgs=False) |
|
786 if res: |
|
787 dia.exec() |