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: more option argument completions #707

Merged
merged 10 commits into from
Feb 20, 2024
4 changes: 2 additions & 2 deletions nox/_option_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import functools
from argparse import ArgumentError as ArgumentError
from argparse import ArgumentParser, Namespace
from collections.abc import Callable, Sequence
from collections.abc import Callable, Iterable
from typing import Any

import argcomplete
Expand Down Expand Up @@ -92,7 +92,7 @@ def __init__(
| list[str]
| Callable[[], bool | str | None | list[str]] = None,
hidden: bool = False,
completer: Callable[..., Sequence[str]] | None = None,
completer: Callable[..., Iterable[str]] | None = None,
**kwargs: Any,
) -> None:
self.name = name
Expand Down
50 changes: 42 additions & 8 deletions nox/_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@

import argparse
import functools
import itertools
import os
import sys
from collections.abc import Iterable
from typing import Any, Callable, Sequence

import argcomplete

from nox import _option_set
from nox.tasks import discover_manifest, filter_manifest, load_nox_module

Expand Down Expand Up @@ -229,19 +233,42 @@ def _posargs_finalizer(
return posargs[dash_index + 1 :]


def _python_completer(
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
) -> Iterable[str]:
module = load_nox_module(parsed_args)
manifest = discover_manifest(module, parsed_args)
return filter(
None,
(
session.func.python # type:ignore[misc] # str sequences flattened, other non-strs falsey and filtered out
for session, _ in manifest.list_all_sessions()
),
)


def _session_completer(
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
) -> list[str]:
global_config = parsed_args
global_config.list_sessions = True
module = load_nox_module(global_config)
manifest = discover_manifest(module, global_config)
filtered_manifest = filter_manifest(manifest, global_config)
) -> Iterable[str]:
parsed_args.list_sessions = True
module = load_nox_module(parsed_args)
manifest = discover_manifest(module, parsed_args)
filtered_manifest = filter_manifest(manifest, parsed_args)
if isinstance(filtered_manifest, int):
return []
return [
return (
session.friendly_name for session, _ in filtered_manifest.list_all_sessions()
]
)


def _tag_completer(
prefix: str, parsed_args: argparse.Namespace, **kwargs: Any
) -> Iterable[str]:
module = load_nox_module(parsed_args)
manifest = discover_manifest(module, parsed_args)
return itertools.chain.from_iterable(
scop marked this conversation as resolved.
Show resolved Hide resolved
filter(None, (session.tags for session, _ in manifest.list_all_sessions()))
)


options.add_options(
Expand Down Expand Up @@ -300,6 +327,7 @@ def _session_completer(
nargs="*",
default=default_env_var_list_factory("NOXPYTHON"),
help="Only run sessions that use the given python interpreter versions.",
completer=_python_completer,
),
_option_set.Option(
"keywords",
Expand All @@ -309,6 +337,7 @@ def _session_completer(
noxfile=True,
merge_func=functools.partial(_sessions_merge_func, "keywords"),
help="Only run sessions that match the given expression.",
completer=argcomplete.completers.ChoicesCompleter(()),
),
_option_set.Option(
"tags",
Expand All @@ -319,6 +348,7 @@ def _session_completer(
merge_func=functools.partial(_sessions_merge_func, "tags"),
nargs="*",
help="Only run sessions with the given tags.",
completer=_tag_completer,
),
_option_set.Option(
"posargs",
Expand Down Expand Up @@ -423,6 +453,7 @@ def _session_completer(
merge_func=_envdir_merge_func,
group=options.groups["environment"],
help="Directory where Nox will store virtualenvs, this is ``.nox`` by default.",
completer=argcomplete.completers.DirectoriesCompleter(),
),
_option_set.Option(
"extra_pythons",
Expand All @@ -432,6 +463,7 @@ def _session_completer(
nargs="*",
default=default_env_var_list_factory("NOXEXTRAPYTHON"),
help="Additionally, run sessions using the given python interpreter versions.",
completer=_python_completer,
),
_option_set.Option(
"force_pythons",
Expand All @@ -445,6 +477,7 @@ def _session_completer(
" Noxfile. This is a shorthand for ``--python=X.Y --extra-python=X.Y``."
),
finalizer_func=_force_pythons_finalizer,
completer=_python_completer,
),
*_option_set.make_flag_pair(
"stop_on_first_error",
Expand Down Expand Up @@ -496,6 +529,7 @@ def _session_completer(
group=options.groups["reporting"],
noxfile=True,
help="Output a report of all sessions to the given filename.",
completer=argcomplete.completers.FilesCompleter(("json",)),
),
_option_set.Option(
"non_interactive",
Expand Down
10 changes: 10 additions & 0 deletions tests/resources/noxfile_pythons.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@
@nox.parametrize("cheese", ["cheddar", "jack", "brie"])
def snack(unused_session, cheese):
print(f"Noms, {cheese} so good!")


@nox.session(python=False)
def nopy(unused_session):
print("No pythons here.")


@nox.session(python="3.12")
def strpy(unused_session):
print("Python-in-a-str here.")
18 changes: 18 additions & 0 deletions tests/resources/noxfile_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

import nox


@nox.session # no tags
def no_tags(unused_session):
print("Look ma, no tags!")


@nox.session(tags=["tag1"])
def one_tag(unused_session):
print("Lonesome tag here.")


@nox.session(tags=["tag1", "tag2", "tag3"])
def moar_tags(unused_session):
print("Some more tags here.")
26 changes: 25 additions & 1 deletion tests/test__option_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_session_completer(self):
)

expected_sessions = ["testytest", "lintylint", "typeytype"]
assert expected_sessions == actual_sessions_from_file
assert expected_sessions == list(actual_sessions_from_file)

def test_session_completer_invalid_sessions(self):
parsed_args = _options.options.namespace(
Expand All @@ -105,3 +105,27 @@ def test_session_completer_invalid_sessions(self):
prefix=None, parsed_args=parsed_args
)
assert len(all_nox_sessions) == 0

def test_python_completer(self):
parsed_args = _options.options.namespace(
posargs=[],
noxfile=str(RESOURCES.joinpath("noxfile_pythons.py")),
)
actual_pythons_from_file = _options._python_completer(
prefix=None, parsed_args=parsed_args
)

expected_pythons = {"3.6", "3.12"}
assert expected_pythons == set(actual_pythons_from_file)

def test_tag_completer(self):
parsed_args = _options.options.namespace(
posargs=[],
noxfile=str(RESOURCES.joinpath("noxfile_tags.py")),
)
actual_tags_from_file = _options._tag_completer(
prefix=None, parsed_args=parsed_args
)

expected_tags = {"tag1", "tag2", "tag3"}
assert expected_tags == set(actual_tags_from_file)