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

Allow nox.parametrize to select the session Python #413

Merged
merged 3 commits into from
Apr 24, 2021
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
38 changes: 38 additions & 0 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,44 @@ Produces these sessions when running ``nox --list``:
* tests(mysql, new)


Parametrizing the session Python
--------------------------------

You can use parametrization to select the Python interpreter for a session.
These two examples are equivalent:

.. code-block:: python

@nox.session
@nox.parametrize("python", ["3.6", "3.7", "3.8"])
def tests(session):
...

@nox.session(python=["3.6", "3.7", "3.8"])
def tests(session):
...

The first form can be useful if you need to exclude some combinations of Python
versions with other parameters. For example, you may want to test against
multiple versions of a dependency, but the latest version doesn't run on older
Pythons:

.. code-block:: python

@nox.session
@nox.parametrize(
"python,dependency",
[
(python, dependency)
for python in ("3.6", "3.7", "3.8")
for dependency in ("1.0", "2.0")
if (python, dependency) != ("3.6", "2.0")
],
)
def tests(session, dependency):
...


The session object
------------------

Expand Down
24 changes: 20 additions & 4 deletions nox/_decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import functools
import inspect
import types
from typing import Any, Callable, Dict, Iterable, List, Optional, cast

Expand Down Expand Up @@ -66,20 +67,35 @@ def copy(self, name: str = None) -> "Func":

class Call(Func):
def __init__(self, func: Func, param_spec: "Param") -> None:
call_spec = param_spec.call_spec
session_signature = "({})".format(param_spec)

# Determine the Python interpreter for the session using either @session
# or @parametrize. For backwards compatibility, we only use a "python"
# parameter in @parametrize if the session function does not expect it
# as a normal argument, and if the @session decorator does not already
# specify `python`.

python = func.python
if python is None and "python" in call_spec:
signature = inspect.signature(func.func)
if "python" not in signature.parameters:
python = call_spec.pop("python")

super().__init__(
func,
func.python,
python,
func.reuse_venv,
None,
func.venv_backend,
func.venv_params,
func.should_warn,
)
self.param_spec = param_spec
self.session_signature = "({})".format(param_spec)
self.call_spec = call_spec
self.session_signature = session_signature

def __call__(self, *args: Any, **kwargs: Any) -> Any:
kwargs.update(self.param_spec.call_spec)
kwargs.update(self.call_spec)
return super().__call__(*args, **kwargs)

@classmethod
Expand Down
68 changes: 67 additions & 1 deletion tests/test__parametrize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from unittest import mock

import pytest
from nox import _decorators, _parametrize
from nox import _decorators, _parametrize, parametrize, session


@pytest.mark.parametrize(
Expand Down Expand Up @@ -242,3 +242,69 @@ def test_generate_calls_ids():
f.assert_called_with(foo=1)
calls[1]()
f.assert_called_with(foo=2)


def test_generate_calls_session_python():
called_with = []

@session
@parametrize("python,dependency", [("3.8", "0.9"), ("3.9", "0.9"), ("3.9", "1.0")])
def f(session, dependency):
called_with.append((session, dependency))

calls = _decorators.Call.generate_calls(f, f.parametrize)

assert len(calls) == 3

assert calls[0].python == "3.8"
assert calls[1].python == "3.9"
assert calls[2].python == "3.9"

assert calls[0].session_signature == "(python='3.8', dependency='0.9')"
assert calls[1].session_signature == "(python='3.9', dependency='0.9')"
assert calls[2].session_signature == "(python='3.9', dependency='1.0')"

session_ = ()

calls[0](session_)
calls[1](session_)
calls[2](session_)

assert len(called_with) == 3

assert called_with[0] == (session_, "0.9")
assert called_with[1] == (session_, "0.9")
assert called_with[2] == (session_, "1.0")


def test_generate_calls_python_compatibility():
called_with = []

@session
@parametrize("python,dependency", [("3.8", "0.9"), ("3.9", "0.9"), ("3.9", "1.0")])
def f(session, python, dependency):
called_with.append((session, python, dependency))

calls = _decorators.Call.generate_calls(f, f.parametrize)

assert len(calls) == 3

assert calls[0].python is None
assert calls[1].python is None
assert calls[2].python is None

assert calls[0].session_signature == "(python='3.8', dependency='0.9')"
assert calls[1].session_signature == "(python='3.9', dependency='0.9')"
assert calls[2].session_signature == "(python='3.9', dependency='1.0')"

session_ = ()

calls[0](session_)
calls[1](session_)
calls[2](session_)

assert len(called_with) == 3

assert called_with[0] == (session_, "3.8", "0.9")
assert called_with[1] == (session_, "3.9", "0.9")
assert called_with[2] == (session_, "3.9", "1.0")