Skip to content

Commit 9a4068f

Browse files
authored
feat: add micromamba support (#807)
* 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]>
1 parent 11dac8c commit 9a4068f

File tree

7 files changed

+103
-19
lines changed

7 files changed

+103
-19
lines changed

docs/config.rst

+3-3
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ You can also specify that the virtualenv should *always* be reused instead of re
167167
def tests(session):
168168
pass
169169
170-
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, uv, conda, mamba, or virtualenv (default):
170+
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):
171171

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

183183
Finally, custom backend parameters are supported:

docs/tutorial.rst

+7-3
Original file line numberDiff line numberDiff line change
@@ -397,12 +397,13 @@ Install packages with conda:
397397

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

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

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

416420
Parametrization
417421
---------------

docs/usage.rst

+6-6
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Then running ``nox --session tests`` will actually run all parametrized versions
126126
Changing the sessions default backend
127127
-------------------------------------
128128

129-
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')``.
129+
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')``.
130130

131131

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

143143
.. note::
144144

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

149149
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.
150150

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

159-
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.
159+
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.
160160

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

169-
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')``.
169+
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')``.
170170

171171
.. code-block:: console
172172

nox/sessions.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,9 @@ def conda_install(
554554
555555
You can specify a conda channel using `channel=`; a falsey value will
556556
not change the current channels. You can specify a list of channels if
557-
needed.
557+
needed. It is highly recommended to specify this; micromamba does not
558+
set default channels, and default channels vary for conda. Note that
559+
"defaults" is also not permissivly licenced like "conda-forge" is.
558560
559561
Additional keyword args are the same as for :meth:`run`.
560562

nox/virtualenv.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ class CondaEnv(ProcessEnv):
236236
"""
237237

238238
is_sandboxed = True
239-
allowed_globals = ("conda", "mamba")
239+
allowed_globals = ("conda", "mamba", "micromamba")
240240

241241
def __init__(
242242
self,
@@ -305,6 +305,12 @@ def create(self) -> bool:
305305
return False
306306

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

309315
cmd.extend(self.venv_params)
310316

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

317-
logger.info(f"Creating conda env in {self.location_name} with {python_dep}")
323+
logger.info(
324+
f"Creating {self.conda_cmd} env in {self.location_name} with {python_dep}"
325+
)
318326
nox.command.run(cmd, silent=True, log=nox.options.verbose or False)
319327

320328
return True
@@ -589,6 +597,7 @@ def venv_backend(self) -> str:
589597
ALL_VENVS: dict[str, Callable[..., ProcessEnv]] = {
590598
"conda": functools.partial(CondaEnv, conda_cmd="conda"),
591599
"mamba": functools.partial(CondaEnv, conda_cmd="mamba"),
600+
"micromamba": functools.partial(CondaEnv, conda_cmd="micromamba"),
592601
"virtualenv": functools.partial(VirtualEnv, venv_backend="virtualenv"),
593602
"venv": functools.partial(VirtualEnv, venv_backend="venv"),
594603
"uv": functools.partial(VirtualEnv, venv_backend="uv"),
@@ -601,5 +610,6 @@ def venv_backend(self) -> str:
601610
OPTIONAL_VENVS = {
602611
"conda": shutil.which("conda") is not None,
603612
"mamba": shutil.which("mamba") is not None,
613+
"micromamba": shutil.which("micromamba") is not None,
604614
"uv": HAS_UV,
605615
}

noxfile.py

+27-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@
3434
nox.options.sessions = ["tests", "cover", "lint", "docs"]
3535
if shutil.which("conda"):
3636
nox.options.sessions.append("conda_tests")
37+
if shutil.which("mamba"):
38+
nox.options.sessions.append("mamba_tests")
39+
if shutil.which("micromamba"):
40+
nox.options.sessions.append("micromamba_tests")
3741

3842

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

8185

82-
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"], venv_backend="conda")
86+
@nox.session(venv_backend="conda")
8387
def conda_tests(session: nox.Session) -> None:
84-
"""Run test suite with pytest."""
85-
session.create_tmp() # Fixes permission errors on Windows
88+
"""Run test suite set up with conda."""
89+
session.conda_install(
90+
"--file", "requirements-conda-test.txt", channel="conda-forge"
91+
)
92+
session.install("-e", ".", "--no-deps")
93+
session.run("pytest", *session.posargs)
94+
95+
96+
@nox.session(venv_backend="mamba")
97+
def mamba_tests(session: nox.Session) -> None:
98+
"""Run test suite set up with mamba."""
99+
session.conda_install(
100+
"--file", "requirements-conda-test.txt", channel="conda-forge"
101+
)
102+
session.install("-e", ".", "--no-deps")
103+
session.run("pytest", *session.posargs)
104+
105+
106+
@nox.session(venv_backend="micromamba")
107+
def micromamba_tests(session: nox.Session) -> None:
108+
"""Run test suite set up with micromamba."""
86109
session.conda_install(
87-
"--file", "requirements-conda-test.txt", "--channel", "conda-forge"
110+
"--file", "requirements-conda-test.txt", channel="conda-forge"
88111
)
89112
session.install("-e", ".", "--no-deps")
90113
session.run("pytest", *session.posargs)

tests/test_virtualenv.py

+45
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,51 @@ def test_reuse_conda_environment(make_one):
483483
assert reused
484484

485485

486+
# This mocks micromamba so that it doesn't need to be installed.
487+
@has_conda
488+
def test_micromamba_environment(make_one, monkeypatch):
489+
conda_path = shutil.which("conda")
490+
which = shutil.which
491+
monkeypatch.setattr(
492+
shutil, "which", lambda x: conda_path if x == "micromamba" else which(x)
493+
)
494+
venv, _ = make_one(reuse_existing=True, venv_backend="micromamba")
495+
run = mock.Mock()
496+
monkeypatch.setattr(nox.command, "run", run)
497+
venv.create()
498+
run.assert_called_once()
499+
# .args requires Python 3.8+
500+
((args,), _) = run.call_args
501+
assert args[0] == "micromamba"
502+
assert "--channel=conda-forge" in args
503+
504+
505+
# This mocks micromamba so that it doesn't need to be installed.
506+
@pytest.mark.parametrize(
507+
"params",
508+
[["--channel=default"], ["-cdefault"], ["-c", "default"], ["--channel", "default"]],
509+
)
510+
@has_conda
511+
def test_micromamba_channel_environment(make_one, monkeypatch, params):
512+
conda_path = shutil.which("conda")
513+
which = shutil.which
514+
monkeypatch.setattr(
515+
shutil, "which", lambda x: conda_path if x == "micromamba" else which(x)
516+
)
517+
venv, _ = make_one(reuse_existing=True, venv_backend="micromamba")
518+
run = mock.Mock()
519+
monkeypatch.setattr(nox.command, "run", run)
520+
venv.venv_params = params
521+
venv.create()
522+
run.assert_called_once()
523+
# .args requires Python 3.8+
524+
((args,), _) = run.call_args
525+
assert args[0] == "micromamba"
526+
for p in params:
527+
assert p in args
528+
assert "--channel=conda-forge" not in args
529+
530+
486531
@pytest.mark.parametrize(
487532
("frm", "to", "result"),
488533
[

0 commit comments

Comments
 (0)