Repository: ipython/ipython

Issue:
Add %mamba magic to install packages in current kernel
              If you wish, you could define a `%mamba` magic similarly to how `%pip` and `%conda` are defined in #11524

_Originally posted by @jakevdp in https://github.com/ipython/ipython/issues/9517#issuecomment-848985175_
            


Discussion:
This issue continues to come up for hub deployments with more complex environments.  Just wanted to resurface a feature request for a %mamba magic.  
I wondered that too.  This worked as a workaround:
````
%alias mamba conda
%mamba env list
````
A mamba magic would be great but what's the problem of just using `!mamba`?
> A mamba magic would be great but what's the problem of just using `!mamba`?

[According to the creator of IPython](https://github.com/jupyterlab/jupyterlab-desktop/issues/234#issuecomment-928484514), directly calling the shell can result in unexpected behaviour with respect to which environment the packages are actually installed in. And see [this SO post](https://stackoverflow.com/questions/70602866/is-there-an-advantage-of-using-over-in-jupyter-when-the-same-command-exi/70604999#70604999) with more links/details.
> > A mamba magic would be great but what's the problem of just using `!mamba`?
> 
> [According to the creator of IPython](https://github.com/jupyterlab/jupyterlab-desktop/issues/234#issuecomment-928484514), directly calling the shell can result in unexpected behaviour with respect to which environment the packages are actually installed in. And see [this SO post](https://stackoverflow.com/questions/70602866/is-there-an-advantage-of-using-over-in-jupyter-when-the-same-command-exi/70604999#70604999) with more links/details.

I think this was caused by the PATH environment is according to that one you open notebook instead of kernel, but that can be configured by kernelspec, I have a snippet for that:

```bash
    python -m ipykernel install --user --name $CONDA_DEFAULT_ENV --display-name "Python $(python-version) ($CONDA_DEFAULT_ENV)"
    json_file="$HOME/.local/share/jupyter/kernels/$CONDA_DEFAULT_ENV/kernel.json"
    # jq '. + {env: {PATH: env.PATH}}' $json_file > $json_file
    cat <<< $(jq '. + {env: {PATH: env.PATH}}' $json_file) > $json_file
```

I would like to crate a PR to add mamba magic but I doubt whether it will be accepted because there is a simple workaround (like this). 
OK!  I think there is still value in a PR for `%mamba`, which is simpler than the workaround you mention.  The average new jupyter/ipython user won't understand nuances here.

Thanks @lizeidsness, though I'm not sure https://github.com/ipython/ipython/issues/14075#issuecomment-1654167248 solves the underlying issue, as this should just run conda, not mamba, which offers improved performance over conda, esp for more complex environments.
While we are considering this, it would also be great to have support for a `%micromamba` magic, which is a smaller, pure C++ implementation of `mamba`, that's also independent of `conda` (i.e., you don't need conda installed in your environment at all).

I do realize, though, that at some point, you just have to say no to these requests. Not to mention, the folks behind `mamba` and `micromamba` are now making _yet another_ package manager called `pixi`: \[[ 1](https://prefix.dev/) \]\[ [2](https://github.com/prefix-dev/pixi) \]
@dshean 

> Thanks @lizeidsness, though I'm not sure [#14075 (comment)](https://github.com/ipython/ipython/issues/14075#issuecomment-1654167248) solves the underlying issue, as this should just run conda, not mamba, which offers improved performance over conda, esp for more complex environments.

Noted - but was meant as a temporary workaround until it was maybe implemented.  
`%conda` has become too cumbersome for certain installs. Switching to `!mamba`, but I hope that `%mamba` gets implemented at some point.

Resolving patch:
diff --git a/IPython/core/magics/packaging.py b/IPython/core/magics/packaging.py
--- a/IPython/core/magics/packaging.py
+++ b/IPython/core/magics/packaging.py
@@ -8,6 +8,7 @@
 #  The full license is in the file COPYING.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
+import functools
 import re
 import shlex
 import sys
@@ -16,33 +17,49 @@
 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?
-    return Path(sys.prefix, "conda-meta", "history").exists()
+def is_conda_environment(func):
+    @functools.wraps(func)
+    def wrapper(*args, **kwargs):
+        """Return True if the current Python executable is in a conda env"""
+        # TODO: does this need to change on windows?
+        if not Path(sys.prefix, "conda-meta", "history").exists():
+            raise ValueError(
+                "The python kernel does not appear to be a conda environment.  "
+                "Please use ``%pip install`` instead."
+            )
+        return func(*args, **kwargs)
 
+    return wrapper
 
-def _get_conda_executable():
-    """Find the path to the conda executable"""
+
+def _get_conda_like_executable(command):
+    """Find the path to the given executable
+
+    Parameters
+    ----------
+
+    executable: string
+        Value should be: conda, mamba or micromamba
+    """
     # 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 = Path(sys.executable).parent / "conda"
-    if conda.is_file():
-        return str(conda)
+    executable = Path(sys.executable).parent / command
+    if executable.is_file():
+        return str(executable)
 
     # Otherwise, attempt to extract the executable from conda history.
     # This applies in any conda environment.
     history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
     match = re.search(
-        r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
+        rf"^#\s*cmd:\s*(?P<command>.*{executable})\s[create|install]",
         history,
         flags=re.MULTILINE,
     )
     if match:
         return match.groupdict()["command"]
 
-    # Fallback: assume conda is available on the system path.
-    return "conda"
+    # Fallback: assume the executable is available on the system path.
+    return command
 
 
 CONDA_COMMANDS_REQUIRING_PREFIX = {
@@ -76,18 +93,7 @@ def pip(self, line):
 
         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()
+    def _run_command(self, cmd, line):
         args = shlex.split(line)
         command = args[0] if len(args) > 0 else ""
         args = args[1:] if len(args) > 1 else [""]
@@ -108,5 +114,38 @@ def conda(self, line):
         if needs_prefix and not has_prefix:
             extra_args.extend(["--prefix", sys.prefix])
 
-        self.shell.system(' '.join([conda, command] + extra_args + args))
+        self.shell.system(" ".join([cmd, command] + extra_args + args))
         print("\nNote: you may need to restart the kernel to use updated packages.")
+
+    @line_magic
+    @is_conda_environment
+    def conda(self, line):
+        """Run the conda package manager within the current kernel.
+
+        Usage:
+          %conda install [pkgs]
+        """
+        conda = _get_conda_like_executable("conda")
+        self._run_command(conda, line)
+
+    @line_magic
+    @is_conda_environment
+    def mamba(self, line):
+        """Run the mamba package manager within the current kernel.
+
+        Usage:
+          %mamba install [pkgs]
+        """
+        mamba = _get_conda_like_executable("mamba")
+        self._run_command(mamba, line)
+
+    @line_magic
+    @is_conda_environment
+    def micromamba(self, line):
+        """Run the conda package manager within the current kernel.
+
+        Usage:
+          %micromamba install [pkgs]
+        """
+        micromamba = _get_conda_like_executable("micromamba")
+        self._run_command(micromamba, line)


Test patch: