Skip to content

Commit

Permalink
Add ability to specify which Python versions to run with from the com…
Browse files Browse the repository at this point in the history
…mand line (#304)

* Add .idea to gitignore

* Add --pythons commandline argument

* Add new filter to the manifest

* Add tests

* Update docs
  • Loading branch information
maciej-lech committed Mar 28, 2020
1 parent f001918 commit a6442a6
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -73,3 +73,6 @@ target/

# pyenv
.python-version

# PyCharm
.idea/
7 changes: 4 additions & 3 deletions docs/config.rst
Expand Up @@ -274,7 +274,7 @@ If you only want to run one of the parametrized sessions, see :ref:`running_para
Giving friendly names to parametrized sessions
----------------------------------------------

The automatically generated names for parametrized sessions, such as ``tests(django='1.9', database='postgres')``, can be long and unwieldy to work with even with using :ref:`keyword filtering <opt-sessions-and-keywords>`. You can give parametrized sessions custom IDs to help in this scenario. These two examples are equivalent:
The automatically generated names for parametrized sessions, such as ``tests(django='1.9', database='postgres')``, can be long and unwieldy to work with even with using :ref:`keyword filtering <opt-sessions-pythons-and-keywords>`. You can give parametrized sessions custom IDs to help in this scenario. These two examples are equivalent:

.. code-block:: python
Expand Down Expand Up @@ -372,8 +372,9 @@ Or, if you wanted to provide a set of sessions that are run by default:
The following options can be specified in the Noxfile:

* ``nox.options.envdir`` is equivalent to specifying :ref:`--envdir <opt-envdir>`.
* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions <opt-sessions-and-keywords>`.
* ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords <opt-sessions-and-keywords>`.
* ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions <opt-sessions-pythons-and-keywords>`.
* ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons <opt-sessions-pythons-and-keywords>`.
* ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords <opt-sessions-pythons-and-keywords>`.
* ``nox.options.reuse_existing_virtualenvs`` is equivalent to specifying :ref:`--reuse-existing-virtualenvs <opt-reuse-existing-virtualenvs>`. You can force this off by specifying ``--no-reuse-existing-virtualenvs`` during invocation.
* ``nox.options.stop_on_first_error`` is equivalent to specifying :ref:`--stop-on-first-error <opt-stop-on-first-error>`. You can force this off by specifying ``--no-stop-on-first-error`` during invocation.
* ``nox.options.error_on_missing_interpreters`` is equivalent to specifying :ref:`--error-on-missing-interpreters <opt-error-on-missing-interpreters>`. You can force this off by specifying ``--no-error-on-missing-interpreters`` during invocation.
Expand Down
9 changes: 8 additions & 1 deletion docs/usage.rst
Expand Up @@ -45,7 +45,7 @@ You can run every session by just executing ``nox`` without any arguments:
The order that sessions are executed is the order that they appear in the Noxfile.


.. _opt-sessions-and-keywords:
.. _opt-sessions-pythons-and-keywords:

Specifying one or more sessions
-------------------------------
Expand All @@ -67,6 +67,13 @@ You can also use the ``NOXSESSION`` environment variable:
Nox will run these sessions in the same order they are specified.

If you have a :ref:`configured session's virtualenv <virtualenv config>`, you can choose to run only sessions with given Python versions:

.. code-block:: console
nox --python 3.8
nox -p 3.7 3.8
You can also use `pytest-style keywords`_ to filter test sessions:

.. code-block:: console
Expand Down
27 changes: 19 additions & 8 deletions nox/_options.py
Expand Up @@ -39,21 +39,21 @@
)


def _sessions_and_keywords_merge_func(
def _session_filters_merge_func(
key: str, command_args: argparse.Namespace, noxfile_args: argparse.Namespace
) -> List[str]:
"""Only return the Noxfile value for sessions/keywords if neither sessions
or keywords are specified on the command-line.
"""Only return the Noxfile value for sessions/pythons/keywords if neither sessions,
pythons or keywords are specified on the command-line.
Args:
key (str): This function is used for both the "sessions" and "keywords"
key (str): This function is used for the "sessions", "pythons" and "keywords"
options, this allows using ``funtools.partial`` to pass the
same function for both options.
command_args (_option_set.Namespace): The options specified on the
command-line.
noxfile_Args (_option_set.Namespace): The options specified in the
noxfile_args (_option_set.Namespace): The options specified in the
Noxfile."""
if not command_args.sessions and not command_args.keywords:
if not any((command_args.sessions, command_args.pythons, command_args.keywords)):
return getattr(noxfile_args, key)
return getattr(command_args, key)

Expand Down Expand Up @@ -176,18 +176,29 @@ def _session_completer(
"--session",
group="primary",
noxfile=True,
merge_func=functools.partial(_sessions_and_keywords_merge_func, "sessions"),
merge_func=functools.partial(_session_filters_merge_func, "sessions"),
nargs="*",
default=_sessions_default,
help="Which sessions to run. By default, all sessions will run.",
completer=_session_completer,
),
_option_set.Option(
"pythons",
"-p",
"--pythons",
"--python",
group="primary",
noxfile=True,
merge_func=functools.partial(_session_filters_merge_func, "pythons"),
nargs="*",
help="Only run sessions that use the given python interpreter versions.",
),
_option_set.Option(
"keywords",
"-k",
"--keywords",
noxfile=True,
merge_func=functools.partial(_sessions_and_keywords_merge_func, "keywords"),
merge_func=functools.partial(_session_filters_merge_func, "keywords"),
help="Only run sessions that match the given expression.",
),
_option_set.Option(
Expand Down
12 changes: 11 additions & 1 deletion nox/manifest.py
Expand Up @@ -15,7 +15,7 @@
import argparse
import collections.abc
import itertools
from typing import Any, Iterable, Iterator, List, Mapping, Set, Tuple, Union
from typing import Any, Iterable, Iterator, List, Mapping, Sequence, Set, Tuple, Union

from nox._decorators import Call, Func
from nox.sessions import Session, SessionRunner
Expand Down Expand Up @@ -132,6 +132,16 @@ def filter_by_name(self, specified_sessions: Iterable[str]) -> None:
if missing_sessions:
raise KeyError("Sessions not found: {}".format(", ".join(missing_sessions)))

def filter_by_python_interpreter(self, specified_pythons: Sequence[str]) -> None:
"""Filter sessions in the queue based on the user-specified
python interpreter versions.
Args:
specified_pythons (Sequence[str]): A list of specified
python interpreter versions.
"""
self._queue = [x for x in self._queue if x.func.python in specified_pythons]

def filter_by_keywords(self, keywords: str) -> None:
"""Filter sessions using pytest-like keyword expressions.
Expand Down
6 changes: 6 additions & 0 deletions nox/tasks.py
Expand Up @@ -125,6 +125,12 @@ def filter_manifest(
logger.error(exc.args[0])
return 3

# Filter by python interpreter versions.
# This function never errors, but may cause an empty list of sessions
# (which is an error condition later).
if global_config.pythons:
manifest.filter_by_python_interpreter(global_config.pythons)

# Filter by keywords.
# This function never errors, but may cause an empty list of sessions
# (which is an error condition later).
Expand Down
10 changes: 10 additions & 0 deletions tests/test_manifest.py
Expand Up @@ -137,6 +137,16 @@ def test_filter_by_name_not_found():
manifest.filter_by_name(("baz",))


def test_filter_by_python_interpreter():
sessions = create_mock_sessions()
manifest = Manifest(sessions, mock.sentinel.CONFIG)
manifest["foo"].func.python = "3.8"
manifest["bar"].func.python = "3.7"
manifest.filter_by_python_interpreter(("3.8",))
assert "foo" in manifest
assert "bar" not in manifest


def test_filter_by_keyword():
sessions = create_mock_sessions()
manifest = Manifest(sessions, mock.sentinel.CONFIG)
Expand Down
24 changes: 21 additions & 3 deletions tests/test_tasks.py
Expand Up @@ -35,6 +35,13 @@ def session_func():
session_func.python = None


def session_func_with_python():
pass


session_func_with_python.python = "3.8"


def test_load_nox_module():
config = _options.options.namespace(noxfile=os.path.join(RESOURCES, "noxfile.py"))
noxfile_module = tasks.load_nox_module(config)
Expand Down Expand Up @@ -91,22 +98,33 @@ def notasession():


def test_filter_manifest():
config = _options.options.namespace(sessions=(), keywords=())
config = _options.options.namespace(sessions=(), pythons=(), keywords=())
manifest = Manifest({"foo": session_func, "bar": session_func}, config)
return_value = tasks.filter_manifest(manifest, config)
assert return_value is manifest
assert len(manifest) == 2


def test_filter_manifest_not_found():
config = _options.options.namespace(sessions=("baz",), keywords=())
config = _options.options.namespace(sessions=("baz",), pythons=(), keywords=())
manifest = Manifest({"foo": session_func, "bar": session_func}, config)
return_value = tasks.filter_manifest(manifest, config)
assert return_value == 3


def test_filter_manifest_pythons():
config = _options.options.namespace(sessions=(), pythons=("3.8",), keywords=())
manifest = Manifest(
{"foo": session_func_with_python, "bar": session_func, "baz": session_func},
config,
)
return_value = tasks.filter_manifest(manifest, config)
assert return_value is manifest
assert len(manifest) == 1


def test_filter_manifest_keywords():
config = _options.options.namespace(sessions=(), keywords="foo or bar")
config = _options.options.namespace(sessions=(), pythons=(), keywords="foo or bar")
manifest = Manifest(
{"foo": session_func, "bar": session_func, "baz": session_func}, config
)
Expand Down

0 comments on commit a6442a6

Please sign in to comment.