##// END OF EJS Templates
Use environment variable to identify conda / mamba...
Shaun Walbridge -
Show More
@@ -1,151 +1,164
1 """Implementation of packaging-related magic functions.
1 """Implementation of packaging-related magic functions.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2018 The IPython Development Team.
4 # Copyright (c) 2018 The IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 import functools
11 import functools
12 import os
12 import re
13 import re
13 import shlex
14 import shlex
14 import sys
15 import sys
15 from pathlib import Path
16 from pathlib import Path
16
17
17 from IPython.core.magic import Magics, magics_class, line_magic
18 from IPython.core.magic import Magics, magics_class, line_magic
18
19
19
20
20 def is_conda_environment(func):
21 def is_conda_environment(func):
21 @functools.wraps(func)
22 @functools.wraps(func)
22 def wrapper(*args, **kwargs):
23 def wrapper(*args, **kwargs):
23 """Return True if the current Python executable is in a conda env"""
24 """Return True if the current Python executable is in a conda env"""
24 # TODO: does this need to change on windows?
25 # TODO: does this need to change on windows?
25 if not Path(sys.prefix, "conda-meta", "history").exists():
26 if not Path(sys.prefix, "conda-meta", "history").exists():
26 raise ValueError(
27 raise ValueError(
27 "The python kernel does not appear to be a conda environment. "
28 "The python kernel does not appear to be a conda environment. "
28 "Please use ``%pip install`` instead."
29 "Please use ``%pip install`` instead."
29 )
30 )
30 return func(*args, **kwargs)
31 return func(*args, **kwargs)
31
32
32 return wrapper
33 return wrapper
33
34
34
35
35 def _get_conda_like_executable(command):
36 def _get_conda_like_executable(command):
36 """Find the path to the given executable
37 """Find the path to the given executable
37
38
38 Parameters
39 Parameters
39 ----------
40 ----------
40
41
41 executable: string
42 executable: string
42 Value should be: conda, mamba or micromamba
43 Value should be: conda, mamba or micromamba
43 """
44 """
45 # Check for a environment variable bound to the base executable, both conda and mamba
46 # set these when activating an environment.
47 base_executable = "CONDA_EXE"
48 if 'mamba' in command.lower():
49 base_executable = "MAMBA_EXE"
50 if base_executable in os.environ:
51 executable = Path(os.environ[base_executable])
52 if executable.is_file():
53 return str(executable.resolve())
54
44 # Check if there is a conda executable in the same directory as the Python executable.
55 # Check if there is a conda executable in the same directory as the Python executable.
45 # This is the case within conda's root environment.
56 # This is the case within conda's root environment.
46 executable = Path(sys.executable).parent / command
57 executable = Path(sys.executable).parent / command
47 if executable.is_file():
58 if executable.is_file():
48 return str(executable)
59 return str(executable)
49
60
50 # Otherwise, attempt to extract the executable from conda history.
61 # Otherwise, attempt to extract the executable from conda history.
51 # This applies in any conda environment.
62 # This applies in any conda environment. Parsing this way is error prone because
63 # different versions of conda and mamba include differing cmd values such as
64 # `conda`, `conda-script.py`, or `path/to/conda`, here use the raw command provided.
52 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
65 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
53 match = re.search(
66 match = re.search(
54 rf"^#\s*cmd:\s*(?P<command>.*{executable})\s[create|install]",
67 rf"^#\s*cmd:\s*(?P<command>.*{command})\s[create|install]",
55 history,
68 history,
56 flags=re.MULTILINE,
69 flags=re.MULTILINE,
57 )
70 )
58 if match:
71 if match:
59 return match.groupdict()["command"]
72 return match.groupdict()["command"]
60
73
61 # Fallback: assume the executable is available on the system path.
74 # Fallback: assume the executable is available on the system path.
62 return command
75 return command
63
76
64
77
65 CONDA_COMMANDS_REQUIRING_PREFIX = {
78 CONDA_COMMANDS_REQUIRING_PREFIX = {
66 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
79 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
67 }
80 }
68 CONDA_COMMANDS_REQUIRING_YES = {
81 CONDA_COMMANDS_REQUIRING_YES = {
69 'install', 'remove', 'uninstall', 'update', 'upgrade',
82 'install', 'remove', 'uninstall', 'update', 'upgrade',
70 }
83 }
71 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
84 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
72 CONDA_YES_FLAGS = {'-y', '--y'}
85 CONDA_YES_FLAGS = {'-y', '--y'}
73
86
74
87
75 @magics_class
88 @magics_class
76 class PackagingMagics(Magics):
89 class PackagingMagics(Magics):
77 """Magics related to packaging & installation"""
90 """Magics related to packaging & installation"""
78
91
79 @line_magic
92 @line_magic
80 def pip(self, line):
93 def pip(self, line):
81 """Run the pip package manager within the current kernel.
94 """Run the pip package manager within the current kernel.
82
95
83 Usage:
96 Usage:
84 %pip install [pkgs]
97 %pip install [pkgs]
85 """
98 """
86 python = sys.executable
99 python = sys.executable
87 if sys.platform == "win32":
100 if sys.platform == "win32":
88 python = '"' + python + '"'
101 python = '"' + python + '"'
89 else:
102 else:
90 python = shlex.quote(python)
103 python = shlex.quote(python)
91
104
92 self.shell.system(" ".join([python, "-m", "pip", line]))
105 self.shell.system(" ".join([python, "-m", "pip", line]))
93
106
94 print("Note: you may need to restart the kernel to use updated packages.")
107 print("Note: you may need to restart the kernel to use updated packages.")
95
108
96 def _run_command(self, cmd, line):
109 def _run_command(self, cmd, line):
97 args = shlex.split(line)
110 args = shlex.split(line)
98 command = args[0] if len(args) > 0 else ""
111 command = args[0] if len(args) > 0 else ""
99 args = args[1:] if len(args) > 1 else [""]
112 args = args[1:] if len(args) > 1 else [""]
100
113
101 extra_args = []
114 extra_args = []
102
115
103 # When the subprocess does not allow us to respond "yes" during the installation,
116 # When the subprocess does not allow us to respond "yes" during the installation,
104 # we need to insert --yes in the argument list for some commands
117 # we need to insert --yes in the argument list for some commands
105 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
118 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
106 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
119 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
107 has_yes = set(args).intersection(CONDA_YES_FLAGS)
120 has_yes = set(args).intersection(CONDA_YES_FLAGS)
108 if stdin_disabled and needs_yes and not has_yes:
121 if stdin_disabled and needs_yes and not has_yes:
109 extra_args.append("--yes")
122 extra_args.append("--yes")
110
123
111 # Add --prefix to point conda installation to the current environment
124 # Add --prefix to point conda installation to the current environment
112 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
125 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
113 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
126 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
114 if needs_prefix and not has_prefix:
127 if needs_prefix and not has_prefix:
115 extra_args.extend(["--prefix", sys.prefix])
128 extra_args.extend(["--prefix", sys.prefix])
116
129
117 self.shell.system(" ".join([cmd, command] + extra_args + args))
130 self.shell.system(" ".join([cmd, command] + extra_args + args))
118 print("\nNote: you may need to restart the kernel to use updated packages.")
131 print("\nNote: you may need to restart the kernel to use updated packages.")
119
132
120 @line_magic
133 @line_magic
121 @is_conda_environment
134 @is_conda_environment
122 def conda(self, line):
135 def conda(self, line):
123 """Run the conda package manager within the current kernel.
136 """Run the conda package manager within the current kernel.
124
137
125 Usage:
138 Usage:
126 %conda install [pkgs]
139 %conda install [pkgs]
127 """
140 """
128 conda = _get_conda_like_executable("conda")
141 conda = _get_conda_like_executable("conda")
129 self._run_command(conda, line)
142 self._run_command(conda, line)
130
143
131 @line_magic
144 @line_magic
132 @is_conda_environment
145 @is_conda_environment
133 def mamba(self, line):
146 def mamba(self, line):
134 """Run the mamba package manager within the current kernel.
147 """Run the mamba package manager within the current kernel.
135
148
136 Usage:
149 Usage:
137 %mamba install [pkgs]
150 %mamba install [pkgs]
138 """
151 """
139 mamba = _get_conda_like_executable("mamba")
152 mamba = _get_conda_like_executable("mamba")
140 self._run_command(mamba, line)
153 self._run_command(mamba, line)
141
154
142 @line_magic
155 @line_magic
143 @is_conda_environment
156 @is_conda_environment
144 def micromamba(self, line):
157 def micromamba(self, line):
145 """Run the conda package manager within the current kernel.
158 """Run the conda package manager within the current kernel.
146
159
147 Usage:
160 Usage:
148 %micromamba install [pkgs]
161 %micromamba install [pkgs]
149 """
162 """
150 micromamba = _get_conda_like_executable("micromamba")
163 micromamba = _get_conda_like_executable("micromamba")
151 self._run_command(micromamba, line)
164 self._run_command(micromamba, line)
General Comments 0
You need to be logged in to leave comments. Login now