-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add %pip and %conda magic functions #11524
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
"""Implementation of packaging-related magic functions. | ||
""" | ||
#----------------------------------------------------------------------------- | ||
# Copyright (c) 2018 The IPython Development Team. | ||
# | ||
# Distributed under the terms of the Modified BSD License. | ||
# | ||
# The full license is in the file COPYING.txt, distributed with this software. | ||
#----------------------------------------------------------------------------- | ||
|
||
import os | ||
import re | ||
import shlex | ||
import sys | ||
from subprocess import Popen, PIPE | ||
|
||
from IPython.core.magic import Magics, magics_class, line_magic | ||
|
||
|
||
def _is_conda_environment(): | ||
"""Return True if the current Python executable is in a conda env""" | ||
# TODO: does this need to change on windows? | ||
conda_history = os.path.join(sys.prefix, 'conda-meta', 'history') | ||
return os.path.exists(conda_history) | ||
|
||
|
||
def _get_conda_executable(): | ||
"""Find the path to the conda executable""" | ||
# Check if there is a conda executable in the same directory as the Python executable. | ||
# This is the case within conda's root environment. | ||
conda = os.path.join(os.path.dirname(sys.executable), 'conda') | ||
if os.path.isfile(conda): | ||
return conda | ||
|
||
# Otherwise, attempt to extract the executable from conda history. | ||
# This applies in any conda environment. | ||
R = re.compile(r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]") | ||
for line in open(os.path.join(sys.prefix, 'conda-meta', 'history')): | ||
match = R.match(line) | ||
if match: | ||
return match.groupdict()['command'] | ||
|
||
# Fallback: assume conda is available on the system path. | ||
return "conda" | ||
|
||
|
||
CONDA_COMMANDS_REQUIRING_PREFIX = { | ||
'install', 'list', 'remove', 'uninstall', 'update', 'upgrade', | ||
} | ||
CONDA_COMMANDS_REQUIRING_YES = { | ||
'install', 'remove', 'uninstall', 'update', 'upgrade', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love this organization around args needing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It really is gloriously nice. |
||
} | ||
CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'} | ||
CONDA_YES_FLAGS = {'-y', '--y'} | ||
|
||
|
||
@magics_class | ||
class PackagingMagics(Magics): | ||
"""Magics related to packaging & installation""" | ||
|
||
@line_magic | ||
def pip(self, line): | ||
"""Run the pip package manager within the current kernel. | ||
|
||
Usage: | ||
%pip install [pkgs] | ||
""" | ||
self.shell.system(' '.join([sys.executable, '-m', 'pip', line])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works pretty well, the only hidden API surface to this is that this allows the full Needless to say, I'm cool with this implementation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 We likely want to remind user they might need to restart the kernel. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 594d000 |
||
print("Note: you may need to restart the kernel to use updated packages.") | ||
|
||
@line_magic | ||
def conda(self, line): | ||
"""Run the conda package manager within the current kernel. | ||
|
||
Usage: | ||
%conda install [pkgs] | ||
""" | ||
if not _is_conda_environment(): | ||
raise ValueError("The python kernel does not appear to be a conda environment. " | ||
"Please use ``%pip install`` instead.") | ||
|
||
conda = _get_conda_executable() | ||
args = shlex.split(line) | ||
command = args[0] | ||
args = args[1:] | ||
extra_args = [] | ||
|
||
# When the subprocess does not allow us to respond "yes" during the installation, | ||
# we need to insert --yes in the argument list for some commands | ||
stdin_disabled = getattr(self.shell, 'kernel', None) is not None | ||
needs_yes = command in CONDA_COMMANDS_REQUIRING_YES | ||
has_yes = set(args).intersection(CONDA_YES_FLAGS) | ||
if stdin_disabled and needs_yes and not has_yes: | ||
extra_args.append("--yes") | ||
|
||
# Add --prefix to point conda installation to the current environment | ||
needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX | ||
has_prefix = set(args).intersection(CONDA_ENV_FLAGS) | ||
if needs_prefix and not has_prefix: | ||
extra_args.extend(["--prefix", sys.prefix]) | ||
|
||
self.shell.system(' '.join([conda, command] + extra_args + args)) | ||
print("\nNote: you may need to restart the kernel to use updated packages.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhirschfeld would this correctly detect being in a conda environment on Windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A better way might be to check if
Anaconda
is insys.version
. That should work cross-platform.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that only works if you happen to be using the Python built by Anaconda. For example:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes you're right