From 11c7c8258e264c7cdef8ae5068e1317ec23c3436 Mon Sep 17 00:00:00 2001 From: Diego Ramirez Date: Thu, 23 Dec 2021 10:38:00 -0600 Subject: [PATCH 1/7] Drop Python 3.6 from GHA --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73f83023..c3b6ae0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04, windows-2019] - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} From fe42be1ce00fb47f7d77b17055b2350163c6738f Mon Sep 17 00:00:00 2001 From: Diego Ramirez Date: Thu, 23 Dec 2021 11:03:58 -0600 Subject: [PATCH 2/7] Drop Python 3.6 from our noxfile --- noxfile.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/noxfile.py b/noxfile.py index 38781efd..55d9f113 100644 --- a/noxfile.py +++ b/noxfile.py @@ -29,24 +29,13 @@ nox.options.sessions.append("conda_tests") -def is_python_version(session, version): - if not version.startswith(session.python): - return False - py_version = session.run("python", "-V", silent=True) - py_version = py_version.partition(" ")[2].strip() - return py_version.startswith(version) - - -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10"]) def tests(session): """Run test suite with pytest.""" session.create_tmp() session.install("-r", "requirements-test.txt") session.install("-e", ".[tox_to_nox]") tests = session.posargs or ["tests/"] - if is_python_version(session, "3.6.0"): - session.run("pytest", *tests) - return session.run( "pytest", "--cov=nox", @@ -59,7 +48,7 @@ def tests(session): session.notify("cover") -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10"], venv_backend="conda") +@nox.session(python=["3.7", "3.8", "3.9", "3.10"], venv_backend="conda") def conda_tests(session): """Run test suite with pytest.""" session.create_tmp() From e2d6beee9b425b5a6612b0bfbb940989e306fbab Mon Sep 17 00:00:00 2001 From: Diego Ramirez Date: Thu, 23 Dec 2021 11:05:56 -0600 Subject: [PATCH 3/7] Remove Python 3.6 from our packaging classifiers --- setup.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 76cbc883..f0561600 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,7 +21,6 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -44,7 +43,7 @@ install_requires = typing_extensions>=3.7.4;python_version < '3.8' virtualenv>=14.0.0 importlib_metadata;python_version < '3.8' -python_requires = >=3.6 +python_requires = >=3.7 include_package_data = True zip_safe = False From 7b13bf93878b6008df69676b7794f5fa06a6ea34 Mon Sep 17 00:00:00 2001 From: Diego Ramirez Date: Wed, 12 Jan 2022 12:49:23 -0600 Subject: [PATCH 4/7] Inject isort and pyupgrade stuff --- .pre-commit-config.yaml | 13 ++-- docs/CHANGELOG.md | 2 +- docs/CONTRIBUTING.md | 2 +- docs/conf.py | 2 + nox/__init__.py | 4 +- nox/__main__.py | 2 + nox/_decorators.py | 20 ++--- nox/_option_set.py | 31 ++++---- nox/_options.py | 12 +-- nox/_parametrize.py | 30 +++---- nox/_typing.py | 2 + nox/_version.py | 11 +-- nox/command.py | 20 ++--- nox/logger.py | 2 + nox/manifest.py | 47 +++++------ nox/popen.py | 14 ++-- nox/registry.py | 20 ++--- nox/sessions.py | 82 +++++++++----------- nox/tasks.py | 23 +++--- nox/tox_to_nox.py | 2 + nox/virtualenv.py | 26 ++++--- nox/workflow.py | 6 +- noxfile.py | 2 + tests/resources/noxfile.py | 2 + tests/resources/noxfile_multiple_sessions.py | 2 + tests/resources/noxfile_nested.py | 2 + tests/resources/noxfile_normalization.py | 2 + tests/resources/noxfile_options.py | 2 + tests/resources/noxfile_options_pythons.py | 2 + tests/resources/noxfile_pythons.py | 2 + tests/resources/noxfile_spaces.py | 2 + tests/test__option_set.py | 2 + tests/test__parametrize.py | 2 + tests/test__version.py | 5 +- tests/test_command.py | 5 +- tests/test_logger.py | 2 + tests/test_main.py | 2 + tests/test_manifest.py | 2 + tests/test_registry.py | 2 + tests/test_sessions.py | 2 + tests/test_tasks.py | 2 + tests/test_tox_to_nox.py | 2 + tests/test_virtualenv.py | 5 +- tests/test_workflow.py | 2 + 44 files changed, 237 insertions(+), 189 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3448a9f..f02ec972 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,16 +22,17 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace -- repo: https://github.com/asottile/pyupgrade - rev: v2.29.1 - hooks: - - id: pyupgrade - args: [--py36-plus] - - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: - id: isort + args: ["-a", "from __future__ import annotations"] + +- repo: https://github.com/asottile/pyupgrade + rev: v2.29.1 + hooks: + - id: pyupgrade + args: [--py37-plus] - repo: https://github.com/asottile/setup-cfg-fmt rev: v1.20.0 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 04c99a55..60b7a6ed 120000 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1 +1 @@ -../CHANGELOG.md \ No newline at end of file +../CHANGELOG.md diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 44fcc634..5c10c35f 120000 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1 +1 @@ -../CONTRIBUTING.md \ No newline at end of file +../CONTRIBUTING.md diff --git a/docs/conf.py b/docs/conf.py index afbe274c..d88c9f37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,6 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +from __future__ import annotations + import os import sys diff --git a/nox/__init__.py b/nox/__init__.py index 78c5283d..1afe51a7 100644 --- a/nox/__init__.py +++ b/nox/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional +from __future__ import annotations from nox._options import noxfile_options as options from nox._parametrize import Param as param @@ -20,6 +20,6 @@ from nox.registry import session_decorator as session from nox.sessions import Session -needs_version: Optional[str] = None +needs_version: str | None = None __all__ = ["needs_version", "parametrize", "param", "session", "options", "Session"] diff --git a/nox/__main__.py b/nox/__main__.py index 34786a69..86735094 100644 --- a/nox/__main__.py +++ b/nox/__main__.py @@ -19,6 +19,8 @@ control to :meth:``nox.workflow.execute``. """ +from __future__ import annotations + import sys from nox import _options, tasks, workflow diff --git a/nox/_decorators.py b/nox/_decorators.py index a2e5b6ad..9720ba49 100644 --- a/nox/_decorators.py +++ b/nox/_decorators.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import copy import functools import inspect import types -from typing import Any, Callable, Dict, Iterable, List, Optional +from typing import Any, Callable, Iterable from . import _typing @@ -27,12 +29,12 @@ class FunctionDecorator: def __new__( cls, func: Callable[..., Any], *args: Any, **kwargs: Any - ) -> "FunctionDecorator": + ) -> FunctionDecorator: obj = super().__new__(cls) return functools.wraps(func)(obj) -def _copy_func(src: Callable, name: Optional[str] = None) -> Callable: +def _copy_func(src: Callable, name: str | None = None) -> Callable: dst = types.FunctionType( src.__code__, src.__globals__, # type: ignore[attr-defined] @@ -51,11 +53,11 @@ def __init__( self, func: Callable, python: _typing.Python = None, - reuse_venv: Optional[bool] = None, - name: Optional[str] = None, + reuse_venv: bool | None = None, + name: str | None = None, venv_backend: Any = None, venv_params: Any = None, - should_warn: Optional[Dict[str, Any]] = None, + should_warn: dict[str, Any] | None = None, ): self.func = func self.python = python @@ -67,7 +69,7 @@ def __init__( def __call__(self, *args: Any, **kwargs: Any) -> Any: return self.func(*args, **kwargs) - def copy(self, name: Optional[str] = None) -> "Func": + def copy(self, name: str | None = None) -> Func: return Func( _copy_func(self.func, name), self.python, @@ -80,7 +82,7 @@ def copy(self, name: Optional[str] = None) -> "Func": class Call(Func): - def __init__(self, func: Func, param_spec: "Param") -> None: + def __init__(self, func: Func, param_spec: Param) -> None: call_spec = param_spec.call_spec session_signature = f"({param_spec})" @@ -113,5 +115,5 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: return super().__call__(*args, **kwargs) @classmethod - def generate_calls(cls, func: Func, param_specs: "Iterable[Param]") -> "List[Call]": + def generate_calls(cls, func: Func, param_specs: Iterable[Param]) -> list[Call]: return [cls(func, param_spec) for param_spec in param_specs] diff --git a/nox/_option_set.py b/nox/_option_set.py index 237020d7..6c2ba62a 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -16,13 +16,14 @@ can be specified from the command line and the noxfile, easily used in tests, and surfaced in documentation.""" +from __future__ import annotations import argparse import collections import functools from argparse import ArgumentError as ArgumentError from argparse import ArgumentParser, Namespace -from typing import Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable import argcomplete @@ -77,14 +78,14 @@ def __init__( self, name: str, *flags: str, - group: Optional[OptionGroup], - help: Optional[str] = None, + group: OptionGroup | None, + help: str | None = None, noxfile: bool = False, - merge_func: Optional[Callable[[Namespace, Namespace], Any]] = None, - finalizer_func: Optional[Callable[[Any, Namespace], Any]] = None, - default: Union[Any, Callable[[], Any]] = None, + merge_func: Callable[[Namespace, Namespace], Any] | None = None, + finalizer_func: Callable[[Any, Namespace], Any] | None = None, + default: Any | Callable[[], Any] = None, hidden: bool = False, - completer: Optional[Callable[..., List[str]]] = None, + completer: Callable[..., list[str]] | None = None, **kwargs: Any, ) -> None: self.name = name @@ -100,7 +101,7 @@ def __init__( self._default = default @property - def default(self) -> Optional[Union[bool, str]]: + def default(self) -> bool | str | None: if callable(self._default): return self._default() return self._default @@ -154,10 +155,10 @@ def flag_pair_merge_func( def make_flag_pair( name: str, - enable_flags: Union[Tuple[str, str], Tuple[str]], - disable_flags: Tuple[str], + enable_flags: tuple[str, str] | tuple[str], + disable_flags: tuple[str], **kwargs: Any, -) -> Tuple[Option, Option]: +) -> tuple[Option, Option]: """Returns two options - one to enable a behavior and another to disable it. The positive option is considered to be available to the noxfile, as @@ -191,10 +192,10 @@ class OptionSet: def __init__(self, *args: Any, **kwargs: Any) -> None: self.parser_args = args self.parser_kwargs = kwargs - self.options: "collections.OrderedDict[str, Option]" = collections.OrderedDict() - self.groups: "collections.OrderedDict[str, OptionGroup]" = ( - collections.OrderedDict() - ) + self.options: collections.OrderedDict[str, Option] = collections.OrderedDict() + self.groups: collections.OrderedDict[ + str, OptionGroup + ] = collections.OrderedDict() def add_options(self, *args: Option) -> None: """Adds a sequence of Options to the OptionSet. diff --git a/nox/_options.py b/nox/_options.py index 9481ed37..d20f44f1 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse import functools import os import sys -from typing import Any, List, Optional, Sequence, Union +from typing import Any, Sequence from nox import _option_set from nox.tasks import discover_manifest, filter_manifest, load_nox_module @@ -63,7 +65,7 @@ def _sessions_and_keywords_merge_func( key: str, command_args: argparse.Namespace, noxfile_args: argparse.Namespace -) -> List[str]: +) -> list[str]: """Only return the Noxfile value for sessions/keywords if neither sessions or keywords are specified on the command-line. @@ -137,7 +139,7 @@ def _envdir_merge_func( return command_args.envdir or noxfile_args.envdir or ".nox" -def _sessions_default() -> Optional[List[str]]: +def _sessions_default() -> list[str] | None: """Looks at the NOXSESSION env var to set the default value for sessions.""" nox_env = os.environ.get("NOXSESSION") env_sessions = nox_env.split(",") if nox_env else None @@ -186,7 +188,7 @@ def _R_finalizer(value: bool, args: argparse.Namespace) -> bool: def _posargs_finalizer( value: Sequence[Any], args: argparse.Namespace -) -> Union[Sequence[Any], List[Any]]: +) -> Sequence[Any] | list[Any]: """Removes the leading "--"s in the posargs array (if any) and asserts that remaining arguments came after a "--". """ @@ -212,7 +214,7 @@ def _posargs_finalizer( def _session_completer( prefix: str, parsed_args: argparse.Namespace, **kwargs: Any -) -> List[str]: +) -> list[str]: global_config = parsed_args module = load_nox_module(global_config) manifest = discover_manifest(module, global_config) diff --git a/nox/_parametrize.py b/nox/_parametrize.py index 8bcac353..5849abb0 100644 --- a/nox/_parametrize.py +++ b/nox/_parametrize.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import functools import itertools -from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union +from typing import Any, Callable, Iterable, Sequence, Union class Param: @@ -31,8 +33,8 @@ class Param: def __init__( self, *args: Any, - arg_names: Optional[Sequence[str]] = None, - id: Optional[str] = None, + arg_names: Sequence[str] | None = None, + id: str | None = None, ) -> None: self.args = tuple(args) self.id = id @@ -43,7 +45,7 @@ def __init__( self.arg_names = tuple(arg_names) @property - def call_spec(self) -> Dict[str, Any]: + def call_spec(self) -> dict[str, Any]: return dict(zip(self.arg_names, self.args)) def __str__(self) -> str: @@ -56,11 +58,11 @@ def __str__(self) -> str: __repr__ = __str__ - def copy(self) -> "Param": + def copy(self) -> Param: new = self.__class__(*self.args, arg_names=self.arg_names, id=self.id) return new - def update(self, other: "Param") -> None: + def update(self, other: Param) -> None: self.id = ", ".join([str(self), str(other)]) self.args = self.args + other.args self.arg_names = self.arg_names + other.arg_names @@ -78,7 +80,7 @@ def __eq__(self, other: object) -> bool: raise NotImplementedError -def _apply_param_specs(param_specs: List[Param], f: Any) -> Any: +def _apply_param_specs(param_specs: list[Param], f: Any) -> Any: previous_param_specs = getattr(f, "parametrize", None) new_param_specs = update_param_specs(previous_param_specs, param_specs) f.parametrize = new_param_specs @@ -89,9 +91,9 @@ def _apply_param_specs(param_specs: List[Param], f: Any) -> Any: def parametrize_decorator( - arg_names: Union[str, List[str], Tuple[str]], - arg_values_list: Union[Iterable[ArgValue], ArgValue], - ids: Optional[Iterable[Optional[str]]] = None, + arg_names: str | list[str] | tuple[str], + arg_values_list: Iterable[ArgValue] | ArgValue, + ids: Iterable[str | None] | None = None, ) -> Callable[[Any], Any]: """Parametrize a session. @@ -119,7 +121,7 @@ def parametrize_decorator( # If there's only one arg_name, arg_values_list should be a single item # or list. Transform it so it'll work with the combine step. - _arg_values_list: List[Union[Param, Iterable[Union[Any, ArgValue]]]] = [] + _arg_values_list: list[Param | Iterable[Any | ArgValue]] = [] if len(arg_names) == 1: # In this case, the arg_values_list can also just be a single item. # Must be mutable for the transformation steps @@ -141,7 +143,7 @@ def parametrize_decorator( ids = [] # Generate params for each item in the param_args_values list. - param_specs: List[Param] = [] + param_specs: list[Param] = [] for param_arg_values, param_id in itertools.zip_longest(_arg_values_list, ids): if isinstance(param_arg_values, Param): param_spec = param_arg_values @@ -155,8 +157,8 @@ def parametrize_decorator( def update_param_specs( - param_specs: Optional[Iterable[Param]], new_specs: List[Param] -) -> List[Param]: + param_specs: Iterable[Param] | None, new_specs: list[Param] +) -> list[Param]: """Produces all combinations of the given sets of specs.""" if not param_specs: return new_specs diff --git a/nox/_typing.py b/nox/_typing.py index f746afe7..2c6a30a8 100644 --- a/nox/_typing.py +++ b/nox/_typing.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + __all__ = ["TYPE_CHECKING", "ClassVar", "NoReturn", "Python"] import typing as _typing diff --git a/nox/_version.py b/nox/_version.py index feeb5c73..f5b9f25f 100644 --- a/nox/_version.py +++ b/nox/_version.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import ast import contextlib import sys -from typing import Optional from packaging.specifiers import InvalidSpecifier, SpecifierSet from packaging.version import InvalidVersion, Version @@ -39,7 +40,7 @@ def get_nox_version() -> str: return metadata.version("nox") # type: ignore[no-untyped-call] -def _parse_string_constant(node: ast.AST) -> Optional[str]: # pragma: no cover +def _parse_string_constant(node: ast.AST) -> str | None: # pragma: no cover """Return the value of a string constant.""" if sys.version_info < (3, 8): if isinstance(node, ast.Str) and isinstance(node.s, str): @@ -49,9 +50,9 @@ def _parse_string_constant(node: ast.AST) -> Optional[str]: # pragma: no cover return None -def _parse_needs_version(source: str, filename: str = "") -> Optional[str]: +def _parse_needs_version(source: str, filename: str = "") -> str | None: """Parse ``nox.needs_version`` from the user's noxfile.""" - value: Optional[str] = None + value: str | None = None module: ast.Module = ast.parse(source, filename=filename) for statement in module.body: if isinstance(statement, ast.Assign): @@ -66,7 +67,7 @@ def _parse_needs_version(source: str, filename: str = "") -> Optional[s return value -def _read_needs_version(filename: str) -> Optional[str]: +def _read_needs_version(filename: str) -> str | None: """Read ``nox.needs_version`` from the user's noxfile.""" with open(filename) as io: source = io.read() diff --git a/nox/command.py b/nox/command.py index 7e3bfcda..8162db21 100644 --- a/nox/command.py +++ b/nox/command.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import os import shlex import sys -from typing import Any, Iterable, List, Optional, Sequence, Union +from typing import Any, Iterable, Sequence import py @@ -31,12 +33,12 @@ class CommandFailed(Exception): """Raised when an executed command returns a non-success status code.""" - def __init__(self, reason: Optional[str] = None) -> None: + def __init__(self, reason: str | None = None) -> None: super().__init__(reason) self.reason = reason -def which(program: str, paths: Optional[List[str]]) -> str: +def which(program: str, paths: list[str] | None) -> str: """Finds the full path to an executable.""" full_path = None @@ -55,7 +57,7 @@ def which(program: str, paths: Optional[List[str]]) -> str: raise CommandFailed(f"Program {program} not found") -def _clean_env(env: Optional[dict]) -> Optional[dict]: +def _clean_env(env: dict | None) -> dict | None: if env is None: return None @@ -76,14 +78,14 @@ def _shlex_join(args: Sequence[str]) -> str: def run( args: Sequence[str], *, - env: Optional[dict] = None, + env: dict | None = None, silent: bool = False, - paths: Optional[List[str]] = None, - success_codes: Optional[Iterable[int]] = None, + paths: list[str] | None = None, + success_codes: Iterable[int] | None = None, log: bool = True, - external: Union[Literal["error"], bool] = False, + external: Literal["error"] | bool = False, **popen_kws: Any, -) -> Union[str, bool]: +) -> str | bool: """Run a command-line program.""" if success_codes is None: diff --git a/nox/logger.py b/nox/logger.py index 7077d8b2..f53adf65 100644 --- a/nox/logger.py +++ b/nox/logger.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import logging from typing import Any, cast diff --git a/nox/manifest.py b/nox/manifest.py index ccbd08c2..9ff557eb 100644 --- a/nox/manifest.py +++ b/nox/manifest.py @@ -12,23 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse import ast import collections.abc import itertools from collections import OrderedDict -from typing import ( - Any, - Iterable, - Iterator, - List, - Mapping, - Optional, - Sequence, - Set, - Tuple, - Union, -) +from typing import Any, Iterable, Iterator, Mapping, Sequence from nox._decorators import Call, Func from nox.sessions import Session, SessionRunner @@ -36,7 +27,7 @@ WARN_PYTHONS_IGNORED = "python_ignored" -def _unique_list(*args: str) -> List[str]: +def _unique_list(*args: str) -> list[str]: """Return a list without duplicates, while preserving order.""" return list(OrderedDict.fromkeys(args)) @@ -61,22 +52,22 @@ class Manifest: def __init__( self, - session_functions: Mapping[str, "Func"], + session_functions: Mapping[str, Func], global_config: argparse.Namespace, - module_docstring: Optional[str] = None, + module_docstring: str | None = None, ) -> None: - self._all_sessions: List[SessionRunner] = [] - self._queue: List[SessionRunner] = [] - self._consumed: List[SessionRunner] = [] + self._all_sessions: list[SessionRunner] = [] + self._queue: list[SessionRunner] = [] + self._consumed: list[SessionRunner] = [] self._config: argparse.Namespace = global_config - self.module_docstring: Optional[str] = module_docstring + self.module_docstring: str | None = module_docstring # Create the sessions based on the provided session functions. for name, func in session_functions.items(): for session in self.make_session(name, func): self.add_session(session) - def __contains__(self, needle: Union[str, SessionRunner]) -> bool: + def __contains__(self, needle: str | SessionRunner) -> bool: if needle in self._queue or needle in self._consumed: return True for session in self._queue + self._consumed: @@ -84,7 +75,7 @@ def __contains__(self, needle: Union[str, SessionRunner]) -> bool: return True return False - def __iter__(self) -> "Manifest": + def __iter__(self) -> Manifest: return self def __getitem__(self, key: str) -> SessionRunner: @@ -108,7 +99,7 @@ def __next__(self) -> SessionRunner: def __len__(self) -> int: return len(self._queue) + len(self._consumed) - def list_all_sessions(self) -> Iterator[Tuple[SessionRunner, bool]]: + def list_all_sessions(self) -> Iterator[tuple[SessionRunner, bool]]: """Yields all sessions and whether or not they're selected.""" for session in self._all_sessions: yield session, session in self._queue @@ -186,8 +177,8 @@ def filter_by_keywords(self, keywords: str) -> None: ] def make_session( - self, name: str, func: "Func", multi: bool = False - ) -> List[SessionRunner]: + self, name: str, func: Func, multi: bool = False + ) -> list[SessionRunner]: """Create a session object from the session function. Args: @@ -217,7 +208,7 @@ def make_session( if self._config.extra_pythons: # If extra python is provided, expand the func.python list to # include additional python interpreters - extra_pythons: List[str] = self._config.extra_pythons + extra_pythons: list[str] = self._config.extra_pythons if isinstance(func.python, (list, tuple, set)): func.python = _unique_list(*func.python, *extra_pythons) elif not multi and func.python: @@ -280,7 +271,7 @@ def next(self) -> SessionRunner: return self.__next__() def notify( - self, session: Union[str, SessionRunner], posargs: Optional[List[str]] = None + self, session: str | SessionRunner, posargs: list[str] | None = None ) -> bool: """Enqueue the specified session in the queue. @@ -328,7 +319,7 @@ class KeywordLocals(collections.abc.Mapping): returns False. """ - def __init__(self, keywords: Set[str]) -> None: + def __init__(self, keywords: set[str]) -> None: self._keywords = keywords def __getitem__(self, variable_name: str) -> bool: @@ -367,7 +358,7 @@ def _normalized_session_match(session_name: str, session: SessionRunner) -> bool return False -def _normalize_arg(arg: str) -> Union[str]: +def _normalize_arg(arg: str) -> str: """Normalize arg for comparison.""" try: return str(ast.dump(ast.parse(arg))) diff --git a/nox/popen.py b/nox/popen.py index 010dd321..5ab088b9 100644 --- a/nox/popen.py +++ b/nox/popen.py @@ -12,14 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import contextlib import locale import subprocess import sys -from typing import IO, Mapping, Optional, Sequence, Tuple, Union +from typing import IO, Mapping, Sequence -def shutdown_process(proc: subprocess.Popen) -> Tuple[bytes, bytes]: +def shutdown_process(proc: subprocess.Popen) -> tuple[bytes, bytes]: """Gracefully shutdown a child process.""" with contextlib.suppress(subprocess.TimeoutExpired): @@ -54,11 +56,11 @@ def decode_output(output: bytes) -> str: def popen( args: Sequence[str], - env: Optional[Mapping[str, str]] = None, + env: Mapping[str, str] | None = None, silent: bool = False, - stdout: Optional[Union[int, IO]] = None, - stderr: Union[int, IO] = subprocess.STDOUT, -) -> Tuple[int, str]: + stdout: int | IO | None = None, + stderr: int | IO = subprocess.STDOUT, +) -> tuple[int, str]: if silent and stdout is not None: raise ValueError( "Can not specify silent and stdout; passing a custom stdout always silences the commands output in Nox's log." diff --git a/nox/registry.py b/nox/registry.py index eefcf384..3c54c8b2 100644 --- a/nox/registry.py +++ b/nox/registry.py @@ -12,17 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import collections import copy import functools -from typing import Any, Callable, Optional, TypeVar, Union, overload +from typing import Any, Callable, TypeVar, overload from ._decorators import Func from ._typing import Python F = TypeVar("F", bound=Callable[..., Any]) -_REGISTRY: "collections.OrderedDict[str, Func]" = collections.OrderedDict() +_REGISTRY: collections.OrderedDict[str, Func] = collections.OrderedDict() @overload @@ -35,8 +37,8 @@ def session_decorator( __func: None = ..., python: Python = ..., py: Python = ..., - reuse_venv: Optional[bool] = ..., - name: Optional[str] = ..., + reuse_venv: bool | None = ..., + name: str | None = ..., venv_backend: Any = ..., venv_params: Any = ..., ) -> Callable[[F], F]: @@ -44,14 +46,14 @@ def session_decorator( def session_decorator( - func: Optional[F] = None, + func: F | None = None, python: Python = None, py: Python = None, - reuse_venv: Optional[bool] = None, - name: Optional[str] = None, + reuse_venv: bool | None = None, + name: str | None = None, venv_backend: Any = None, venv_params: Any = None, -) -> Union[F, Callable[[F], F]]: +) -> F | Callable[[F], F]: """Designate the decorated function as a session.""" # If `func` is provided, then this is the decorator call with the function # being sent as part of the Python syntax (`@nox.session`). @@ -85,7 +87,7 @@ def session_decorator( return fn -def get() -> "collections.OrderedDict[str, Func]": +def get() -> collections.OrderedDict[str, Func]: """Return a shallow copy of the registry. This ensures that the registry is not accidentally modified by diff --git a/nox/sessions.py b/nox/sessions.py index a1693966..0ca37eb4 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse import enum import hashlib @@ -22,19 +24,7 @@ import unicodedata import warnings from types import TracebackType -from typing import ( - Any, - Callable, - Dict, - Iterable, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) +from typing import Any, Callable, Iterable, Mapping, Sequence import py @@ -48,7 +38,7 @@ from nox.manifest import Manifest -def _normalize_path(envdir: str, path: Union[str, bytes]) -> str: +def _normalize_path(envdir: str, path: str | bytes) -> str: """Normalizes a string to be a "safe" filesystem path for a virtualenv.""" if isinstance(path, bytes): path = path.decode("utf-8") @@ -75,7 +65,7 @@ def _normalize_path(envdir: str, path: Union[str, bytes]) -> str: return full_path -def _dblquote_pkg_install_args(args: Tuple[str, ...]) -> Tuple[str, ...]: +def _dblquote_pkg_install_args(args: tuple[str, ...]) -> tuple[str, ...]: """Double-quote package install arguments in case they contain '>' or '<' symbols""" # routine used to handle a single arg @@ -119,18 +109,18 @@ class Status(enum.Enum): class _WorkingDirContext: - def __init__(self, dir: Union[str, os.PathLike]) -> None: + def __init__(self, dir: str | os.PathLike) -> None: self._prev_working_dir = os.getcwd() os.chdir(dir) - def __enter__(self) -> "_WorkingDirContext": + def __enter__(self) -> _WorkingDirContext: return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_value: Optional[BaseException], - traceback: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, ) -> None: os.chdir(self._prev_working_dir) @@ -144,11 +134,11 @@ class Session: __slots__ = ("_runner",) - def __init__(self, runner: "SessionRunner") -> None: + def __init__(self, runner: SessionRunner) -> None: self._runner = runner @property - def __dict__(self) -> "Dict[str, SessionRunner]": # type: ignore[override] + def __dict__(self) -> dict[str, SessionRunner]: # type: ignore[override] """Attribute dictionary for object inspection. This is needed because ``__slots__`` turns off ``__dict__`` by @@ -168,7 +158,7 @@ def env(self) -> dict: return self.virtualenv.env @property - def posargs(self) -> List[str]: + def posargs(self) -> list[str]: """Any extra arguments from the ``nox`` commandline or :class:`Session.notify`.""" return self._runner.posargs @@ -181,12 +171,12 @@ def virtualenv(self) -> ProcessEnv: return venv @property - def python(self) -> Optional[Union[str, Sequence[str], bool]]: + def python(self) -> str | Sequence[str] | bool | None: """The python version passed into ``@nox.session``.""" return self._runner.func.python @property - def bin_paths(self) -> Optional[List[str]]: + def bin_paths(self) -> list[str] | None: """The bin directories for the virtualenv.""" return self.virtualenv.bin_paths @@ -229,7 +219,7 @@ def invoked_from(self) -> str: """ return self._runner.global_config.invoked_from - def chdir(self, dir: Union[str, os.PathLike]) -> _WorkingDirContext: + def chdir(self, dir: str | os.PathLike) -> _WorkingDirContext: """Change the current working directory. Can be used as a context manager to automatically restore the working directory:: @@ -260,8 +250,8 @@ def _run_func( raise nox.command.CommandFailed() def run( - self, *args: str, env: Optional[Mapping[str, str]] = None, **kwargs: Any - ) -> Optional[Any]: + self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any + ) -> Any | None: """Run a command. Commands must be specified as a list of strings, for example:: @@ -328,8 +318,8 @@ def run( return self._run(*args, env=env, **kwargs) def run_always( - self, *args: str, env: Optional[Mapping[str, str]] = None, **kwargs: Any - ) -> Optional[Any]: + self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any + ) -> Any | None: """Run a command **always**. This is a variant of :meth:`run` that runs even in the presence of @@ -371,7 +361,7 @@ def run_always( return self._run(*args, env=env, **kwargs) def _run( - self, *args: str, env: Optional[Mapping[str, str]] = None, **kwargs: Any + self, *args: str, env: Mapping[str, str] | None = None, **kwargs: Any ) -> Any: """Like run(), except that it runs even if --install-only is provided.""" # Legacy support - run a function given. @@ -404,7 +394,7 @@ def conda_install( self, *args: str, auto_offline: bool = True, - channel: Union[str, Sequence[str]] = "", + channel: str | Sequence[str] = "", **kwargs: Any, ) -> None: """Install invokes `conda install`_ to install packages inside of the @@ -442,7 +432,7 @@ def conda_install( """ venv = self._runner.venv - prefix_args: Tuple[str, ...] = () + prefix_args: tuple[str, ...] = () if isinstance(venv, CondaEnv): prefix_args = ("--prefix", venv.location) elif not isinstance(venv, PassthroughEnv): # pragma: no cover @@ -462,7 +452,7 @@ def conda_install( if "silent" not in kwargs: kwargs["silent"] = True - extraopts: List[str] = [] + extraopts: list[str] = [] if auto_offline and venv.is_offline(): logger.warning( "Automatically setting the `--offline` flag as conda repo seems unreachable." @@ -541,8 +531,8 @@ def install(self, *args: str, **kwargs: Any) -> None: def notify( self, - target: "Union[str, SessionRunner]", - posargs: Optional[Iterable[str]] = None, + target: str | SessionRunner, + posargs: Iterable[str] | None = None, ) -> None: """Place the given session at the end of the queue. @@ -588,11 +578,11 @@ def debug(self, *args: Any, **kwargs: Any) -> None: """Outputs a debug-level message during the session.""" logger.debug(*args, **kwargs) - def error(self, *args: Any) -> "_typing.NoReturn": + def error(self, *args: Any) -> _typing.NoReturn: """Immediately aborts the session and optionally logs an error.""" raise _SessionQuit(*args) - def skip(self, *args: Any) -> "_typing.NoReturn": + def skip(self, *args: Any) -> _typing.NoReturn: """Immediately skips the session and optionally logs a warning.""" raise _SessionSkip(*args) @@ -601,21 +591,21 @@ class SessionRunner: def __init__( self, name: str, - signatures: List[str], + signatures: list[str], func: Func, global_config: argparse.Namespace, - manifest: "Manifest", + manifest: Manifest, ) -> None: self.name = name self.signatures = signatures self.func = func self.global_config = global_config self.manifest = manifest - self.venv: Optional[ProcessEnv] = None - self.posargs: List[str] = global_config.posargs[:] + self.venv: ProcessEnv | None = None + self.posargs: list[str] = global_config.posargs[:] @property - def description(self) -> Optional[str]: + def description(self) -> str | None: doc = self.func.__doc__ if doc: first_line = doc.strip().split("\n")[0] @@ -679,7 +669,7 @@ def _create_venv(self) -> None: self.venv.create() - def execute(self) -> "Result": + def execute(self) -> Result: logger.warning(f"Running session {self.friendly_name}") try: @@ -725,7 +715,7 @@ class Result: """An object representing the result of a session.""" def __init__( - self, session: SessionRunner, status: Status, reason: Optional[str] = None + self, session: SessionRunner, status: Status, reason: str | None = None ) -> None: """Initialize the Result object. @@ -775,7 +765,7 @@ def log(self, message: str) -> None: log_function = logger.error log_function(message) - def serialize(self) -> Dict[str, Any]: + def serialize(self) -> dict[str, Any]: """Return a serialized representation of this result. Returns: diff --git a/nox/tasks.py b/nox/tasks.py index a33f3138..93b42a34 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import ast import importlib.util import json @@ -19,7 +21,6 @@ import sys import types from argparse import Namespace -from typing import List, Union from colorlog.escape_codes import parse_colors @@ -66,7 +67,7 @@ def _load_and_exec_nox_module(global_config: Namespace) -> types.ModuleType: return module -def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]: +def load_nox_module(global_config: Namespace) -> types.ModuleType | int: """Load the user's noxfile and return the module object for it. .. note:: @@ -130,7 +131,7 @@ def merge_noxfile_options( def discover_manifest( - module: Union[types.ModuleType, int], global_config: Namespace + module: types.ModuleType | int, global_config: Namespace ) -> Manifest: """Discover all session functions in the noxfile module. @@ -153,9 +154,7 @@ def discover_manifest( return Manifest(functions, global_config, module_docstring) -def filter_manifest( - manifest: Manifest, global_config: Namespace -) -> Union[Manifest, int]: +def filter_manifest(manifest: Manifest, global_config: Namespace) -> Manifest | int: """Filter the manifest according to the provided configuration. Args: @@ -259,9 +258,7 @@ def _produce_listing(manifest: Manifest, global_config: Namespace) -> None: ) -def honor_list_request( - manifest: Manifest, global_config: Namespace -) -> Union[Manifest, int]: +def honor_list_request(manifest: Manifest, global_config: Namespace) -> Manifest | int: """If --list was passed, simply list the manifest and exit cleanly. Args: @@ -280,7 +277,7 @@ def honor_list_request( return 0 -def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]: +def run_manifest(manifest: Manifest, global_config: Namespace) -> list[Result]: """Run the full manifest of sessions. Args: @@ -320,7 +317,7 @@ def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]: return results -def print_summary(results: List[Result], global_config: Namespace) -> List[Result]: +def print_summary(results: list[Result], global_config: Namespace) -> list[Result]: """Print a summary of the results. Args: @@ -347,7 +344,7 @@ def print_summary(results: List[Result], global_config: Namespace) -> List[Resul return results -def create_report(results: List[Result], global_config: Namespace) -> List[Result]: +def create_report(results: list[Result], global_config: Namespace) -> list[Result]: """Write a report to the location designated in the config, if any. Args: @@ -377,7 +374,7 @@ def create_report(results: List[Result], global_config: Namespace) -> List[Resul return results -def final_reduce(results: List[Result], global_config: Namespace) -> int: +def final_reduce(results: list[Result], global_config: Namespace) -> int: """Reduce the results to a final exit code. Args: diff --git a/nox/tox_to_nox.py b/nox/tox_to_nox.py index 0d6c6bb6..cd5df3ae 100644 --- a/nox/tox_to_nox.py +++ b/nox/tox_to_nox.py @@ -14,6 +14,8 @@ """Naively converts tox.ini files into noxfile.py files.""" +from __future__ import annotations + import argparse import pkgutil from typing import Any, Iterator diff --git a/nox/virtualenv.py b/nox/virtualenv.py index 19b265dd..d40c236a 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import os import platform import re import shutil import sys from socket import gethostbyname -from typing import Any, List, Mapping, Optional, Tuple, Union +from typing import Any, Mapping import py @@ -52,10 +54,10 @@ class ProcessEnv: is_sandboxed = False # Special programs that aren't included in the environment. - allowed_globals: _typing.ClassVar[Tuple[Any, ...]] = () + allowed_globals: _typing.ClassVar[tuple[Any, ...]] = () def __init__( - self, bin_paths: None = None, env: Optional[Mapping[str, str]] = None + self, bin_paths: None = None, env: Mapping[str, str] | None = None ) -> None: self._bin_paths = bin_paths self.env = os.environ.copy() @@ -73,7 +75,7 @@ def __init__( ) @property - def bin_paths(self) -> Optional[List[str]]: + def bin_paths(self) -> list[str] | None: return self._bin_paths @property @@ -88,7 +90,7 @@ def create(self) -> bool: raise NotImplementedError("ProcessEnv.create should be overwritten in subclass") -def locate_via_py(version: str) -> Optional[str]: +def locate_via_py(version: str) -> str | None: """Find the Python executable using the Windows Launcher. This is based on :pep:397 which details that executing @@ -116,7 +118,7 @@ def locate_via_py(version: str) -> Optional[str]: return None -def locate_using_path_and_version(version: str) -> Optional[str]: +def locate_using_path_and_version(version: str) -> str | None: """Check the PATH's python interpreter and return it if the version matches. @@ -191,7 +193,7 @@ class CondaEnv(ProcessEnv): def __init__( self, location: str, - interpreter: Optional[str] = None, + interpreter: str | None = None, reuse_existing: bool = False, venv_params: Any = None, *, @@ -229,7 +231,7 @@ def _clean_location(self) -> bool: return True @property - def bin_paths(self) -> List[str]: + def bin_paths(self) -> list[str]: """Returns the location of the conda env's bin folder.""" # see https://github.com/conda/conda/blob/f60f0f1643af04ed9a51da3dd4fa242de81e32f4/conda/activate.py#L563-L572 if _SYSTEM == "Windows": @@ -314,7 +316,7 @@ class VirtualEnv(ProcessEnv): def __init__( self, location: str, - interpreter: Optional[str] = None, + interpreter: str | None = None, reuse_existing: bool = False, *, venv: bool = False, @@ -323,7 +325,7 @@ def __init__( self.location_name = location self.location = os.path.abspath(location) self.interpreter = interpreter - self._resolved: Union[None, str, InterpreterNotFound] = None + self._resolved: None | str | InterpreterNotFound = None self.reuse_existing = reuse_existing self.venv_or_virtualenv = "venv" if venv else "virtualenv" self.venv_params = venv_params if venv_params else [] @@ -379,7 +381,7 @@ def _check_reused_environment_interpreter(self) -> bool: return original == created - def _read_base_prefix_from_pyvenv_cfg(self) -> Optional[str]: + def _read_base_prefix_from_pyvenv_cfg(self) -> str | None: """Return the base-prefix entry from pyvenv.cfg, if present.""" path = os.path.join(self.location, "pyvenv.cfg") if os.path.isfile(path): @@ -455,7 +457,7 @@ def _resolved_interpreter(self) -> str: raise self._resolved @property - def bin_paths(self) -> List[str]: + def bin_paths(self) -> list[str]: """Returns the location of the virtualenv's bin folder.""" if _SYSTEM == "Windows": return [os.path.join(self.location, "Scripts")] diff --git a/nox/workflow.py b/nox/workflow.py index bb2d7fcd..ed577644 100644 --- a/nox/workflow.py +++ b/nox/workflow.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse -from typing import Any, Callable, Iterable, List +from typing import Any, Callable, Iterable def execute( @@ -43,7 +45,7 @@ def execute( return_value = None for function_ in workflow: # Send the previous task's return value if there was one. - args: List[Any] = [] + args: list[Any] = [] if return_value is not None: args.append(return_value) return_value = function_(*args, global_config=global_config) diff --git a/noxfile.py b/noxfile.py index c73fad74..511b06c4 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,6 +13,8 @@ # limitations under the License. +from __future__ import annotations + import functools import os import platform diff --git a/tests/resources/noxfile.py b/tests/resources/noxfile.py index af183c1a..0bec9605 100644 --- a/tests/resources/noxfile.py +++ b/tests/resources/noxfile.py @@ -12,4 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + SIGIL = "123" diff --git a/tests/resources/noxfile_multiple_sessions.py b/tests/resources/noxfile_multiple_sessions.py index 47a6da75..33a5fc54 100644 --- a/tests/resources/noxfile_multiple_sessions.py +++ b/tests/resources/noxfile_multiple_sessions.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import nox # Deliberately giving these silly names so we know this is not confused diff --git a/tests/resources/noxfile_nested.py b/tests/resources/noxfile_nested.py index 7a254acd..7e601052 100644 --- a/tests/resources/noxfile_nested.py +++ b/tests/resources/noxfile_nested.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import nox diff --git a/tests/resources/noxfile_normalization.py b/tests/resources/noxfile_normalization.py index 36a24880..29ea5c1b 100644 --- a/tests/resources/noxfile_normalization.py +++ b/tests/resources/noxfile_normalization.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import nox diff --git a/tests/resources/noxfile_options.py b/tests/resources/noxfile_options.py index fb32d420..96df3914 100644 --- a/tests/resources/noxfile_options.py +++ b/tests/resources/noxfile_options.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import nox nox.options.reuse_existing_virtualenvs = True diff --git a/tests/resources/noxfile_options_pythons.py b/tests/resources/noxfile_options_pythons.py index 000d4683..80903df1 100644 --- a/tests/resources/noxfile_options_pythons.py +++ b/tests/resources/noxfile_options_pythons.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import nox nox.options.sessions = ["{default_session}"] diff --git a/tests/resources/noxfile_pythons.py b/tests/resources/noxfile_pythons.py index a9a03d30..63cb8d95 100644 --- a/tests/resources/noxfile_pythons.py +++ b/tests/resources/noxfile_pythons.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import nox diff --git a/tests/resources/noxfile_spaces.py b/tests/resources/noxfile_spaces.py index f11ca9a4..0704136e 100644 --- a/tests/resources/noxfile_spaces.py +++ b/tests/resources/noxfile_spaces.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import nox diff --git a/tests/test__option_set.py b/tests/test__option_set.py index 99d2d95a..cee49e24 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from pathlib import Path import pytest diff --git a/tests/test__parametrize.py b/tests/test__parametrize.py index d7bd2c3d..3af4b22a 100644 --- a/tests/test__parametrize.py +++ b/tests/test__parametrize.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from unittest import mock import pytest diff --git a/tests/test__version.py b/tests/test__version.py index fe3cd245..a2c7520a 100644 --- a/tests/test__version.py +++ b/tests/test__version.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from pathlib import Path from textwrap import dedent -from typing import Optional import pytest @@ -93,7 +94,7 @@ def test_get_nox_version() -> None: ), ], ) -def test_parse_needs_version(text: str, expected: Optional[str]) -> None: +def test_parse_needs_version(text: str, expected: str | None) -> None: """It is parsed successfully.""" assert expected == _parse_needs_version(text) diff --git a/tests/test_command.py b/tests/test_command.py index 0b6e4d43..e6f6b0cc 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import ctypes import logging import os @@ -297,8 +299,7 @@ def run_pytest_in_new_console_session(test): [sys.executable, "-m", "pytest", f"{__file__}::{test}"], env=env, check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, creationflags=creationflags, ) diff --git a/tests/test_logger.py b/tests/test_logger.py index eef9dcf3..b914bbee 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import logging from unittest import mock diff --git a/tests/test_main.py b/tests/test_main.py index 9cf65b38..7284ac38 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import contextlib import os import sys diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 8da925db..ff84e8be 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import collections from unittest import mock diff --git a/tests/test_registry.py b/tests/test_registry.py index 3c36f9a1..aef09527 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import pytest from nox import registry diff --git a/tests/test_sessions.py b/tests/test_sessions.py index a90aa687..dad453b0 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse import logging import operator diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 50f7f86b..543c49aa 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import argparse import builtins import copy diff --git a/tests/test_tox_to_nox.py b/tests/test_tox_to_nox.py index 57ec2bf7..6deb5020 100644 --- a/tests/test_tox_to_nox.py +++ b/tests/test_tox_to_nox.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import sys import textwrap diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py index 485e2128..ce5dd232 100644 --- a/tests/test_virtualenv.py +++ b/tests/test_virtualenv.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import os import re import shutil @@ -257,8 +259,7 @@ def test_condaenv_detection(make_conda): [py.path.local.sysfind("conda").strpath, "list"], env=venv.env, check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, ) output = proc_result.stdout.decode() path_regex = re.compile(r"packages in environment at (?P.+):") diff --git a/tests/test_workflow.py b/tests/test_workflow.py index 2e8eb661..48899902 100644 --- a/tests/test_workflow.py +++ b/tests/test_workflow.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + from unittest import mock from nox import workflow From f035cbe5fb7df8b941cd2aed32c55f3e2bc143ad Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 12 Jan 2022 13:58:55 -0500 Subject: [PATCH 5/7] fix: symlinks were broken --- docs/CHANGELOG.md | 2 +- docs/CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 60b7a6ed..04c99a55 120000 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1 +1 @@ -../CHANGELOG.md +../CHANGELOG.md \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 5c10c35f..44fcc634 120000 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1 +1 @@ -../CONTRIBUTING.md +../CONTRIBUTING.md \ No newline at end of file From f847de4d5e1c004c789a05f7e7317e4f64b73d39 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 12 Jan 2022 14:10:50 -0500 Subject: [PATCH 6/7] fix: mypy set to 3.7 too --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cdf68931..fc00b077 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ exclude_lines = [ [tool.mypy] files = ["nox"] -python_version = "3.6" +python_version = "3.7" warn_unused_configs = true disallow_any_generics = false disallow_subclassing_any = false From 48e7f4bbe75cd1c2cf52bc7cda6a439a87b32c17 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 12 Jan 2022 14:15:44 -0500 Subject: [PATCH 7/7] chore: drop one usage of 3.6 only --- tests/test_command.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_command.py b/tests/test_command.py index e6f6b0cc..a05ab820 100644 --- a/tests/test_command.py +++ b/tests/test_command.py @@ -289,11 +289,7 @@ def format_program(program, marker): def run_pytest_in_new_console_session(test): """Run the given test in a separate console session.""" env = dict(os.environ, SECONDARY_CONSOLE_SESSION="") - creationflags = ( - subprocess.CREATE_NO_WINDOW - if sys.version_info[:2] >= (3, 7) - else subprocess.CREATE_NEW_CONSOLE - ) + creationflags = subprocess.CREATE_NO_WINDOW subprocess.run( [sys.executable, "-m", "pytest", f"{__file__}::{test}"],