Repository: theacodes/nox (python)

Issue:
conda: set channel via keyword
**How would this feature be useful?**
Minor addition for a common situation.

**Describe the solution you'd like**

What about having a `channel=` keyword argument for `conda_install` so that one could write `channel="conda-forge"` if selecting a channel? It would be a bit more natural than `"--channel=conda-forge"`, I think, and likely is very common.

It is possible to set multiple channels - this is required for bioconda, for example. This could be an iterable, `channel=["conda-forge", "bioconda"]`. Maybe the keyword should be "channels" and always takes a list?

**Describe alternatives you've considered**

Leave as is.




Pull request description:
feat: add channel keyword to conda
Closes #446.

This adds a channel keyword, which can take a string or a list/tuple of channels.

Other options included `channels=`, and only allowing for a sequence of channels, but that probably is optimizing for the less-common case. Simpler API though. Can change if desired.



Patch:
diff --git a/nox/sessions.py b/nox/sessions.py
index 83ea167..b1836a0 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -360,7 +360,11 @@ class Session:
         return nox.command.run(args, env=env, paths=self.bin_paths, **kwargs)
 
     def conda_install(
-        self, *args: str, auto_offline: bool = True, **kwargs: Any
+        self,
+        *args: str,
+        auto_offline: bool = True,
+        channel: Union[str, Sequence[str]] = "",
+        **kwargs: Any,
     ) -> None:
         """Install invokes `conda install`_ to install packages inside of the
         session's environment.
@@ -369,7 +373,7 @@ class Session:
 
             session.conda_install('pandas')
             session.conda_install('numpy', 'scipy')
-            session.conda_install('--channel=conda-forge', 'dask==2.1.0')
+            session.conda_install('dask==2.1.0', channel='conda-forge')
 
         To install packages from a ``requirements.txt`` file::
 
@@ -387,6 +391,10 @@ class Session:
             # Install in editable mode.
             session.install('-e', '.', '--no-deps')
 
+        You can specify a conda channel using `channel=`; a falsy value will
+        not change the current channels. You can specify a list of channels if
+        needed.
+
         Additional keyword args are the same as for :meth:`run`.
 
         .. _conda install:
@@ -413,12 +421,18 @@ class Session:
         if "silent" not in kwargs:
             kwargs["silent"] = True
 
-        extraopts = ()  # type: Tuple[str, ...]
+        extraopts = []  # type: List[str]
         if auto_offline and venv.is_offline():
             logger.warning(
                 "Automatically setting the `--offline` flag as conda repo seems unreachable."
             )
-            extraopts = ("--offline",)
+            extraopts.append("--offline")
+
+        if channel:
+            if isinstance(channel, str):
+                extraopts.append(f"--channel={channel}")
+            else:
+                extraopts += [f"--channel={c}" for c in channel]
 
         self._run(
             venv.conda_cmd,


Test patch:
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index a7d1642..d5a895d 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -385,7 +385,12 @@ class TestSession:
     )
     @pytest.mark.parametrize("offline", [False, True], ids="offline={}".format)
     @pytest.mark.parametrize("conda", ["conda", "mamba"], ids=str)
-    def test_conda_install(self, auto_offline, offline, conda):
+    @pytest.mark.parametrize(
+        "channel",
+        ["", "conda-forge", ["conda-forge", "bioconda"]],
+        ids=["default", "conda-forge", "bioconda"],
+    )
+    def test_conda_install(self, auto_offline, offline, conda, channel):
         runner = nox.sessions.SessionRunner(
             name="test",
             signatures=["test"],
@@ -405,8 +410,14 @@ class TestSession:
         session = SessionNoSlots(runner=runner)
 
         with mock.patch.object(session, "_run", autospec=True) as run:
-            args = ("--offline",) if auto_offline and offline else ()
-            session.conda_install("requests", "urllib3", auto_offline=auto_offline)
+            args = ["--offline"] if auto_offline and offline else []
+            if channel and isinstance(channel, str):
+                args.append(f"--channel={channel}")
+            else:
+                args += [f"--channel={c}" for c in channel]
+            session.conda_install(
+                "requests", "urllib3", auto_offline=auto_offline, channel=channel
+            )
             run.assert_called_once_with(
                 conda,
                 "install",