Repository: wntrblm/nox
Pull request #811 (2024-04-07 04:09:50)

feat: support PEP 723 with a toml load function
This is the suggestion from #808. As detailed there, I think this is a flexible first step that will help noxfile authors support PEP 723 in scripts, and could have a followup later that provides a more integrated solution.

This also allows noxfile authors to finally integrate with pyproject.toml on all versions of Python, rather than requiring nox be installed on Python 3.11+, by ensuring that a toml library is always present. You might need to read the version, etc. from `pyproject.toml` in your noxfile, and now you can.

I made all the errors ValueError because that's what the PEP did in the example implementation. Happy to make them something else if there's something better.

cjolowicz: Should we fail the session instead of raising ValueError? Nox can be more opinionated about the error handling than say a library, and it would avoid the traceback.
henryiii: Hmm, we could, but most of the improperly configured calls seem to also raise ValueError, such as 

https://github.com/wntrblm/nox/blob/956f10c5691b4f4d1a7e2d648fa2ca2eb4b27ab5/nox/sessions.py#L219

and

https://github.com/wntrblm/nox/blob/956f10c5691b4f4d1a7e2d648fa2ca2eb4b27ab5/nox/sessions.py#L388-L393

henryiii: Current name (from the issue) is `nox.project.load_toml`. `nox.project` is imported by default.

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ec34957..72e48e7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -40,6 +40,7 @@ repos:
           - jinja2
           - packaging
           - importlib_metadata
+          - tomli
           - uv
 
   - repo: https://github.com/codespell-project/codespell
diff --git a/docs/config.rst b/docs/config.rst
index 53ab693..7d2a4bb 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -26,6 +26,10 @@ with ``@nox.session``. For example:
 
 You can also configure sessions to run against multiple Python versions as described in :ref:`virtualenv config` and  parametrize sessions as described in :ref:`parametrized sessions <parametrized>`.
 
+By default, all sessions will be run if no sessions are specified. You can
+remove sessions from this default list by passing ``default=False`` in the
+``@nox.session(...)`` decorator. You can also specify a list of sessions to run by
+default using the ``nox.options.sessions = [...]`` configuration option.
 
 Session description
 -------------------
@@ -167,7 +171,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
 
@@ -176,8 +180,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:
@@ -421,7 +425,7 @@ Nox has various :doc:`command line arguments <usage>` that can be used to modify
     def tests(session):
         ...
 
-Or, if you wanted to provide a set of sessions that are run by default:
+Or, if you wanted to provide a set of sessions that are run by default (this overrides the ``default=`` argument to sessions):
 
 .. code-block:: python
 
diff --git a/docs/tutorial.rst b/docs/tutorial.rst
index 4b3131f..633b625 100644
--- a/docs/tutorial.rst
+++ b/docs/tutorial.rst
@@ -169,6 +169,46 @@ dependency (e.g. ``libfoo``) is available during installation:
 These commands will run even if you are only installing, and will not run if
 the environment is being reused without reinstallation.
 
+
+Loading dependencies from pyproject.toml or scripts
+---------------------------------------------------
+
+One common need is loading a dependency list from a ``pyproject.toml`` file
+(say to prepare an environment without installing the package) or supporting
+`PEP 723 <https://peps.python.org/pep-0723>`_ scripts. Nox provides a helper to
+load these with ``nox.project.load_toml``. It can be passed a filepath to a toml
+file or to a script file following PEP 723. For example, if you have the
+following ``peps.py``:
+
+
+.. code-block:: python
+
+    # /// script
+    # requires-python = ">=3.11"
+    # dependencies = [
+    #   "requests<3",
+    #   "rich",
+    # ]
+    # ///
+
+    import requests
+    from rich.pretty import pprint
+
+    resp = requests.get("https://peps.python.org/api/peps.json")
+    data = resp.json()
+    pprint([(k, v["title"]) for k, v in data.items()][:10])
+
+You can make a session for it like this:
+
+.. code-block:: python
+
+   @nox.session
+   def peps(session):
+       requirements = nox.project.load_toml("peps.py")["dependencies"]
+       session.install(*requirements)
+       session.run("peps.py")
+
+
 Running commands
 ----------------
 
@@ -397,12 +437,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
 
@@ -412,6 +453,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
 ---------------
diff --git a/docs/usage.rst b/docs/usage.rst
index 887b4d1..069f123 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -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::
@@ -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.
 
@@ -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.
@@ -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
 
diff --git a/nox/__init__.py b/nox/__init__.py
index 1afe51a..2e47ad5 100644
--- a/nox/__init__.py
+++ b/nox/__init__.py
@@ -14,6 +14,7 @@
 
 from __future__ import annotations
 
+from nox import project
 from nox._options import noxfile_options as options
 from nox._parametrize import Param as param
 from nox._parametrize import parametrize_decorator as parametrize
@@ -22,4 +23,12 @@ from nox.sessions import Session
 
 needs_version: str | None = None
 
-__all__ = ["needs_version", "parametrize", "param", "session", "options", "Session"]
+__all__ = [
+    "needs_version",
+    "parametrize",
+    "param",
+    "session",
+    "options",
+    "Session",
+    "project",
+]
diff --git a/nox/_decorators.py b/nox/_decorators.py
index aaef96a..25f13de 100644
--- a/nox/_decorators.py
+++ b/nox/_decorators.py
@@ -69,6 +69,8 @@ class Func(FunctionDecorator):
         venv_params: Any = None,
         should_warn: Mapping[str, Any] | None = None,
         tags: Sequence[str] | None = None,
+        *,
+        default: bool = True,
     ) -> None:
         self.func = func
         self.python = python
@@ -78,6 +80,7 @@ class Func(FunctionDecorator):
         self.venv_params = venv_params
         self.should_warn = dict(should_warn or {})
         self.tags = list(tags or [])
+        self.default = default
 
     def __call__(self, *args: Any, **kwargs: Any) -> Any:
         return self.func(*args, **kwargs)
@@ -94,6 +97,7 @@ class Func(FunctionDecorator):
             self.venv_params,
             self.should_warn,
             self.tags,
+            default=self.default,
         )
 
 
@@ -125,6 +129,7 @@ class Call(Func):
             func.venv_params,
             func.should_warn,
             func.tags,
+            default=func.default,
         )
         self.call_spec = call_spec
         self.session_signature = session_signature
diff --git a/nox/command.py b/nox/command.py
index b1f544d..4f948e6 100644
--- a/nox/command.py
+++ b/nox/command.py
@@ -17,18 +17,23 @@ from __future__ import annotations
 import os
 import shlex
 import shutil
+import subprocess
 import sys
 from collections.abc import Iterable, Mapping, Sequence
-from typing import Any
 
 from nox.logger import logger
-from nox.popen import popen
+from nox.popen import DEFAULT_INTERRUPT_TIMEOUT, DEFAULT_TERMINATE_TIMEOUT, popen
 
 if sys.version_info < (3, 8):
     from typing_extensions import Literal
 else:
     from typing import Literal
 
+TYPE_CHECKING = False
+
+if TYPE_CHECKING:
+    from typing import IO
+
 ExternalType = Literal["error", True, False]
 
 
@@ -55,17 +60,15 @@ def which(program: str | os.PathLike[str], paths: Sequence[str] | None) -> str:
     raise CommandFailed(f"Program {program} not found")
 
 
-def _clean_env(env: Mapping[str, str] | None = None) -> dict[str, str] | None:
+def _clean_env(env: Mapping[str, str | None] | None = None) -> dict[str, str] | None:
     if env is None:
         return None
 
-    clean_env: dict[str, str] = {}
+    clean_env = {k: v for k, v in env.items() if v is not None}
 
     # Ensure systemroot is passed down, otherwise Windows will explode.
     if sys.platform == "win32":
-        clean_env["SYSTEMROOT"] = os.environ.get("SYSTEMROOT", "")
-
-    clean_env.update(env)
+        clean_env.setdefault("SYSTEMROOT", os.environ.get("SYSTEMROOT", ""))
 
     return clean_env
 
@@ -77,13 +80,16 @@ def _shlex_join(args: Sequence[str | os.PathLike[str]]) -> str:
 def run(
     args: Sequence[str | os.PathLike[str]],
     *,
-    env: Mapping[str, str] | None = None,
+    env: Mapping[str, str | None] | None = None,
     silent: bool = False,
     paths: Sequence[str] | None = None,
     success_codes: Iterable[int] | None = None,
     log: bool = True,
     external: ExternalType = False,
-    **popen_kws: Any,
+    stdout: int | IO[str] | None = None,
+    stderr: int | IO[str] = subprocess.STDOUT,
+    interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+    terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
 ) -> str | bool:
     """Run a command-line program."""
 
@@ -121,7 +127,13 @@ def run(
 
     try:
         return_code, output = popen(
-            [cmd_path, *str_args], silent=silent, env=env, **popen_kws
+            [cmd_path, *str_args],
+            silent=silent,
+            env=env,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
         )
 
         if return_code not in success_codes:
diff --git a/nox/manifest.py b/nox/manifest.py
index fa9f797..e813f02 100644
--- a/nox/manifest.py
+++ b/nox/manifest.py
@@ -155,6 +155,11 @@ class Manifest:
         if missing_sessions:
             raise KeyError(f"Sessions not found: {', '.join(missing_sessions)}")
 
+    def filter_by_default(self) -> None:
+        """Filter sessions in the queue based on the default flag."""
+
+        self._queue = [x for x in self._queue if x.func.default]
+
     def filter_by_python_interpreter(self, specified_pythons: Sequence[str]) -> None:
         """Filter sessions in the queue based on the user-specified
         python interpreter versions.
diff --git a/nox/popen.py b/nox/popen.py
index 2d37b55..f975146 100644
--- a/nox/popen.py
+++ b/nox/popen.py
@@ -21,6 +21,9 @@ import sys
 from collections.abc import Mapping, Sequence
 from typing import IO
 
+DEFAULT_INTERRUPT_TIMEOUT = 0.3
+DEFAULT_TERMINATE_TIMEOUT = 0.2
+
 
 def shutdown_process(
     proc: subprocess.Popen[bytes],
@@ -64,8 +67,8 @@ def popen(
     silent: bool = False,
     stdout: int | IO[str] | None = None,
     stderr: int | IO[str] = subprocess.STDOUT,
-    interrupt_timeout: float | None = 0.3,
-    terminate_timeout: float | None = 0.2,
+    interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+    terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
 ) -> tuple[int, str]:
     if silent and stdout is not None:
         raise ValueError(
diff --git a/nox/project.py b/nox/project.py
new file mode 100644
index 0000000..20a76aa
--- /dev/null
+++ b/nox/project.py
@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+import os
+import re
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any
+
+if sys.version_info < (3, 11):
+    import tomli as tomllib
+else:
+    import tomllib
+
+
+__all__ = ["load_toml"]
+
+
+def __dir__() -> list[str]:
+    return __all__
+
+
+# Note: the implementation (including this regex) taken from PEP 723
+# https://peps.python.org/pep-0723
+
+REGEX = re.compile(
+    r"(?m)^# /// (?P<type>[a-zA-Z0-9-]+)$\s(?P<content>(^#(| .*)$\s)+)^# ///$"
+)
+
+
+def load_toml(filename: os.PathLike[str] | str) -> dict[str, Any]:
+    """
+    Load a toml file or a script with a PEP 723 script block.
+
+    The file must have a ``.toml`` extension to be considered a toml file or a
+    ``.py`` extension / no extension to be considered a script. Other file
+    extensions are not valid in this function.
+    """
+    filepath = Path(filename)
+    if filepath.suffix == ".toml":
+        return _load_toml_file(filepath)
+    if filepath.suffix in {".py", ""}:
+        return _load_script_block(filepath)
+    msg = f"Extension must be .py or .toml, got {filepath.suffix}"
+    raise ValueError(msg)
+
+
+def _load_toml_file(filepath: Path) -> dict[str, Any]:
+    with filepath.open("rb") as f:
+        return tomllib.load(f)
+
+
+def _load_script_block(filepath: Path) -> dict[str, Any]:
+    name = "script"
+    script = filepath.read_text(encoding="utf-8")
+    matches = list(filter(lambda m: m.group("type") == name, REGEX.finditer(script)))
+
+    if not matches:
+        raise ValueError(f"No {name} block found in {filepath}")
+    if len(matches) > 1:
+        raise ValueError(f"Multiple {name} blocks found in {filepath}")
+
+    content = "".join(
+        line[2:] if line.startswith("# ") else line[1:]
+        for line in matches[0].group("content").splitlines(keepends=True)
+    )
+    return tomllib.loads(content)
diff --git a/nox/registry.py b/nox/registry.py
index 60765ae..4e5c72a 100644
--- a/nox/registry.py
+++ b/nox/registry.py
@@ -43,6 +43,8 @@ def session_decorator(
     venv_backend: Any | None = ...,
     venv_params: Any | None = ...,
     tags: Sequence[str] | None = ...,
+    *,
+    default: bool = ...,
 ) -> Callable[[F], F]:
     ...
 
@@ -56,6 +58,8 @@ def session_decorator(
     venv_backend: Any | None = None,
     venv_params: Any | None = None,
     tags: Sequence[str] | None = None,
+    *,
+    default: bool = True,
 ) -> F | Callable[[F], F]:
     """Designate the decorated function as a session."""
     # If `func` is provided, then this is the decorator call with the function
@@ -75,6 +79,7 @@ def session_decorator(
             venv_backend=venv_backend,
             venv_params=venv_params,
             tags=tags,
+            default=default,
         )
 
     if py is not None and python is not None:
@@ -88,7 +93,14 @@ def session_decorator(
 
     final_name = name or func.__name__
     fn = Func(
-        func, python, reuse_venv, final_name, venv_backend, venv_params, tags=tags
+        func,
+        python,
+        reuse_venv,
+        final_name,
+        venv_backend,
+        venv_params,
+        tags=tags,
+        default=default,
     )
     _REGISTRY[final_name] = fn
     return fn
diff --git a/nox/sessions.py b/nox/sessions.py
index 57cc7bd..756b09b 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -21,6 +21,7 @@ import hashlib
 import os
 import pathlib
 import re
+import subprocess
 import sys
 import unicodedata
 from collections.abc import (
@@ -41,9 +42,13 @@ import nox.command
 import nox.virtualenv
 from nox._decorators import Func
 from nox.logger import logger
+from nox.popen import DEFAULT_INTERRUPT_TIMEOUT, DEFAULT_TERMINATE_TIMEOUT
 from nox.virtualenv import UV, CondaEnv, PassthroughEnv, ProcessEnv, VirtualEnv
 
 if TYPE_CHECKING:
+    from typing import IO
+
+    from nox.command import ExternalType
     from nox.manifest import Manifest
 
 
@@ -269,13 +274,11 @@ class Session:
     cd = chdir
     """An alias for :meth:`chdir`."""
 
-    def _run_func(
-        self, func: Callable[..., Any], args: Iterable[Any], kwargs: Mapping[str, Any]
-    ) -> Any:
+    def _run_func(self, func: Callable[..., Any], args: Iterable[Any]) -> Any:
         """Legacy support for running a function through :func`run`."""
-        self.log(f"{func}(args={args!r}, kwargs={kwargs!r})")
+        self.log(f"{func}(args={args!r})")
         try:
-            return func(*args, **kwargs)
+            return func(*args)
         except Exception as e:
             logger.exception(f"Function {func!r} raised {e!r}.")
             raise nox.command.CommandFailed() from e
@@ -283,9 +286,16 @@ class Session:
     def run(
         self,
         *args: str | os.PathLike[str],
-        env: Mapping[str, str] | None = None,
+        env: Mapping[str, str | None] | None = None,
         include_outer_env: bool = True,
-        **kwargs: Any,
+        silent: bool = False,
+        success_codes: Iterable[int] | None = None,
+        log: bool = True,
+        external: ExternalType | None = None,
+        stdout: int | IO[str] | None = None,
+        stderr: int | IO[str] = subprocess.STDOUT,
+        interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+        terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
     ) -> Any | None:
         """Run a command.
 
@@ -350,7 +360,9 @@ class Session:
             print("Current Git commit is", out.strip())
 
         :param env: A dictionary of environment variables to expose to the
-            command. By default, all environment variables are passed.
+            command. By default, all environment variables are passed. You
+            can block an environment variable from the outer environment by
+            setting it to None.
         :type env: dict or None
         :param include_outer_env: Boolean parameter that determines if the
             environment variables from the nox invocation environment should
@@ -400,15 +412,29 @@ class Session:
             *args,
             env=env,
             include_outer_env=include_outer_env,
-            **kwargs,
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
+            external=external,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
         )
 
     def run_install(
         self,
         *args: str | os.PathLike[str],
-        env: Mapping[str, str] | None = None,
+        env: Mapping[str, str | None] | None = None,
         include_outer_env: bool = True,
-        **kwargs: Any,
+        silent: bool = False,
+        success_codes: Iterable[int] | None = None,
+        log: bool = True,
+        external: ExternalType | None = None,
+        stdout: int | IO[str] | None = None,
+        stderr: int | IO[str] = subprocess.STDOUT,
+        interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+        terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
     ) -> Any | None:
         """Run a command in the install step.
 
@@ -468,15 +494,29 @@ class Session:
             *args,
             env=env,
             include_outer_env=include_outer_env,
-            **kwargs,
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
+            external=external,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
         )
 
     def run_always(
         self,
         *args: str | os.PathLike[str],
-        env: Mapping[str, str] | None = None,
+        env: Mapping[str, str | None] | None = None,
         include_outer_env: bool = True,
-        **kwargs: Any,
+        silent: bool = False,
+        success_codes: Iterable[int] | None = None,
+        log: bool = True,
+        external: ExternalType | None = None,
+        stdout: int | IO[str] | None = None,
+        stderr: int | IO[str] = subprocess.STDOUT,
+        interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+        terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
     ) -> Any | None:
         """This is an alias to ``run_install``, which better describes the use case.
 
@@ -484,48 +524,86 @@ class Session:
         """
 
         return self.run_install(
-            *args, env=env, include_outer_env=include_outer_env, **kwargs
+            *args,
+            env=env,
+            include_outer_env=include_outer_env,
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
+            external=external,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
         )
 
     def _run(
         self,
         *args: str | os.PathLike[str],
-        env: Mapping[str, str] | None = None,
-        include_outer_env: bool = True,
-        **kwargs: Any,
+        env: Mapping[str, str | None] | None = None,
+        include_outer_env: bool,
+        silent: bool,
+        success_codes: Iterable[int] | None,
+        log: bool,
+        external: ExternalType | None,
+        stdout: int | IO[str] | None,
+        stderr: int | IO[str],
+        interrupt_timeout: float | None,
+        terminate_timeout: float | None,
     ) -> Any:
         """Like run(), except that it runs even if --install-only is provided."""
         # Legacy support - run a function given.
         if callable(args[0]):
-            return self._run_func(args[0], args[1:], kwargs)  # type: ignore[unreachable]
+            return self._run_func(args[0], args[1:])  # type: ignore[unreachable]
 
         # Combine the env argument with our virtualenv's env vars.
         if include_outer_env:
-            overlay_env = env
-            env = self.env.copy()
-            if overlay_env is not None:
-                env.update(overlay_env)
+            overlay_env = env or {}
+            env = {**self.env, **overlay_env}
 
         # If --error-on-external-run is specified, error on external programs.
-        if self._runner.global_config.error_on_external_run:
-            kwargs.setdefault("external", "error")
+        if self._runner.global_config.error_on_external_run and external is None:
+            external = "error"
 
         # Allow all external programs when running outside a sandbox.
-        if not self.virtualenv.is_sandboxed:
-            kwargs["external"] = True
+        if (
+            not self.virtualenv.is_sandboxed
+            or args[0] in self.virtualenv.allowed_globals
+        ):
+            external = True
 
-        if args[0] in self.virtualenv.allowed_globals:
-            kwargs["external"] = True
+        if external is None:
+            external = False
 
         # Run a shell command.
-        return nox.command.run(args, env=env, paths=self.bin_paths, **kwargs)
+        return nox.command.run(
+            args,
+            env=env,
+            paths=self.bin_paths,
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
+            external=external,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
+        )
 
     def conda_install(
         self,
         *args: str,
         auto_offline: bool = True,
         channel: str | Sequence[str] = "",
-        **kwargs: Any,
+        env: Mapping[str, str] | None = None,
+        include_outer_env: bool = True,
+        silent: bool | None = None,
+        success_codes: Iterable[int] | None = None,
+        log: bool = True,
+        stdout: int | IO[str] | None = None,
+        stderr: int | IO[str] = subprocess.STDOUT,
+        interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+        terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
     ) -> None:
         """Install invokes `conda install`_ to install packages inside of the
         session's environment.
@@ -554,7 +632,9 @@ class Session:
 
         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`.
 
@@ -582,8 +662,8 @@ class Session:
         # Escape args that should be (conda-specific; pip install does not need this)
         args = _dblquote_pkg_install_args(args)
 
-        if "silent" not in kwargs:
-            kwargs["silent"] = True
+        if silent is None:
+            silent = True
 
         extraopts: list[str] = []
         if auto_offline and venv.is_offline():
@@ -606,11 +686,32 @@ class Session:
             *extraopts,
             *prefix_args,
             *args,
+            env=env,
+            include_outer_env=include_outer_env,
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
             external="error",
-            **kwargs,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
         )
 
-    def install(self, *args: str, **kwargs: Any) -> None:
+    def install(
+        self,
+        *args: str,
+        env: Mapping[str, str] | None = None,
+        include_outer_env: bool = True,
+        silent: bool | None = None,
+        success_codes: Iterable[int] | None = None,
+        log: bool = True,
+        external: ExternalType | None = None,
+        stdout: int | IO[str] | None = None,
+        stderr: int | IO[str] = subprocess.STDOUT,
+        interrupt_timeout: float | None = DEFAULT_INTERRUPT_TIMEOUT,
+        terminate_timeout: float | None = DEFAULT_TERMINATE_TIMEOUT,
+    ) -> None:
         """Install invokes `pip`_ to install packages inside of the session's
         virtualenv.
 
@@ -664,15 +765,27 @@ class Session:
         if self._runner.global_config.no_install and venv._reused:
             return
 
-        if "silent" not in kwargs:
-            kwargs["silent"] = True
+        if silent is None:
+            silent = True
 
         if isinstance(venv, VirtualEnv) and venv.venv_backend == "uv":
-            self._run(UV, "pip", "install", *args, external="error", **kwargs)
+            cmd = [UV, "pip", "install"]
         else:
-            self._run(
-                "python", "-m", "pip", "install", *args, external="error", **kwargs
-            )
+            cmd = ["python", "-m", "pip", "install"]
+        self._run(
+            *cmd,
+            *args,
+            env=env,
+            include_outer_env=include_outer_env,
+            external="error",
+            silent=silent,
+            success_codes=success_codes,
+            log=log,
+            stdout=stdout,
+            stderr=stderr,
+            interrupt_timeout=interrupt_timeout,
+            terminate_timeout=terminate_timeout,
+        )
 
     def notify(
         self,
diff --git a/nox/tasks.py b/nox/tasks.py
index 595a405..818dc93 100644
--- a/nox/tasks.py
+++ b/nox/tasks.py
@@ -173,7 +173,9 @@ def filter_manifest(manifest: Manifest, global_config: Namespace) -> Manifest |
     # This can raise KeyError if a specified session does not exist;
     # log this if it happens. The sessions does not come from the Noxfile
     # if keywords is not empty.
-    if global_config.sessions is not None:
+    if global_config.sessions is None:
+        manifest.filter_by_default()
+    else:
         try:
             manifest.filter_by_name(global_config.sessions)
         except KeyError as exc:
diff --git a/nox/virtualenv.py b/nox/virtualenv.py
index b561054..90ee085 100644
--- a/nox/virtualenv.py
+++ b/nox/virtualenv.py
@@ -34,7 +34,12 @@ from nox.logger import logger
 # Problematic environment variables that are stripped from all commands inside
 # of a virtualenv. See https://github.com/theacodes/nox/issues/44
 _BLACKLISTED_ENV_VARS = frozenset(
-    ["PIP_RESPECT_VIRTUALENV", "PIP_REQUIRE_VIRTUALENV", "__PYVENV_LAUNCHER__"]
+    [
+        "PIP_RESPECT_VIRTUALENV",
+        "PIP_REQUIRE_VIRTUALENV",
+        "__PYVENV_LAUNCHER__",
+        "UV_SYSTEM_PYTHON",
+    ]
 )
 _SYSTEM = platform.system()
 
@@ -78,15 +83,13 @@ class ProcessEnv(abc.ABC):
         self, bin_paths: None = None, env: Mapping[str, str | None] | None = None
     ) -> None:
         self._bin_paths = bin_paths
-        self.env = os.environ.copy()
         self._reused = False
-        env = env or {}
 
-        for k, v in env.items():
-            if v is None:
-                self.env.pop(k, None)
-            else:
-                self.env[k] = v
+        # Filter envs now so `.env` is dict[str, str] (easier to use)
+        # even though .command's env supports None.
+        env = env or {}
+        env = {**os.environ, **env}
+        self.env = {k: v for k, v in env.items() if v is not None}
 
         for key in _BLACKLISTED_ENV_VARS:
             self.env.pop(key, None)
@@ -236,7 +239,7 @@ class CondaEnv(ProcessEnv):
     """
 
     is_sandboxed = True
-    allowed_globals = ("conda", "mamba")
+    allowed_globals = ("conda", "mamba", "micromamba")
 
     def __init__(
         self,
@@ -305,6 +308,12 @@ class CondaEnv(ProcessEnv):
             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)
 
@@ -314,7 +323,9 @@ class CondaEnv(ProcessEnv):
         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
@@ -589,6 +600,7 @@ class VirtualEnv(ProcessEnv):
 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"),
@@ -601,5 +613,6 @@ ALL_VENVS: dict[str, Callable[..., ProcessEnv]] = {
 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,
 }
diff --git a/noxfile.py b/noxfile.py
index a151e5b..075f579 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -34,6 +34,10 @@ nox.options.default_venv_backend = "uv|virtualenv"
 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
@@ -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)
@@ -96,7 +119,7 @@ def cover(session: nox.Session) -> None:
     if ON_WINDOWS_CI:
         return
 
-    session.install("coverage[toml]>=5.3")
+    session.install("coverage[toml]>=7.3")
     session.run("coverage", "combine")
     session.run("coverage", "report", "--fail-under=100", "--show-missing")
     session.run("coverage", "erase")
diff --git a/pyproject.toml b/pyproject.toml
index 38b6ec8..913ae1d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,6 +44,7 @@ dependencies = [
   "colorlog<7.0.0,>=2.6.1",
   'importlib-metadata; python_version < "3.8"',
   "packaging>=20.9",
+  'tomli>=1; python_version < "3.11"',
   'typing-extensions>=3.7.4; python_version < "3.8"',
   "virtualenv>=20.14.1",
 ]
@@ -107,7 +108,7 @@ relative_files = true
 source_pkgs = [ "nox" ]
 
 [tool.coverage.report]
-exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", "@overload" ]
+exclude_also = [ "def __dir__()", "if TYPE_CHECKING:", "@overload" ]
 
 [tool.mypy]
 files = [ "nox/**/*.py", "noxfile.py" ]


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
index eb05f07..b4240b7 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1,4 +1,4 @@
-coverage[toml]>=5.3
+coverage[toml]>=7.2
 flask
 myst-parser
 pytest>=6.0
diff --git a/tests/test_command.py b/tests/test_command.py
index 68f370a..ae398e9 100644
--- a/tests/test_command.py
+++ b/tests/test_command.py
@@ -144,6 +144,19 @@ def test_run_env_unicode():
     assert "123" in result
 
 
+def test_run_env_remove(monkeypatch):
+    monkeypatch.setenv("EMPTY", "notempty")
+    nox.command.run(
+        [PYTHON, "-c", 'import os; assert "EMPTY" in os.environ'],
+        silent=True,
+    )
+    nox.command.run(
+        [PYTHON, "-c", 'import os; assert "EMPTY" not in os.environ'],
+        silent=True,
+        env={"EMPTY": None},
+    )
+
+
 @mock.patch("sys.platform", "win32")
 def test_run_env_systemroot():
     systemroot = os.environ.setdefault("SYSTEMROOT", "sigil")
@@ -182,7 +195,15 @@ def test_run_path_existent(tmp_path: Path):
     with mock.patch("nox.command.popen") as mock_command:
         mock_command.return_value = (0, "")
         nox.command.run([executable_name], silent=True, paths=[str(tmp_path)])
-        mock_command.assert_called_with([str(executable)], env=mock.ANY, silent=True)
+        mock_command.assert_called_with(
+            [str(executable)],
+            env=None,
+            silent=True,
+            stdout=None,
+            stderr=subprocess.STDOUT,
+            interrupt_timeout=0.3,
+            terminate_timeout=0.2,
+        )
 
 
 def test_run_external_warns(tmpdir, caplog):
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index e720c61..c5295f5 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -19,6 +19,7 @@ import logging
 import operator
 import os
 import shutil
+import subprocess
 import sys
 import tempfile
 from pathlib import Path
@@ -28,6 +29,7 @@ import pytest
 
 import nox.command
 import nox.manifest
+import nox.popen
 import nox.registry
 import nox.sessions
 import nox.virtualenv
@@ -38,6 +40,38 @@ HAS_CONDA = shutil.which("conda") is not None
 has_conda = pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.")
 
 
+def run_with_defaults(**kwargs):
+    return {
+        "env": None,
+        "silent": False,
+        "paths": None,
+        "success_codes": None,
+        "log": True,
+        "external": False,
+        "stdout": None,
+        "stderr": subprocess.STDOUT,
+        "interrupt_timeout": nox.popen.DEFAULT_INTERRUPT_TIMEOUT,
+        "terminate_timeout": nox.popen.DEFAULT_TERMINATE_TIMEOUT,
+        **kwargs,
+    }
+
+
+def _run_with_defaults(**kwargs):
+    return {
+        "env": None,
+        "include_outer_env": True,
+        "silent": False,
+        "success_codes": None,
+        "log": True,
+        "external": False,
+        "stdout": None,
+        "stderr": subprocess.STDOUT,
+        "interrupt_timeout": nox.popen.DEFAULT_INTERRUPT_TIMEOUT,
+        "terminate_timeout": nox.popen.DEFAULT_TERMINATE_TIMEOUT,
+        **kwargs,
+    }
+
+
 def test__normalize_path():
     envdir = "envdir"
     normalize = nox.sessions._normalize_path
@@ -250,10 +284,7 @@ class TestSession:
 
         run.assert_called_once_with(
             ("python", "-m", "pip", "install", "spam"),
-            env=mock.ANY,
-            external=mock.ANY,
-            paths=mock.ANY,
-            silent=mock.ANY,
+            **run_with_defaults(paths=mock.ANY, silent=True, env={}, external="error"),
         )
 
     def test_run_success(self):
@@ -271,14 +302,15 @@ class TestSession:
         session, runner = self.make_session_and_runner()
         runner.venv.env["A"] = "1"
         runner.venv.env["B"] = "2"
+        runner.venv.env["C"] = "4"
         result = session.run(
             sys.executable,
             "-c",
-            'import os; print(os.environ["A"], os.environ["B"])',
-            env={"B": "3"},
+            'import os; print(os.environ["A"], os.environ["B"], os.environ.get("C", "5"))',
+            env={"B": "3", "C": None},
             silent=True,
         )
-        assert result.strip() == "1 3"
+        assert result.strip() == "1 3 5"
 
     def test_by_default_all_invocation_env_vars_are_passed(self):
         session, runner = self.make_session_and_runner()
@@ -322,9 +354,7 @@ class TestSession:
 
         run.assert_called_once_with(
             (sys.executable, "--version"),
-            external=True,
-            env=mock.ANY,
-            paths=None,
+            **run_with_defaults(external=True, env=mock.ANY),
         )
 
     def test_run_external_condaenv(self):
@@ -341,9 +371,9 @@ class TestSession:
 
         run.assert_called_once_with(
             ("conda", "--version"),
-            external=True,
-            env=mock.ANY,
-            paths=["/path/to/env/bin"],
+            **run_with_defaults(
+                external=True, env=mock.ANY, paths=["/path/to/env/bin"]
+            ),
         )
 
     def test_run_external_with_error_on_external_run(self):
@@ -551,8 +581,7 @@ class TestSession:
                 "/path/to/conda/env",
                 "requests",
                 "urllib3",
-                silent=True,
-                external="error",
+                **_run_with_defaults(silent=True, external="error"),
             )
 
     @pytest.mark.parametrize(
@@ -626,8 +655,7 @@ class TestSession:
                 "requests",
                 # this will be double quoted if unquoted constraint is present
                 passed_arg,
-                silent=False,
-                external="error",
+                **_run_with_defaults(silent=False, external="error"),
             )
 
     def test_install_bad_args_no_arg(self):
@@ -672,8 +700,7 @@ class TestSession:
                 "install",
                 "requests",
                 "urllib3",
-                silent=True,
-                external="error",
+                **_run_with_defaults(silent=True, external="error"),
             )
 
     def test_install_non_default_kwargs(self):
@@ -702,8 +729,7 @@ class TestSession:
                 "install",
                 "requests",
                 "urllib3",
-                silent=False,
-                external="error",
+                **_run_with_defaults(silent=False, external="error"),
             )
 
     def test_install_no_venv_failure(self):
@@ -848,8 +874,7 @@ class TestSession:
                 "install",
                 "requests",
                 "urllib3",
-                silent=False,
-                external="error",
+                **_run_with_defaults(silent=False, external="error"),
             )
 
     def test___slots__(self):
diff --git a/tests/test_tasks.py b/tests/test_tasks.py
index 947239e..122cc0e 100644
--- a/tests/test_tasks.py
+++ b/tests/test_tasks.py
@@ -40,6 +40,7 @@ session_func.python = None
 session_func.venv_backend = None
 session_func.should_warn = {}
 session_func.tags = []
+session_func.default = True
 
 
 def session_func_with_python():
@@ -48,6 +49,7 @@ def session_func_with_python():
 
 session_func_with_python.python = "3.8"
 session_func_with_python.venv_backend = None
+session_func_with_python.default = True
 
 
 def session_func_venv_pythons_warning():
@@ -346,6 +348,40 @@ def test_merge_sessions_and_tags(reset_global_nox_options, generate_noxfile_opti
     assert len(manifest) == 2
 
 
+@pytest.mark.parametrize("selection", [None, ["qux"], ["quuz"], ["qux", "quuz"]])
+def test_default_false(selection):
+    @nox.session()
+    def qux():
+        pass
+
+    @nox.session()
+    def quux():
+        pass
+
+    @nox.session(default=False)
+    def quuz():
+        pass
+
+    @nox.session(default=False)
+    def corge():
+        pass
+
+    config = _options.options.namespace(sessions=selection, pythons=(), posargs=[])
+    manifest = Manifest(
+        {
+            "qux": qux,
+            "quux": quux,
+            "quuz": quuz,
+            "corge": corge,
+        },
+        config,
+    )
+    return_value = tasks.filter_manifest(manifest, config)
+    assert return_value is manifest
+    expected = 2 if selection is None else len(selection)
+    assert len(manifest) == expected
+
+
 def test_honor_list_request_noop():
     config = _options.options.namespace(list_sessions=False)
     manifest = {"thing": mock.sentinel.THING}
diff --git a/tests/test_toml.py b/tests/test_toml.py
new file mode 100644
index 0000000..833f1d1
--- /dev/null
+++ b/tests/test_toml.py
@@ -0,0 +1,107 @@
+import textwrap
+from pathlib import Path
+
+import pytest
+
+import nox
+
+
+def test_load_pyproject(tmp_path: Path) -> None:
+    filepath = tmp_path / "example.toml"
+    filepath.write_text(
+        """
+        [project]
+        name = "hi"
+        version = "1.0"
+        dependencies = ["numpy", "requests"]
+        """
+    )
+
+    toml = nox.project.load_toml(filepath)
+    assert toml["project"]["dependencies"] == ["numpy", "requests"]
+
+
+@pytest.mark.parametrize("example", ["example.py", "example"])
+def test_load_script_block(tmp_path: Path, example: str) -> None:
+    filepath = tmp_path / example
+    filepath.write_text(
+        textwrap.dedent(
+            """\
+            #!/usr/bin/env pipx run
+            # /// script
+            # requires-python = ">=3.11"
+            # dependencies = [
+            #   "requests<3",
+            #   "rich",
+            # ]
+            # ///
+
+            import requests
+            from rich.pretty import pprint
+
+            resp = requests.get("https://peps.python.org/api/peps.json")
+            data = resp.json()
+            pprint([(k, v["title"]) for k, v in data.items()][:10])
+            """
+        )
+    )
+
+    toml = nox.project.load_toml(filepath)
+    assert toml["dependencies"] == ["requests<3", "rich"]
+
+
+def test_load_no_script_block(tmp_path: Path) -> None:
+    filepath = tmp_path / "example.py"
+    filepath.write_text(
+        textwrap.dedent(
+            """\
+            #!/usr/bin/python
+
+            import requests
+            from rich.pretty import pprint
+
+            resp = requests.get("https://peps.python.org/api/peps.json")
+            data = resp.json()
+            pprint([(k, v["title"]) for k, v in data.items()][:10])
+            """
+        )
+    )
+
+    with pytest.raises(ValueError, match="No script block found"):
+        nox.project.load_toml(filepath)
+
+
+def test_load_multiple_script_block(tmp_path: Path) -> None:
+    filepath = tmp_path / "example.py"
+    filepath.write_text(
+        textwrap.dedent(
+            """\
+            # /// script
+            # dependencies = [
+            #   "requests<3",
+            #   "rich",
+            # ]
+            # ///
+
+            # /// script
+            # requires-python = ">=3.11"
+            # ///
+
+            import requests
+            from rich.pretty import pprint
+
+            resp = requests.get("https://peps.python.org/api/peps.json")
+            data = resp.json()
+            pprint([(k, v["title"]) for k, v in data.items()][:10])
+            """
+        )
+    )
+
+    with pytest.raises(ValueError, match="Multiple script blocks found"):
+        nox.project.load_toml(filepath)
+
+
+def test_load_non_recongnised_extension():
+    msg = "Extension must be .py or .toml, got .txt"
+    with pytest.raises(ValueError, match=msg):
+        nox.project.load_toml("some.txt")
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
index 340c2f9..f1dfb82 100644
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -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"),
     [