Skip to content
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

feat: add micromamba support #807

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Comment on lines -557 to +559
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@henryiii This sounds ominous, Anaconda's defaults channel is commercially licensed and is free to use for educational users and companies with fewer than 200 employees.


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")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth mentioning this default in the docs as it is not quite the same as relying on PyPI (which doesn't re-package software from 3rd party authors as conda-forge does). Linking to the conda-forge security considerations would be even better to inform users about that.


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(
henryiii marked this conversation as resolved.
Show resolved Hide resolved
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
Loading