Skip to content

Commit

Permalink
feat: add micromamba support (#807)
Browse files Browse the repository at this point in the history
* feat: add micromamba support

Signed-off-by: Henry Schreiner <[email protected]>

* tests: add micromamba test

Signed-off-by: Henry Schreiner <[email protected]>

* tests: support Python 3.7

Signed-off-by: Henry Schreiner <[email protected]>

* fix: don't override user set channels for micromamba env creation

Signed-off-by: Henry Schreiner <[email protected]>

---------

Signed-off-by: Henry Schreiner <[email protected]>
  • Loading branch information
henryiii authored Apr 8, 2024
1 parent 11dac8c commit 9a4068f
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 19 deletions.
6 changes: 3 additions & 3 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ You can also specify that the virtualenv should *always* be reused instead of re
def tests(session):
pass
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, uv, conda, mamba, or virtualenv (default):
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, uv, conda, mamba, micromamba, or virtualenv (default):

.. code-block:: python
Expand All @@ -176,8 +176,8 @@ You are not limited to virtualenv, there is a selection of backends you can choo
pass
You can chain together optional backends with ``|``, such as ``uv|virtualenv``
or ``mamba|conda``, and the first available backend will be selected. You
cannot put anything after a backend that can't be missing like ``venv`` or
or ``micromamba|mamba|conda``, and the first available backend will be selected.
You cannot put anything after a backend that can't be missing like ``venv`` or
``virtualenv``.

Finally, custom backend parameters are supported:
Expand Down
10 changes: 7 additions & 3 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,13 @@ Install packages with conda:

.. code-block:: python
session.conda_install("pytest")
session.conda_install("pytest", channels=["conda-forge"])
It is possible to install packages with pip into the conda environment, but
it's a best practice only install pip packages with the ``--no-deps`` option.
This prevents pip from breaking the conda environment by installing
incompatible versions of packages already installed with conda.
This prevents pip from breaking the conda environment by installing incompatible
versions of packages already installed with conda. You should always specify
channels for consistency; default channels can vary (and ``micromamba`` has none).

.. code-block:: python
Expand All @@ -412,6 +413,9 @@ incompatible versions of packages already installed with conda.
``"mamba"`` is also allowed as a choice for ``venv_backend``, which will
use/require `mamba <https://github.com/mamba-org/mamba>`_ instead of conda.

``"micromamba"`` is also allowed as a choice for ``venv_backend``, which will
use/require `micromamba <https://mamba.readthedocs.io/en/latest/user_guide/micromamba.html#>`_
instead of conda.

Parametrization
---------------
Expand Down
12 changes: 6 additions & 6 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Then running ``nox --session tests`` will actually run all parametrized versions
Changing the sessions default backend
-------------------------------------

By default Nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``uv``, ``conda``, ``mamba``, and ``venv`` as well as no backend (passthrough to whatever python environment Nox is running on). You can change the default behaviour by using ``-db <backend>`` or ``--default-venv-backend <backend>``. Supported names are ``('none', 'uv', 'virtualenv', 'conda', 'mamba', 'venv')``.
By default Nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``uv``, ``conda``, ``mamba``, ``micromamba``, and ``venv`` as well as no backend (passthrough to whatever python environment Nox is running on). You can change the default behaviour by using ``-db <backend>`` or ``--default-venv-backend <backend>``. Supported names are ``('none', 'uv', 'virtualenv', 'conda', 'mamba', 'venv')``.


.. tabs::
Expand All @@ -142,9 +142,9 @@ By default Nox uses ``virtualenv`` as the virtual environment backend for the se

.. note::

The ``uv``, ``conda``, and ``mamba`` backends require their respective
programs be pre-installed. ``uv`` is distributed as a Python package
and can be installed with the ``nox[uv]`` extra.
The ``uv``, ``conda``, ``mamba``, and ``micromamba`` backends require their
respective programs be pre-installed. ``uv`` is distributed as a Python
package and can be installed with the ``nox[uv]`` extra.

You can also set this option with the ``NOX_DEFAULT_VENV_BACKEND`` environment variable, or in the Noxfile with ``nox.options.default_venv_backend``. In case more than one is provided, the command line argument overrides the environment variable, which in turn overrides the Noxfile configuration.

Expand All @@ -156,7 +156,7 @@ Note that using this option does not change the backend for sessions where ``ven
as ``uv pip`` is used to install programs instead. If you need to manually
interact with pip, you should install it with ``session.install("pip")``.

Backends that could be missing (``uv``, ``conda``, and ``mamba``) can have a fallback using ``|``, such as ``uv|virtualenv`` or ``mamba|conda``. This will use the first item that is available on the users system.
Backends that could be missing (``uv``, ``conda``, ``mamba``, and ``micromamba``) can have a fallback using ``|``, such as ``uv|virtualenv`` or ``micromamba|mamba|conda``. This will use the first item that is available on the users system.

If you need to check to see which backend was selected, you can access it via
``session.venv_backend`` in your noxfile.
Expand All @@ -166,7 +166,7 @@ If you need to check to see which backend was selected, you can access it via
Forcing the sessions backend
----------------------------

You might work in a different environment than a project's default continuous integration settings, and might wish to get a quick way to execute the same tasks but on a different venv backend. For this purpose, you can temporarily force the backend used by **all** sessions in the current Nox execution by using ``-fb <backend>`` or ``--force-venv-backend <backend>``. No exceptions are made, the backend will be forced for all sessions run whatever the other options values and Noxfile configuration. Supported names are ``('none', 'uv', 'virtualenv', 'conda', 'mamba', 'venv')``.
You might work in a different environment than a project's default continuous integration settings, and might wish to get a quick way to execute the same tasks but on a different venv backend. For this purpose, you can temporarily force the backend used by **all** sessions in the current Nox execution by using ``-fb <backend>`` or ``--force-venv-backend <backend>``. No exceptions are made, the backend will be forced for all sessions run whatever the other options values and Noxfile configuration. Supported names are ``('none', 'uv', 'virtualenv', 'conda', 'mamba', 'micromamba', 'venv')``.

.. code-block:: console
Expand Down
4 changes: 3 additions & 1 deletion nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,9 @@ def conda_install(
You can specify a conda channel using `channel=`; a falsey value will
not change the current channels. You can specify a list of channels if
needed.
needed. It is highly recommended to specify this; micromamba does not
set default channels, and default channels vary for conda. Note that
"defaults" is also not permissivly licenced like "conda-forge" is.
Additional keyword args are the same as for :meth:`run`.
Expand Down
14 changes: 12 additions & 2 deletions nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class CondaEnv(ProcessEnv):
"""

is_sandboxed = True
allowed_globals = ("conda", "mamba")
allowed_globals = ("conda", "mamba", "micromamba")

def __init__(
self,
Expand Down Expand Up @@ -305,6 +305,12 @@ def create(self) -> bool:
return False

cmd = [self.conda_cmd, "create", "--yes", "--prefix", self.location]
if self.conda_cmd == "micromamba" and not any(
v.startswith(("--channel=", "-c")) or v == "--channel"
for v in self.venv_params
):
# Micromamba doesn't have any default channels
cmd.append("--channel=conda-forge")

cmd.extend(self.venv_params)

Expand All @@ -314,7 +320,9 @@ def create(self) -> bool:
python_dep = f"python={self.interpreter}" if self.interpreter else "python"
cmd.append(python_dep)

logger.info(f"Creating conda env in {self.location_name} with {python_dep}")
logger.info(
f"Creating {self.conda_cmd} env in {self.location_name} with {python_dep}"
)
nox.command.run(cmd, silent=True, log=nox.options.verbose or False)

return True
Expand Down Expand Up @@ -589,6 +597,7 @@ def venv_backend(self) -> str:
ALL_VENVS: dict[str, Callable[..., ProcessEnv]] = {
"conda": functools.partial(CondaEnv, conda_cmd="conda"),
"mamba": functools.partial(CondaEnv, conda_cmd="mamba"),
"micromamba": functools.partial(CondaEnv, conda_cmd="micromamba"),
"virtualenv": functools.partial(VirtualEnv, venv_backend="virtualenv"),
"venv": functools.partial(VirtualEnv, venv_backend="venv"),
"uv": functools.partial(VirtualEnv, venv_backend="uv"),
Expand All @@ -601,5 +610,6 @@ def venv_backend(self) -> str:
OPTIONAL_VENVS = {
"conda": shutil.which("conda") is not None,
"mamba": shutil.which("mamba") is not None,
"micromamba": shutil.which("micromamba") is not None,
"uv": HAS_UV,
}
31 changes: 27 additions & 4 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
nox.options.sessions = ["tests", "cover", "lint", "docs"]
if shutil.which("conda"):
nox.options.sessions.append("conda_tests")
if shutil.which("mamba"):
nox.options.sessions.append("mamba_tests")
if shutil.which("micromamba"):
nox.options.sessions.append("micromamba_tests")


# Because there is a dependency conflict between argcomplete and the latest tox
Expand Down Expand Up @@ -79,12 +83,31 @@ def tests(session: nox.Session, tox_version: str) -> None:
con.execute("DELETE FROM file WHERE SUBSTR(path, 2, 1) == ':'")


@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"], venv_backend="conda")
@nox.session(venv_backend="conda")
def conda_tests(session: nox.Session) -> None:
"""Run test suite with pytest."""
session.create_tmp() # Fixes permission errors on Windows
"""Run test suite set up with conda."""
session.conda_install(
"--file", "requirements-conda-test.txt", channel="conda-forge"
)
session.install("-e", ".", "--no-deps")
session.run("pytest", *session.posargs)


@nox.session(venv_backend="mamba")
def mamba_tests(session: nox.Session) -> None:
"""Run test suite set up with mamba."""
session.conda_install(
"--file", "requirements-conda-test.txt", channel="conda-forge"
)
session.install("-e", ".", "--no-deps")
session.run("pytest", *session.posargs)


@nox.session(venv_backend="micromamba")
def micromamba_tests(session: nox.Session) -> None:
"""Run test suite set up with micromamba."""
session.conda_install(
"--file", "requirements-conda-test.txt", "--channel", "conda-forge"
"--file", "requirements-conda-test.txt", channel="conda-forge"
)
session.install("-e", ".", "--no-deps")
session.run("pytest", *session.posargs)
Expand Down
45 changes: 45 additions & 0 deletions tests/test_virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,51 @@ def test_reuse_conda_environment(make_one):
assert reused


# This mocks micromamba so that it doesn't need to be installed.
@has_conda
def test_micromamba_environment(make_one, monkeypatch):
conda_path = shutil.which("conda")
which = shutil.which
monkeypatch.setattr(
shutil, "which", lambda x: conda_path if x == "micromamba" else which(x)
)
venv, _ = make_one(reuse_existing=True, venv_backend="micromamba")
run = mock.Mock()
monkeypatch.setattr(nox.command, "run", run)
venv.create()
run.assert_called_once()
# .args requires Python 3.8+
((args,), _) = run.call_args
assert args[0] == "micromamba"
assert "--channel=conda-forge" in args


# This mocks micromamba so that it doesn't need to be installed.
@pytest.mark.parametrize(
"params",
[["--channel=default"], ["-cdefault"], ["-c", "default"], ["--channel", "default"]],
)
@has_conda
def test_micromamba_channel_environment(make_one, monkeypatch, params):
conda_path = shutil.which("conda")
which = shutil.which
monkeypatch.setattr(
shutil, "which", lambda x: conda_path if x == "micromamba" else which(x)
)
venv, _ = make_one(reuse_existing=True, venv_backend="micromamba")
run = mock.Mock()
monkeypatch.setattr(nox.command, "run", run)
venv.venv_params = params
venv.create()
run.assert_called_once()
# .args requires Python 3.8+
((args,), _) = run.call_args
assert args[0] == "micromamba"
for p in params:
assert p in args
assert "--channel=conda-forge" not in args


@pytest.mark.parametrize(
("frm", "to", "result"),
[
Expand Down

0 comments on commit 9a4068f

Please sign in to comment.