diff --git a/nox/_options.py b/nox/_options.py index 096c6817..c7ebd4db 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -134,7 +134,7 @@ def _session_completer( ) -> List[str]: global_config = parsed_args module = load_nox_module(global_config) - manifest = discover_manifest(module, global_config) # type: ignore + manifest = discover_manifest(module, global_config) filtered_manifest = filter_manifest(manifest, global_config) if isinstance(filtered_manifest, int): return [] diff --git a/nox/manifest.py b/nox/manifest.py index 0e8994b0..16ce01ad 100644 --- a/nox/manifest.py +++ b/nox/manifest.py @@ -13,6 +13,7 @@ # limitations under the License. import argparse +import collections.abc import itertools from typing import Any, Iterable, Iterator, List, Mapping, Set, Tuple, Union @@ -243,7 +244,7 @@ def notify(self, session: Union[str, SessionRunner]) -> bool: raise ValueError("Session {} not found.".format(session)) -class KeywordLocals: +class KeywordLocals(collections.abc.Mapping): """Eval locals using keywords. When looking up a local variable the variable name is compared against @@ -261,11 +262,17 @@ def __getitem__(self, variable_name: str) -> bool: return True return False + def __iter__(self) -> Iterator[str]: + return iter(self._keywords) + + def __len__(self) -> int: + return len(self._keywords) + def keyword_match(expression: str, keywords: Iterable[str]) -> Any: """See if an expression matches the given set of keywords.""" locals = KeywordLocals(set(keywords)) - return eval(expression, {}, locals) # type: ignore + return eval(expression, {}, locals) def _null_session_func_(session: Session) -> None: diff --git a/nox/sessions.py b/nox/sessions.py index add777b6..59a5cf4b 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -109,7 +109,7 @@ def __dict__(self) -> "Dict[str, SessionRunner]": # type: ignore @property def env(self) -> dict: """A dictionary of environment variables to pass into all commands.""" - return self._runner.venv.env # type: ignore + return self.virtualenv.env @property def posargs(self) -> List[str]: @@ -118,9 +118,12 @@ def posargs(self) -> List[str]: return self._runner.global_config.posargs @property - def virtualenv(self) -> Optional[ProcessEnv]: + def virtualenv(self) -> ProcessEnv: """The virtualenv that all commands are run in.""" - return self._runner.venv + venv = self._runner.venv + if venv is None: + raise ValueError("A virtualenv has not been created for this session") + return venv @property def python(self) -> Optional[Union[str, Sequence[str], bool]]: @@ -128,9 +131,9 @@ def python(self) -> Optional[Union[str, Sequence[str], bool]]: return self._runner.func.python @property - def bin(self) -> str: + def bin(self) -> Optional[str]: """The bin directory for the virtualenv.""" - return self._runner.venv.bin # type: ignore + return self.virtualenv.bin @property def interactive(self) -> bool: @@ -233,10 +236,10 @@ def _run(self, *args: str, env: Mapping[str, str] = None, **kwargs: Any) -> Any: kwargs.setdefault("external", "error") # Allow all external programs when running outside a sandbox. - if not self.virtualenv.is_sandboxed: # type: ignore + if not self.virtualenv.is_sandboxed: kwargs["external"] = True - if args[0] in self.virtualenv.allowed_globals: # type: ignore + if args[0] in self.virtualenv.allowed_globals: kwargs["external"] = True # Run a shell command. @@ -268,7 +271,8 @@ def conda_install(self, *args: str, **kwargs: Any) -> None: .. _conda install: """ - if not isinstance(self.virtualenv, CondaEnv): + venv = self._runner.venv + if not isinstance(venv, CondaEnv): raise ValueError( "A session without a conda environment can not install dependencies from conda." ) @@ -283,7 +287,7 @@ def conda_install(self, *args: str, **kwargs: Any) -> None: "install", "--yes", "--prefix", - self.virtualenv.location, + venv.location, *args, external="error", **kwargs @@ -314,7 +318,7 @@ def install(self, *args: str, **kwargs: Any) -> None: .. _pip: https://pip.readthedocs.org """ - if not isinstance(self.virtualenv, (CondaEnv, VirtualEnv)): + if not isinstance(self._runner.venv, (CondaEnv, VirtualEnv)): raise ValueError( "A session without a virtualenv can not install dependencies." ) @@ -337,7 +341,7 @@ def notify(self, target: "Union[str, SessionRunner]") -> None: may be specified as the appropriate string (same as used for ``nox -s``) or using the function object. """ - self._runner.manifest.notify(target) # type: ignore + self._runner.manifest.notify(target) def log(self, *args: Any, **kwargs: Any) -> None: """Outputs a log during the session.""" @@ -359,7 +363,7 @@ def __init__( signatures: List[str], func: Func, global_config: argparse.Namespace, - manifest: "Optional[Manifest]" = None, + manifest: "Manifest", ) -> None: self.name = name self.signatures = signatures diff --git a/nox/tasks.py b/nox/tasks.py index 7ff23623..41f1fceb 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -79,7 +79,9 @@ def merge_noxfile_options( return module -def discover_manifest(module: types.ModuleType, global_config: Namespace) -> Manifest: +def discover_manifest( + module: Union[types.ModuleType, int], global_config: Namespace +) -> Manifest: """Discover all session functions in the noxfile module. Args: diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 62b16642..cd29a5ed 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -18,7 +18,7 @@ import nox import pytest from nox._decorators import Func -from nox.manifest import Manifest, _null_session_func +from nox.manifest import KeywordLocals, Manifest, _null_session_func def create_mock_sessions(): @@ -301,3 +301,14 @@ def test_null_session_function(): session = mock.Mock(spec=("skip",)) _null_session_func(session) assert session.skip.called + + +def test_keyword_locals_length(): + kw = KeywordLocals({"foo", "bar"}) + assert len(kw) == 2 + + +def test_keyword_locals_iter(): + values = ["foo", "bar"] + kw = KeywordLocals(values) + assert list(kw) == values diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 86a229bd..8af3f858 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -83,6 +83,14 @@ def test_properties(self): assert session.bin is runner.venv.bin assert session.python is runner.func.python + def test_virtualenv_as_none(self): + session, runner = self.make_session_and_runner() + + runner.venv = None + + with pytest.raises(ValueError, match="virtualenv"): + _ = session.virtualenv + def test_interactive(self): session, runner = self.make_session_and_runner()