CondaInterface/Conda.py

branch
maintenance
changeset 6826
c6dda2cbe081
parent 6728
ba077788a882
equal deleted inserted replaced
6764:d14ddbfbbd36 6826:c6dda2cbe081
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Package implementing the conda GUI logic.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode # __IGNORE_EXCEPTION__
13 except NameError:
14 pass
15
16 import json
17 import os
18
19 from PyQt5.QtCore import pyqtSignal, QObject, QProcess, QCoreApplication
20 from PyQt5.QtWidgets import QDialog
21
22 from E5Gui import E5MessageBox
23
24 import Globals
25 import Preferences
26
27 from . import rootPrefix, condaVersion
28 from .CondaExecDialog import CondaExecDialog
29
30
31 class Conda(QObject):
32 """
33 Class implementing the conda GUI logic.
34
35 @signal condaEnvironmentCreated() emitted to indicate the creation of
36 a new environment
37 @signal condaEnvironmentRemoved() emitted to indicate the removal of
38 an environment
39 """
40 condaEnvironmentCreated = pyqtSignal()
41 condaEnvironmentRemoved = pyqtSignal()
42
43 RootName = QCoreApplication.translate("Conda", "<root>")
44
45 def __init__(self, parent=None):
46 """
47 Constructor
48
49 @param parent parent
50 @type QObject
51 """
52 super(Conda, self).__init__(parent)
53
54 self.__ui = parent
55
56 #######################################################################
57 ## environment related methods below
58 #######################################################################
59
60 def createCondaEnvironment(self, arguments):
61 """
62 Public method to create a conda environment.
63
64 @param arguments list of command line arguments
65 @type list of str
66 @return tuple containing a flag indicating success, the directory of
67 the created environment (aka. prefix) and the corresponding Python
68 interpreter
69 @rtype tuple of (bool, str, str)
70 """
71 args = ["create", "--json", "--yes"] + arguments
72
73 dlg = CondaExecDialog("create", self.__ui)
74 dlg.start(args)
75 dlg.exec_()
76 ok, resultDict = dlg.getResult()
77
78 if ok:
79 if "actions" in resultDict and \
80 "PREFIX" in resultDict["actions"]:
81 prefix = resultDict["actions"]["PREFIX"]
82 elif "prefix" in resultDict:
83 prefix = resultDict["prefix"]
84 elif "dst_prefix" in resultDict:
85 prefix = resultDict["dst_prefix"]
86 else:
87 prefix = ""
88
89 # determine Python executable
90 if prefix:
91 pathPrefixes = [
92 prefix,
93 rootPrefix()
94 ]
95 else:
96 pathPrefixes = [
97 rootPrefix()
98 ]
99 for pathPrefix in pathPrefixes:
100 if Globals.isWindowsPlatform():
101 python = os.path.join(pathPrefix, "python.exe")
102 else:
103 python = os.path.join(pathPrefix, "bin", "python")
104 if os.path.exists(python):
105 break
106 else:
107 python = ""
108
109 self.condaEnvironmentCreated.emit()
110 return True, prefix, python
111 else:
112 return False, "", ""
113
114 def removeCondaEnvironment(self, name="", prefix=""):
115 """
116 Public method to remove a conda environment.
117
118 @param name name of the environment
119 @type str
120 @param prefix prefix of the environment
121 @type str
122 @return flag indicating success
123 @rtype bool
124 @exception RuntimeError raised to indicate an error in parameters
125
126 Note: only one of name or prefix must be given.
127 """
128 if name and prefix:
129 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
130
131 if not name and not prefix:
132 raise RuntimeError("One of 'name' or 'prefix' must be given.")
133
134 args = [
135 "remove",
136 "--json",
137 "--quiet",
138 "--all",
139 ]
140 if name:
141 args.extend(["--name", name])
142 elif prefix:
143 args.extend(["--prefix", prefix])
144
145 exe = Preferences.getConda("CondaExecutable")
146 if not exe:
147 exe = "conda"
148
149 proc = QProcess()
150 proc.start(exe, args)
151 if not proc.waitForStarted(15000):
152 E5MessageBox.critical(
153 self.__ui,
154 self.tr("conda remove"),
155 self.tr("""The conda executable could not be started."""))
156 return False
157 else:
158 proc.waitForFinished(15000)
159 output = str(proc.readAllStandardOutput(),
160 Preferences.getSystem("IOEncoding"),
161 'replace').strip()
162 try:
163 jsonDict = json.loads(output)
164 except Exception:
165 E5MessageBox.critical(
166 self.__ui,
167 self.tr("conda remove"),
168 self.tr("""The conda executable returned invalid data."""))
169 return False
170
171 if "error" in jsonDict:
172 E5MessageBox.critical(
173 self.__ui,
174 self.tr("conda remove"),
175 self.tr("<p>The conda executable returned an error.</p>"
176 "<p>{0}</p>").format(jsonDict["message"]))
177 return False
178
179 if jsonDict["success"]:
180 self.condaEnvironmentRemoved.emit()
181
182 return jsonDict["success"]
183
184 return False
185
186 def getCondaEnvironmentsList(self):
187 """
188 Public method to get a list of all Conda environments.
189
190 @return list of tuples containing the environment name and prefix
191 @rtype list of tuples of (str, str)
192 """
193 exe = Preferences.getConda("CondaExecutable")
194 if not exe:
195 exe = "conda"
196
197 environmentsList = []
198
199 proc = QProcess()
200 proc.start(exe, ["info", "--json"])
201 if proc.waitForStarted(15000):
202 if proc.waitForFinished(15000):
203 output = str(proc.readAllStandardOutput(),
204 Preferences.getSystem("IOEncoding"),
205 'replace').strip()
206 try:
207 jsonDict = json.loads(output)
208 except Exception:
209 jsonDict = {}
210
211 if "envs" in jsonDict:
212 for prefix in jsonDict["envs"][:]:
213 if prefix == jsonDict["root_prefix"]:
214 if not jsonDict["root_writable"]:
215 # root prefix is listed but not writable
216 continue
217 name = self.RootName
218 else:
219 name = os.path.basename(prefix)
220
221 environmentsList.append((name, prefix))
222
223 return environmentsList
224
225 #######################################################################
226 ## package related methods below
227 #######################################################################
228
229 def getInstalledPackages(self, name="", prefix=""):
230 """
231 Public method to get a list of installed packages of a conda
232 environment.
233
234 @param name name of the environment
235 @type str
236 @param prefix prefix of the environment
237 @type str
238 @return list of installed packages. Each entry is a tuple containing
239 the package name, version and build.
240 @rtype list of tuples of (str, str, str)
241 @exception RuntimeError raised to indicate an error in parameters
242
243 Note: only one of name or prefix must be given.
244 """
245 if name and prefix:
246 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
247
248 if not name and not prefix:
249 raise RuntimeError("One of 'name' or 'prefix' must be given.")
250
251 args = [
252 "list",
253 "--json",
254 ]
255 if name:
256 args.extend(["--name", name])
257 elif prefix:
258 args.extend(["--prefix", prefix])
259
260 exe = Preferences.getConda("CondaExecutable")
261 if not exe:
262 exe = "conda"
263
264 packages = []
265
266 proc = QProcess()
267 proc.start(exe, args)
268 if proc.waitForStarted(15000):
269 if proc.waitForFinished(30000):
270 output = str(proc.readAllStandardOutput(),
271 Preferences.getSystem("IOEncoding"),
272 'replace').strip()
273 try:
274 jsonList = json.loads(output)
275 except Exception:
276 jsonList = []
277
278 for package in jsonList:
279 if isinstance(package, dict):
280 packages.append((
281 package["name"],
282 package["version"],
283 package["build_string"]
284 ))
285 else:
286 packages.append(tuple(package.rsplit("-", 2)))
287
288 return packages
289
290 def getUpdateablePackages(self, name="", prefix=""):
291 """
292 Public method to get a list of updateable packages of a conda
293 environment.
294
295 @param name name of the environment
296 @type str
297 @param prefix prefix of the environment
298 @type str
299 @return list of installed packages. Each entry is a tuple containing
300 the package name, version and build.
301 @rtype list of tuples of (str, str, str)
302 @exception RuntimeError raised to indicate an error in parameters
303
304 Note: only one of name or prefix must be given.
305 """
306 if name and prefix:
307 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
308
309 if not name and not prefix:
310 raise RuntimeError("One of 'name' or 'prefix' must be given.")
311
312 args = [
313 "update",
314 "--json",
315 "--quiet",
316 "--all",
317 "--dry-run",
318 ]
319 if name:
320 args.extend(["--name", name])
321 elif prefix:
322 args.extend(["--prefix", prefix])
323
324 exe = Preferences.getConda("CondaExecutable")
325 if not exe:
326 exe = "conda"
327
328 packages = []
329
330 proc = QProcess()
331 proc.start(exe, args)
332 if proc.waitForStarted(15000):
333 if proc.waitForFinished(30000):
334 output = str(proc.readAllStandardOutput(),
335 Preferences.getSystem("IOEncoding"),
336 'replace').strip()
337 try:
338 jsonDict = json.loads(output)
339 except Exception:
340 jsonDict = {}
341
342 if "actions" in jsonDict and "LINK" in jsonDict["actions"]:
343 for linkEntry in jsonDict["actions"]["LINK"]:
344 if isinstance(linkEntry, dict):
345 packages.append((
346 linkEntry["name"],
347 linkEntry["version"],
348 linkEntry["build_string"]
349 ))
350 else:
351 package = linkEntry.split()[0]
352 packages.append(tuple(package.rsplit("-", 2)))
353
354 return packages
355
356 def updatePackages(self, packages, name="", prefix=""):
357 """
358 Public method to update packages of a conda environment.
359
360 @param packages list of package names to be updated
361 @type list of str
362 @param name name of the environment
363 @type str
364 @param prefix prefix of the environment
365 @type str
366 @return flag indicating success
367 @rtype bool
368 @exception RuntimeError raised to indicate an error in parameters
369
370 Note: only one of name or prefix must be given.
371 """
372 if name and prefix:
373 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
374
375 if not name and not prefix:
376 raise RuntimeError("One of 'name' or 'prefix' must be given.")
377
378 if packages:
379 args = [
380 "update",
381 "--json",
382 "--yes",
383 ]
384 if name:
385 args.extend(["--name", name])
386 elif prefix:
387 args.extend(["--prefix", prefix])
388 args.extend(packages)
389
390 dlg = CondaExecDialog("update", self.__ui)
391 dlg.start(args)
392 dlg.exec_()
393 ok, _ = dlg.getResult()
394 else:
395 ok = False
396
397 return ok
398
399 def updateAllPackages(self, name="", prefix=""):
400 """
401 Public method to update all packages of a conda environment.
402
403 @param name name of the environment
404 @type str
405 @param prefix prefix of the environment
406 @type str
407 @return flag indicating success
408 @rtype bool
409 @exception RuntimeError raised to indicate an error in parameters
410
411 Note: only one of name or prefix must be given.
412 """
413 if name and prefix:
414 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
415
416 if not name and not prefix:
417 raise RuntimeError("One of 'name' or 'prefix' must be given.")
418
419 args = [
420 "update",
421 "--json",
422 "--yes",
423 "--all"
424 ]
425 if name:
426 args.extend(["--name", name])
427 elif prefix:
428 args.extend(["--prefix", prefix])
429
430 dlg = CondaExecDialog("update", self.__ui)
431 dlg.start(args)
432 dlg.exec_()
433 ok, _ = dlg.getResult()
434
435 return ok
436
437 def installPackages(self, packages, name="", prefix=""):
438 """
439 Public method to install packages into a conda environment.
440
441 @param packages list of package names to be installed
442 @type list of str
443 @param name name of the environment
444 @type str
445 @param prefix prefix of the environment
446 @type str
447 @return flag indicating success
448 @rtype bool
449 @exception RuntimeError raised to indicate an error in parameters
450
451 Note: only one of name or prefix must be given.
452 """
453 if name and prefix:
454 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
455
456 if not name and not prefix:
457 raise RuntimeError("One of 'name' or 'prefix' must be given.")
458
459 if packages:
460 args = [
461 "install",
462 "--json",
463 "--yes",
464 ]
465 if name:
466 args.extend(["--name", name])
467 elif prefix:
468 args.extend(["--prefix", prefix])
469 args.extend(packages)
470
471 dlg = CondaExecDialog("install", self.__ui)
472 dlg.start(args)
473 dlg.exec_()
474 ok, _ = dlg.getResult()
475 else:
476 ok = False
477
478 return ok
479
480 def uninstallPackages(self, packages, name="", prefix=""):
481 """
482 Public method to uninstall packages of a conda environment (including
483 all no longer needed dependencies).
484
485 @param packages list of package names to be uninstalled
486 @type list of str
487 @param name name of the environment
488 @type str
489 @param prefix prefix of the environment
490 @type str
491 @return flag indicating success
492 @rtype bool
493 @exception RuntimeError raised to indicate an error in parameters
494
495 Note: only one of name or prefix must be given.
496 """
497 if name and prefix:
498 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
499
500 if not name and not prefix:
501 raise RuntimeError("One of 'name' or 'prefix' must be given.")
502
503 if packages:
504 from UI.DeleteFilesConfirmationDialog import \
505 DeleteFilesConfirmationDialog
506 dlg = DeleteFilesConfirmationDialog(
507 self.parent(),
508 self.tr("Uninstall Packages"),
509 self.tr(
510 "Do you really want to uninstall these packages and"
511 " their dependencies?"),
512 packages)
513 if dlg.exec_() == QDialog.Accepted:
514 args = [
515 "remove",
516 "--json",
517 "--yes",
518 ]
519 if condaVersion() >= (4, 4, 0):
520 args.append("--prune",)
521 if name:
522 args.extend(["--name", name])
523 elif prefix:
524 args.extend(["--prefix", prefix])
525 args.extend(packages)
526
527 dlg = CondaExecDialog("remove", self.__ui)
528 dlg.start(args)
529 dlg.exec_()
530 ok, _ = dlg.getResult()
531 else:
532 ok = False
533 else:
534 ok = False
535
536 return ok
537
538 def searchPackages(self, pattern, fullNameOnly=False, packageSpec=False,
539 platform="", name="", prefix=""):
540 """
541 Public method to search for a package pattern of a conda environment.
542
543 @param pattern package search pattern
544 @type str
545 @param fullNameOnly flag indicating to search for full names only
546 @type bool
547 @param packageSpec flag indicating to search a package specification
548 @type bool
549 @param platform type of platform to be searched for
550 @type str
551 @param name name of the environment
552 @type str
553 @param prefix prefix of the environment
554 @type str
555 @return flag indicating success and a dictionary with package name as
556 key and list of dictionaries containing detailed data for the found
557 packages as values
558 @rtype tuple of (bool, dict of list of dict)
559 @exception RuntimeError raised to indicate an error in parameters
560
561 Note: only one of name or prefix must be given.
562 """
563 if name and prefix:
564 raise RuntimeError("Only one of 'name' or 'prefix' must be given.")
565
566 args = [
567 "search",
568 "--json",
569 ]
570 if fullNameOnly:
571 args.append("--full-name")
572 if packageSpec:
573 args.append("--spec")
574 if platform:
575 args.extend(["--platform", platform])
576 if name:
577 args.extend(["--name", name])
578 elif prefix:
579 args.extend(["--prefix", prefix])
580 args.append(pattern)
581
582 exe = Preferences.getConda("CondaExecutable")
583 if not exe:
584 exe = "conda"
585
586 packages = {}
587 ok = False
588
589 proc = QProcess()
590 proc.start(exe, args)
591 if proc.waitForStarted(15000):
592 if proc.waitForFinished(30000):
593 output = str(proc.readAllStandardOutput(),
594 Preferences.getSystem("IOEncoding"),
595 'replace').strip()
596 try:
597 packages = json.loads(output)
598 ok = "error" not in packages
599 except Exception:
600 # return values for errors is already set
601 pass
602
603 return ok, packages
604
605 #######################################################################
606 ## special methods below
607 #######################################################################
608
609 def updateConda(self):
610 """
611 Public method to update conda itself.
612
613 @return flag indicating success
614 @rtype bool
615 """
616 args = [
617 "update",
618 "--json",
619 "--yes",
620 "conda"
621 ]
622
623 dlg = CondaExecDialog("update", self.__ui)
624 dlg.start(args)
625 dlg.exec_()
626 ok, _ = dlg.getResult()
627
628 return ok
629
630 def writeDefaultConfiguration(self):
631 """
632 Public method to create a conda configuration with default values.
633 """
634 args = [
635 "config",
636 "--write-default",
637 "--quiet"
638 ]
639
640 exe = Preferences.getConda("CondaExecutable")
641 if not exe:
642 exe = "conda"
643
644 proc = QProcess()
645 proc.start(exe, args)
646 proc.waitForStarted(15000)
647 proc.waitForFinished(30000)
648
649 def getCondaInformation(self):
650 """
651 Public method to get a dictionary containing information about conda.
652
653 @return dictionary containing information about conda
654 @rtype dict
655 """
656 exe = Preferences.getConda("CondaExecutable")
657 if not exe:
658 exe = "conda"
659
660 infoDict = {}
661
662 proc = QProcess()
663 proc.start(exe, ["info", "--json"])
664 if proc.waitForStarted(15000):
665 if proc.waitForFinished(30000):
666 output = str(proc.readAllStandardOutput(),
667 Preferences.getSystem("IOEncoding"),
668 'replace').strip()
669 try:
670 infoDict = json.loads(output)
671 except Exception:
672 infoDict = {}
673
674 return infoDict
675
676 def runProcess(self, args):
677 """
678 Public method to execute the conda with the given arguments.
679
680 The conda executable is called with the given arguments and
681 waited for its end.
682
683 @param args list of command line arguments
684 @type list of str
685 @return tuple containing a flag indicating success and the output
686 of the process
687 @rtype tuple of (bool, str)
688 """
689 exe = Preferences.getConda("CondaExecutable")
690 if not exe:
691 exe = "conda"
692
693 process = QProcess()
694 process.start(exe, args)
695 procStarted = process.waitForStarted(15000)
696 if procStarted:
697 finished = process.waitForFinished(30000)
698 if finished:
699 if process.exitCode() == 0:
700 output = str(process.readAllStandardOutput(),
701 Preferences.getSystem("IOEncoding"),
702 'replace').strip()
703 return True, output
704 else:
705 return (False,
706 self.tr("conda exited with an error ({0}).")
707 .format(process.exitCode()))
708 else:
709 process.terminate()
710 process.waitForFinished(2000)
711 process.kill()
712 process.waitForFinished(3000)
713 return False, self.tr("conda did not finish within"
714 " 30 seconds.")
715
716 return False, self.tr("conda could not be started.")
717
718 def cleanConda(self, cleanAction):
719 """
720 Public method to update conda itself.
721
722 @param cleanAction cleaning action to be performed (must be one of
723 the command line parameters without '--')
724 @type str
725 """
726 args = [
727 "clean",
728 "--yes",
729 "--{0}".format(cleanAction),
730 ]
731
732 dlg = CondaExecDialog("clean", self.__ui)
733 dlg.start(args)
734 dlg.exec_()

eric ide

mercurial