diff --git a/.gitignore b/.gitignore index 3cac2474a59..6eec2daddc5 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ pip-wheel-metadata/ # pytest debug logs generated via --debug pytestdebug.log +/.dmypy.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce1cce6e089..c93aec87ae3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: rev: v3.12.0 hooks: - id: reorder-python-imports - args: ['--application-directories=.:src', --py38-plus] + args: ['--application-directories=.:src', --py38-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: diff --git a/bench/bench.py b/bench/bench.py index c40fc8636c0..84160cfc3b7 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys if __name__ == "__main__": diff --git a/bench/bench_argcomplete.py b/bench/bench_argcomplete.py index 335733df72b..3e70e771419 100644 --- a/bench/bench_argcomplete.py +++ b/bench/bench_argcomplete.py @@ -2,6 +2,8 @@ # 2.7.5 3.3.2 # FilesCompleter 75.1109 69.2116 # FastFilesCompleter 0.7383 1.0760 +from __future__ import annotations + import timeit imports = [ diff --git a/bench/empty.py b/bench/empty.py index 4e7371b6f80..b7af6478c39 100644 --- a/bench/empty.py +++ b/bench/empty.py @@ -1,2 +1,4 @@ +from __future__ import annotations + for i in range(1000): exec("def test_func_%d(): pass" % i) diff --git a/bench/manyparam.py b/bench/manyparam.py index 1226c73bd9c..579f7b2488d 100644 --- a/bench/manyparam.py +++ b/bench/manyparam.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/bench/skip.py b/bench/skip.py index f0c9d1ddbef..5593f7cec0d 100644 --- a/bench/skip.py +++ b/bench/skip.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest SKIP = True diff --git a/bench/unit_test.py b/bench/unit_test.py index ad52069dbfd..958f571ed02 100644 --- a/bench/unit_test.py +++ b/bench/unit_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase # noqa: F401 for i in range(15000): diff --git a/bench/xunit.py b/bench/xunit.py index 3a77dcdce42..54b9f8d2aee 100644 --- a/bench/xunit.py +++ b/bench/xunit.py @@ -1,3 +1,5 @@ +from __future__ import annotations + for i in range(5000): exec( f""" diff --git a/doc/en/conf.py b/doc/en/conf.py index 2bc18be5828..b4c3aca77a7 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,6 +15,8 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. +from __future__ import annotations + import os import shutil import sys @@ -438,7 +440,7 @@ } -def configure_logging(app: "sphinx.application.Sphinx") -> None: +def configure_logging(app: sphinx.application.Sphinx) -> None: """Configure Sphinx's WarningHandler to handle (expected) missing include.""" import sphinx.util.logging import logging @@ -461,7 +463,7 @@ def filter(self, record: logging.LogRecord) -> bool: warn_handler[0].filters.insert(0, WarnLogFilter()) -def setup(app: "sphinx.application.Sphinx") -> None: +def setup(app: sphinx.application.Sphinx) -> None: app.add_crossref_type( "fixture", "fixture", diff --git a/doc/en/conftest.py b/doc/en/conftest.py index 1a62e1b5df5..abe755f084d 100644 --- a/doc/en/conftest.py +++ b/doc/en/conftest.py @@ -1 +1,3 @@ +from __future__ import annotations + collect_ignore = ["conf.py"] diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index abb9bce5097..96c38b2509e 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from pytest import raises diff --git a/doc/en/example/assertion/global_testmodule_config/conftest.py b/doc/en/example/assertion/global_testmodule_config/conftest.py index 7cdf18cdbc1..76731e1a4b2 100644 --- a/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import pytest diff --git a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py index a31a601a1ce..824b12277b9 100644 --- a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py +++ b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py @@ -1,3 +1,5 @@ +from __future__ import annotations + hello = "world" diff --git a/doc/en/example/assertion/test_failures.py b/doc/en/example/assertion/test_failures.py index 350518b43c7..7102dab31b3 100644 --- a/doc/en/example/assertion/test_failures.py +++ b/doc/en/example/assertion/test_failures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import shutil diff --git a/doc/en/example/assertion/test_setup_flow_example.py b/doc/en/example/assertion/test_setup_flow_example.py index 0e7eded06b6..fe11c2bf3f2 100644 --- a/doc/en/example/assertion/test_setup_flow_example.py +++ b/doc/en/example/assertion/test_setup_flow_example.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def setup_module(module): module.TestStateFullThing.classcount = 0 diff --git a/doc/en/example/conftest.py b/doc/en/example/conftest.py index 66e70f14dd7..8920201f354 100644 --- a/doc/en/example/conftest.py +++ b/doc/en/example/conftest.py @@ -1 +1,3 @@ +from __future__ import annotations + collect_ignore = ["nonpython", "customdirectory"] diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py index 350893cab43..27eb12146f8 100644 --- a/doc/en/example/customdirectory/conftest.py +++ b/doc/en/example/customdirectory/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/doc/en/example/customdirectory/tests/test_first.py b/doc/en/example/customdirectory/tests/test_first.py index 0a78de59945..9953dd37785 100644 --- a/doc/en/example/customdirectory/tests/test_first.py +++ b/doc/en/example/customdirectory/tests/test_first.py @@ -1,3 +1,6 @@ # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/doc/en/example/customdirectory/tests/test_second.py b/doc/en/example/customdirectory/tests/test_second.py index eed724a7d96..df264f48b3b 100644 --- a/doc/en/example/customdirectory/tests/test_second.py +++ b/doc/en/example/customdirectory/tests/test_second.py @@ -1,3 +1,6 @@ # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/doc/en/example/customdirectory/tests/test_third.py b/doc/en/example/customdirectory/tests/test_third.py index 61cf59dc16c..b8b072dd770 100644 --- a/doc/en/example/customdirectory/tests/test_third.py +++ b/doc/en/example/customdirectory/tests/test_third.py @@ -1,3 +1,6 @@ # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py index ec282ab4b2b..04cbc268b7f 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py index de0c2642793..828fa4cf6d6 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py index ba01ad32f57..ebd5d10f5bb 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index e76e3f93c62..1c59f010341 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py index 5d9487cab34..4b4260fbdcd 100644 --- a/doc/en/example/fixtures/test_fixtures_order_scope.py +++ b/doc/en/example/fixtures/test_fixtures_order_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py index 00e2e46d845..dee61f8c4d7 100644 --- a/doc/en/example/fixtures/test_fixtures_request_different_scope.py +++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 8d76ed483e8..1b895045cbd 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -1,5 +1,7 @@ """Module containing a parametrized tests testing cross-python serialization via the pickle module.""" +from __future__ import annotations + import shutil import subprocess import textwrap diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index e969e3e2518..b7bdc77a004 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import pytest diff --git a/doc/en/example/pythoncollection.py b/doc/en/example/pythoncollection.py index 8742526a191..7595ee02ca4 100644 --- a/doc/en/example/pythoncollection.py +++ b/doc/en/example/pythoncollection.py @@ -1,5 +1,6 @@ # run this with $ pytest --collect-only test_collectonly.py # +from __future__ import annotations def test_function(): diff --git a/doc/en/example/xfail_demo.py b/doc/en/example/xfail_demo.py index 01e6da1ad2e..a064c4bcec4 100644 --- a/doc/en/example/xfail_demo.py +++ b/doc/en/example/xfail_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest xfail = pytest.mark.xfail diff --git a/extra/get_issues.py b/extra/get_issues.py index 4aaa3c3ec31..c32c1232376 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from pathlib import Path diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py index d22a5cf4c44..1e610b29fbf 100644 --- a/scripts/generate-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -8,6 +8,8 @@ Requires Python3.6+. """ +from __future__ import annotations + import re import sys from pathlib import Path diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index ce8242a74a3..5132b03c053 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -13,6 +13,8 @@ **Token**: currently the token from the GitHub Actions is used, pushed with `pytest bot ` commit author. """ +from __future__ import annotations + import argparse import re from pathlib import Path diff --git a/scripts/publish-gh-release-notes.py b/scripts/publish-gh-release-notes.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/release.py b/scripts/release.py index 66617feb5a9..37c1d3df466 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,5 +1,7 @@ # mypy: disallow-untyped-defs """Invoke development tasks.""" +from __future__ import annotations + import argparse import os from pathlib import Path diff --git a/scripts/towncrier-draft-to-file.py b/scripts/towncrier-draft-to-file.py index 7b2748aa840..c1bff54cf57 100644 --- a/scripts/towncrier-draft-to-file.py +++ b/scripts/towncrier-draft-to-file.py @@ -1,7 +1,10 @@ -# mypy: disallow-untyped-defs +from __future__ import annotations + import sys from subprocess import call +# mypy: disallow-untyped-defs + def main() -> int: """ diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index 0f811b778ed..b6a2462a8ba 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -1,4 +1,6 @@ # mypy: disallow-untyped-defs +from __future__ import annotations + import datetime import pathlib import re diff --git a/src/_pytest/__init__.py b/src/_pytest/__init__.py index 8a406c5c751..991f2782528 100644 --- a/src/_pytest/__init__.py +++ b/src/_pytest/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + __all__ = ["__version__", "version_tuple"] try: diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index 6a8083770ae..0bbc7bc68e5 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -61,13 +61,13 @@ which should throw a KeyError: 'COMPLINE' (which is properly set by the global argcomplete script). """ +from __future__ import annotations + import argparse import os import sys from glob import glob from typing import Any -from typing import List -from typing import Optional class FastFilesCompleter: @@ -76,7 +76,7 @@ class FastFilesCompleter: def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix: str, **kwargs: Any) -> List[str]: + def __call__(self, prefix: str, **kwargs: Any) -> list[str]: # Only called on non option completions. if os.sep in prefix[1:]: prefix_dir = len(os.path.dirname(prefix) + os.sep) @@ -103,7 +103,7 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]: import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter() + filescompleter: FastFilesCompleter | None = FastFilesCompleter() def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/src/_pytest/_code/__init__.py b/src/_pytest/_code/__init__.py index 511d0dde661..2de16f3590d 100644 --- a/src/_pytest/_code/__init__.py +++ b/src/_pytest/_code/__init__.py @@ -1,4 +1,6 @@ """Python inspection/code generation API.""" +from __future__ import annotations + from .code import Code from .code import ExceptionInfo from .code import filter_traceback diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 0288d7a54f5..2f865a7867e 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import ast +import builtins import dataclasses import inspect import os @@ -16,7 +19,6 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import Final from typing import final from typing import Generic @@ -24,16 +26,11 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence -from typing import Set from typing import SupportsIndex -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union import pluggy @@ -65,7 +62,7 @@ def __init__(self, obj: CodeType) -> None: self.raw = obj @classmethod - def from_function(cls, obj: object) -> "Code": + def from_function(cls, obj: object) -> Code: return cls(getrawcode(obj)) def __eq__(self, other): @@ -83,7 +80,7 @@ def name(self) -> str: return self.raw.co_name @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Return a path object pointing to source code, or an ``str`` in case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: @@ -100,17 +97,17 @@ def path(self) -> Union[Path, str]: return self.raw.co_filename @property - def fullsource(self) -> Optional["Source"]: + def fullsource(self) -> Source | None: """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) return full - def source(self) -> "Source": + def source(self) -> Source: """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code return Source(self.raw) - def getargs(self, var: bool = False) -> Tuple[str, ...]: + def getargs(self, var: bool = False) -> tuple[str, ...]: """Return a tuple with the argument names for the code object. If 'var' is set True also return the names of the variable and @@ -139,11 +136,11 @@ def lineno(self) -> int: return self.raw.f_lineno - 1 @property - def f_globals(self) -> Dict[str, Any]: + def f_globals(self) -> dict[str, Any]: return self.raw.f_globals @property - def f_locals(self) -> Dict[str, Any]: + def f_locals(self) -> dict[str, Any]: return self.raw.f_locals @property @@ -151,7 +148,7 @@ def code(self) -> Code: return Code(self.raw.f_code) @property - def statement(self) -> "Source": + def statement(self) -> Source: """Statement this frame is at.""" if self.code.fullsource is None: return Source("") @@ -195,14 +192,14 @@ class TracebackEntry: def __init__( self, rawentry: TracebackType, - repr_style: Optional['Literal["short", "long"]'] = None, + repr_style: Literal["short", "long"] | None = None, ) -> None: - self._rawentry: "Final" = rawentry - self._repr_style: "Final" = repr_style + self._rawentry: Final = rawentry + self._repr_style: Final = repr_style def with_repr_style( - self, repr_style: Optional['Literal["short", "long"]'] - ) -> "TracebackEntry": + self, repr_style: Literal["short", "long"] | None + ) -> TracebackEntry: return TracebackEntry(self._rawentry, repr_style) @property @@ -221,19 +218,19 @@ def __repr__(self) -> str: return "" % (self.frame.code.path, self.lineno + 1) @property - def statement(self) -> "Source": + def statement(self) -> Source: """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource assert source is not None return source.getstatement(self.lineno) @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Path to the source code.""" return self.frame.code.path @property - def locals(self) -> Dict[str, Any]: + def locals(self) -> dict[str, Any]: """Locals of underlying frame.""" return self.frame.f_locals @@ -241,8 +238,8 @@ def getfirstlinesource(self) -> int: return self.frame.code.firstlineno def getsource( - self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None - ) -> Optional["Source"]: + self, astcache: dict[str | Path, ast.AST] | None = None + ) -> Source | None: """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing @@ -268,7 +265,7 @@ def getsource( source = property(getsource) - def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -277,9 +274,7 @@ def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: Mostly for internal use. """ - tbh: Union[ - bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool] - ] = False + tbh: (bool | Callable[[ExceptionInfo[BaseException] | None], bool]) = False for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): # in normal cases, f_locals and f_globals are dictionaries # however via `exec(...)` / `eval(...)` they can be other types @@ -324,13 +319,13 @@ class Traceback(List[TracebackEntry]): def __init__( self, - tb: Union[TracebackType, Iterable[TracebackEntry]], + tb: TracebackType | Iterable[TracebackEntry], ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: Optional[TracebackType] = cur + cur_: TracebackType | None = cur while cur_ is not None: yield TracebackEntry(cur_) cur_ = cur_.tb_next @@ -341,11 +336,11 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]: def cut( self, - path: Optional[Union["os.PathLike[str]", str]] = None, - lineno: Optional[int] = None, - firstlineno: Optional[int] = None, - excludepath: Optional["os.PathLike[str]"] = None, - ) -> "Traceback": + path: os.PathLike[str] | str | None = None, + lineno: int | None = None, + firstlineno: int | None = None, + excludepath: os.PathLike[str] | None = None, + ) -> Traceback: """Return a Traceback instance wrapping part of this Traceback. By providing any combination of path, lineno and firstlineno, the @@ -376,16 +371,14 @@ def cut( return self @overload - def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... @overload - def __getitem__(self, key: slice) -> "Traceback": + def __getitem__(self, key: slice) -> Traceback: ... - def __getitem__( - self, key: Union["SupportsIndex", slice] - ) -> Union[TracebackEntry, "Traceback"]: + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: if isinstance(key, slice): return self.__class__(super().__getitem__(key)) else: @@ -393,12 +386,11 @@ def __getitem__( def filter( self, - excinfo_or_fn: Union[ - "ExceptionInfo[BaseException]", - Callable[[TracebackEntry], bool], - ], + excinfo_or_fn: ( + ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool] + ), /, - ) -> "Traceback": + ) -> Traceback: """Return a Traceback instance with certain items removed. If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s @@ -414,10 +406,10 @@ def filter( fn = excinfo_or_fn return Traceback(filter(fn, self)) - def recursionindex(self) -> Optional[int]: + def recursionindex(self) -> int | None: """Return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred.""" - cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} + cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi @@ -446,15 +438,15 @@ class ExceptionInfo(Generic[E]): _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] + _excinfo: tuple[builtins.type[E], E, TracebackType] | None _striptext: str - _traceback: Optional[Traceback] + _traceback: Traceback | None def __init__( self, - excinfo: Optional[Tuple[Type["E"], "E", TracebackType]], + excinfo: tuple[builtins.type[E], E, TracebackType] | None, striptext: str = "", - traceback: Optional[Traceback] = None, + traceback: Traceback | None = None, *, _ispytest: bool = False, ) -> None: @@ -470,8 +462,8 @@ def from_exception( # This is OK to ignore because this class is (conceptually) readonly. # See https://github.com/python/mypy/issues/7049. exception: E, # type: ignore[misc] - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Return an ExceptionInfo for an existing exception. The exception must have a non-``None`` ``__traceback__`` attribute, @@ -495,9 +487,9 @@ def from_exception( @classmethod def from_exc_info( cls, - exc_info: Tuple[Type[E], E, TracebackType], - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exc_info: tuple[builtins.type[E], E, TracebackType], + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Like :func:`from_exception`, but using old-style exc_info tuple.""" _striptext = "" if exprinfo is None and isinstance(exc_info[1], AssertionError): @@ -510,9 +502,7 @@ def from_exc_info( return cls(exc_info, _striptext, _ispytest=True) @classmethod - def from_current( - cls, exprinfo: Optional[str] = None - ) -> "ExceptionInfo[BaseException]": + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: """Return an ExceptionInfo matching the current traceback. .. warning:: @@ -532,17 +522,19 @@ def from_current( return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod - def for_later(cls) -> "ExceptionInfo[E]": + def for_later(cls) -> ExceptionInfo[E]: """Return an unfilled ExceptionInfo.""" return cls(None, _ispytest=True) - def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None: + def fill_unfilled( + self, exc_info: tuple[builtins.type[E], E, TracebackType] + ) -> None: """Fill an unfilled ExceptionInfo created with ``for_later()``.""" assert self._excinfo is None, "ExceptionInfo was already filled" self._excinfo = exc_info @property - def type(self) -> Type[E]: + def type(self) -> builtins.type[E]: """The exception class.""" assert ( self._excinfo is not None @@ -608,7 +600,8 @@ def exconly(self, tryshort: bool = False) -> str: return text def errisinstance( - self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] + self, + exc: builtins.type[BaseException] | tuple[builtins.type[BaseException], ...], ) -> bool: """Return True if the exception is an instance of exc. @@ -616,7 +609,7 @@ def errisinstance( """ return isinstance(self.value, exc) - def _getreprcrash(self) -> Optional["ReprFileLocation"]: + def _getreprcrash(self) -> ReprFileLocation | None: # Find last non-hidden traceback entry that led to the exception of the # traceback, or None if all hidden. for i in range(-1, -len(self.traceback) - 1, -1): @@ -632,13 +625,11 @@ def getrepr( showlocals: bool = False, style: _TracebackStyle = "long", abspath: bool = False, - tbfilter: Union[ - bool, Callable[["ExceptionInfo[BaseException]"], Traceback] - ] = True, + tbfilter: (bool | Callable[[ExceptionInfo[BaseException]], Traceback]) = True, funcargs: bool = False, truncate_locals: bool = True, chain: bool = True, - ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + ) -> ReprExceptionInfo | ExceptionChainRepr: """Return str()able representation of this exception info. :param bool showlocals: @@ -705,7 +696,7 @@ def _stringify_exception(self, exc: BaseException) -> str: ] ) - def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": + def match(self, regexp: str | Pattern[str]) -> Literal[True]: """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. @@ -723,9 +714,10 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": def _group_contains( self, exc_group: BaseExceptionGroup[BaseException], - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], - match: Union[str, Pattern[str], None], - target_depth: Optional[int] = None, + expected_exception: builtins.type[BaseException] + | tuple[builtins.type[BaseException], ...], + match: str | Pattern[str] | None, + target_depth: int | None = None, current_depth: int = 1, ) -> bool: """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" @@ -752,10 +744,11 @@ def _group_contains( def group_contains( self, - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + expected_exception: builtins.type[BaseException] + | tuple[builtins.type[BaseException], ...], *, - match: Union[str, Pattern[str], None] = None, - depth: Optional[int] = None, + match: str | Pattern[str] | None = None, + depth: int | None = None, ) -> bool: """Check whether a captured exception group contains a matching exception. @@ -795,15 +788,15 @@ class FormattedExcinfo: showlocals: bool = False style: _TracebackStyle = "long" abspath: bool = True - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True funcargs: bool = False truncate_locals: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + astcache: dict[str | Path, ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False ) - def _getindent(self, source: "Source") -> int: + def _getindent(self, source: Source) -> int: # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) @@ -818,13 +811,13 @@ def _getindent(self, source: "Source") -> int: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: + def _getentrysource(self, entry: TracebackEntry) -> Source | None: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): @@ -834,11 +827,11 @@ def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: def get_source( self, - source: Optional["Source"], + source: Source | None, line_index: int = -1, - excinfo: Optional[ExceptionInfo[BaseException]] = None, + excinfo: ExceptionInfo[BaseException] | None = None, short: bool = False, - ) -> List[str]: + ) -> list[str]: """Return formatted and marked up source lines.""" lines = [] if source is not None and line_index < 0: @@ -867,7 +860,7 @@ def get_exconly( excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, - ) -> List[str]: + ) -> list[str]: lines = [] indentstr = " " * indent # Get the real exception information out. @@ -879,7 +872,7 @@ def get_exconly( failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -907,10 +900,10 @@ def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: def repr_traceback_entry( self, - entry: Optional[TracebackEntry], - excinfo: Optional[ExceptionInfo[BaseException]] = None, - ) -> "ReprEntry": - lines: List[str] = [] + entry: TracebackEntry | None, + excinfo: ExceptionInfo[BaseException] | None = None, + ) -> ReprEntry: + lines: list[str] = [] style = ( entry._repr_style if entry is not None and entry._repr_style is not None @@ -945,7 +938,7 @@ def repr_traceback_entry( lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path: Union[Path, str]) -> str: + def _makepath(self, path: Path | str) -> str: if not self.abspath and isinstance(path, Path): try: np = bestrelpath(Path.cwd(), path) @@ -955,7 +948,7 @@ def _makepath(self, path: Union[Path, str]) -> str: return np return str(path) - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: traceback = excinfo.traceback if callable(self.tbfilter): traceback = self.tbfilter(excinfo) @@ -986,7 +979,7 @@ def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTracebac def _truncate_recursive_traceback( self, traceback: Traceback - ) -> Tuple[Traceback, Optional[str]]: + ) -> tuple[Traceback, str | None]: """Truncate the given recursive traceback trying to find the starting point of the recursion. @@ -1003,7 +996,7 @@ def _truncate_recursive_traceback( recursionindex = traceback.recursionindex() except Exception as e: max_frames = 10 - extraline: Optional[str] = ( + extraline: str | None = ( "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" " The following exception happened when comparing locals in the stack frame:\n" " {exc_type}: {exc_msg}\n" @@ -1026,16 +1019,12 @@ def _truncate_recursive_traceback( return traceback, extraline - def repr_excinfo( - self, excinfo: ExceptionInfo[BaseException] - ) -> "ExceptionChainRepr": - repr_chain: List[ - Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] - ] = [] - e: Optional[BaseException] = excinfo.value - excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo + def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: + repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] + e: BaseException | None = excinfo.value + excinfo_: ExceptionInfo[BaseException] | None = excinfo descr = None - seen: Set[int] = set() + seen: set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) @@ -1044,9 +1033,9 @@ def repr_excinfo( # full support for exception groups added to ExceptionInfo. # See https://github.com/pytest-dev/pytest/issues/9159 if isinstance(e, BaseExceptionGroup): - reprtraceback: Union[ - ReprTracebackNative, ReprTraceback - ] = ReprTracebackNative( + reprtraceback: ( + ReprTracebackNative | ReprTraceback + ) = ReprTracebackNative( traceback.format_exception( type(excinfo_.value), excinfo_.value, @@ -1102,9 +1091,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] - sections: List[Tuple[str, str, str]] = dataclasses.field( + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( init=False, default_factory=list ) @@ -1119,13 +1108,11 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] def __init__( self, - chain: Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ], + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. @@ -1146,8 +1133,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1156,8 +1143,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): - reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] - extraline: Optional[str] + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None style: _TracebackStyle entrysep: ClassVar = "_ " @@ -1201,9 +1188,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] - reprfuncargs: Optional["ReprFuncArgs"] - reprlocals: Optional["ReprLocals"] - reprfileloc: Optional["ReprFileLocation"] + reprfuncargs: ReprFuncArgs | None + reprlocals: ReprLocals | None + reprfileloc: ReprFileLocation | None style: _TracebackStyle def _write_entry_lines(self, tw: TerminalWriter) -> None: @@ -1228,9 +1215,9 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: # such as "> assert 0" fail_marker = f"{FormattedExcinfo.fail_marker} " indent_size = len(fail_marker) - indents: List[str] = [] - source_lines: List[str] = [] - failure_lines: List[str] = [] + indents: list[str] = [] + source_lines: list[str] = [] + failure_lines: list[str] = [] for index, line in enumerate(self.lines): is_failure_line = line.startswith(fail_marker) if is_failure_line: @@ -1309,7 +1296,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None: @dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): - args: Sequence[Tuple[str, object]] + args: Sequence[tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: @@ -1330,7 +1317,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: +def getfslineno(obj: object) -> tuple[str | Path, int]: """Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1). diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index cc7ac407e52..003f17b224d 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ast import inspect import textwrap @@ -7,11 +9,7 @@ from bisect import bisect_right from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import overload -from typing import Tuple -from typing import Union class Source: @@ -22,7 +20,7 @@ class Source: def __init__(self, obj: object = None) -> None: if not obj: - self.lines: List[str] = [] + self.lines: list[str] = [] elif isinstance(obj, Source): self.lines = obj.lines elif isinstance(obj, (tuple, list)): @@ -50,10 +48,10 @@ def __getitem__(self, key: int) -> str: ... @overload - def __getitem__(self, key: slice) -> "Source": + def __getitem__(self, key: slice) -> Source: ... - def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: + def __getitem__(self, key: int | slice) -> str | Source: if isinstance(key, int): return self.lines[key] else: @@ -69,7 +67,7 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self.lines) - def strip(self) -> "Source": + def strip(self) -> Source: """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): @@ -80,20 +78,20 @@ def strip(self) -> "Source": source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> "Source": + def indent(self, indent: str = " " * 4) -> Source: """Return a copy of the source object with all lines indented by the given indent-string.""" newsource = Source() newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> "Source": + def getstatement(self, lineno: int) -> Source: """Return Source statement which contains the given linenumber (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno: int) -> Tuple[int, int]: + def getstatementrange(self, lineno: int) -> tuple[int, int]: """Return (start, end) tuple which spans the minimal statement region which containing the given lineno.""" if not (0 <= lineno < len(self)): @@ -101,7 +99,7 @@ def getstatementrange(self, lineno: int) -> Tuple[int, int]: ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> "Source": + def deindent(self) -> Source: """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) @@ -116,7 +114,7 @@ def __str__(self) -> str: # -def findsource(obj) -> Tuple[Optional[Source], int]: +def findsource(obj) -> tuple[Source | None, int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: @@ -139,14 +137,14 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> List[str]: +def deindent(lines: Iterable[str]) -> list[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: +def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: # Flatten all statements and except handlers into one lineno-list. # AST's line numbers start indexing at 1. - values: List[int] = [] + values: list[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): # The lineno points to the class/def, so need to include the decorators. @@ -155,7 +153,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i values.append(d.lineno - 1) values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val: Optional[List[ast.stmt]] = getattr(x, name, None) + val: list[ast.stmt] | None = getattr(x, name, None) if val: # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) @@ -173,8 +171,8 @@ def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, - astnode: Optional[ast.AST] = None, -) -> Tuple[ast.AST, int, int]: + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: diff --git a/src/_pytest/_io/__init__.py b/src/_pytest/_io/__init__.py index db001e918cb..b0155b18b60 100644 --- a/src/_pytest/_io/__init__.py +++ b/src/_pytest/_io/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .terminalwriter import get_terminal_width from .terminalwriter import TerminalWriter diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 7559c6778df..3502202a74e 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -12,6 +12,8 @@ # tuples with fairly non-descriptive content. This is modeled very much # after Lisp/Scheme - style pretty-printing of lists. If you find it # useful, thank small children who sleep at night. +from __future__ import annotations + import collections as _collections import dataclasses as _dataclasses import re @@ -19,13 +21,8 @@ from io import StringIO as _StringIO from typing import Any from typing import Callable -from typing import Dict from typing import IO from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple class _safe_key: @@ -63,7 +60,7 @@ def __init__( self, indent: int = 4, width: int = 80, - depth: Optional[int] = None, + depth: int | None = None, ) -> None: """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -99,7 +96,7 @@ def _format( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: objid = id(object) @@ -135,7 +132,7 @@ def _pprint_dataclass( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: cls_name = object.__class__.__name__ @@ -148,9 +145,9 @@ def _pprint_dataclass( self._format_namespace_items(items, stream, indent, allowance, context, level) stream.write(")") - _dispatch: Dict[ + _dispatch: dict[ Callable[..., str], - Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None], + Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], ] = {} def _pprint_dict( @@ -159,7 +156,7 @@ def _pprint_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -176,7 +173,7 @@ def _pprint_ordered_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -195,7 +192,7 @@ def _pprint_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("[") @@ -210,7 +207,7 @@ def _pprint_tuple( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("(") @@ -225,7 +222,7 @@ def _pprint_set( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -251,7 +248,7 @@ def _pprint_str( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -310,7 +307,7 @@ def _pprint_bytes( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -339,7 +336,7 @@ def _pprint_bytearray( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -357,7 +354,7 @@ def _pprint_mappingproxy( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("mappingproxy(") @@ -372,7 +369,7 @@ def _pprint_simplenamespace( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if type(object) is _types.SimpleNamespace: @@ -390,11 +387,11 @@ def _pprint_simplenamespace( def _format_dict_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -414,11 +411,11 @@ def _format_dict_items( def _format_namespace_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -451,11 +448,11 @@ def _format_namespace_items( def _format_items( self, - items: List[Any], + items: list[Any], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -472,7 +469,7 @@ def _format_items( write("\n" + " " * indent) - def _repr(self, object: Any, context: Set[int], level: int) -> str: + def _repr(self, object: Any, context: set[int], level: int) -> str: return self._safe_repr(object, context.copy(), self._depth, level) def _pprint_default_dict( @@ -481,7 +478,7 @@ def _pprint_default_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: rdf = self._repr(object.default_factory, context, level) @@ -497,7 +494,7 @@ def _pprint_counter( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -518,7 +515,7 @@ def _pprint_chain_map( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): @@ -537,7 +534,7 @@ def _pprint_deque( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -556,7 +553,7 @@ def _pprint_user_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -569,7 +566,7 @@ def _pprint_user_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -582,7 +579,7 @@ def _pprint_user_string( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -590,7 +587,7 @@ def _pprint_user_string( _dispatch[_collections.UserString.__repr__] = _pprint_user_string def _safe_repr( - self, object: Any, context: Set[int], maxlevels: Optional[int], level: int + self, object: Any, context: set[int], maxlevels: int | None, level: int ) -> str: typ = type(object) if typ in _builtin_scalars: @@ -607,7 +604,7 @@ def _safe_repr( if objid in context: return _recursion(object) context.add(objid) - components: List[str] = [] + components: list[str] = [] append = components.append level += 1 for k, v in sorted(object.items(), key=_safe_tuple): diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index c51578ed488..9f72d7b926c 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import pprint import reprlib -from typing import Optional def _try_repr_or_str(obj: object) -> str: @@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -97,7 +98,7 @@ def safeformat(obj: object) -> str: def saferepr( - obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False ) -> str: """Return a size-limited safe repr-string for the given object. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 56107d56647..9537fd0a1e8 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -1,10 +1,11 @@ """Helper functions for writing to terminals and files.""" +from __future__ import annotations + import os import shutil import sys from typing import final from typing import Literal -from typing import Optional from typing import Sequence from typing import TextIO @@ -63,7 +64,7 @@ class TerminalWriter: invert=7, ) - def __init__(self, file: Optional[TextIO] = None) -> None: + def __init__(self, file: TextIO | None = None) -> None: if file is None: file = sys.stdout if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": @@ -77,7 +78,7 @@ def __init__(self, file: Optional[TextIO] = None) -> None: self._file = file self.hasmarkup = should_do_markup(file) self._current_line = "" - self._terminal_width: Optional[int] = None + self._terminal_width: int | None = None self.code_highlight = True @property @@ -108,8 +109,8 @@ def markup(self, text: str, **markup: bool) -> str: def sep( self, sepchar: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: if fullwidth is None: diff --git a/src/_pytest/_io/wcwidth.py b/src/_pytest/_io/wcwidth.py index e5c7bf4d868..5beb8435aa8 100644 --- a/src/_pytest/_io/wcwidth.py +++ b/src/_pytest/_io/wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unicodedata from functools import lru_cache diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index e1e7a5e6663..346d2c8f777 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -1,9 +1,9 @@ """Support for presenting detailed information in failing assertions.""" +from __future__ import annotations + import sys from typing import Any from typing import Generator -from typing import List -from typing import Optional from typing import TYPE_CHECKING from _pytest.assertion import rewrite @@ -91,7 +91,7 @@ class AssertionState: def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook: Optional[rewrite.AssertionRewritingHook] = None + self.hook: rewrite.AssertionRewritingHook | None = None def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: @@ -110,7 +110,7 @@ def undo() -> None: return hook -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: # This hook is only called when test modules are collected # so for example not in the managing process of pytest-xdist # (which does not collect test modules). @@ -131,7 +131,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: ihook = item.ihook - def callbinrepr(op, left: object, right: object) -> Optional[str]: + def callbinrepr(op, left: object, right: object) -> str | None: """Call the pytest_assertrepr_compare hook and prepare the result. This uses the first result from the hook and then ensures the @@ -177,7 +177,7 @@ def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: util._config = None -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: assertstate = session.config.stash.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: @@ -186,5 +186,5 @@ def pytest_sessionfinish(session: "Session") -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any -) -> Optional[List[str]]: +) -> list[str] | None: return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 149101e716f..a8aa7ebd3b9 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -1,4 +1,6 @@ """Rewrite assertion AST to produce nice error messages.""" +from __future__ import annotations + import ast import errno import functools @@ -17,17 +19,11 @@ from pathlib import Path from pathlib import PurePath from typing import Callable -from typing import Dict from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr @@ -70,17 +66,17 @@ def __init__(self, config: Config) -> None: self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session: Optional[Session] = None - self._rewritten_names: Dict[str, Path] = {} - self._must_rewrite: Set[str] = set() + self.session: Session | None = None + self._rewritten_names: dict[str, Path] = {} + self._must_rewrite: set[str] = set() # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache: Dict[str, bool] = {} + self._marked_for_rewrite_cache: dict[str, bool] = {} self._session_paths_checked = False - def set_session(self, session: Optional[Session]) -> None: + def set_session(self, session: Session | None) -> None: self.session = session self._session_paths_checked = False @@ -90,9 +86,9 @@ def set_session(self, session: Optional[Session]) -> None: def find_spec( self, name: str, - path: Optional[Sequence[Union[str, bytes]]] = None, - target: Optional[types.ModuleType] = None, - ) -> Optional[importlib.machinery.ModuleSpec]: + path: Sequence[str | bytes] | None = None, + target: types.ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: if self._writing_pyc: return None state = self.config.stash[assertstate_key] @@ -129,7 +125,7 @@ def find_spec( def create_module( self, spec: importlib.machinery.ModuleSpec - ) -> Optional[types.ModuleType]: + ) -> types.ModuleType | None: return None # default behaviour is fine def exec_module(self, module: types.ModuleType) -> None: @@ -174,7 +170,7 @@ def exec_module(self, module: types.ModuleType) -> None: state.trace(f"found cached rewritten pyc for {fn}") exec(co, module.__dict__) - def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: + def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: """A fast way to get out of rewriting modules. Profiling has shown that the call to PathFinder.find_spec (inside of @@ -213,7 +209,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: state.trace(f"early skip of rewriting module: {name}") return True - def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: + def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: # always rewrite conftest files if os.path.basename(fn) == "conftest.py": state.trace(f"rewriting conftest file: {fn!r}") @@ -234,7 +230,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -275,7 +271,7 @@ def _warn_already_imported(self, name: str) -> None: stacklevel=5, ) - def get_data(self, pathname: Union[str, bytes]) -> bytes: + def get_data(self, pathname: str | bytes) -> bytes: """Optional PEP302 get_data API.""" with open(pathname, "rb") as f: return f.read() @@ -316,7 +312,7 @@ def _write_pyc_fp( def _write_pyc( - state: "AssertionState", + state: AssertionState, co: types.CodeType, source_stat: os.stat_result, pyc: Path, @@ -340,7 +336,7 @@ def _write_pyc( return True -def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: +def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: """Read and rewrite *fn* and return the code object.""" stat = os.stat(fn) source = fn.read_bytes() @@ -353,7 +349,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT def _read_pyc( source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> Optional[types.CodeType]: +) -> types.CodeType | None: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. @@ -403,8 +399,8 @@ def _read_pyc( def rewrite_asserts( mod: ast.Module, source: bytes, - module_path: Optional[str] = None, - config: Optional[Config] = None, + module_path: str | None = None, + config: Config | None = None, ) -> None: """Rewrite the assert statements in mod.""" AssertionRewriter(module_path, config, source).run(mod) @@ -424,7 +420,7 @@ def _saferepr(obj: object) -> str: return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") -def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]: +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: """Get `maxsize` configuration for saferepr based on the given config object.""" if config is None: verbosity = 0 @@ -542,14 +538,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]: @functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> Dict[int, str]: +def _get_assertion_exprs(src: bytes) -> dict[int, str]: """Return a mapping from {lineno: "assertion test expression"}.""" - ret: Dict[int, str] = {} + ret: dict[int, str] = {} depth = 0 - lines: List[str] = [] - assert_lineno: Optional[int] = None - seen_lines: Set[int] = set() + lines: list[str] = [] + assert_lineno: int | None = None + seen_lines: set[int] = set() def _write_and_reset() -> None: nonlocal depth, lines, assert_lineno, seen_lines @@ -656,7 +652,7 @@ class AssertionRewriter(ast.NodeVisitor): """ def __init__( - self, module_path: Optional[str], config: Optional[Config], source: bytes + self, module_path: str | None, config: Config | None, source: bytes ) -> None: super().__init__() self.module_path = module_path @@ -670,7 +666,7 @@ def __init__( self.source = source self.scope: tuple[ast.AST, ...] = () self.variables_overwrite: defaultdict[ - tuple[ast.AST, ...], Dict[str, str] + tuple[ast.AST, ...], dict[str, str] ] = defaultdict(dict) def run(self, mod: ast.Module) -> None: @@ -736,7 +732,7 @@ def run(self, mod: ast.Module) -> None: # Collect asserts. self.scope = (mod,) - nodes: List[Union[ast.AST, Sentinel]] = [mod] + nodes: list[ast.AST | Sentinel] = [mod] while nodes: node = nodes.pop() if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): @@ -748,7 +744,7 @@ def run(self, mod: ast.Module) -> None: assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): - new: List[ast.AST] = [] + new: list[ast.AST] = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -820,7 +816,7 @@ def push_format_context(self) -> None: to format a string of %-formatted values as added by .explanation_param(). """ - self.explanation_specifiers: Dict[str, ast.expr] = {} + self.explanation_specifiers: dict[str, ast.expr] = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: @@ -843,13 +839,13 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) return ast.Name(name, ast.Load()) - def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]: + def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: """Handle expressions we don't have custom code for.""" assert isinstance(node, ast.expr) res = self.assign(node) return res, self.explanation_param(self.display(res)) - def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: + def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: """Return the AST statements to replace the ast.Assert instance. This rewrites the test of an assertion to provide @@ -872,15 +868,15 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: lineno=assert_.lineno, ) - self.statements: List[ast.stmt] = [] - self.variables: List[str] = [] + self.statements: list[ast.stmt] = [] + self.variables: list[str] = [] self.variable_counter = itertools.count() if self.enable_assertion_pass_hook: - self.format_variables: List[str] = [] + self.format_variables: list[str] = [] - self.stack: List[Dict[str, ast.expr]] = [] - self.expl_stmts: List[ast.stmt] = [] + self.stack: list[dict[str, ast.expr]] = [] + self.expl_stmts: list[ast.stmt] = [] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) @@ -966,7 +962,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: ast.copy_location(node, assert_) return self.statements - def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: + def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: # This method handles the 'walrus operator' repr of the target # name if it's a local variable or _should_repr_global_name() # thinks it's acceptable. @@ -978,7 +974,7 @@ def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) return name, self.explanation_param(expr) - def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: + def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) @@ -988,7 +984,7 @@ def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) return name, self.explanation_param(expr) - def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: + def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: res_var = self.variable() expl_list = self.assign(ast.List([], ast.Load())) app = ast.Attribute(expl_list, "append", ast.Load()) @@ -1000,7 +996,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner: List[ast.stmt] = [] + fail_inner: list[ast.stmt] = [] # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa self.expl_stmts = fail_inner @@ -1030,7 +1026,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: cond: ast.expr = res if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner: List[ast.stmt] = [] + inner: list[ast.stmt] = [] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save @@ -1039,13 +1035,13 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]: + def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: pattern = UNARY_MAP[unary.op.__class__] operand_res, operand_expl = self.visit(unary.operand) res = self.assign(ast.UnaryOp(unary.op, operand_res)) return res, pattern % (operand_expl,) - def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: + def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: symbol = BINOP_MAP[binop.op.__class__] left_expr, left_expl = self.visit(binop.left) right_expr, right_expl = self.visit(binop.right) @@ -1053,7 +1049,7 @@ def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -1089,13 +1085,13 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" return res, outer_expl - def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]: + def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: # A Starred node can appear in a function call. res, expl = self.visit(starred.value) new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: + def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) value, value_expl = self.visit(attr.value) @@ -1105,7 +1101,7 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: + def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: self.push_format_context() # We first check if we have overwritten a variable in the previous assert if isinstance( diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 16de27f256e..c8bc21a1256 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -3,8 +3,7 @@ Current default behaviour is to truncate assertion explanations at terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. """ -from typing import List -from typing import Optional +from __future__ import annotations from _pytest.assertion import util from _pytest.config import Config @@ -17,8 +16,8 @@ def truncate_if_required( - explanation: List[str], item: Item, max_length: Optional[int] = None -) -> List[str]: + explanation: list[str], item: Item, max_length: int | None = None +) -> list[str]: """Truncate this assertion explanation if the given test item is eligible.""" if _should_truncate_item(item): return _truncate_explanation(explanation) @@ -32,10 +31,10 @@ def _should_truncate_item(item: Item) -> bool: def _truncate_explanation( - input_lines: List[str], - max_lines: Optional[int] = None, - max_chars: Optional[int] = None, -) -> List[str]: + input_lines: list[str], + max_lines: int | None = None, + max_chars: int | None = None, +) -> list[str]: """Truncate given list of strings that makes up the assertion explanation. Truncates to either 8 lines, or 640 characters - whichever the input reaches @@ -98,7 +97,7 @@ def _truncate_explanation( ] -def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: +def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: # Find point at which input length exceeds total allowed length iterated_char_count = 0 for iterated_index, input_line in enumerate(input_lines): diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 6f97101a932..8394c633fc3 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -1,4 +1,6 @@ """Utilities for assertion debugging.""" +from __future__ import annotations + import collections.abc import os import pprint @@ -6,10 +8,8 @@ from typing import Any from typing import Callable from typing import Iterable -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Protocol from typing import Sequence from unicodedata import normalize @@ -25,14 +25,14 @@ # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None +_reprcompare: Callable[[str, object, object], str | None] | None = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. -_assertion_pass: Optional[Callable[[int, str, str], None]] = None +_assertion_pass: Callable[[int, str, str], None] | None = None # Config object which is assigned during pytest_runtest_protocol. -_config: Optional[Config] = None +_config: Config | None = None class _HighlightFunc(Protocol): @@ -55,7 +55,7 @@ def format_explanation(explanation: str) -> str: return "\n".join(result) -def _split_explanation(explanation: str) -> List[str]: +def _split_explanation(explanation: str) -> list[str]: r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. @@ -72,7 +72,7 @@ def _split_explanation(explanation: str) -> List[str]: return lines -def _format_lines(lines: Sequence[str]) -> List[str]: +def _format_lines(lines: Sequence[str]) -> list[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting @@ -166,7 +166,7 @@ def has_default_eq( def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False -) -> Optional[List[str]]: +) -> list[str] | None: """Return specialised explanations for some operators/operands.""" verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) @@ -237,7 +237,7 @@ def assertrepr_compare( def _compare_eq_any( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 -) -> List[str]: +) -> list[str]: explanation = [] if istext(left) and istext(right): explanation = _diff_text(left, right, verbose) @@ -272,7 +272,7 @@ def _compare_eq_any( return explanation -def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: +def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]: """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing @@ -280,7 +280,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: """ from difflib import ndiff - explanation: List[str] = [] + explanation: list[str] = [] if verbose < 1: i = 0 # just in case left or right has zero length @@ -325,7 +325,7 @@ def _compare_eq_iterable( right: Iterable[Any], highligher: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: if verbose <= 0 and not running_on_ci(): return ["Use -v to get more diff"] # dynamic import to speedup pytest @@ -354,9 +354,9 @@ def _compare_eq_sequence( right: Sequence[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - explanation: List[str] = [] + explanation: list[str] = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -415,7 +415,7 @@ def _compare_eq_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = [] explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) @@ -427,7 +427,7 @@ def _compare_gt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_gte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -439,7 +439,7 @@ def _compare_lt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_lte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -451,7 +451,7 @@ def _compare_gte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("right", right, left, highlighter) @@ -460,7 +460,7 @@ def _compare_lte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("left", left, right, highlighter) @@ -469,7 +469,7 @@ def _set_one_sided_diff( set1: AbstractSet[Any], set2: AbstractSet[Any], highlighter: _HighlightFunc, -) -> List[str]: +) -> list[str]: explanation = [] diff = set1 - set2 if diff: @@ -484,8 +484,8 @@ def _compare_eq_dict( right: Mapping[Any, Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: - explanation: List[str] = [] +) -> list[str]: + explanation: list[str] = [] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) @@ -529,7 +529,7 @@ def _compare_eq_dict( def _compare_eq_cls( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int -) -> List[str]: +) -> list[str]: if not has_default_eq(left): return [] if isdatacls(left): @@ -582,7 +582,7 @@ def _compare_eq_cls( return explanation -def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: +def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: index = text.find(term) head = text[:index] tail = text[index + len(term) :] diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 793e796de69..2ec3ee677e4 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -1,18 +1,15 @@ """Implementation of the cache provider.""" # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +from __future__ import annotations + import dataclasses import json import os from pathlib import Path -from typing import Dict from typing import final from typing import Generator from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Union from .pathlib import resolve_from_str from .pathlib import rm_rf @@ -72,7 +69,7 @@ def __init__( self._config = config @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": + def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: """Create the Cache instance for a Config. :meta private: @@ -213,7 +210,7 @@ def _ensure_supporting_files(self) -> None: class LFPluginCollWrapper: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False @@ -227,7 +224,7 @@ def pytest_make_collect_report( lf_paths = self.lfplugin._last_failed_paths # Use stable sort to priorize last failed. - def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + def sort_key(node: nodes.Item | nodes.Collector) -> bool: return node.path in lf_paths res.result = sorted( @@ -265,13 +262,13 @@ def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: class LFPluginCollSkipfiles: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin @hookimpl def pytest_make_collect_report( self, collector: nodes.Collector - ) -> Optional[CollectReport]: + ) -> CollectReport | None: if isinstance(collector, File): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -290,9 +287,9 @@ def __init__(self, config: Config) -> None: active_keys = "lf", "failedfirst" self.active = any(config.getoption(key) for key in active_keys) assert config.cache - self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count: Optional[int] = None - self._report_status: Optional[str] = None + self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._previously_failed_count: int | None = None + self._report_status: str | None = None self._skipped_files = 0 # count skipped files during collection due to --lf if config.getoption("lf"): @@ -301,7 +298,7 @@ def __init__(self, config: Config) -> None: LFPluginCollWrapper(self), "lfplugin-collwrapper" ) - def get_last_failed_paths(self) -> Set[Path]: + def get_last_failed_paths(self) -> set[Path]: """Return a set with all Paths of the previously failed nodeids and their parents.""" rootpath = self.config.rootpath @@ -312,7 +309,7 @@ def get_last_failed_paths(self) -> Set[Path]: result.update(path.parents) return {x for x in result if x.exists()} - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.active and self.config.getoption("verbose") >= 0: return "run-last-failure: %s" % self._report_status return None @@ -334,7 +331,7 @@ def pytest_collectreport(self, report: CollectReport) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield @@ -408,13 +405,13 @@ def __init__(self, config: Config) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, items: List[nodes.Item] + self, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield if self.active: - new_items: Dict[str, nodes.Item] = {} - other_items: Dict[str, nodes.Item] = {} + new_items: dict[str, nodes.Item] = {} + other_items: dict[str, nodes.Item] = {} for item in items: if item.nodeid not in self.cached_nodeids: new_items[item.nodeid] = item @@ -430,7 +427,7 @@ def pytest_collection_modifyitems( return res - def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) # type: ignore[no-any-return] def pytest_sessionfinish(self) -> None: @@ -507,7 +504,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session @@ -538,7 +535,7 @@ def cache(request: FixtureRequest) -> Cache: return request.config.cache -def pytest_report_header(config: Config) -> Optional[str]: +def pytest_report_header(config: Config) -> str | None: """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": assert config.cache is not None diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index ebdcaedcea1..7df0203d17f 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1,4 +1,6 @@ """Per-test stdout/stderr capturing mechanism.""" +from __future__ import annotations + import abc import collections import contextlib @@ -17,15 +19,10 @@ from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import NamedTuple -from typing import Optional from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from _pytest.config import Config from _pytest.config import hookimpl @@ -211,7 +208,7 @@ def read(self, size: int = -1) -> str: def __next__(self) -> str: return self.readline() - def readlines(self, hint: Optional[int] = -1) -> List[str]: + def readlines(self, hint: int | None = -1) -> list[str]: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) @@ -243,7 +240,7 @@ def seekable(self) -> bool: def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: Optional[int] = None) -> int: + def truncate(self, size: int | None = None) -> int: raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: @@ -255,14 +252,14 @@ def writelines(self, lines: Iterable[str]) -> None: def writable(self) -> bool: return False - def __enter__(self) -> "DontReadFromInput": + def __enter__(self) -> DontReadFromInput: return self def __exit__( self, - type: Optional[Type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: pass @@ -337,7 +334,7 @@ def writeorg(self, data: str) -> None: class SysCaptureBase(CaptureBase[AnyStr]): def __init__( - self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False ) -> None: name = patchsysdict[fd] self._old: TextIO = getattr(sys, name) @@ -368,7 +365,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -455,7 +452,7 @@ def __init__(self, targetfd: int) -> None: # Further complications are the need to support suspend() and the # possibility of FD reuse (e.g. the tmpfile getting the very same # target FD). The following approach is robust, I believe. - self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -488,7 +485,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -609,13 +606,13 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: Optional[CaptureBase[AnyStr]], - out: Optional[CaptureBase[AnyStr]], - err: Optional[CaptureBase[AnyStr]], + in_: CaptureBase[AnyStr] | None, + out: CaptureBase[AnyStr] | None, + err: CaptureBase[AnyStr] | None, ) -> None: - self.in_: Optional[CaptureBase[AnyStr]] = in_ - self.out: Optional[CaptureBase[AnyStr]] = out - self.err: Optional[CaptureBase[AnyStr]] = err + self.in_: CaptureBase[AnyStr] | None = in_ + self.out: CaptureBase[AnyStr] | None = out + self.err: CaptureBase[AnyStr] | None = err def __repr__(self) -> str: return "".format( @@ -635,7 +632,7 @@ def start_capturing(self) -> None: if self.err: self.err.start() - def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: @@ -728,15 +725,15 @@ class CaptureManager: def __init__(self, method: _CaptureMethod) -> None: self._method: Final = method - self._global_capturing: Optional[MultiCapture[str]] = None - self._capture_fixture: Optional[CaptureFixture[Any]] = None + self._global_capturing: MultiCapture[str] | None = None + self._capture_fixture: CaptureFixture[Any] | None = None def __repr__(self) -> str: return "".format( self._method, self._global_capturing, self._capture_fixture ) - def is_capturing(self) -> Union[str, bool]: + def is_capturing(self) -> str | bool: if self.is_globally_capturing(): return "global" if self._capture_fixture: @@ -784,7 +781,7 @@ def read_global_capture(self) -> CaptureResult[str]: # Fixture Control - def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: + def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: if self._capture_fixture: current_fixture = self._capture_fixture.request.fixturename requested_fixture = capture_fixture.request.fixturename @@ -901,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]): def __init__( self, - captureclass: Type[CaptureBase[AnyStr]], + captureclass: type[CaptureBase[AnyStr]], request: SubRequest, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass: Type[CaptureBase[AnyStr]] = captureclass + self.captureclass: type[CaptureBase[AnyStr]] = captureclass self.request = request - self._capture: Optional[MultiCapture[AnyStr]] = None + self._capture: MultiCapture[AnyStr] | None = None self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 49d63a357c5..15883bb6cd5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,4 +1,6 @@ """Command line options, ini-file and conftest.py processing.""" +from __future__ import annotations + import argparse import collections.abc import copy @@ -21,22 +23,16 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import Type from typing import TYPE_CHECKING -from typing import Union from pluggy import HookimplMarker from pluggy import HookimplOpts @@ -112,7 +108,7 @@ class ConftestImportFailure(Exception): def __init__( self, path: Path, - excinfo: Tuple[Type[Exception], Exception, TracebackType], + excinfo: tuple[type[Exception], Exception, TracebackType], ) -> None: super().__init__(path, excinfo) self.path = path @@ -136,9 +132,9 @@ def filter_traceback_for_conftest_import_failure( def main( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> Union[int, ExitCode]: + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: """Perform an in-process test run. :param args: @@ -169,9 +165,7 @@ def main( return ExitCode.USAGE_ERROR else: try: - ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( - config=config - ) + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) try: return ExitCode(ret) except ValueError: @@ -273,9 +267,9 @@ def directory_arg(path: str, optname: str) -> str: def get_config( - args: Optional[List[str]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() config = Config( @@ -297,7 +291,7 @@ def get_config( return config -def get_plugin_manager() -> "PytestPluginManager": +def get_plugin_manager() -> PytestPluginManager: """Obtain a new instance of the :py:class:`pytest.PytestPluginManager`, with default plugins already loaded. @@ -309,9 +303,9 @@ def get_plugin_manager() -> "PytestPluginManager": def _prepareconfig( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: if args is None: args = sys.argv[1:] elif isinstance(args, os.PathLike): @@ -351,14 +345,14 @@ def _get_directory(path: Path) -> Path: def _get_legacy_hook_marks( method: Any, hook_type: str, - opt_names: Tuple[str, ...], -) -> Dict[str, bool]: + opt_names: tuple[str, ...], +) -> dict[str, bool]: if TYPE_CHECKING: # abuse typeguard from importlib to avoid massive method type union thats lacking a alias assert inspect.isroutine(method) - known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])} - must_warn: List[str] = [] - opts: Dict[str, bool] = {} + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} for opt_name in opt_names: opt_attr = getattr(method, opt_name, AttributeError) if opt_attr is not AttributeError: @@ -397,13 +391,13 @@ def __init__(self) -> None: # -- State related to local conftest plugins. # All loaded conftest modules. - self._conftest_plugins: Set[types.ModuleType] = set() + self._conftest_plugins: set[types.ModuleType] = set() # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} + self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Optional[Path] = None + self._confcutdir: Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -412,12 +406,14 @@ def __init__(self) -> None: # session (#9478), often with the same path, so cache it. self._get_directory = lru_cache(256)(_get_directory) + self._duplicatepaths: set[Path] = set() + # plugins that were explicitly skipped with pytest.skip # list of (module name, skip reason) # previously we would issue a warning when a plugin was skipped, but # since we refactored warnings as first citizens of Config, they are # just stored here to be used later. - self.skipped_plugins: List[Tuple[str, str]] = [] + self.skipped_plugins: list[tuple[str, str]] = [] self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -443,7 +439,7 @@ def __init__(self) -> None: def parse_hookimpl_opts( self, plugin: _PluggyPlugin, name: str - ) -> Optional[HookimplOpts]: + ) -> HookimplOpts | None: """:meta private:""" # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes @@ -467,7 +463,7 @@ def parse_hookimpl_opts( method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") ) - def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]: + def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: """:meta private:""" opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: @@ -480,9 +476,7 @@ def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOp ) return opts - def register( - self, plugin: _PluggyPlugin, name: Optional[str] = None - ) -> Optional[str]: + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( @@ -493,7 +487,7 @@ def register( ) ) return None - ret: Optional[str] = super().register(plugin, name) + ret: str | None = super().register(plugin, name) if ret: self.hook.pytest_plugin_registered.call_historic( kwargs=dict(plugin=plugin, manager=self) @@ -505,14 +499,14 @@ def register( def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: Optional[_PluggyPlugin] = self.get_plugin(name) + plugin: _PluggyPlugin | None = self.get_plugin(name) return plugin def hasplugin(self, name: str) -> bool: """Return whether a plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config: "Config") -> None: + def pytest_configure(self, config: Config) -> None: """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. @@ -535,12 +529,12 @@ def pytest_configure(self, config: "Config") -> None: # def _set_initial_conftests( self, - args: Sequence[Union[str, Path]], + args: Sequence[str | Path], pyargs: bool, noconftest: bool, rootpath: Path, - confcutdir: Optional[Path], - importmode: Union[ImportMode, str], + confcutdir: Path | None, + importmode: ImportMode | str, ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -580,7 +574,7 @@ def _is_in_confcutdir(self, path: Path) -> bool: return path not in self._confcutdir.parents def _try_load_conftest( - self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path + self, anchor: Path, importmode: str | ImportMode, rootpath: Path ) -> None: self._loadconftestmodules(anchor, importmode, rootpath) # let's also consider test* subdirs @@ -590,7 +584,7 @@ def _try_load_conftest( self._loadconftestmodules(x, importmode, rootpath) def _loadconftestmodules( - self, path: Path, importmode: Union[str, ImportMode], rootpath: Path + self, path: Path, importmode: str | ImportMode, rootpath: Path ) -> None: if self._noconftest: return @@ -622,7 +616,7 @@ def _rget_with_confmod( self, name: str, path: Path, - ) -> Tuple[types.ModuleType, Any]: + ) -> tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path) for mod in reversed(modules): try: @@ -632,7 +626,7 @@ def _rget_with_confmod( raise KeyError(name) def _importconftest( - self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path + self, conftestpath: Path, importmode: str | ImportMode, rootpath: Path ) -> types.ModuleType: existing = self.get_plugin(str(conftestpath)) if existing is not None: @@ -752,7 +746,7 @@ def consider_module(self, mod: types.ModuleType) -> None: self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) def _import_plugin_specs( - self, spec: Union[None, types.ModuleType, str, Sequence[str]] + self, spec: None | types.ModuleType | str | Sequence[str] ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: @@ -797,8 +791,8 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No def _get_plugin_specs_as_list( - specs: Union[None, types.ModuleType, str, Sequence[str]] -) -> List[str]: + specs: None | types.ModuleType | str | Sequence[str], +) -> list[str]: """Parse a plugins specification into a list of plugin names.""" # None means empty. if specs is None: @@ -927,9 +921,9 @@ class InvocationParams: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] + args: tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] + plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" @@ -938,7 +932,7 @@ def __init__( self, *, args: Iterable[str], - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + plugins: Sequence[str | _PluggyPlugin] | None, dir: Path, ) -> None: object.__setattr__(self, "args", tuple(args)) @@ -963,7 +957,7 @@ def __init__( self, pluginmanager: PytestPluginManager, *, - invocation_params: Optional[InvocationParams] = None, + invocation_params: InvocationParams | None = None, ) -> None: from .argparsing import Parser, FILE_OR_DIR @@ -1007,22 +1001,22 @@ def __init__( self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook # type: ignore[assignment] - self._inicache: Dict[str, Any] = {} + self._inicache: dict[str, Any] = {} self._override_ini: Sequence[str] = () - self._opt2dest: Dict[str, str] = {} - self._cleanup: List[Callable[[], None]] = [] + self._opt2dest: dict[str, str] = {} + self._cleanup: list[Callable[[], None]] = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) self.args_source = Config.ArgsSource.ARGS - self.args: List[str] = [] + self.args: list[str] = [] if TYPE_CHECKING: from _pytest.cacheprovider import Cache - self.cache: Optional[Cache] = None + self.cache: Cache | None = None @property def rootpath(self) -> Path: @@ -1035,7 +1029,7 @@ def rootpath(self) -> Path: return self._rootpath @property - def inipath(self) -> Optional[Path]: + def inipath(self) -> Path | None: """The path to the :ref:`configfile `. :type: Optional[pathlib.Path] @@ -1066,15 +1060,15 @@ def _ensure_unconfigure(self) -> None: fin() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( + terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( "terminalreporter" ) assert terminalreporter is not None return terminalreporter._tw def pytest_cmdline_parse( - self, pluginmanager: PytestPluginManager, args: List[str] - ) -> "Config": + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: try: self.parse(args) except UsageError: @@ -1100,7 +1094,7 @@ def pytest_cmdline_parse( def notify_exception( self, excinfo: ExceptionInfo[BaseException], - option: Optional[argparse.Namespace] = None, + option: argparse.Namespace | None = None, ) -> None: if option and getattr(option, "fulltrace", False): style: _TracebackStyle = "long" @@ -1123,7 +1117,7 @@ def cwd_relative_nodeid(self, nodeid: str) -> str: return nodeid @classmethod - def fromdictargs(cls, option_dict, args) -> "Config": + def fromdictargs(cls, option_dict, args) -> Config: """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) @@ -1132,7 +1126,7 @@ def fromdictargs(cls, option_dict, args) -> "Config": config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt: "Argument") -> None: + def _processopt(self, opt: Argument) -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest @@ -1141,7 +1135,7 @@ def _processopt(self, opt: "Argument") -> None: setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: "Config") -> None: + def pytest_load_initial_conftests(self, early_config: Config) -> None: # We haven't fully parsed the command line arguments yet, so # early_config.args it not set yet. But we need it for # discovering the initial conftests. So "pre-run" the logic here. @@ -1228,7 +1222,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None: for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args: List[str], via: str) -> List[str]: + def _validate_args(self, args: list[str], via: str) -> list[str]: """Validate known args.""" self._parser._config_source_hint = via # type: ignore try: @@ -1243,13 +1237,13 @@ def _validate_args(self, args: List[str], via: str) -> List[str]: def _decide_args( self, *, - args: List[str], + args: list[str], pyargs: bool, - testpaths: List[str], + testpaths: list[str], invocation_dir: Path, rootpath: Path, warn: bool, - ) -> Tuple[List[str], ArgsSource]: + ) -> tuple[list[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. :param warn: Whether can issue warnings. @@ -1283,7 +1277,7 @@ def _decide_args( result = [str(invocation_dir)] return result, source - def _preparse(self, args: List[str], addopts: bool = True) -> None: + def _preparse(self, args: list[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") if len(env_addopts): @@ -1411,11 +1405,11 @@ def _warn_or_fail_if_strict(self, message: str) -> None: self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) - def _get_unknown_ini_keys(self) -> List[str]: + def _get_unknown_ini_keys(self) -> list[str]: parser_inicfg = self._parser._inidict return [name for name in self.inicfg if name not in parser_inicfg] - def parse(self, args: List[str], addopts: bool = True) -> None: + def parse(self, args: list[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. assert ( self.args == [] @@ -1519,7 +1513,7 @@ def getini(self, name: str): # Meant for easy monkeypatching by legacypath plugin. # Can be inlined back (with no cover removed) once legacypath is gone. - def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): + def _getini_unknown_type(self, name: str, type: str, value: str | list[str]): msg = f"unknown configuration type: {type}" raise ValueError(msg, value) # pragma: no cover @@ -1573,14 +1567,14 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: + def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None: try: mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None modpath = Path(mod.__file__).parent - values: List[Path] = [] + values: list[Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): relroot = Path(relroot) @@ -1590,7 +1584,7 @@ def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: values.append(relroot) return values - def _get_override_ini_value(self, name: str) -> Optional[str]: + def _get_override_ini_value(self, name: str) -> str | None: value = None # override_ini is a list of "ini=value" options. # Always use the last item if multiple values are set for same ini-name, @@ -1645,7 +1639,7 @@ def getvalueorskip(self, name: str, path=None): VERBOSITY_ASSERTIONS: Final = "assertions" _VERBOSITY_INI_DEFAULT: Final = "auto" - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: r"""Retrieve the verbosity level for a fine-grained verbosity type. :param verbosity_type: Verbosity type to get level for. If a level is @@ -1696,7 +1690,7 @@ def _verbosity_ini_name(verbosity_type: str) -> str: return f"verbosity_{verbosity_type}" @staticmethod - def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None: + def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: """Add a output verbosity configuration option for the given output type. :param parser: Parser for command line arguments and ini-file values. @@ -1752,7 +1746,7 @@ def _assertion_supported() -> bool: def create_terminal_writer( - config: Config, file: Optional[TextIO] = None + config: Config, file: TextIO | None = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. @@ -1796,7 +1790,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1838,11 +1832,11 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) try: - action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] + action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined] except warnings._OptionError as e: raise UsageError(error_template.format(error=str(e))) try: - category: Type[Warning] = _resolve_warning_category(category_) + category: type[Warning] = _resolve_warning_category(category_) except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") @@ -1865,7 +1859,7 @@ def parse_warning_filter( return action, message, category, module, lineno -def _resolve_warning_category(category: str) -> Type[Warning]: +def _resolve_warning_category(category: str) -> type[Warning]: """ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) propagate so we can get access to their tracebacks (#9218). diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 39e41760518..d027ae33c36 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import os import sys @@ -5,16 +7,12 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import _pytest._io from _pytest.config.exceptions import UsageError @@ -39,32 +37,32 @@ class Parser: there's an error processing the command line arguments. """ - prog: Optional[str] = None + prog: str | None = None def __init__( self, - usage: Optional[str] = None, - processopt: Optional[Callable[["Argument"], None]] = None, + usage: str | None = None, + processopt: Callable[[Argument], None] | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) - self._groups: List[OptionGroup] = [] + self._groups: list[OptionGroup] = [] self._processopt = processopt self._usage = usage - self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {} - self._ininames: List[str] = [] - self.extra_info: Dict[str, Any] = {} + self._inidict: dict[str, tuple[str, str | None, Any]] = {} + self._ininames: list[str] = [] + self.extra_info: dict[str, Any] = {} - def processoption(self, option: "Argument") -> None: + def processoption(self, option: Argument) -> None: if self._processopt: if option.dest: self._processopt(option) def getgroup( - self, name: str, description: str = "", after: Optional[str] = None - ) -> "OptionGroup": + self, name: str, description: str = "", after: str | None = None + ) -> OptionGroup: """Get (or create) a named option Group. :param name: Name of the option group. @@ -106,8 +104,8 @@ def addoption(self, *opts: str, **attrs: Any) -> None: def parse( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: from _pytest._argcomplete import try_argcomplete @@ -116,7 +114,7 @@ def parse( strargs = [os.fspath(x) for x in args] return self.optparser.parse_args(strargs, namespace=namespace) - def _getparser(self) -> "MyOptionParser": + def _getparser(self) -> MyOptionParser: from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) @@ -137,10 +135,10 @@ def _getparser(self) -> "MyOptionParser": def parse_setoption( self, - args: Sequence[Union[str, "os.PathLike[str]"]], + args: Sequence[str | os.PathLike[str]], option: argparse.Namespace, - namespace: Optional[argparse.Namespace] = None, - ) -> List[str]: + namespace: argparse.Namespace | None = None, + ) -> list[str]: parsedoption = self.parse(args, namespace=namespace) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) @@ -148,8 +146,8 @@ def parse_setoption( def parse_known_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Parse the known arguments at this point. @@ -159,9 +157,9 @@ def parse_known_args( def parse_known_and_unknown_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, - ) -> Tuple[argparse.Namespace, List[str]]: + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> tuple[argparse.Namespace, list[str]]: """Parse the known arguments at this point, and also return the remaining unknown arguments. @@ -177,9 +175,8 @@ def addini( self, name: str, help: str, - type: Optional[ - Literal["string", "paths", "pathlist", "args", "linelist", "bool"] - ] = None, + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] + | None = None, default: Any = NOT_SET, ) -> None: """Register an ini-file option. @@ -215,7 +212,7 @@ def addini( def get_ini_default_for_type( - type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]] + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None ) -> Any: """ Used by addini to get the default value for a given ini-option type, when @@ -235,7 +232,7 @@ class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" - def __init__(self, msg: str, option: Union["Argument", str]) -> None: + def __init__(self, msg: str, option: Argument | str) -> None: self.msg = msg self.option_id = str(option) @@ -258,8 +255,8 @@ class Argument: def __init__(self, *names: str, **attrs: Any) -> None: """Store params in private vars for use in add_argument.""" self._attrs = attrs - self._short_opts: List[str] = [] - self._long_opts: List[str] = [] + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] try: self.type = attrs["type"] except KeyError: @@ -270,7 +267,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: except KeyError: pass self._set_opt_strings(names) - dest: Optional[str] = attrs.get("dest") + dest: str | None = attrs.get("dest") if dest: self.dest = dest elif self._long_opts: @@ -282,7 +279,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: self.dest = "???" # Needed for the error repr. raise ArgumentError("need a long or short option", self) from e - def names(self) -> List[str]: + def names(self) -> list[str]: return self._short_opts + self._long_opts def attrs(self) -> Mapping[str, Any]: @@ -326,7 +323,7 @@ def _set_opt_strings(self, opts: Sequence[str]) -> None: self._long_opts.append(opt) def __repr__(self) -> str: - args: List[str] = [] + args: list[str] = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -346,14 +343,14 @@ def __init__( self, name: str, description: str = "", - parser: Optional[Parser] = None, + parser: Parser | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self.name = name self.description = description - self.options: List[Argument] = [] + self.options: list[Argument] = [] self.parser = parser def addoption(self, *opts: str, **attrs: Any) -> None: @@ -382,7 +379,7 @@ def _addoption(self, *opts: str, **attrs: Any) -> None: option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: + def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: if not shortupper: for opt in option._short_opts: if opt[0] == "-" and opt[1].islower(): @@ -396,8 +393,8 @@ class MyOptionParser(argparse.ArgumentParser): def __init__( self, parser: Parser, - extra_info: Optional[Dict[str, Any]] = None, - prog: Optional[str] = None, + extra_info: dict[str, Any] | None = None, + prog: str | None = None, ) -> None: self._parser = parser super().__init__( @@ -424,8 +421,8 @@ def error(self, message: str) -> NoReturn: # Type ignored because typeshed has a very complex type in the superclass. def parse_args( # type: ignore self, - args: Optional[Sequence[str]] = None, - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str] | None = None, + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Allow splitting of positional arguments.""" parsed, unrecognized = self.parse_known_args(args, namespace) @@ -444,7 +441,7 @@ def parse_args( # type: ignore # disable long --argument abbreviations without breaking short flags. def _parse_optional( self, arg_string: str - ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: + ) -> tuple[argparse.Action | None, str, str | None] | None: if not arg_string: return None if not arg_string[0] in self.prefix_chars: @@ -496,7 +493,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: Optional[str] = getattr(action, "_formatted_action_invocation", None) + res: str | None = getattr(action, "_formatted_action_invocation", None) if res: return res options = orgstr.split(", ") @@ -505,7 +502,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - short_long: Dict[str, str] = {} + short_long: dict[str, str] = {} for option in options: if len(option) == 2 or option[2] == " ": continue diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py index 4031ea732f3..90108eca904 100644 --- a/src/_pytest/config/exceptions.py +++ b/src/_pytest/config/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import final diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index fc30533b6e4..9b8eed2537c 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,13 +1,10 @@ +from __future__ import annotations + import os import sys from pathlib import Path -from typing import Dict from typing import Iterable -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import iniconfig @@ -32,7 +29,7 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: def load_config_dict_from_file( filepath: Path, -) -> Optional[Dict[str, Union[str, List[str]]]]: +) -> dict[str, str | list[str]] | None: """Load pytest configuration from the given file path, if supported. Return None if the file does not contain valid pytest configuration. @@ -78,7 +75,7 @@ def load_config_dict_from_file( # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), # however we need to convert all scalar values to str for compatibility with the rest # of the configuration system, which expects strings only. - def make_scalar(v: object) -> Union[str, List[str]]: + def make_scalar(v: object) -> str | list[str]: return v if isinstance(v, list) else str(v) return {k: make_scalar(v) for k, v in result.items()} @@ -88,7 +85,7 @@ def make_scalar(v: object) -> Union[str, List[str]]: def locate_config( args: Iterable[Path], -) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]: """Search in the list of arguments for a valid ini-file for pytest, and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ @@ -114,7 +111,7 @@ def locate_config( def get_common_ancestor(paths: Iterable[Path]) -> Path: - common_ancestor: Optional[Path] = None + common_ancestor: Path | None = None for path in paths: if not path.exists(): continue @@ -136,7 +133,7 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path: return common_ancestor -def get_dirs_from_args(args: Iterable[str]) -> List[Path]: +def get_dirs_from_args(args: Iterable[str]) -> list[Path]: def is_option(x: str) -> bool: return x.startswith("-") @@ -162,11 +159,11 @@ def get_dir_from_path(path: Path) -> Path: def determine_setup( - inifile: Optional[str], + inifile: str | None, args: Sequence[str], - rootdir_cmd_arg: Optional[str] = None, - invocation_dir: Optional[Path] = None, -) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: + rootdir_cmd_arg: str | None = None, + invocation_dir: Path | None = None, +) -> tuple[Path, Path | None, dict[str, str | list[str]]]: """Determine the rootdir, inifile and ini configuration values from the command line arguments. @@ -184,7 +181,7 @@ def determine_setup( dirs = get_dirs_from_args(args) if inifile: inipath_ = absolutepath(inifile) - inipath: Optional[Path] = inipath_ + inipath: Path | None = inipath_ inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: rootdir = inipath_.parent diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 69ec58c5b8c..1470424fa52 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -1,4 +1,6 @@ """Interactive debugging with PDB, the Python Debugger.""" +from __future__ import annotations + import argparse import functools import sys @@ -7,12 +9,7 @@ from typing import Any from typing import Callable from typing import Generator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from _pytest import outcomes from _pytest._code import ExceptionInfo @@ -30,7 +27,7 @@ from _pytest.runner import CallInfo -def _validate_usepdb_cls(value: str) -> Tuple[str, str]: +def _validate_usepdb_cls(value: str) -> tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") @@ -95,22 +92,22 @@ def fin() -> None: class pytestPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: Optional[PytestPluginManager] = None - _config: Optional[Config] = None - _saved: List[ - Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]] + _pluginmanager: PytestPluginManager | None = None + _config: Config | None = None + _saved: list[ + tuple[Callable[..., None], PytestPluginManager | None, Config | None] ] = [] _recursive_debug = 0 - _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None + _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None @classmethod - def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: + def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): + def _import_pdb_cls(cls, capman: CaptureManager | None): if not cls._config: import pdb @@ -149,7 +146,7 @@ def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): import _pytest.config # Type ignored because mypy doesn't support "dynamic" @@ -241,7 +238,7 @@ def _init_pdb(cls, method, *args, **kwargs): import _pytest.config if cls._pluginmanager is None: - capman: Optional[CaptureManager] = None + capman: CaptureManager | None = None else: capman = cls._pluginmanager.getplugin("capturemanager") if capman: @@ -285,7 +282,7 @@ def set_trace(cls, *args, **kwargs) -> None: class PdbInvoke: def pytest_exception_interact( - self, node: Node, call: "CallInfo[Any]", report: BaseReport + self, node: Node, call: CallInfo[Any], report: BaseReport ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 1bc2cf57e80..ceaab6da08f 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -8,6 +8,8 @@ :class:`PytestWarning`, or :class:`UnformattedWarning` in case of warnings which need to format their messages. """ +from __future__ import annotations + from warnings import warn from _pytest.warning_types import PytestDeprecationWarning diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index a0125e93c2d..4f2ddc0ea8d 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -1,4 +1,6 @@ """Discover and run doctests in modules and test files.""" +from __future__ import annotations + import bdb import functools import inspect @@ -12,17 +14,11 @@ from pathlib import Path from typing import Any from typing import Callable -from typing import Dict from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from _pytest import outcomes from _pytest._code.code import ExceptionInfo @@ -64,7 +60,7 @@ # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None +CHECKER_CLASS: type[doctest.OutputChecker] | None = None def pytest_addoption(parser: Parser) -> None: @@ -126,7 +122,7 @@ def pytest_unconfigure() -> None: def pytest_collect_file( file_path: Path, parent: Collector, -) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: +) -> DoctestModule | DoctestTextfile | None: config = parent.config if file_path.suffix == ".py": if config.option.doctestmodules and not any( @@ -160,7 +156,7 @@ def _is_main_py(path: Path) -> bool: class ReprFailDoctest(TerminalRepr): def __init__( - self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] ) -> None: self.reprlocation_lines = reprlocation_lines @@ -172,12 +168,12 @@ def toterminal(self, tw: TerminalWriter) -> None: class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> Type["doctest.DocTestRunner"]: +def _init_runner_class() -> type[doctest.DocTestRunner]: import doctest class PytestDoctestRunner(doctest.DebugRunner): @@ -189,8 +185,8 @@ class PytestDoctestRunner(doctest.DebugRunner): def __init__( self, - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: @@ -200,8 +196,8 @@ def __init__( def report_failure( self, out, - test: "doctest.DocTest", - example: "doctest.Example", + test: doctest.DocTest, + example: doctest.Example, got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) @@ -213,9 +209,9 @@ def report_failure( def report_unexpected_exception( self, out, - test: "doctest.DocTest", - example: "doctest.Example", - exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + test: doctest.DocTest, + example: doctest.Example, + exc_info: tuple[type[BaseException], BaseException, types.TracebackType], ) -> None: if isinstance(exc_info[1], OutcomeException): raise exc_info[1] @@ -231,11 +227,11 @@ def report_unexpected_exception( def _get_runner( - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> "doctest.DocTestRunner": +) -> doctest.DocTestRunner: # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -254,9 +250,9 @@ class DoctestItem(Item): def __init__( self, name: str, - parent: "Union[DoctestTextfile, DoctestModule]", - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", + parent: DoctestTextfile | DoctestModule, + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, ) -> None: super().__init__(name, parent) self.runner = runner @@ -273,18 +269,18 @@ def __init__( @classmethod def from_parent( # type: ignore cls, - parent: "Union[DoctestTextfile, DoctestModule]", + parent: DoctestTextfile | DoctestModule, *, name: str, - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, ): # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] def setup(self) -> None: @@ -297,7 +293,7 @@ def setup(self) -> None: def runtest(self) -> None: _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures: List["doctest.DocTestFailure"] = [] + failures: list[doctest.DocTestFailure] = [] # Type ignored because we change the type of `out` from what # doctest expects. self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] @@ -319,12 +315,12 @@ def _disable_output_capturing_for_darwin(self) -> None: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: import doctest - failures: Optional[ - Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = None + failures: None | ( + Sequence[doctest.DocTestFailure | doctest.UnexpectedException] + ) = None if isinstance( excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): @@ -380,11 +376,11 @@ def repr_failure( # type: ignore[override] reprlocation_lines.append((reprlocation, lines)) return ReprFailDoctest(reprlocation_lines) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: return self.path, self.dtest.lineno, "[doctest] %s" % self.name -def _get_flag_lookup() -> Dict[str, int]: +def _get_flag_lookup() -> dict[str, int]: import doctest return dict( @@ -450,7 +446,7 @@ def collect(self) -> Iterable[DoctestItem]: ) -def _check_all_skipped(test: "doctest.DocTest") -> None: +def _check_all_skipped(test: doctest.DocTest) -> None: """Raise pytest.skip() if all examples in the given DocTest have the SKIP option set.""" import doctest @@ -476,7 +472,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]: real_unwrap = inspect.unwrap def _mock_aware_unwrap( - func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None ) -> Any: try: if stop is None or stop is _is_mocked: @@ -593,7 +589,7 @@ def _from_module(self, module, object): ) -def _init_checker_class() -> Type["doctest.OutputChecker"]: +def _init_checker_class() -> type[doctest.OutputChecker]: import doctest import re @@ -661,8 +657,8 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return got offset = 0 for w, g in zip(wants, gots): - fraction: Optional[str] = w.group("fraction") - exponent: Optional[str] = w.group("exponent1") + fraction: str | None = w.group("fraction") + exponent: str | None = w.group("exponent1") if exponent is None: exponent = w.group("exponent2") precision = 0 if fraction is None else len(fraction) @@ -681,7 +677,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return LiteralsOutputChecker -def _get_checker() -> "doctest.OutputChecker": +def _get_checker() -> doctest.OutputChecker: """Return a doctest.OutputChecker subclass that supports some additional options: @@ -740,7 +736,7 @@ def _get_report_choice(key: str) -> int: @fixture(scope="session") -def doctest_namespace() -> Dict[str, Any]: +def doctest_namespace() -> dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index 1bccd18c636..cc5f8890ebc 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from typing import Generator diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 89046ddd0bd..6b51344fd0f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import dataclasses import functools @@ -12,20 +14,17 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import MutableMapping from typing import NoReturn from typing import Optional from typing import overload from typing import Sequence -from typing import Set from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar @@ -105,21 +104,21 @@ @dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): - cached_result: "_FixtureCachedResult[FixtureValue]" + cached_result: _FixtureCachedResult[FixtureValue] _scope: Scope -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._fixturemanager = FixtureManager(session) def get_scope_package( node: nodes.Item, - fixturedef: "FixtureDef[object]", -) -> Optional[Union[nodes.Item, nodes.Collector]]: + fixturedef: FixtureDef[object], +) -> nodes.Item | nodes.Collector | None: from _pytest.python import Package - current: Optional[Union[nodes.Item, nodes.Collector]] = node + current: nodes.Item | nodes.Collector | None = node while current and ( not isinstance(current, Package) or current.nodeid != fixturedef.baseid ): @@ -131,7 +130,7 @@ def get_scope_package( def get_scope_node( node: nodes.Node, scope: Scope -) -> Optional[Union[nodes.Item, nodes.Collector]]: +) -> nodes.Item | nodes.Collector | None: import _pytest.python if scope is Scope.Function: @@ -150,7 +149,7 @@ def get_scope_node( assert_never(scope) -def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: +def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: """Return fixturemarker or None if it doesn't exist or raised exceptions.""" return cast( @@ -163,8 +162,8 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: class FixtureArgKey: argname: str param_index: int - scoped_item_path: Optional[Path] - item_cls: Optional[type] + scoped_item_path: Path | None + item_cls: type | None def get_parametrized_fixture_keys( @@ -209,9 +208,9 @@ def get_parametrized_fixture_keys( # setups and teardowns. -def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {} - items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {} +def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: + argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]] = {} + items_by_argkey: dict[Scope, dict[FixtureArgKey, Deque[nodes.Item]]] = {} for scope in HIGH_SCOPES: scoped_argkeys_cache = argkeys_cache[scope] = {} scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque) @@ -229,8 +228,8 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: def fix_cache_order( item: nodes.Item, - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], - items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], + argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]], + items_by_argkey: dict[Scope, dict[FixtureArgKey, Deque[nodes.Item]]], ) -> None: for scope in HIGH_SCOPES: for key in argkeys_cache[scope].get(item, []): @@ -238,20 +237,20 @@ def fix_cache_order( def reorder_items_atscope( - items: Dict[nodes.Item, None], - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], - items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], + items: dict[nodes.Item, None], + argkeys_cache: dict[Scope, dict[nodes.Item, dict[FixtureArgKey, None]]], + items_by_argkey: dict[Scope, dict[FixtureArgKey, Deque[nodes.Item]]], scope: Scope, -) -> Dict[nodes.Item, None]: +) -> dict[nodes.Item, None]: if scope is Scope.Function or len(items) < 3: return items - ignore: Set[Optional[FixtureArgKey]] = set() + ignore: set[FixtureArgKey | None] = set() items_deque = deque(items) - items_done: Dict[nodes.Item, None] = {} + items_done: dict[nodes.Item, None] = {} scoped_items_by_argkey = items_by_argkey[scope] scoped_argkeys_cache = argkeys_cache[scope] while items_deque: - no_argkey_group: Dict[nodes.Item, None] = {} + no_argkey_group: dict[nodes.Item, None] = {} slicing_argkey = None while items_deque: item = items_deque.popleft() @@ -300,19 +299,19 @@ class FuncFixtureInfo: __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") # Fixture names that the item requests directly by function parameters. - argnames: Tuple[str, ...] + argnames: tuple[str, ...] # Fixture names that the item immediately requires. These include # argnames + fixture names specified via usefixtures and via autouse=True in # fixture definitions. - initialnames: Tuple[str, ...] + initialnames: tuple[str, ...] # The transitive closure of the fixture names that the item requires. # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). - names_closure: List[str] + names_closure: list[str] # A map from a fixture name in the transitive closure to the FixtureDefs # matching the name which are applicable to this function. # There may be multiple overriding fixtures with the same name. The # sequence is ordered from furthest to closes to the function. - name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] + name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] def prune_dependency_tree(self) -> None: """Recompute names_closure from initialnames and name2fixturedefs. @@ -325,7 +324,7 @@ def prune_dependency_tree(self) -> None: tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure: Set[str] = set() + closure: set[str] = set() working_set = set(self.initialnames) while working_set: argname = working_set.pop() @@ -351,11 +350,11 @@ class FixtureRequest(abc.ABC): def __init__( self, - pyfuncitem: "Function", - fixturename: Optional[str], - arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]], - arg2index: Dict[str, int], - fixture_defs: Dict[str, "FixtureDef[Any]"], + pyfuncitem: Function, + fixturename: str | None, + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], + arg2index: dict[str, int], + fixture_defs: dict[str, FixtureDef[Any]], *, _ispytest: bool = False, ) -> None: @@ -392,7 +391,7 @@ def __init__( self.param: Any @property - def _fixturemanager(self) -> "FixtureManager": + def _fixturemanager(self) -> FixtureManager: return self._pyfuncitem.session._fixturemanager @property @@ -406,7 +405,7 @@ def scope(self) -> _ScopeName: return self._scope.value @property - def fixturenames(self) -> List[str]: + def fixturenames(self) -> list[str]: """Names of all active fixtures in this request.""" result = list(self._pyfuncitem.fixturenames) result.extend(set(self._fixture_defs).difference(result)) @@ -418,7 +417,7 @@ def node(self): """Underlying collection node (depends on current request scope).""" raise NotImplementedError() - def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]": + def _getnextfixturedef(self, argname: str) -> FixtureDef[Any]: fixturedefs = self._arg2fixturedefs.get(argname, None) if fixturedefs is None: # We arrive here because of a dynamic call to @@ -498,7 +497,7 @@ def keywords(self) -> MutableMapping[str, Any]: return node.keywords @property - def session(self) -> "Session": + def session(self) -> Session: """Pytest session object.""" return self._pyfuncitem.session # type: ignore[no-any-return] @@ -508,7 +507,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: the last test within the requesting test context finished execution.""" raise NotImplementedError() - def applymarker(self, marker: Union[str, MarkDecorator]) -> None: + def applymarker(self, marker: str | MarkDecorator) -> None: """Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker @@ -519,7 +518,7 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> NoReturn: + def raiseerror(self, msg: str | None) -> NoReturn: """Raise a FixtureLookupError exception. :param msg: @@ -553,7 +552,7 @@ def getfixturevalue(self, argname: str) -> Any: def _get_active_fixturedef( self, argname: str - ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: + ) -> FixtureDef[object] | PseudoFixtureDef[object]: fixturedef = self._fixture_defs.get(argname) if fixturedef is None: try: @@ -567,16 +566,16 @@ def _get_active_fixturedef( self._fixture_defs[argname] = fixturedef return fixturedef - def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + def _get_fixturestack(self) -> list[FixtureDef[Any]]: current = self - values: List[FixtureDef[Any]] = [] + values: list[FixtureDef[Any]] = [] while isinstance(current, SubRequest): values.append(current._fixturedef) # type: ignore[has-type] current = current._parent_request values.reverse() return values - def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: + def _compute_fixture_value(self, fixturedef: FixtureDef[object]) -> None: """Create a SubRequest based on "self" and call the execute method of the given FixtureDef object. @@ -654,7 +653,7 @@ def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: self._schedule_finalizers(fixturedef, subrequest) def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + self, fixturedef: FixtureDef[object], subrequest: SubRequest ) -> None: # If fixture function failed it might have registered finalizers. finalizer = functools.partial(fixturedef.finish, request=subrequest) @@ -665,7 +664,7 @@ def _schedule_finalizers( class TopRequest(FixtureRequest): """The type of the ``request`` fixture in a test function.""" - def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None: + def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: super().__init__( fixturename=None, pyfuncitem=pyfuncitem, @@ -707,7 +706,7 @@ def __init__( scope: Scope, param: Any, param_index: int, - fixturedef: "FixtureDef[object]", + fixturedef: FixtureDef[object], *, _ispytest: bool = False, ) -> None: @@ -738,7 +737,7 @@ def node(self): scope = self._scope if scope is Scope.Function: # This might also be a non-function Item despite its attribute name. - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem + node: nodes.Item | nodes.Collector | None = self._pyfuncitem elif scope is Scope.Package: node = get_scope_package(self._pyfuncitem, self._fixturedef) else: @@ -769,7 +768,7 @@ def _check_scope( pytrace=False, ) - def _factorytraceback(self) -> List[str]: + def _factorytraceback(self) -> list[str]: lines = [] for fixturedef in self._get_fixturestack(): factory = fixturedef.func @@ -789,7 +788,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._fixturedef.addfinalizer(finalizer) def _schedule_finalizers( - self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + self, fixturedef: FixtureDef[object], subrequest: SubRequest ) -> None: # If the executing fixturedef was not explicitly requested in the argument list (via # getfixturevalue inside the fixture call) then ensure this fixture def will be finished @@ -809,15 +808,15 @@ class FixtureLookupError(LookupError): """Could not return a requested fixture (missing or invalid).""" def __init__( - self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + self, argname: str | None, request: FixtureRequest, msg: str | None = None ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() self.msg = msg - def formatrepr(self) -> "FixtureLookupErrorRepr": - tblines: List[str] = [] + def formatrepr(self) -> FixtureLookupErrorRepr: + tblines: list[str] = [] addline = tblines.append stack = [self.request._pyfuncitem.obj] stack.extend(map(lambda x: x.func, self.fixturestack)) @@ -866,11 +865,11 @@ def formatrepr(self) -> "FixtureLookupErrorRepr": class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: Union[str, "os.PathLike[str]"], + filename: str | os.PathLike[str], firstlineno: int, tblines: Sequence[str], errorstring: str, - argname: Optional[str], + argname: str | None, ) -> None: self.tblines = tblines self.errorstring = errorstring @@ -905,7 +904,7 @@ def fail_fixturefunc(fixturefunc, msg: str) -> NoReturn: def call_fixture_func( - fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs + fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: if is_generator(fixturefunc): fixturefunc = cast( @@ -971,16 +970,14 @@ class FixtureDef(Generic[FixtureValue]): def __init__( self, - fixturemanager: "FixtureManager", - baseid: Optional[str], + fixturemanager: FixtureManager, + baseid: str | None, argname: str, - func: "_FixtureFunc[FixtureValue]", - scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None], - params: Optional[Sequence[object]], + func: _FixtureFunc[FixtureValue], + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, + params: Sequence[object] | None, unittest: bool = False, - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + ids: None | (tuple[object | None, ...] | Callable[[Any], object | None]) = None, *, _ispytest: bool = False, ) -> None: @@ -1030,8 +1027,8 @@ def __init__( self.unittest: Final = unittest # If the fixture was executed, the current value of the fixture. # Can change if the fixture is executed with different parameters. - self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: Final[List[Callable[[], object]]] = [] + self.cached_result: _FixtureCachedResult[FixtureValue] | None = None + self._finalizers: Final[list[Callable[[], object]]] = [] @property def scope(self) -> _ScopeName: @@ -1106,7 +1103,7 @@ def __repr__(self) -> str: def resolve_fixture_function( fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> "_FixtureFunc[FixtureValue]": +) -> _FixtureFunc[FixtureValue]: """Get the actual callable that can be called to obtain the fixture value, dealing with unittest-specific instances and bound methods.""" fixturefunc = fixturedef.func @@ -1161,7 +1158,7 @@ def pytest_fixture_setup( def wrap_function_to_error_out_if_called_directly( function: FixtureFunction, - fixture_marker: "FixtureFunctionMarker", + fixture_marker: FixtureFunctionMarker, ) -> FixtureFunction: """Wrap the given fixture function so we can raise an error about it being called directly, instead of used as an argument in a test function.""" @@ -1186,13 +1183,11 @@ def result(*args, **kwargs): @final @dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] + scope: _ScopeName | Callable[[str, Config], _ScopeName] + params: tuple[object, ...] | None autouse: bool = False - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None - name: Optional[str] = None + ids: None | (tuple[object | None, ...] | Callable[[Any], object | None]) = None + name: str | None = None _ispytest: dataclasses.InitVar[bool] = False @@ -1232,13 +1227,11 @@ def __call__(self, function: FixtureFunction) -> FixtureFunction: def fixture( fixture_function: FixtureFunction, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = ..., + ids: None | (Sequence[object | None] | Callable[[Any], object | None]) = ..., + name: str | None = ..., ) -> FixtureFunction: ... @@ -1247,28 +1240,24 @@ def fixture( def fixture( # noqa: F811 fixture_function: None = ..., *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = None, + ids: None | (Sequence[object | None] | Callable[[Any], object | None]) = ..., + name: str | None = None, ) -> FixtureFunctionMarker: ... def fixture( # noqa: F811 - fixture_function: Optional[FixtureFunction] = None, + fixture_function: FixtureFunction | None = None, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", - params: Optional[Iterable[object]] = None, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Iterable[object] | None = None, autouse: bool = False, - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - name: Optional[str] = None, -) -> Union[FixtureFunctionMarker, FixtureFunction]: + ids: None | (Sequence[object | None] | Callable[[Any], object | None]) = None, + name: str | None = None, +) -> FixtureFunctionMarker | FixtureFunction: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1385,7 +1374,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: +def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: """Return all direct parametrization arguments of a node, so we don't mistake them for fixtures. @@ -1394,7 +1383,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: These things are done later as well when dealing with parametrization so this could be improved. """ - parametrize_argnames: Set[str] = set() + parametrize_argnames: set[str] = set() for marker in node.iter_markers(name="parametrize"): if not marker.kwargs.get("indirect", False): p_argnames, _ = ParameterSet._parse_parametrize_args( @@ -1404,7 +1393,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: return parametrize_argnames -def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]: +def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: """De-duplicate the sequence of names while keeping the original order.""" # Ideally we would use a set, but it does not preserve insertion order. return tuple(dict.fromkeys(name for seq in seqs for name in seq)) @@ -1444,17 +1433,17 @@ class FixtureManager: FixtureLookupError = FixtureLookupError FixtureLookupErrorRepr = FixtureLookupErrorRepr - def __init__(self, session: "Session") -> None: + def __init__(self, session: Session) -> None: self.session = session self.config: Config = session.config # Maps a fixture name (argname) to all of the FixtureDefs in the test # suite/plugins defined with this name. Populated by parsefactories(). # TODO: The order of the FixtureDefs list of each arg is significant, # explain. - self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {} - self._holderobjseen: Final[Set[object]] = set() + self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[set[object]] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Final[Dict[str, List[str]]] = { + self._nodeid_autousenames: Final[dict[str, list[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") @@ -1462,8 +1451,8 @@ def __init__(self, session: "Session") -> None: def getfixtureinfo( self, node: nodes.Item, - func: Optional[Callable[..., object]], - cls: Optional[type], + func: Callable[..., object] | None, + cls: type | None, ) -> FuncFixtureInfo: """Calculate the :class:`FuncFixtureInfo` for an item. @@ -1532,9 +1521,9 @@ def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: def getfixtureclosure( self, parentnode: nodes.Node, - initialnames: Tuple[str, ...], + initialnames: tuple[str, ...], ignore_args: AbstractSet[str], - ) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: # Collect the closure of all fixtures, starting with the given # fixturenames as the initial set. As we have to visit all # factory definitions anyway, we also return an arg2fixturedefs @@ -1545,7 +1534,7 @@ def getfixtureclosure( parentid = parentnode.nodeid fixturenames_closure = list(initialnames) - arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} lastlen = -1 while lastlen != len(fixturenames_closure): lastlen = len(fixturenames_closure) @@ -1572,7 +1561,7 @@ def sort_by_scope(arg_name: str) -> Scope: fixturenames_closure.sort(key=sort_by_scope, reverse=True) return fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc: "Metafunc") -> None: + def pytest_generate_tests(self, metafunc: Metafunc) -> None: """Generate new tests based on parametrized fixtures used by the given metafunc""" def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: @@ -1617,7 +1606,7 @@ def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: # Try next super fixture, if any. - def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: + def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: # Separate parametrized setups. items[:] = reorder_items(items) @@ -1634,7 +1623,7 @@ def parsefactories( def parsefactories( # noqa: F811 self, node_or_obj: object, - nodeid: Optional[str], + nodeid: str | None, *, unittest: bool = ..., ) -> None: @@ -1642,8 +1631,8 @@ def parsefactories( # noqa: F811 def parsefactories( # noqa: F811 self, - node_or_obj: Union[nodes.Node, object], - nodeid: Union[str, NotSetType, None] = NOTSET, + node_or_obj: nodes.Node | object, + nodeid: str | NotSetType | None = NOTSET, *, unittest: bool = False, ) -> None: @@ -1727,7 +1716,7 @@ def parsefactories( # noqa: F811 def getfixturedefs( self, argname: str, nodeid: str - ) -> Optional[Sequence[FixtureDef[Any]]]: + ) -> Sequence[FixtureDef[Any]] | None: """Get FixtureDefs for a fixture name which are applicable to a given node. diff --git a/src/_pytest/freeze_support.py b/src/_pytest/freeze_support.py index 9f8ea231fed..9ebb78980cc 100644 --- a/src/_pytest/freeze_support.py +++ b/src/_pytest/freeze_support.py @@ -1,12 +1,12 @@ """Provides a function to report all internal modules for using freezing tools.""" +from __future__ import annotations + import types from typing import Iterator -from typing import List -from typing import Union -def freeze_includes() -> List[str]: +def freeze_includes() -> list[str]: """Return a list of module names used by pytest that should be included by cx_freeze.""" import _pytest @@ -16,7 +16,7 @@ def freeze_includes() -> List[str]: def _iter_all_modules( - package: Union[str, types.ModuleType], + package: str | types.ModuleType, prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 364bf4c4276..b286950896c 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -1,11 +1,10 @@ """Version info, help messages, tracing configuration.""" +from __future__ import annotations + import os import sys from argparse import Action from typing import Generator -from typing import List -from typing import Optional -from typing import Union import pytest from _pytest.config import Config @@ -147,7 +146,7 @@ def showversion(config: Config) -> None: sys.stdout.write(f"pytest {pytest.__version__}\n") -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.version > 0: showversion(config) return 0 @@ -162,7 +161,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: def showhelp(config: Config) -> None: import textwrap - reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( "terminalreporter" ) assert reporter is not None @@ -239,7 +238,7 @@ def showhelp(config: Config) -> None: conftest_options = [("pytest_plugins", "list of plugin names to load")] -def getpluginversioninfo(config: Config) -> List[str]: +def getpluginversioninfo(config: Config) -> list[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -251,7 +250,7 @@ def getpluginversioninfo(config: Config) -> List[str]: return lines -def pytest_report_header(config: Config) -> List[str]: +def pytest_report_header(config: Config) -> list[str]: lines = [] if config.option.debug or config.option.traceconfig: lines.append(f"using: pytest-{pytest.__version__}") diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index c1963b4d7b2..a2d9a6021fb 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -1,15 +1,12 @@ """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" +from __future__ import annotations + from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Mapping -from typing import Optional from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from pluggy import HookspecMarker @@ -50,7 +47,7 @@ @hookspec(historic=True) -def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: +def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: """Called at plugin registration time to allow adding new hooks via a call to :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. @@ -63,7 +60,7 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: @hookspec(historic=True) def pytest_plugin_registered( - plugin: "_PluggyPlugin", manager: "PytestPluginManager" + plugin: _PluggyPlugin, manager: PytestPluginManager ) -> None: """A new pytest plugin got registered. @@ -76,7 +73,7 @@ def pytest_plugin_registered( @hookspec(historic=True) -def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: """Register argparse-style options and ini-style config values, called once at the beginning of a test run. @@ -115,7 +112,7 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> @hookspec(historic=True) -def pytest_configure(config: "Config") -> None: +def pytest_configure(config: Config) -> None: """Allow plugins and conftest files to perform initial configuration. This hook is called for every plugin and initial conftest file @@ -139,8 +136,8 @@ def pytest_configure(config: "Config") -> None: @hookspec(firstresult=True) def pytest_cmdline_parse( - pluginmanager: "PytestPluginManager", args: List[str] -) -> Optional["Config"]: + pluginmanager: PytestPluginManager, args: list[str] +) -> Config | None: """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. @@ -157,7 +154,7 @@ def pytest_cmdline_parse( @hookspec(firstresult=True) -def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: +def pytest_cmdline_main(config: Config) -> ExitCode | int | None: """Called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. @@ -169,7 +166,7 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: def pytest_load_initial_conftests( - early_config: "Config", parser: "Parser", args: List[str] + early_config: Config, parser: Parser, args: list[str] ) -> None: """Called to implement the loading of initial conftest files ahead of command line option parsing. @@ -189,7 +186,7 @@ def pytest_load_initial_conftests( @hookspec(firstresult=True) -def pytest_collection(session: "Session") -> Optional[object]: +def pytest_collection(session: Session) -> object | None: """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. @@ -226,7 +223,7 @@ def pytest_collection(session: "Session") -> Optional[object]: def pytest_collection_modifyitems( - session: "Session", config: "Config", items: List["Item"] + session: Session, config: Config, items: list[Item] ) -> None: """Called after collection has been performed. May filter or re-order the items in-place. @@ -237,7 +234,7 @@ def pytest_collection_modifyitems( """ -def pytest_collection_finish(session: "Session") -> None: +def pytest_collection_finish(session: Session) -> None: """Called after collection has been performed and modified. :param session: The pytest session object. @@ -245,7 +242,7 @@ def pytest_collection_finish(session: "Session") -> None: @hookspec(firstresult=True) -def pytest_ignore_collect(collection_path: Path, config: "Config") -> Optional[bool]: +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: """Return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling @@ -267,7 +264,7 @@ def pytest_ignore_collect(collection_path: Path, config: "Config") -> Optional[b @hookspec(firstresult=True) -def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]": +def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: """Create a :class:`~pytest.Collector` for the given directory, or None if not relevant. @@ -287,7 +284,7 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle """ -def pytest_collect_file(file_path: Path, parent: "Collector") -> "Optional[Collector]": +def pytest_collect_file(file_path: Path, parent: Collector) -> Collector | None: """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. For best results, the returned collector should be a subclass of @@ -310,7 +307,7 @@ def pytest_collect_file(file_path: Path, parent: "Collector") -> "Optional[Colle # logging hooks for collection -def pytest_collectstart(collector: "Collector") -> None: +def pytest_collectstart(collector: Collector) -> None: """Collector starts collecting. :param collector: @@ -318,7 +315,7 @@ def pytest_collectstart(collector: "Collector") -> None: """ -def pytest_itemcollected(item: "Item") -> None: +def pytest_itemcollected(item: Item) -> None: """We just collected a test item. :param item: @@ -326,7 +323,7 @@ def pytest_itemcollected(item: "Item") -> None: """ -def pytest_collectreport(report: "CollectReport") -> None: +def pytest_collectreport(report: CollectReport) -> None: """Collector finished collecting. :param report: @@ -334,7 +331,7 @@ def pytest_collectreport(report: "CollectReport") -> None: """ -def pytest_deselected(items: Sequence["Item"]) -> None: +def pytest_deselected(items: Sequence[Item]) -> None: """Called for deselected test items, e.g. by keyword. May be called multiple times. @@ -345,7 +342,7 @@ def pytest_deselected(items: Sequence["Item"]) -> None: @hookspec(firstresult=True) -def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]": +def pytest_make_collect_report(collector: Collector) -> CollectReport | None: """Perform :func:`collector.collect() ` and return a :class:`~pytest.CollectReport`. @@ -362,7 +359,7 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor @hookspec(firstresult=True) -def pytest_pycollect_makemodule(module_path: Path, parent) -> Optional["Module"]: +def pytest_pycollect_makemodule(module_path: Path, parent) -> Module | None: """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. @@ -385,8 +382,8 @@ def pytest_pycollect_makemodule(module_path: Path, parent) -> Optional["Module"] @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: + collector: Module | Class, name: str, obj: object +) -> None | Item | Collector | list[Item | Collector]: """Return a custom item/collector for a Python object in a module, or None. Stops at first non-None result, see :ref:`firstresult`. @@ -403,7 +400,7 @@ def pytest_pycollect_makeitem( @hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. @@ -413,7 +410,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """ -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: """Generate (multiple) parametrized calls to a test function. :param metafunc: @@ -422,9 +419,7 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None: @hookspec(firstresult=True) -def pytest_make_parametrize_id( - config: "Config", val: object, argname: str -) -> Optional[str]: +def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls, or None if the hook doesn't know about ``val``. @@ -445,7 +440,7 @@ def pytest_make_parametrize_id( @hookspec(firstresult=True) -def pytest_runtestloop(session: "Session") -> Optional[object]: +def pytest_runtestloop(session: Session) -> object | None: """Perform the main runtest loop (after collection finished). The default hook implementation performs the runtest protocol for all items @@ -466,9 +461,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]: @hookspec(firstresult=True) -def pytest_runtest_protocol( - item: "Item", nextitem: "Optional[Item]" -) -> Optional[object]: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: """Perform the runtest protocol for a single test item. The default runtest protocol is this (see individual hooks for full details): @@ -503,9 +496,7 @@ def pytest_runtest_protocol( """ -def pytest_runtest_logstart( - nodeid: str, location: Tuple[str, Optional[int], str] -) -> None: +def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: """Called at the start of running the runtest protocol for a single item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. @@ -518,7 +509,7 @@ def pytest_runtest_logstart( def pytest_runtest_logfinish( - nodeid: str, location: Tuple[str, Optional[int], str] + nodeid: str, location: tuple[str, int | None, str] ) -> None: """Called at the end of running the runtest protocol for a single item. @@ -531,7 +522,7 @@ def pytest_runtest_logfinish( """ -def pytest_runtest_setup(item: "Item") -> None: +def pytest_runtest_setup(item: Item) -> None: """Called to perform the setup phase for a test item. The default implementation runs ``setup()`` on ``item`` and all of its @@ -544,7 +535,7 @@ def pytest_runtest_setup(item: "Item") -> None: """ -def pytest_runtest_call(item: "Item") -> None: +def pytest_runtest_call(item: Item) -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. @@ -554,7 +545,7 @@ def pytest_runtest_call(item: "Item") -> None: """ -def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: """Called to perform the teardown phase for a test item. The default implementation runs the finalizers and calls ``teardown()`` @@ -573,9 +564,7 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: @hookspec(firstresult=True) -def pytest_runtest_makereport( - item: "Item", call: "CallInfo[None]" -) -> Optional["TestReport"]: +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: """Called to create a :class:`~pytest.TestReport` for each of the setup, call and teardown runtest phases of a test item. @@ -588,7 +577,7 @@ def pytest_runtest_makereport( """ -def pytest_runtest_logreport(report: "TestReport") -> None: +def pytest_runtest_logreport(report: TestReport) -> None: """Process the :class:`~pytest.TestReport` produced for each of the setup, call and teardown runtest phases of an item. @@ -598,9 +587,9 @@ def pytest_runtest_logreport(report: "TestReport") -> None: @hookspec(firstresult=True) def pytest_report_to_serializable( - config: "Config", - report: Union["CollectReport", "TestReport"], -) -> Optional[Dict[str, Any]]: + config: Config, + report: CollectReport | TestReport, +) -> dict[str, Any] | None: """Serialize the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON. @@ -611,9 +600,9 @@ def pytest_report_to_serializable( @hookspec(firstresult=True) def pytest_report_from_serializable( - config: "Config", - data: Dict[str, Any], -) -> Optional[Union["CollectReport", "TestReport"]]: + config: Config, + data: dict[str, Any], +) -> CollectReport | TestReport | None: """Restore a report object previously serialized with :hook:`pytest_report_to_serializable`. @@ -628,8 +617,8 @@ def pytest_report_from_serializable( @hookspec(firstresult=True) def pytest_fixture_setup( - fixturedef: "FixtureDef[Any]", request: "SubRequest" -) -> Optional[object]: + fixturedef: FixtureDef[Any], request: SubRequest +) -> object | None: """Perform fixture setup execution. :param fixturdef: @@ -649,7 +638,7 @@ def pytest_fixture_setup( def pytest_fixture_post_finalizer( - fixturedef: "FixtureDef[Any]", request: "SubRequest" + fixturedef: FixtureDef[Any], request: SubRequest ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not @@ -667,7 +656,7 @@ def pytest_fixture_post_finalizer( # ------------------------------------------------------------------------- -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. @@ -676,8 +665,8 @@ def pytest_sessionstart(session: "Session") -> None: def pytest_sessionfinish( - session: "Session", - exitstatus: Union[int, "ExitCode"], + session: Session, + exitstatus: int | ExitCode, ) -> None: """Called after whole test run finished, right before returning the exit status to the system. @@ -686,7 +675,7 @@ def pytest_sessionfinish( """ -def pytest_unconfigure(config: "Config") -> None: +def pytest_unconfigure(config: Config) -> None: """Called before test process is exited. :param config: The pytest config object. @@ -699,8 +688,8 @@ def pytest_unconfigure(config: "Config") -> None: def pytest_assertrepr_compare( - config: "Config", op: str, left: object, right: object -) -> Optional[List[str]]: + config: Config, op: str, left: object, right: object +) -> list[str] | None: """Return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list @@ -715,7 +704,7 @@ def pytest_assertrepr_compare( """ -def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None: +def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: """Called whenever an assertion passes. .. versionadded:: 5.0 @@ -749,8 +738,8 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No def pytest_report_header( # type:ignore[empty-body] - config: "Config", start_path: Path -) -> Union[str, List[str]]: + config: Config, start_path: Path +) -> str | list[str]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param config: The pytest config object. @@ -780,10 +769,10 @@ def pytest_report_header( # type:ignore[empty-body] def pytest_report_collectionfinish( # type:ignore[empty-body] - config: "Config", + config: Config, start_path: Path, - items: Sequence["Item"], -) -> Union[str, List[str]]: + items: Sequence[Item], +) -> str | list[str]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -814,8 +803,8 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] @hookspec(firstresult=True) def pytest_report_teststatus( # type:ignore[empty-body] - report: Union["CollectReport", "TestReport"], config: "Config" -) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": + report: CollectReport | TestReport, config: Config +) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: """Return result-category, shortletter and verbose word for status reporting. @@ -841,9 +830,9 @@ def pytest_report_teststatus( # type:ignore[empty-body] def pytest_terminal_summary( - terminalreporter: "TerminalReporter", - exitstatus: "ExitCode", - config: "Config", + terminalreporter: TerminalReporter, + exitstatus: ExitCode, + config: Config, ) -> None: """Add a section to terminal summary reporting. @@ -858,10 +847,10 @@ def pytest_terminal_summary( @hookspec(historic=True) def pytest_warning_recorded( - warning_message: "warnings.WarningMessage", - when: "Literal['config', 'collect', 'runtest']", + warning_message: warnings.WarningMessage, + when: Literal["config", "collect", "runtest"], nodeid: str, - location: Optional[Tuple[str, int, str]], + location: tuple[str, int, str] | None, ) -> None: """Process a warning captured by the internal pytest warnings plugin. @@ -894,8 +883,8 @@ def pytest_warning_recorded( def pytest_markeval_namespace( # type:ignore[empty-body] - config: "Config", -) -> Dict[str, Any]: + config: Config, +) -> dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -917,9 +906,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body] def pytest_internalerror( - excrepr: "ExceptionRepr", - excinfo: "ExceptionInfo[BaseException]", -) -> Optional[bool]: + excrepr: ExceptionRepr, + excinfo: ExceptionInfo[BaseException], +) -> bool | None: """Called for internal errors. Return True to suppress the fallback handling of printing an @@ -931,7 +920,7 @@ def pytest_internalerror( def pytest_keyboard_interrupt( - excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", + excinfo: ExceptionInfo[KeyboardInterrupt | Exit], ) -> None: """Called for keyboard interrupt. @@ -940,9 +929,9 @@ def pytest_keyboard_interrupt( def pytest_exception_interact( - node: Union["Item", "Collector"], - call: "CallInfo[Any]", - report: Union["CollectReport", "TestReport"], + node: Item | Collector, + call: CallInfo[Any], + report: CollectReport | TestReport, ) -> None: """Called when an exception was raised which can potentially be interactively handled. @@ -965,7 +954,7 @@ def pytest_exception_interact( """ -def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called upon pdb.set_trace(). Can be used by plugins to take special action just before the python @@ -976,7 +965,7 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: """ -def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 9ee35b84e84..b95314cf9e4 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -6,6 +6,8 @@ Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ +from __future__ import annotations + import functools import os import platform @@ -13,12 +15,7 @@ import xml.etree.ElementTree as ET from datetime import datetime from typing import Callable -from typing import Dict -from typing import List from typing import Match -from typing import Optional -from typing import Tuple -from typing import Union import pytest from _pytest import nodes @@ -87,15 +84,15 @@ def merge_family(left, right) -> None: class _NodeReporter: - def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: + def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: self.id = nodeid self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family self.duration = 0.0 - self.properties: List[Tuple[str, str]] = [] - self.nodes: List[ET.Element] = [] - self.attrs: Dict[str, str] = {} + self.properties: list[tuple[str, str]] = [] + self.nodes: list[ET.Element] = [] + self.attrs: dict[str, str] = {} def append(self, node: ET.Element) -> None: self.xml.add_stats(node.tag) @@ -107,7 +104,7 @@ def add_property(self, name: str, value: object) -> None: def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self) -> Optional[ET.Element]: + def make_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.properties: properties = ET.Element("properties") @@ -122,7 +119,7 @@ def record_testreport(self, testreport: TestReport) -> None: classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs: Dict[str, str] = { + attrs: dict[str, str] = { "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], @@ -154,7 +151,7 @@ def to_xml(self) -> ET.Element: testcase.extend(self.nodes) return testcase - def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None: + def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: node = ET.Element(tag, message=message) node.text = bin_xml_escape(data) self.append(node) @@ -199,7 +196,7 @@ def append_failure(self, report: TestReport) -> None: self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( + reprcrash: ReprFileLocation | None = getattr( report.longrepr, "reprcrash", None ) if reprcrash is not None: @@ -219,9 +216,7 @@ def append_collect_skipped(self, report: TestReport) -> None: def append_error(self, report: TestReport) -> None: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( - report.longrepr, "reprcrash", None - ) + reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) if reprcrash is not None: reason = reprcrash.message else: @@ -450,7 +445,7 @@ def pytest_unconfigure(config: Config) -> None: config.pluginmanager.unregister(xml) -def mangle_test_address(address: str) -> List[str]: +def mangle_test_address(address: str) -> list[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") # Convert file path to dotted path. @@ -465,7 +460,7 @@ class LogXML: def __init__( self, logfile, - prefix: Optional[str], + prefix: str | None, suite_name: str = "pytest", logging: str = "no", report_duration: str = "total", @@ -480,17 +475,15 @@ def __init__( self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats: Dict[str, int] = dict.fromkeys( + self.stats: dict[str, int] = dict.fromkeys( ["error", "passed", "failure", "skipped"], 0 ) - self.node_reporters: Dict[ - Tuple[Union[str, TestReport], object], _NodeReporter - ] = {} - self.node_reporters_ordered: List[_NodeReporter] = [] - self.global_properties: List[Tuple[str, str]] = [] + self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} + self.node_reporters_ordered: list[_NodeReporter] = [] + self.global_properties: list[tuple[str, str]] = [] # List of reports that failed on call but teardown is pending. - self.open_reports: List[TestReport] = [] + self.open_reports: list[TestReport] = [] self.cnt_double_fail_tests = 0 # Replaces convenience family with real family. @@ -509,8 +502,8 @@ def finalize(self, report: TestReport) -> None: if reporter is not None: reporter.finalize() - def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: - nodeid: Union[str, TestReport] = getattr(report, "nodeid", report) + def node_reporter(self, report: TestReport | str) -> _NodeReporter: + nodeid: str | TestReport = getattr(report, "nodeid", report) # Local hack to handle xdist report order. workernode = getattr(report, "node", None) @@ -690,7 +683,7 @@ def add_global_property(self, name: str, value: object) -> None: _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) - def _get_global_properties_node(self) -> Optional[ET.Element]: + def _get_global_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.global_properties: properties = ET.Element("properties") diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index b2dd87436e1..69db5ad532d 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,4 +1,6 @@ """Add backward compatibility support for the legacy py path type.""" +from __future__ import annotations + import dataclasses import os import shlex @@ -6,10 +8,7 @@ from pathlib import Path from typing import Final from typing import final -from typing import List -from typing import Optional from typing import TYPE_CHECKING -from typing import Union from iniconfig import SectionWrapper @@ -45,7 +44,7 @@ # fmt: on -def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: """Internal wrapper to prepare lazy proxies for legacy_path instances""" return LEGACY_PATH(path) @@ -61,8 +60,8 @@ class Testdir: __test__ = False - CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN - TimeoutExpired: "Final" = Pytester.TimeoutExpired + CLOSE_STDIN: Final = Pytester.CLOSE_STDIN + TimeoutExpired: Final = Pytester.TimeoutExpired def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -156,7 +155,7 @@ def copy_example(self, name=None) -> LEGACY_PATH: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: + def getnode(self, config: Config, arg) -> Item | Collector | None: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -164,7 +163,7 @@ def getpathnode(self, path): """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: list[Item | Collector]) -> list[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -216,9 +215,7 @@ def getmodulecol(self, source, configargs=(), withinit=False): source, configargs=configargs, withinit=withinit ) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """See :meth:`Pytester.collect_by_name`.""" return self._pytester.collect_by_name(modcol, name) @@ -249,13 +246,11 @@ def runpytest_subprocess(self, *args, timeout=None) -> RunResult: """See :meth:`Pytester.runpytest_subprocess`.""" return self._pytester.runpytest_subprocess(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn_pytest`.""" return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn`.""" return self._pytester.spawn(cmd, expect_timeout=expect_timeout) @@ -385,7 +380,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: +def Config_inifile(self: Config) -> LEGACY_PATH | None: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. @@ -405,9 +400,7 @@ def Session_stardir(self: Session) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config__getini_unknown_type( - self, name: str, type: str, value: Union[str, List[str]] -): +def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): if type == "pathlist": # TODO: This assert is probably not valid in all cases. assert self.inipath is not None diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 5426c35131f..b13f0bab046 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -1,4 +1,6 @@ """Access and control log capturing.""" +from __future__ import annotations + import io import logging import os @@ -20,12 +22,8 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from _pytest import nodes from _pytest._io import TerminalWriter @@ -65,7 +63,7 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: + def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: if datefmt and "%f" in datefmt: ct = self.converter(record.created) tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) @@ -96,7 +94,7 @@ def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._terminalwriter = terminalwriter self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: Dict[int, str] = {} + self._level_to_fmt_mapping: dict[int, str] = {} for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): self.add_color_level(level, *color_opts) @@ -145,12 +143,12 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: + def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: super().__init__(fmt) self._auto_indent = self._get_auto_indent(auto_indent) @staticmethod - def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: + def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: """Determine the current auto indentation setting. Specify auto indent behavior (on/off/fixed) by passing in @@ -339,7 +337,7 @@ class catching_logs(Generic[_HandlerType]): __slots__ = ("handler", "level", "orig_level") - def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: + def __init__(self, handler: _HandlerType, level: int | None = None) -> None: self.handler = handler self.level = level @@ -355,9 +353,9 @@ def __enter__(self) -> _HandlerType: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: root_logger = logging.getLogger() if self.level is not None: @@ -371,7 +369,7 @@ class LogCaptureHandler(logging_StreamHandler): def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) - self.records: List[logging.LogRecord] = [] + self.records: list[logging.LogRecord] = [] def emit(self, record: logging.LogRecord) -> None: """Keep the log records in a list in addition to the log text.""" @@ -402,10 +400,10 @@ class LogCaptureFixture: def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._item = item - self._initial_handler_level: Optional[int] = None + self._initial_handler_level: int | None = None # Dict of log name -> log level. - self._initial_logger_levels: Dict[Optional[str], int] = {} - self._initial_disabled_logging_level: Optional[int] = None + self._initial_logger_levels: dict[str | None, int] = {} + self._initial_disabled_logging_level: int | None = None def _finalize(self) -> None: """Finalize the fixture. @@ -430,7 +428,7 @@ def handler(self) -> LogCaptureHandler: def get_records( self, when: Literal["setup", "call", "teardown"] - ) -> List[logging.LogRecord]: + ) -> list[logging.LogRecord]: """Get the logging records for one of the possible test phases. :param when: @@ -449,12 +447,12 @@ def text(self) -> str: return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self) -> List[logging.LogRecord]: + def records(self) -> list[logging.LogRecord]: """The list of log records.""" return self.handler.records @property - def record_tuples(self) -> List[Tuple[str, int, str]]: + def record_tuples(self) -> list[tuple[str, int, str]]: """A list of a stripped down version of log records intended for use in assertion comparison. @@ -465,7 +463,7 @@ def record_tuples(self) -> List[Tuple[str, int, str]]: return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self) -> List[str]: + def messages(self) -> list[str]: """A list of format-interpolated log messages. Unlike 'records', which contains the format string and parameters for @@ -488,7 +486,7 @@ def clear(self) -> None: self.handler.clear() def _force_enable_logging( - self, level: Union[int, str], logger_obj: logging.Logger + self, level: int | str, logger_obj: logging.Logger ) -> int: """Enable the desired logging level if the global level was disabled via ``logging.disabled``. @@ -521,7 +519,7 @@ def _force_enable_logging( return original_disable_level - def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: + def set_level(self, level: int | str, logger: str | None = None) -> None: """Set the threshold level of a logger for the duration of a test. Logging messages which are less severe than this level will not be captured. @@ -548,7 +546,7 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non @contextmanager def at_level( - self, level: Union[int, str], logger: Optional[str] = None + self, level: int | str, logger: str | None = None ) -> Generator[None, None, None]: """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the level is restored to its original @@ -606,7 +604,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: result._finalize() -def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: +def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: @@ -689,9 +687,9 @@ def __init__(self, config: Config) -> None: assert terminal_reporter is not None capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. - self.log_cli_handler: Union[ - _LiveLoggingStreamHandler, _LiveLoggingNullHandler - ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + self.log_cli_handler: ( + _LiveLoggingStreamHandler | _LiveLoggingNullHandler + ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) else: self.log_cli_handler = _LiveLoggingNullHandler() log_cli_formatter = self._create_formatter( @@ -702,7 +700,7 @@ def __init__(self, config: Config) -> None: self.log_cli_handler.setFormatter(log_cli_formatter) self._disable_loggers(loggers_to_disable=config.option.logger_disable) - def _disable_loggers(self, loggers_to_disable: List[str]) -> None: + def _disable_loggers(self, loggers_to_disable: list[str]) -> None: if not loggers_to_disable: return @@ -827,7 +825,7 @@ def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, Non def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("setup") - empty: Dict[str, List[logging.LogRecord]] = {} + empty: dict[str, list[logging.LogRecord]] = {} item.stash[caplog_records_key] = empty yield from self._runtest_for(item, "setup") @@ -890,7 +888,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): def __init__( self, terminal_reporter: TerminalReporter, - capture_manager: Optional[CaptureManager], + capture_manager: CaptureManager | None, ) -> None: super().__init__(stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager @@ -902,7 +900,7 @@ def reset(self) -> None: """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when: Optional[str]) -> None: + def set_when(self, when: str | None) -> None: """Prepare for the given test phase (setup/call/teardown).""" self._when = when self._section_name_shown = False diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 51be84164b2..295cd49757b 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -1,4 +1,6 @@ """Core implementation of the testing process: init, session, runtest loop.""" +from __future__ import annotations + import argparse import dataclasses import fnmatch @@ -12,16 +14,11 @@ from typing import Callable from typing import Dict from typing import final -from typing import FrozenSet from typing import Iterable from typing import Iterator -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence -from typing import Tuple -from typing import Union import pluggy @@ -256,8 +253,8 @@ def is_ancestor(base: Path, query: Path) -> bool: def wrap_session( - config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] -) -> Union[int, ExitCode]: + config: Config, doit: Callable[[Config, Session], int | ExitCode | None] +) -> int | ExitCode: """Skeleton command line program.""" session = Session.from_config(config) session.exitstatus = ExitCode.OK @@ -276,7 +273,7 @@ def wrap_session( session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED + exitstatus: int | ExitCode = ExitCode.INTERRUPTED if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -314,11 +311,11 @@ def wrap_session( return session.exitstatus -def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: +def pytest_cmdline_main(config: Config) -> int | ExitCode: return wrap_session(config, _main) -def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: +def _main(config: Config, session: Session) -> int | ExitCode | None: """Default command line protocol for initialization, session, running tests and reporting.""" config.hook.pytest_collection(session=session) @@ -331,11 +328,11 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: return None -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: session.perform_collect() -def pytest_runtestloop(session: "Session") -> bool: +def pytest_runtestloop(session: Session) -> bool: if session.testsfailed and not session.config.option.continue_on_collection_errors: raise session.Interrupted( "%d error%s during collection" @@ -375,7 +372,7 @@ def _in_venv(path: Path) -> bool: return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: ignore_paths = config._getconftest_pathlist( "collect_ignore", path=collection_path.parent ) @@ -412,11 +409,11 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: return Dir.from_parent(parent, path=path) -def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: +def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return @@ -490,7 +487,7 @@ def from_parent( # type: ignore[override] parent: nodes.Collector, # type: ignore[override] *, path: Path, - ) -> "Dir": + ) -> Dir: """The public constructor. :param parent: The parent collector of this Dir. @@ -498,9 +495,9 @@ def from_parent( # type: ignore[override] """ return super().from_parent(parent=parent, path=path) # type: ignore[no-any-return] - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path): @@ -537,7 +534,7 @@ class Session(nodes.Collector): _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. _fixturemanager: FixtureManager - exitstatus: Union[int, ExitCode] + exitstatus: int | ExitCode def __init__(self, config: Config) -> None: super().__init__( @@ -550,22 +547,22 @@ def __init__(self, config: Config) -> None: ) self.testsfailed = 0 self.testscollected = 0 - self._shouldstop: Union[bool, str] = False - self._shouldfail: Union[bool, str] = False + self._shouldstop: bool | str = False + self._shouldfail: bool | str = False self.trace = config.trace.root.get("collection") - self._initialpaths: FrozenSet[Path] = frozenset() - self._initialpaths_with_parents: FrozenSet[Path] = frozenset() - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[Tuple[Path, List[str]]] = [] - self._collection_cache: Dict[nodes.Collector, CollectReport] = {} - self.items: List[nodes.Item] = [] + self._initialpaths: frozenset[Path] = frozenset() + self._initialpaths_with_parents: frozenset[Path] = frozenset() + self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: list[tuple[Path, list[str]]] = [] + self._collection_cache: dict[nodes.Collector, CollectReport] = {} + self.items: list[nodes.Item] = [] - self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) + self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) self.config.pluginmanager.register(self, name="session") @classmethod - def from_config(cls, config: Config) -> "Session": + def from_config(cls, config: Config) -> Session: session: Session = cls._create(config=config) return session @@ -579,11 +576,11 @@ def __repr__(self) -> str: ) @property - def shouldstop(self) -> Union[bool, str]: + def shouldstop(self) -> bool | str: return self._shouldstop @shouldstop.setter - def shouldstop(self, value: Union[bool, str]) -> None: + def shouldstop(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldstop: @@ -597,11 +594,11 @@ def shouldstop(self, value: Union[bool, str]) -> None: self._shouldstop = value @property - def shouldfail(self) -> Union[bool, str]: + def shouldfail(self) -> bool | str: return self._shouldfail @shouldfail.setter - def shouldfail(self, value: Union[bool, str]) -> None: + def shouldfail(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldfail: @@ -634,9 +631,7 @@ def pytest_collectstart(self) -> None: raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport( - self, report: Union[TestReport, CollectReport] - ) -> None: + def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: if report.failed and not hasattr(report, "wasxfail"): self.testsfailed += 1 maxfail = self.config.getvalue("maxfail") @@ -647,7 +642,7 @@ def pytest_runtest_logreport( def isinitpath( self, - path: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, with_parents: bool = False, ) -> bool: @@ -669,7 +664,7 @@ def isinitpath( else: return path_ in self._initialpaths - def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: + def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager @@ -689,7 +684,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: def _collect_path( self, path: Path, - path_cache: Dict[Path, Sequence[nodes.Collector]], + path_cache: dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: """Create a Collector for the given path. @@ -701,7 +696,7 @@ def _collect_path( if path.is_dir(): ihook = self.gethookproxy(path.parent) - col: Optional[nodes.Collector] = ihook.pytest_collect_directory( + col: nodes.Collector | None = ihook.pytest_collect_directory( path=path, parent=self ) cols: Sequence[nodes.Collector] = (col,) if col is not None else () @@ -719,19 +714,19 @@ def _collect_path( @overload def perform_collect( - self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... + self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... ) -> Sequence[nodes.Item]: ... @overload def perform_collect( # noqa: F811 - self, args: Optional[Sequence[str]] = ..., genitems: bool = ... - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + self, args: Sequence[str] | None = ..., genitems: bool = ... + ) -> Sequence[nodes.Item | nodes.Collector]: ... def perform_collect( # noqa: F811 - self, args: Optional[Sequence[str]] = None, genitems: bool = True - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + self, args: Sequence[str] | None = None, genitems: bool = True + ) -> Sequence[nodes.Item | nodes.Collector]: """Perform the collection phase for this session. This is called by the default :hook:`pytest_collection` hook @@ -757,10 +752,10 @@ def perform_collect( # noqa: F811 self._initial_parts = [] self._collection_cache = {} self.items = [] - items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items + items: Sequence[nodes.Item | nodes.Collector] = self.items try: - initialpaths: List[Path] = [] - initialpaths_with_parents: List[Path] = [] + initialpaths: list[Path] = [] + initialpaths_with_parents: list[Path] = [] for arg in args: fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, @@ -815,7 +810,7 @@ def _collect_one_node( self, node: nodes.Collector, handle_dupes: bool = True, - ) -> Tuple[CollectReport, bool]: + ) -> tuple[CollectReport, bool]: if node in self._collection_cache and handle_dupes: rep = self._collection_cache[node] return rep, True @@ -824,11 +819,11 @@ def _collect_one_node( self._collection_cache[node] = rep return rep, False - def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterator[nodes.Item | nodes.Collector]: # This is a cache for the root directories of the initial paths. # We can't use collection_cache for Session because of its special # role as the bootstrapping collector. - path_cache: Dict[Path, Sequence[nodes.Collector]] = {} + path_cache: dict[Path, Sequence[nodes.Collector]] = {} pm = self.config.pluginmanager @@ -852,9 +847,9 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # and discarding all nodes which don't match the level's part. any_matched_in_initial_part = False notfound_collectors = [] - work: List[ - Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]] - ] = [(self, paths + names)] + work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ + (self, paths + names) + ] while work: matchnode, matchparts = work.pop() @@ -871,7 +866,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # Collect this level of matching. # Collecting Session (self) is done directly to avoid endless # recursion to this function. - subnodes: Sequence[Union[nodes.Collector, nodes.Item]] + subnodes: Sequence[nodes.Collector | nodes.Item] if isinstance(matchnode, Session): assert isinstance(matchparts[0], Path) subnodes = matchnode._collect_path(matchparts[0], path_cache) @@ -920,9 +915,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: self.trace.root.indent -= 1 - def genitems( - self, node: Union[nodes.Item, nodes.Collector] - ) -> Iterator[nodes.Item]: + def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) @@ -961,7 +954,7 @@ def search_pypath(module_name: str) -> str: def resolve_collection_argument( invocation_path: Path, arg: str, *, as_pypath: bool = False -) -> Tuple[Path, List[str]]: +) -> tuple[Path, list[str]]: """Parse path arguments optionally containing selection parts and return (fspath, names). Command-line arguments can point to files and/or directories, and optionally contain diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index bcee802f3b1..a8b2dec8430 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,11 +1,11 @@ """Generic mechanism for marking and selecting python functions.""" +from __future__ import annotations + import dataclasses from typing import AbstractSet from typing import Collection -from typing import List from typing import Optional from typing import TYPE_CHECKING -from typing import Union from .expression import Expression from .expression import ParseError @@ -42,8 +42,8 @@ def param( *values: object, - marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (), - id: Optional[str] = None, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, ) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures `. @@ -110,7 +110,7 @@ def pytest_addoption(parser: Parser) -> None: @hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: import _pytest.config if config.option.markers: @@ -149,7 +149,7 @@ class KeywordMatcher: _names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "KeywordMatcher": + def from_item(cls, item: Item) -> KeywordMatcher: mapped_names = set() # Add the names of the current item and any parent items, @@ -189,7 +189,7 @@ def __call__(self, subname: str) -> bool: return False -def deselect_by_keyword(items: "List[Item]", config: Config) -> None: +def deselect_by_keyword(items: list[Item], config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return @@ -221,7 +221,7 @@ class MarkMatcher: own_mark_names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "MarkMatcher": + def from_item(cls, item: Item) -> MarkMatcher: mark_names = {mark.name for mark in item.iter_markers()} return cls(mark_names) @@ -229,14 +229,14 @@ def __call__(self, name: str) -> bool: return name in self.own_mark_names -def deselect_by_mark(items: "List[Item]", config: Config) -> None: +def deselect_by_mark(items: list[Item], config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") - remaining: List[Item] = [] - deselected: List[Item] = [] + remaining: list[Item] = [] + deselected: list[Item] = [] for item in items: if expr.evaluate(MarkMatcher.from_item(item)): remaining.append(item) @@ -254,7 +254,7 @@ def _parse_expression(expr: str, exc_message: str) -> Expression: raise UsageError(f"{exc_message}: {expr}: {e}") from None -def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: +def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index b995518bf92..4869174b572 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -14,6 +14,8 @@ - ident evaluates to True of False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. """ +from __future__ import annotations + import ast import dataclasses import enum @@ -23,7 +25,6 @@ from typing import Iterator from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence __all__ = [ @@ -103,7 +104,7 @@ def lex(self, input: str) -> Iterator[Token]: ) yield Token(TokenType.EOF, "", pos) - def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: if self.current.type is type: token = self.current if token.type is not TokenType.EOF: @@ -195,7 +196,7 @@ def __init__(self, code: types.CodeType) -> None: self.code = code @classmethod - def compile(self, input: str) -> "Expression": + def compile(self, input: str) -> Expression: """Compile a match expression. :param input: The input expression - one line. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 55ec67700b6..dba9641840a 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import collections.abc import dataclasses import inspect @@ -8,16 +10,11 @@ from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Mapping from typing import MutableMapping from typing import NamedTuple -from typing import Optional from typing import overload from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -45,7 +42,7 @@ def istestfunc(func) -> bool: def get_empty_parameterset_mark( config: Config, argnames: Sequence[str], func -) -> "MarkDecorator": +) -> MarkDecorator: from ..nodes import Collector fs, lineno = getfslineno(func) @@ -73,17 +70,17 @@ def get_empty_parameterset_mark( class ParameterSet(NamedTuple): - values: Sequence[Union[object, NotSetType]] - marks: Collection[Union["MarkDecorator", "Mark"]] - id: Optional[str] + values: Sequence[object | NotSetType] + marks: Collection[MarkDecorator | Mark] + id: str | None @classmethod def param( cls, *values: object, - marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), - id: Optional[str] = None, - ) -> "ParameterSet": + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, + ) -> ParameterSet: if isinstance(marks, MarkDecorator): marks = (marks,) else: @@ -98,9 +95,9 @@ def param( @classmethod def extract_from( cls, - parameterset: Union["ParameterSet", Sequence[object], object], + parameterset: ParameterSet | Sequence[object] | object, force_tuple: bool = False, - ) -> "ParameterSet": + ) -> ParameterSet: """Extract from an object or objects. :param parameterset: @@ -126,11 +123,11 @@ def extract_from( @staticmethod def _parse_parametrize_args( - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *args, **kwargs, - ) -> Tuple[Sequence[str], bool]: + ) -> tuple[Sequence[str], bool]: if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -140,9 +137,9 @@ def _parse_parametrize_args( @staticmethod def _parse_parametrize_parameters( - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argvalues: Iterable[ParameterSet | Sequence[object] | object], force_tuple: bool, - ) -> List["ParameterSet"]: + ) -> list[ParameterSet]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @@ -150,12 +147,12 @@ def _parse_parametrize_parameters( @classmethod def _for_parametrize( cls, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], func, config: Config, nodeid: str, - ) -> Tuple[Sequence[str], List["ParameterSet"]]: + ) -> tuple[Sequence[str], list[ParameterSet]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -198,24 +195,24 @@ class Mark: #: Name of the mark. name: str #: Positional arguments of the mark decorator. - args: Tuple[Any, ...] + args: tuple[Any, ...] #: Keyword arguments of the mark decorator. kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + _param_ids_generated: Sequence[str] | None = dataclasses.field( default=None, repr=False ) def __init__( self, name: str, - args: Tuple[Any, ...], + args: tuple[Any, ...], kwargs: Mapping[str, Any], - param_ids_from: Optional["Mark"] = None, - param_ids_generated: Optional[Sequence[str]] = None, + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -231,7 +228,7 @@ def __init__( def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: "Mark") -> "Mark": + def combined_with(self, other: Mark) -> Mark: """Return a new Mark which is a combination of this Mark and another Mark. @@ -243,7 +240,7 @@ def combined_with(self, other: "Mark") -> "Mark": assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None + param_ids_from: Mark | None = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -314,7 +311,7 @@ def name(self) -> str: return self.mark.name @property - def args(self) -> Tuple[Any, ...]: + def args(self) -> tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -328,7 +325,7 @@ def markname(self) -> str: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: """Return a MarkDecorator with extra arguments added. Unlike calling the MarkDecorator, with_args() can be used even @@ -345,7 +342,7 @@ def __call__(self, arg: Markable) -> Markable: # type: ignore[misc] pass @overload - def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: pass def __call__(self, *args: object, **kwargs: object): @@ -360,10 +357,10 @@ def __call__(self, *args: object, **kwargs: object): def get_unpacked_marks( - obj: Union[object, type], + obj: object | type, *, consider_mro: bool = True, -) -> List[Mark]: +) -> list[Mark]: """Obtain the unpacked marks that are stored on an object. If obj is a class and consider_mro is true, return marks applied to @@ -392,9 +389,7 @@ def get_unpacked_marks( return list(normalize_mark_list(mark_list)) -def normalize_mark_list( - mark_list: Iterable[Union[Mark, MarkDecorator]] -) -> Iterable[Mark]: +def normalize_mark_list(mark_list: Iterable[Mark | MarkDecorator]) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks by retrieving the `mark` attribute on MarkDecorator instances. @@ -437,14 +432,14 @@ def __call__(self, arg: Markable) -> Markable: ... @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": + def __call__(self, reason: str = ...) -> MarkDecorator: ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], + condition: str | bool = ..., + *conditions: str | bool, reason: str = ..., ) -> MarkDecorator: ... @@ -457,13 +452,11 @@ def __call__(self, arg: Markable) -> Markable: @overload def __call__( self, - condition: Union[str, bool] = False, - *conditions: Union[str, bool], + condition: str | bool = False, + *conditions: str | bool, reason: str = ..., run: bool = ..., - raises: Union[ - None, Type[BaseException], Tuple[Type[BaseException], ...] - ] = ..., + raises: type[BaseException] | tuple[type[BaseException], ...] | None = ..., strict: bool = ..., ) -> MarkDecorator: ... @@ -471,17 +464,16 @@ def __call__( class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *, - indirect: Union[bool, Sequence[str]] = ..., - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ] = ..., - scope: Optional[_ScopeName] = ..., + indirect: bool | Sequence[str] = ..., + ids: None + | ( + Iterable[None | str | float | int | bool] + | Callable[[Any], object | None] + ) = ..., + scope: _ScopeName | None = ..., ) -> MarkDecorator: ... @@ -521,8 +513,8 @@ def test_function(): def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._config: Optional[Config] = None - self._markers: Set[str] = set() + self._config: Config | None = None + self._markers: set[str] = set() def __getattr__(self, name: str) -> MarkDecorator: """Generate a new :class:`MarkDecorator` with the given name.""" @@ -573,7 +565,7 @@ def __getattr__(self, name: str) -> MarkDecorator: class NodeKeywords(MutableMapping[str, Any]): __slots__ = ("node", "parent", "_markers") - def __init__(self, node: "Node") -> None: + def __init__(self, node: Node) -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} @@ -601,7 +593,7 @@ def __contains__(self, key: object) -> bool: def update( # type: ignore[override] self, - other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), **kwds: Any, ) -> None: self._markers.update(other) diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 834700b1b4c..3d7217b50fb 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -1,4 +1,6 @@ """Monkeypatching and mocking functionality.""" +from __future__ import annotations + import os import re import sys @@ -7,14 +9,10 @@ from typing import Any from typing import final from typing import Generator -from typing import List from typing import Mapping from typing import MutableMapping -from typing import Optional from typing import overload -from typing import Tuple from typing import TypeVar -from typing import Union from _pytest.fixtures import fixture from _pytest.warning_types import PytestWarning @@ -27,7 +25,7 @@ @fixture -def monkeypatch() -> Generator["MonkeyPatch", None, None]: +def monkeypatch() -> Generator[MonkeyPatch, None, None]: """A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -96,7 +94,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: return obj -def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: +def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: if not isinstance(import_path, str) or "." not in import_path: raise TypeError(f"must be absolute import path string, not {import_path!r}") module, attr = import_path.rsplit(".", 1) @@ -129,14 +127,14 @@ class MonkeyPatch: """ def __init__(self) -> None: - self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: Optional[str] = None - self._savesyspath: Optional[List[str]] = None + self._setattr: list[tuple[object, str, object]] = [] + self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] + self._cwd: str | None = None + self._savesyspath: list[str] | None = None @classmethod @contextmanager - def context(cls) -> Generator["MonkeyPatch", None, None]: + def context(cls) -> Generator[MonkeyPatch, None, None]: """Context manager that returns a new :class:`MonkeyPatch` object which undoes any patching done inside the ``with`` block upon exit. @@ -183,8 +181,8 @@ def setattr( def setattr( self, - target: Union[str, object], - name: Union[object, str], + target: str | object, + name: object | str, value: object = notset, raising: bool = True, ) -> None: @@ -255,8 +253,8 @@ def setattr( def delattr( self, - target: Union[object, str], - name: Union[str, Notset] = notset, + target: object | str, + name: str | Notset = notset, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -311,7 +309,7 @@ def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict del dic[name] # type: ignore[attr-defined] - def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: + def setenv(self, name: str, value: str, prepend: str | None = None) -> None: """Set environment variable ``name`` to ``value``. If ``prepend`` is a character, read the current environment variable @@ -367,7 +365,7 @@ def syspath_prepend(self, path) -> None: invalidate_caches() - def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: + def chdir(self, path: str | os.PathLike[str]) -> None: """Change the current working directory to the specified path. :param path: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 4cf6768e605..2b65ff92f2c 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import os import warnings @@ -9,16 +11,10 @@ from typing import cast from typing import Iterable from typing import Iterator -from typing import List from typing import MutableMapping -from typing import Optional from typing import overload -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import pluggy @@ -68,7 +64,7 @@ def iterparentnodeids(nodeid: str) -> Iterator[str]: Note that / components are only considered until the first ::. """ pos = 0 - first_colons: Optional[int] = nodeid.find("::") + first_colons: int | None = nodeid.find("::") if first_colons == -1: first_colons = None # The root Session node - always present. @@ -164,11 +160,11 @@ class Node(abc.ABC, metaclass=NodeMeta): def __init__( self, name: str, - parent: "Optional[Node]" = None, - config: Optional[Config] = None, - session: "Optional[Session]" = None, - path: Optional[Path] = None, - nodeid: Optional[str] = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + path: Path | None = None, + nodeid: str | None = None, ) -> None: #: A unique name within the scope of the parent node. self.name: str = name @@ -203,10 +199,10 @@ def __init__( self.keywords: MutableMapping[str, Any] = NodeKeywords(self) #: The marker objects belonging to this node. - self.own_markers: List[Mark] = [] + self.own_markers: list[Mark] = [] #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: Set[str] = set() + self.extra_keyword_matches: set[str] = set() if nodeid is not None: assert "::()" not in nodeid @@ -223,7 +219,7 @@ def __init__( self._store = self.stash @classmethod - def from_parent(cls, parent: "Node", **kw): + def from_parent(cls, parent: Node, **kw): """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -301,23 +297,21 @@ def setup(self) -> None: def teardown(self) -> None: pass - def listchain(self) -> List["Node"]: + def listchain(self) -> list[Node]: """Return list of all parent collectors up to self, starting from the root of collection tree. :returns: The nodes. """ chain = [] - item: Optional[Node] = self + item: Node | None = self while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker( - self, marker: Union[str, MarkDecorator], append: bool = True - ) -> None: + def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: """Dynamically add a marker object to the node. :param marker: @@ -339,7 +333,7 @@ def add_marker( else: self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: + def iter_markers(self, name: str | None = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -348,8 +342,8 @@ def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: return (x[1] for x in self.iter_markers_with_node(name=name)) def iter_markers_with_node( - self, name: Optional[str] = None - ) -> Iterator[Tuple["Node", Mark]]: + self, name: str | None = None + ) -> Iterator[tuple[Node, Mark]]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -361,16 +355,14 @@ def iter_markers_with_node( yield node, mark @overload - def get_closest_marker(self, name: str) -> Optional[Mark]: + def get_closest_marker(self, name: str) -> Mark | None: ... @overload def get_closest_marker(self, name: str, default: Mark) -> Mark: ... - def get_closest_marker( - self, name: str, default: Optional[Mark] = None - ) -> Optional[Mark]: + def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: """Return the first marker matching the name, from closest (for example function) to farther level (for example module level). @@ -379,14 +371,14 @@ def get_closest_marker( """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self) -> Set[str]: + def listextrakeywords(self) -> set[str]: """Return a set of all extra keywords in self and any parents.""" - extra_keywords: Set[str] = set() + extra_keywords: set[str] = set() for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords - def listnames(self) -> List[str]: + def listnames(self) -> list[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: @@ -398,14 +390,14 @@ def addfinalizer(self, fin: Callable[[], object]) -> None: """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: + def getparent(self, cls: type[_NodeType]) -> _NodeType | None: """Get the next parent node (including self) which is an instance of the given class. :param cls: The node class to search for. :returns: The node, if found. """ - current: Optional[Node] = self + current: Node | None = self while current and not isinstance(current, cls): current = current.parent assert current is None or isinstance(current, cls) @@ -417,7 +409,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, + style: _TracebackStyle | None = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError @@ -429,7 +421,7 @@ def _repr_failure_py( if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] if self.config.getoption("fulltrace", False): style = "long" tbfilter = False @@ -472,8 +464,8 @@ def _repr_failure_py( def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, - ) -> Union[str, TerminalRepr]: + style: _TracebackStyle | None = None, + ) -> str | TerminalRepr: """Return a representation of a collection or test failure. .. seealso:: :ref:`non-python tests` @@ -483,7 +475,7 @@ def repr_failure( return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: +def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: """Try to extract the actual location from a node, depending on available attributes: * "location": a pair (path, lineno) @@ -493,7 +485,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. - location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) + location: tuple[str, int | None, str] | None = getattr(node, "location", None) if location is not None: return location[:2] obj = getattr(node, "obj", None) @@ -513,14 +505,14 @@ class CollectError(Exception): """An error during collection, contains a custom message.""" @abc.abstractmethod - def collect(self) -> Iterable[Union["Item", "Collector"]]: + def collect(self) -> Iterable[Item | Collector]: """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException] - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: """Return a representation of a collection failure. :param excinfo: Exception information for the failure. @@ -549,7 +541,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: return excinfo.traceback -def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: +def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None: for initial_path in session._initialpaths: if commonpath(path, initial_path) == initial_path: rel = str(path.relative_to(initial_path)) @@ -562,13 +554,13 @@ class FSCollector(Collector, abc.ABC): def __init__( self, - path_or_parent: Optional[Union[Path, Node]] = None, - path: Optional[Path] = None, - name: Optional[str] = None, - parent: Optional[Node] = None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + path_or_parent: Path | Node | None = None, + path: Path | None = None, + name: str | None = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, ) -> None: if path_or_parent: if isinstance(path_or_parent, Node): @@ -618,7 +610,7 @@ def from_parent( cls, parent, *, - path: Optional[Path] = None, + path: Path | None = None, **kw, ): """The public constructor.""" @@ -662,9 +654,9 @@ def __init__( self, name, parent=None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, **kw, ) -> None: # The first two arguments are intentionally passed positionally, @@ -679,11 +671,11 @@ def __init__( nodeid=nodeid, **kw, ) - self._report_sections: List[Tuple[str, str, str]] = [] + self._report_sections: list[tuple[str, str, str]] = [] #: A list of tuples (name, value) that holds user defined properties #: for this test. - self.user_properties: List[Tuple[str, object]] = [] + self.user_properties: list[tuple[str, object]] = [] self._check_item_and_collector_diamond_inheritance() @@ -743,7 +735,7 @@ def add_report_section(self, when: str, key: str, content: str) -> None: if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: """Get location information for this item for test reports. Returns a tuple with three elements: @@ -757,7 +749,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str return self.path, None, "" @cached_property - def location(self) -> Tuple[str, Optional[int], str]: + def location(self) -> tuple[str, int | None, str]: """ Returns a tuple of ``(relfspath, lineno, testname)`` for this item where ``relfspath`` is file path relative to ``config.rootpath`` diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 8710ba3e85d..f917f0276a7 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -1,11 +1,12 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" +from __future__ import annotations + import sys from typing import Any from typing import Callable from typing import cast from typing import NoReturn -from typing import Optional from typing import Protocol from typing import Type from typing import TypeVar @@ -15,7 +16,7 @@ class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info about test and collection outcomes.""" - def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: + def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: if msg is not None and not isinstance(msg, str): error_msg = ( # type: ignore[unreachable] "{} expected string as 'msg' parameter, got '{}' instead.\n" @@ -44,7 +45,7 @@ class Skipped(OutcomeException): def __init__( self, - msg: Optional[str] = None, + msg: str | None = None, pytrace: bool = True, allow_module_level: bool = False, *, @@ -67,7 +68,7 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: Optional[int] = None + self, msg: str = "unknown reason", returncode: int | None = None ) -> None: self.msg = msg self.returncode = returncode @@ -101,7 +102,7 @@ def decorate(func: _F) -> _WithException[_F, _ET]: @_with_exception(Exit) def exit( reason: str = "", - returncode: Optional[int] = None, + returncode: int | None = None, ) -> NoReturn: """Exit testing process. @@ -191,7 +192,7 @@ def xfail(reason: str = "") -> NoReturn: def importorskip( - modname: str, minversion: Optional[str] = None, reason: Optional[str] = None + modname: str, minversion: str | None = None, reason: str | None = None ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 22c7a622373..7dbf61e77fa 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -1,8 +1,9 @@ """Submit failure or test session information to a pastebin service.""" +from __future__ import annotations + import tempfile from io import StringIO from typing import IO -from typing import Union import pytest from _pytest.config import Config @@ -66,7 +67,7 @@ def pytest_unconfigure(config: Config) -> None: tr.write_line("pastebin session-log: %s\n" % pastebinurl) -def create_new_paste(contents: Union[str, bytes]) -> str: +def create_new_paste(contents: str | bytes) -> str: """Create a new paste using the bpaste.net service. :contents: Paste contents string. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 4cd635ed7e1..877933d24d6 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import atexit import contextlib import fnmatch @@ -24,16 +26,9 @@ from posixpath import sep as posix_sep from types import ModuleType from typing import Callable -from typing import Dict from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union from _pytest.compat import assert_never from _pytest.outcomes import skip @@ -70,10 +65,10 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: def on_rm_rf_error( func, path: str, - excinfo: Union[ - BaseException, - Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], - ], + excinfo: ( + BaseException + | tuple[type[BaseException], BaseException, types.TracebackType | None] + ), *, start_path: Path, ) -> bool: @@ -203,9 +198,7 @@ def parse_num(maybe_num) -> int: return -1 -def _force_symlink( - root: Path, target: Union[str, PurePath], link_to: Union[str, Path] -) -> None: +def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: """Helper to create the current symlink. It's full of race conditions that are reasonably OK to ignore @@ -417,7 +410,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: +def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: """A port of FNMatcher from py.path.common which works with PurePath() instances. The difference between this algorithm and PurePath.match() is that the @@ -453,7 +446,7 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: return fnmatch.fnmatch(name, pattern) -def parts(s: str) -> Set[str]: +def parts(s: str) -> set[str]: parts = s.split(sep) return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} @@ -484,9 +477,9 @@ class ImportPathMismatchError(ImportError): def import_path( - p: Union[str, "os.PathLike[str]"], + p: str | os.PathLike[str], *, - mode: Union[str, ImportMode] = ImportMode.prepend, + mode: str | ImportMode = ImportMode.prepend, root: Path, ) -> ModuleType: """Import and return a module from the given path, which can be a file (a module) or @@ -631,7 +624,7 @@ def module_name_from_path(path: Path, root: Path) -> str: return ".".join(path_parts) -def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None: +def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: """ Used by ``import_path`` to create intermediate modules when using mode=importlib. @@ -640,8 +633,8 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> otherwise "src.tests.test_foo" is not importable by ``__import__``. """ module_parts = module_name.split(".") - child_module: Union[ModuleType, None] = None - module: Union[ModuleType, None] = None + child_module: ModuleType | None = None + module: ModuleType | None = None child_name: str = "" while module_name: if module_name not in modules: @@ -672,7 +665,7 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> module_name = ".".join(module_parts) -def resolve_package_path(path: Path) -> Optional[Path]: +def resolve_package_path(path: Path) -> Path | None: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. @@ -690,9 +683,9 @@ def resolve_package_path(path: Path) -> Optional[Path]: def scandir( - path: Union[str, "os.PathLike[str]"], - sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name, -) -> List["os.DirEntry[str]"]: + path: str | os.PathLike[str], + sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, +) -> list[os.DirEntry[str]]: """Scan a directory recursively, in breadth-first order. The returned entries are sorted according to the given key. @@ -715,8 +708,8 @@ def scandir( def visit( - path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] -) -> Iterator["os.DirEntry[str]"]: + path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool] +) -> Iterator[os.DirEntry[str]]: """Walk a directory recursively, in breadth-first order. The `recurse` predicate determines whether a directory is recursed. @@ -730,7 +723,7 @@ def visit( yield from visit(entry.path, recurse) -def absolutepath(path: Union[Path, str]) -> Path: +def absolutepath(path: Path | str) -> Path: """Convert a path to an absolute path using os.path.abspath. Prefer this over Path.resolve() (see #6523). @@ -739,7 +732,7 @@ def absolutepath(path: Union[Path, str]) -> Path: return Path(os.path.abspath(str(path))) -def commonpath(path1: Path, path2: Path) -> Optional[Path]: +def commonpath(path1: Path, path2: Path) -> Path | None: """Return the common part shared with the other path, or None if there is no common part. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index d388758a2d0..dfa97ba3389 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -2,6 +2,8 @@ PYTEST_DONT_REWRITE """ +from __future__ import annotations + import collections.abc import contextlib import gc @@ -19,22 +21,16 @@ from pathlib import Path from typing import Any from typing import Callable -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from weakref import WeakKeyDictionary from iniconfig import IniConfig @@ -120,7 +116,7 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: - def get_open_files(self) -> List[Tuple[str, str]]: + def get_open_files(self) -> list[tuple[str, str]]: if sys.version_info >= (3, 11): # New in Python 3.11, ignores utf-8 mode encoding = locale.getencoding() @@ -196,7 +192,7 @@ def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object] @fixture -def _pytest(request: FixtureRequest) -> "PytestArg": +def _pytest(request: FixtureRequest) -> PytestArg: """Return a helper which offers a gethookrecorder(hook) method which returns a HookRecorder instance which helps to make assertions about called hooks.""" @@ -207,13 +203,13 @@ class PytestArg: def __init__(self, request: FixtureRequest) -> None: self._request = request - def gethookrecorder(self, hook) -> "HookRecorder": + def gethookrecorder(self, hook) -> HookRecorder: hookrecorder = HookRecorder(hook._pm) self._request.addfinalizer(hookrecorder.finish_recording) return hookrecorder -def get_public_names(values: Iterable[str]) -> List[str]: +def get_public_names(values: Iterable[str]) -> list[str]: """Only return names from iterator values without a leading underscore.""" return [x for x in values if x[0] != "_"] @@ -263,8 +259,8 @@ def __init__( check_ispytest(_ispytest) self._pluginmanager = pluginmanager - self.calls: List[RecordedHookCall] = [] - self.ret: Optional[Union[int, ExitCode]] = None + self.calls: list[RecordedHookCall] = [] + self.ret: int | ExitCode | None = None def before(hook_name: str, hook_impls, kwargs) -> None: self.calls.append(RecordedHookCall(hook_name, kwargs)) @@ -277,13 +273,13 @@ def after(outcome, hook_name: str, hook_impls, kwargs) -> None: def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]: + def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: """Get all recorded calls to hooks with the given names (or name).""" if isinstance(names, str): names = names.split() return [call for call in self.calls if call._name in names] - def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: + def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) @@ -324,45 +320,48 @@ def getcall(self, name: str) -> RecordedHookCall: @overload def getreports( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getreports( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str + | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: ... def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str + | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [x.report for x in self.getcalls(names)] def matchreport( self, inamepart: str = "", - names: Union[str, Iterable[str]] = ( + names: str + | Iterable[str] = ( "pytest_runtest_logreport", "pytest_collectreport", ), - when: Optional[str] = None, - ) -> Union[CollectReport, TestReport]: + when: str | None = None, + ) -> CollectReport | TestReport: """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): @@ -389,34 +388,36 @@ def matchreport( @overload def getfailures( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getfailures( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str + | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: ... def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str + | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [rep for rep in self.getreports(names) if rep.failed] def getfailedcollections(self) -> Sequence[CollectReport]: @@ -424,10 +425,10 @@ def getfailedcollections(self) -> Sequence[CollectReport]: def listoutcomes( self, - ) -> Tuple[ + ) -> tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ]: passed = [] skipped = [] @@ -446,7 +447,7 @@ def listoutcomes( failed.append(rep) return passed, skipped, failed - def countoutcomes(self) -> List[int]: + def countoutcomes(self) -> list[int]: return [len(x) for x in self.listoutcomes()] def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: @@ -466,14 +467,14 @@ def clear(self) -> None: @fixture -def linecomp() -> "LineComp": +def linecomp() -> LineComp: """A :class: `LineComp` instance for checking that an input linearly contains a sequence of strings.""" return LineComp() @fixture(name="LineMatcher") -def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: +def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: """A reference to the :class: `LineMatcher`. This is instantiable with a list of lines (without their trailing newlines). @@ -485,7 +486,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture def pytester( request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> "Pytester": +) -> Pytester: """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -529,13 +530,13 @@ class RunResult: def __init__( self, - ret: Union[int, ExitCode], - outlines: List[str], - errlines: List[str], + ret: int | ExitCode, + outlines: list[str], + errlines: list[str], duration: float, ) -> None: try: - self.ret: Union[int, ExitCode] = ExitCode(ret) + self.ret: int | ExitCode = ExitCode(ret) """The return value.""" except ValueError: self.ret = ret @@ -560,7 +561,7 @@ def __repr__(self) -> str: % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) ) - def parseoutcomes(self) -> Dict[str, int]: + def parseoutcomes(self) -> dict[str, int]: """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. @@ -573,7 +574,7 @@ def parseoutcomes(self) -> Dict[str, int]: return self.parse_summary_nouns(self.outlines) @classmethod - def parse_summary_nouns(cls, lines) -> Dict[str, int]: + def parse_summary_nouns(cls, lines) -> dict[str, int]: """Extract the nouns from a pytest terminal summary line. It always returns the plural noun for consistency:: @@ -604,8 +605,8 @@ def assert_outcomes( errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """ Assert that the specified outcomes appear with the respective @@ -631,7 +632,7 @@ def assert_outcomes( class SysModulesSnapshot: - def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -664,7 +665,7 @@ class Pytester: __test__ = False - CLOSE_STDIN: "Final" = NOTSET + CLOSE_STDIN: Final = NOTSET class TimeoutExpired(Exception): pass @@ -680,7 +681,7 @@ def __init__( check_ispytest(_ispytest) self._request = request self._mod_collections: WeakKeyDictionary[ - Collector, List[Union[Item, Collector]] + Collector, list[Item | Collector] ] = WeakKeyDictionary() if request.function: name: str = request.function.__name__ @@ -692,7 +693,7 @@ def __init__( #: :py:meth:`runpytest`. Initially this is an empty list but plugins can #: be added to the list. The type of items to add to the list depends on #: the method using them so refer to them for details. - self.plugins: List[Union[str, _PluggyPlugin]] = [] + self.plugins: list[str | _PluggyPlugin] = [] self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() self._request.addfinalizer(self._finalize) @@ -760,8 +761,8 @@ def chdir(self) -> None: def _makefile( self, ext: str, - lines: Sequence[Union[Any, bytes]], - files: Dict[str, str], + lines: Sequence[Any | bytes], + files: dict[str, str], encoding: str = "utf-8", ) -> Path: items = list(files.items()) @@ -771,7 +772,7 @@ def _makefile( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" ) - def to_text(s: Union[Any, bytes]) -> str: + def to_text(s: Any | bytes) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) if lines: @@ -894,9 +895,7 @@ def test_something(pytester): """ return self._makefile(".txt", args, kwargs) - def syspathinsert( - self, path: Optional[Union[str, "os.PathLike[str]"]] = None - ) -> None: + def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: """Prepend a directory to sys.path, defaults to :attr:`path`. This is undone automatically when this object dies at the end of each @@ -910,7 +909,7 @@ def syspathinsert( self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkdir(self, name: str | os.PathLike[str]) -> Path: """Create a new (sub)directory. :param name: @@ -922,7 +921,7 @@ def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.mkdir() return p - def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkpydir(self, name: str | os.PathLike[str]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -933,7 +932,7 @@ def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.joinpath("__init__.py").touch() return p - def copy_example(self, name: Optional[str] = None) -> Path: + def copy_example(self, name: str | None = None) -> Path: """Copy file from project's directory into the testdir. :param name: @@ -978,9 +977,7 @@ def copy_example(self, name: Optional[str] = None) -> Path: f'example "{example_path}" is not found as a file or directory' ) - def getnode( - self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: """Get the collection node of a file. :param config: @@ -999,9 +996,7 @@ def getnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode( - self, path: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to @@ -1021,7 +1016,7 @@ def getpathnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the @@ -1033,7 +1028,7 @@ def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: The collected items. """ session = colitems[0].session - result: List[Item] = [] + result: list[Item] = [] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -1067,7 +1062,7 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: values = list(cmdlineargs) + [p] return self.inline_run(*values) - def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: + def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: """Run ``pytest.main(['--collect-only'])`` in-process. Runs the :py:func:`pytest.main` function to run all of pytest inside @@ -1080,7 +1075,7 @@ def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: def inline_run( self, - *args: Union[str, "os.PathLike[str]"], + *args: str | os.PathLike[str], plugins=(), no_reraise_ctrlc: bool = False, ) -> HookRecorder: @@ -1150,7 +1145,7 @@ class reprec: # type: ignore finalizer() def runpytest_inprocess( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any + self, *args: str | os.PathLike[str], **kwargs: Any ) -> RunResult: """Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.""" @@ -1193,9 +1188,7 @@ class reprec: # type: ignore res.reprec = reprec # type: ignore return res - def runpytest( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any - ) -> RunResult: + def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: """Run pytest inline or in a subprocess, depending on the command line option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" new_args = self._ensure_basetemp(args) @@ -1206,8 +1199,8 @@ def runpytest( raise RuntimeError(f"Unrecognized runpytest option: {self._method}") def _ensure_basetemp( - self, args: Sequence[Union[str, "os.PathLike[str]"]] - ) -> List[Union[str, "os.PathLike[str]"]]: + self, args: Sequence[str | os.PathLike[str]] + ) -> list[str | os.PathLike[str]]: new_args = list(args) for x in new_args: if str(x).startswith("--basetemp"): @@ -1216,7 +1209,7 @@ def _ensure_basetemp( new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp")) return new_args - def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfig(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest :class:`pytest.Config` instance from given commandline args. @@ -1240,7 +1233,7 @@ def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: self._request.addfinalizer(config._ensure_unconfigure) return config - def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like @@ -1252,7 +1245,7 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: return config def getitem( - self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" + self, source: str | os.PathLike[str], funcname: str = "test_func" ) -> Item: """Return the test item for a test function. @@ -1275,7 +1268,7 @@ def getitem( funcname, source, items ) - def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: + def getitems(self, source: str | os.PathLike[str]) -> list[Item]: """Return all test items collected from the module. Writes the source to a Python file and runs pytest's collection on @@ -1286,7 +1279,7 @@ def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: def getmodulecol( self, - source: Union[str, "os.PathLike[str]"], + source: str | os.PathLike[str], configargs=(), *, withinit: bool = False, @@ -1318,9 +1311,7 @@ def getmodulecol( self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """Return the collection node for name from the module collection. Searches a module collection node for a collection node matching the @@ -1338,10 +1329,10 @@ def collect_by_name( def popen( self, - cmdargs: Sequence[Union[str, "os.PathLike[str]"]], - stdout: Union[int, TextIO] = subprocess.PIPE, - stderr: Union[int, TextIO] = subprocess.PIPE, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1376,9 +1367,9 @@ def popen( def run( self, - *cmdargs: Union[str, "os.PathLike[str]"], - timeout: Optional[float] = None, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1467,10 +1458,10 @@ def _dump_lines(self, lines, fp): except UnicodeEncodeError: print(f"couldn't print to {fp} because of encoding") - def _getpytestargs(self) -> Tuple[str, ...]: + def _getpytestargs(self) -> tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script: "os.PathLike[str]") -> RunResult: + def runpython(self, script: os.PathLike[str]) -> RunResult: """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) @@ -1479,7 +1470,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1506,9 +1497,7 @@ def runpytest_subprocess( args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect. This makes sure to use the right pytest and sets up the temporary @@ -1522,7 +1511,7 @@ def spawn_pytest( cmd = f"{invoke} --basetemp={basetemp} {string}" return self.spawn(cmd, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run a command using pexpect. The pexpect child is returned. @@ -1567,9 +1556,9 @@ class LineMatcher: ``text.splitlines()``. """ - def __init__(self, lines: List[str]) -> None: + def __init__(self, lines: list[str]) -> None: self.lines = lines - self._log_output: List[str] = [] + self._log_output: list[str] = [] def __str__(self) -> str: """Return the entire original text. @@ -1579,7 +1568,7 @@ def __str__(self) -> str: """ return "\n".join(self.lines) - def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: + def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py index 657e4db5fc3..0d2be1f8627 100644 --- a/src/_pytest/pytester_assertions.py +++ b/src/_pytest/pytester_assertions.py @@ -3,21 +3,19 @@ # contain them itself, since it is imported by the `pytest` module, # hence cannot be subject to assertion rewriting, which requires a # module to not be already imported. -from typing import Dict -from typing import Optional +from __future__ import annotations + from typing import Sequence -from typing import Tuple -from typing import Union from _pytest.reports import CollectReport from _pytest.reports import TestReport def assertoutcome( - outcomes: Tuple[ + outcomes: tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ], passed: int = 0, skipped: int = 0, @@ -36,15 +34,15 @@ def assertoutcome( def assert_outcomes( - outcomes: Dict[str, int], + outcomes: dict[str, int], passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 184399080d0..937da4a7735 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,4 +1,6 @@ """Python test discovery, setup and run of test functions.""" +from __future__ import annotations + import abc import dataclasses import enum @@ -20,15 +22,10 @@ from typing import Generator from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Union import _pytest from _pytest import fixtures @@ -131,7 +128,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.showfixtures: showfixtures(config) return 0 @@ -141,7 +138,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) @@ -181,7 +178,7 @@ def async_warn_and_skip(nodeid: str) -> None: @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: testfunction = pyfuncitem.obj if is_async_function(testfunction): async_warn_and_skip(pyfuncitem.nodeid) @@ -202,7 +199,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: pkginit = path / "__init__.py" if pkginit.is_file(): pkg: Package = Package.from_parent(parent, path=path) @@ -210,7 +207,7 @@ def pytest_collect_directory( return None -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: +def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): if not path_matches_patterns( @@ -230,15 +227,15 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": +def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: mod: Module = Module.from_parent(parent, path=module_path) return mod @hookimpl(trylast=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + collector: Module | Class, name: str, obj: object +) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): @@ -348,7 +345,7 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> parts.reverse() return ".".join(parts) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: # XXX caching? obj = self.obj compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) @@ -358,7 +355,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str assert file_path is not None if file_path.endswith(".pyc"): file_path = file_path[:-1] - path: Union["os.PathLike[str]", str] = file_path + path: os.PathLike[str] | str = file_path lineno = compat_co_firstlineno else: path, lineno = getfslineno(obj) @@ -430,7 +427,7 @@ def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: return True return False - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not getattr(self.obj, "__test__", True): return [] @@ -442,11 +439,11 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: # In each class, nodes should be definition ordered. # __dict__ is definition ordered. - seen: Set[str] = set() - dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] + seen: set[str] = set() + dict_values: list[list[nodes.Item | nodes.Collector]] = [] ihook = self.ihook for dic in dicts: - values: List[Union[nodes.Item, nodes.Collector]] = [] + values: list[nodes.Item | nodes.Collector] = [] # Note: seems like the dict can change during iteration - # be careful not to remove the list() without consideration. for name, obj in list(dic.items()): @@ -473,7 +470,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: result.extend(values) return result - def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: + def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: modulecol = self.getparent(Module) assert modulecol is not None module = modulecol.obj @@ -581,7 +578,7 @@ class Module(nodes.File, PyCollector): def _getobj(self): return importtestmodule(self.path, self.config) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: self._inject_setup_module_fixture() self._inject_setup_function_fixture() self.session._fixturemanager.parsefactories(self) @@ -676,7 +673,7 @@ def __init__( config=None, session=None, nodeid=None, - path: Optional[Path] = None, + path: Path | None = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. # super().__init__(self, fspath, parent=parent) @@ -707,13 +704,13 @@ def setup(self) -> None: func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: # Always collect __init__.py first. - def sort_key(entry: "os.DirEntry[str]") -> object: + def sort_key(entry: os.DirEntry[str]) -> object: return (entry.name != "__init__.py", entry.name) config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path, sort_key): @@ -749,12 +746,12 @@ def _call_with_optional_argument(func, arg) -> None: func() -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: +def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: """Return the attribute from the given object to be used as a setup/teardown xunit-style function, but only if not marked as a fixture to avoid calling it twice. """ for name in names: - meth: Optional[object] = getattr(obj, name, None) + meth: object | None = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: return meth return None @@ -771,7 +768,7 @@ def from_parent(cls, parent, *, name, obj=None, **kw): def newinstance(self): return self.obj() - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): @@ -899,21 +896,21 @@ class IdMaker: parametersets: Sequence[ParameterSet] # Optionally, a user-provided callable to make IDs for parameters in a # ParameterSet. - idfn: Optional[Callable[[Any], Optional[object]]] + idfn: Callable[[Any], object | None] | None # Optionally, explicit IDs for ParameterSets by index. - ids: Optional[Sequence[Optional[object]]] + ids: Sequence[object | None] | None # Optionally, the pytest config. # Used for controlling ASCII escaping, and for calling the # :hook:`pytest_make_parametrize_id` hook. - config: Optional[Config] + config: Config | None # Optionally, the ID of the node being parametrized. # Used only for clearer error messages. - nodeid: Optional[str] + nodeid: str | None # Optionally, the ID of the function being parametrized. # Used only for clearer error messages. - func_name: Optional[str] + func_name: str | None - def make_unique_parameterset_ids(self) -> List[str]: + def make_unique_parameterset_ids(self) -> list[str]: """Make a unique identifier for each ParameterSet, that may be used to identify the parametrization in a node ID. @@ -930,7 +927,7 @@ def make_unique_parameterset_ids(self) -> List[str]: # Record the number of occurrences of each ID. id_counts = Counter(resolved_ids) # Map the ID to its next suffix. - id_suffixes: Dict[str, int] = defaultdict(int) + id_suffixes: dict[str, int] = defaultdict(int) # Suffix non-unique IDs to make them unique. for index, id in enumerate(resolved_ids): if id_counts[id] > 1: @@ -977,9 +974,7 @@ def _idval(self, val: object, argname: str, idx: int) -> str: return idval return self._idval_from_argname(argname, idx) - def _idval_from_function( - self, val: object, argname: str, idx: int - ) -> Optional[str]: + def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: """Try to make an ID for a parameter in a ParameterSet using the user-provided id callable, if given.""" if self.idfn is None: @@ -995,17 +990,17 @@ def _idval_from_function( return None return self._idval_from_value(id) - def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + def _idval_from_hook(self, val: object, argname: str) -> str | None: """Try to make an ID for a parameter in a ParameterSet by calling the :hook:`pytest_make_parametrize_id` hook.""" if self.config: - id: Optional[str] = self.config.hook.pytest_make_parametrize_id( + id: str | None = self.config.hook.pytest_make_parametrize_id( config=self.config, val=val, argname=argname ) return id return None - def _idval_from_value(self, val: object) -> Optional[str]: + def _idval_from_value(self, val: object) -> str | None: """Try to make an ID for a parameter in a ParameterSet from its value, if the value type is supported.""" if isinstance(val, STRING_TYPES): @@ -1063,15 +1058,15 @@ class CallSpec2: # arg name -> arg value which will be passed to a fixture or pseudo-fixture # of the same name. (indirect or direct parametrization respectively) - params: Dict[str, object] = dataclasses.field(default_factory=dict) + params: dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = dataclasses.field(default_factory=dict) + indices: dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. - marks: List[Mark] = dataclasses.field(default_factory=list) + marks: list[Mark] = dataclasses.field(default_factory=list) def setmulti( self, @@ -1079,10 +1074,10 @@ def setmulti( argnames: Iterable[str], valset: Iterable[object], id: str, - marks: Iterable[Union[Mark, MarkDecorator]], + marks: Iterable[Mark | MarkDecorator], scope: Scope, param_index: int, - ) -> "CallSpec2": + ) -> CallSpec2: params = self.params.copy() indices = self.indices.copy() arg2scope = dict(self._arg2scope) @@ -1130,7 +1125,7 @@ class Metafunc: def __init__( self, - definition: "FunctionDefinition", + definition: FunctionDefinition, fixtureinfo: fixtures.FuncFixtureInfo, config: Config, cls=None, @@ -1161,19 +1156,17 @@ def __init__( self._arg2fixturedefs = fixtureinfo.name2fixturedefs # Result of parametrize(). - self._calls: List[CallSpec2] = [] + self._calls: list[CallSpec2] = [] def parametrize( self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], - indirect: Union[bool, Sequence[str]] = False, - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - scope: Optional[_ScopeName] = None, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + indirect: bool | Sequence[str] = False, + ids: None | (Iterable[object | None] | Callable[[Any], object | None]) = None, + scope: _ScopeName | None = None, *, - _param_mark: Optional[Mark] = None, + _param_mark: Mark | None = None, ) -> None: """Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed @@ -1303,7 +1296,7 @@ def parametrize( if node is None: name2pseudofixturedef = None else: - default: Dict[str, FixtureDef[Any]] = {} + default: dict[str, FixtureDef[Any]] = {} name2pseudofixturedef = node.stash.setdefault( name2pseudofixturedef_key, default ) @@ -1351,12 +1344,10 @@ def parametrize( def _resolve_parameter_set_ids( self, argnames: Sequence[str], - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ], + ids: None | (Iterable[object | None] | Callable[[Any], object | None]), parametersets: Sequence[ParameterSet], nodeid: str, - ) -> List[str]: + ) -> list[str]: """Resolve the actual ids for the given parameter sets. :param argnames: @@ -1394,10 +1385,10 @@ def _resolve_parameter_set_ids( def _validate_ids( self, - ids: Iterable[Optional[object]], + ids: Iterable[object | None], parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Optional[object]]: + ) -> list[object | None]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1417,8 +1408,8 @@ def _validate_ids( def _resolve_args_directness( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], - ) -> Dict[str, Literal["indirect", "direct"]]: + indirect: bool | Sequence[str], + ) -> dict[str, Literal["indirect", "direct"]]: """Resolve if each parametrized argument must be considered an indirect parameter to a fixture of the same name, or a direct parameter to the parametrized function, based on the ``indirect`` parameter of the @@ -1431,7 +1422,7 @@ def _resolve_args_directness( :returns A dict mapping each arg name to either "indirect" or "direct". """ - arg_directness: Dict[str, Literal["indirect", "direct"]] + arg_directness: dict[str, Literal["indirect", "direct"]] if isinstance(indirect, bool): arg_directness = dict.fromkeys( argnames, "indirect" if indirect else "direct" @@ -1459,7 +1450,7 @@ def _resolve_args_directness( def _validate_if_using_arg_names( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1492,7 +1483,7 @@ def _validate_if_using_arg_names( def _find_parametrized_scope( argnames: Sequence[str], arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> Scope: """Find the most appropriate scope for a parametrized call based on its arguments. @@ -1521,7 +1512,7 @@ def _find_parametrized_scope( return Scope.Function -def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: +def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: if config is None: escape_option = False else: @@ -1580,7 +1571,7 @@ def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: def write_item(item: nodes.Item) -> None: # Not all items have _fixtureinfo attribute. - info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) + info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None) if info is None or not info.name2fixturedefs: # This test item does not use any fixtures. return @@ -1600,7 +1591,7 @@ def write_item(item: nodes.Item) -> None: write_item(session_item) -def showfixtures(config: Config) -> Union[int, ExitCode]: +def showfixtures(config: Config) -> int | ExitCode: from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) @@ -1617,7 +1608,7 @@ def _showfixtures_main(config: Config, session: Session) -> None: fm = session._fixturemanager available = [] - seen: Set[Tuple[str, str]] = set() + seen: set[tuple[str, str]] = set() for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None @@ -1702,13 +1693,13 @@ def __init__( self, name: str, parent, - config: Optional[Config] = None, - callspec: Optional[CallSpec2] = None, + config: Config | None = None, + callspec: CallSpec2 | None = None, callobj=NOTSET, - keywords: Optional[Mapping[str, Any]] = None, - session: Optional[Session] = None, - fixtureinfo: Optional[FuncFixtureInfo] = None, - originalname: Optional[str] = None, + keywords: Mapping[str, Any] | None = None, + session: Session | None = None, + fixtureinfo: FuncFixtureInfo | None = None, + originalname: str | None = None, ) -> None: super().__init__(name, parent, config=config, session=session) @@ -1754,7 +1745,7 @@ def from_parent(cls, parent, **kw): # todo: determine sound type limitations return super().from_parent(parent=parent, **kw) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = fixtures.TopRequest(self, _ispytest=True) @property @@ -1815,7 +1806,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: style = self.config.getoption("tbstyle", "auto") if style == "auto": style = "long" diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index f914d70e83f..811675b939c 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import math import pprint from collections.abc import Collection @@ -10,9 +12,7 @@ from typing import cast from typing import ContextManager from typing import final -from typing import List from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence @@ -20,7 +20,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import _pytest._code from _pytest.compat import STRING_TYPES @@ -32,12 +31,12 @@ def _compare_approx( full_object: object, - message_data: Sequence[Tuple[str, str, str]], + message_data: Sequence[tuple[str, str, str]], number_of_elements: int, different_ids: Sequence[object], max_abs_diff: float, max_rel_diff: float, -) -> List[str]: +) -> list[str]: message_list = list(message_data) message_list.insert(0, ("Index", "Obtained", "Expected")) max_sizes = [0, 0, 0] @@ -78,7 +77,7 @@ def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: def __repr__(self) -> str: raise NotImplementedError - def _repr_compare(self, other_side: Any) -> List[str]: + def _repr_compare(self, other_side: Any) -> list[str]: return [ "comparison failed", f"Obtained: {other_side}", @@ -102,7 +101,7 @@ def __bool__(self): def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> "ApproxScalar": + def _approx_scalar(self, x) -> ApproxScalar: if isinstance(x, Decimal): return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) @@ -141,12 +140,12 @@ def __repr__(self) -> str: ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: "ndarray") -> List[str]: + def _repr_compare(self, other_side: ndarray) -> list[str]: import itertools import math def get_value_from_nested_list( - nested_list: List[Any], nd_index: Tuple[Any, ...] + nested_list: list[Any], nd_index: tuple[Any, ...] ) -> Any: """ Helper function to get the value out of a nested list, given an n-dimensional index. @@ -241,7 +240,7 @@ def __repr__(self) -> str: {k: self._approx_scalar(v) for k, v in self.expected.items()} ) - def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: + def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: import math approx_side_as_map = { @@ -318,7 +317,7 @@ def __repr__(self) -> str: seq_type(self._approx_scalar(x) for x in self.expected) ) - def _repr_compare(self, other_side: Sequence[float]) -> List[str]: + def _repr_compare(self, other_side: Sequence[float]) -> list[str]: import math if len(self.expected) != len(other_side): @@ -383,8 +382,8 @@ class ApproxScalar(ApproxBase): # Using Real should be better than this Union, but not possible yet: # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 + DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 def __repr__(self) -> str: """Return a string communicating both the expected value and the @@ -715,7 +714,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: Type[ApproxBase] = ApproxDecimal + cls: type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): @@ -749,7 +748,7 @@ def _is_numpy_array(obj: object) -> bool: return _as_numpy_array(obj) is not None -def _as_numpy_array(obj: object) -> Optional["ndarray"]: +def _as_numpy_array(obj: object) -> ndarray | None: """ Return an ndarray if the given object is implicitly convertible to ndarray, and numpy is already imported, otherwise None. @@ -775,16 +774,16 @@ def _as_numpy_array(obj: object) -> Optional["ndarray"]: @overload def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[E]": + match: str | Pattern[str] | None = ..., +) -> RaisesContext[E]: ... @overload def raises( # noqa: F811 - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], func: Callable[..., Any], *args: Any, **kwargs: Any, @@ -793,8 +792,8 @@ def raises( # noqa: F811 def raises( # noqa: F811 - expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any -) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: + expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any +) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]: r"""Assert that a code block/function call raises an exception type, or one of its subclasses. :param expected_exception: @@ -942,7 +941,7 @@ def raises( # noqa: F811 f"any special code to say 'this should never raise an exception'." ) if isinstance(expected_exception, type): - expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) + expected_exceptions: tuple[type[E], ...] = (expected_exception,) else: expected_exceptions = expected_exception for exc in expected_exceptions: @@ -954,7 +953,7 @@ def raises( # noqa: F811 message = f"DID NOT RAISE {expected_exception}" if not args: - match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) + match: str | Pattern[str] | None = kwargs.pop("match", None) if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " msg += ", ".join(sorted(kwargs)) @@ -980,14 +979,14 @@ def raises( # noqa: F811 class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], message: str, - match_expr: Optional[Union[str, Pattern[str]]] = None, + match_expr: str | Pattern[str] | None = None, ) -> None: self.expected_exception = expected_exception self.message = message self.match_expr = match_expr - self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None + self.excinfo: _pytest._code.ExceptionInfo[E] | None = None def __enter__(self) -> _pytest._code.ExceptionInfo[E]: self.excinfo = _pytest._code.ExceptionInfo.for_later() @@ -995,9 +994,9 @@ def __enter__(self) -> _pytest._code.ExceptionInfo[E]: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> bool: __tracebackhide__ = True if exc_type is None: diff --git a/src/_pytest/python_path.py b/src/_pytest/python_path.py index cceabbca12a..6e33c8a39f2 100644 --- a/src/_pytest/python_path.py +++ b/src/_pytest/python_path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import pytest diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index b3279dd314f..34d1f27def6 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,4 +1,6 @@ """Record warnings during test function execution.""" +from __future__ import annotations + import re import warnings from pprint import pformat @@ -8,14 +10,9 @@ from typing import final from typing import Generator from typing import Iterator -from typing import List -from typing import Optional from typing import overload from typing import Pattern -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture @@ -26,7 +23,7 @@ @fixture -def recwarn() -> Generator["WarningsRecorder", None, None]: +def recwarn() -> Generator[WarningsRecorder, None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information @@ -39,9 +36,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: @overload -def deprecated_call( - *, match: Optional[Union[str, Pattern[str]]] = ... -) -> "WarningsRecorder": +def deprecated_call(*, match: str | Pattern[str] | None = ...) -> WarningsRecorder: ... @@ -53,8 +48,8 @@ def deprecated_call( # noqa: F811 def deprecated_call( # noqa: F811 - func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any -) -> Union["WarningsRecorder", Any]: + func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any +) -> WarningsRecorder | Any: """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. This function can be used as a context manager:: @@ -88,16 +83,16 @@ def deprecated_call( # noqa: F811 @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., + expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "WarningsChecker": + match: str | Pattern[str] | None = ..., +) -> WarningsChecker: ... @overload def warns( # noqa: F811 - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], + expected_warning: type[Warning] | tuple[type[Warning], ...], func: Callable[..., T], *args: Any, **kwargs: Any, @@ -106,11 +101,11 @@ def warns( # noqa: F811 def warns( # noqa: F811 - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, *args: Any, - match: Optional[Union[str, Pattern[str]]] = None, + match: str | Pattern[str] | None = None, **kwargs: Any, -) -> Union["WarningsChecker", Any]: +) -> WarningsChecker | Any: r"""Assert that code raises a particular class of warning. Specifically, the parameter ``expected_warning`` can be a warning class or tuple @@ -187,18 +182,18 @@ def __init__(self, *, _ispytest: bool = False) -> None: # Type ignored due to the way typeshed handles warnings.catch_warnings. super().__init__(record=True) # type: ignore[call-arg] self._entered = False - self._list: List[warnings.WarningMessage] = [] + self._list: list[warnings.WarningMessage] = [] @property - def list(self) -> List["warnings.WarningMessage"]: + def list(self) -> list[warnings.WarningMessage]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i: int) -> "warnings.WarningMessage": + def __getitem__(self, i: int) -> warnings.WarningMessage: """Get a recorded warning by index.""" return self._list[i] - def __iter__(self) -> Iterator["warnings.WarningMessage"]: + def __iter__(self) -> Iterator[warnings.WarningMessage]: """Iterate through the recorded warnings.""" return iter(self._list) @@ -206,12 +201,12 @@ def __len__(self) -> int: """The number of recorded warnings.""" return len(self._list) - def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": + def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: """Pop the first recorded warning which is an instance of ``cls``, but not an instance of a child class of any other match. Raises ``AssertionError`` if there is no match. """ - best_idx: Optional[int] = None + best_idx: int | None = None for i, w in enumerate(self._list): if w.category == cls: return self._list.pop(i) # exact match, stop looking @@ -231,7 +226,7 @@ def clear(self) -> None: # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ # -- it returns a List but we only emulate one. - def __enter__(self) -> "WarningsRecorder": # type: ignore + def __enter__(self) -> WarningsRecorder: # type: ignore if self._entered: __tracebackhide__ = True raise RuntimeError(f"Cannot enter {self!r} twice") @@ -244,9 +239,9 @@ def __enter__(self) -> "WarningsRecorder": # type: ignore def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: if not self._entered: __tracebackhide__ = True @@ -263,8 +258,8 @@ def __exit__( class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, - match_expr: Optional[Union[str, Pattern[str]]] = None, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + match_expr: str | Pattern[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -295,9 +290,9 @@ def matches(self, warning: warnings.WarningMessage) -> bool: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: super().__exit__(exc_type, exc_val, exc_tb) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 18f1c948afc..377a5293d10 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,23 +1,19 @@ +from __future__ import annotations + import dataclasses import os from io import StringIO from pprint import pprint from typing import Any from typing import cast -from typing import Dict from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -56,12 +52,12 @@ def getworkerinfoline(node): class BaseReport: - when: Optional[str] - location: Optional[Tuple[str, Optional[int], str]] - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ] - sections: List[Tuple[str, str]] + when: str | None + location: tuple[str, int | None, str] | None + longrepr: ( + None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr + ) + sections: list[tuple[str, str]] nodeid: str outcome: Literal["passed", "failed", "skipped"] @@ -93,7 +89,7 @@ def toterminal(self, out: TerminalWriter) -> None: s = "" out.line(s) - def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: + def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @@ -175,7 +171,7 @@ def count_towards_summary(self) -> bool: return True @property - def head_line(self) -> Optional[str]: + def head_line(self) -> str | None: """**Experimental** The head line shown with longrepr output for this report, more commonly during traceback representation during failures:: @@ -201,7 +197,7 @@ def _get_verbose_word(self, config: Config): ) return verbose - def _to_json(self) -> Dict[str, Any]: + def _to_json(self) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -212,7 +208,7 @@ def _to_json(self) -> Dict[str, Any]: return _report_to_json(self) @classmethod - def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: + def _from_json(cls: type[_R], reportdict: dict[str, object]) -> _R: """Create either a TestReport or CollectReport, depending on the calling class. It is the callers responsibility to know which class to pass here. @@ -226,7 +222,7 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: def _report_unserialization_failure( - type_name: str, report_class: Type[BaseReport], reportdict + type_name: str, report_class: type[BaseReport], reportdict ) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() @@ -255,18 +251,22 @@ class TestReport(BaseReport): def __init__( self, nodeid: str, - location: Tuple[str, Optional[int], str], + location: tuple[str, int | None, str], keywords: Mapping[str, Any], outcome: Literal["passed", "failed", "skipped"], - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ), when: Literal["setup", "call", "teardown"], - sections: Iterable[Tuple[str, str]] = (), + sections: Iterable[tuple[str, str]] = (), duration: float = 0, start: float = 0, stop: float = 0, - user_properties: Optional[Iterable[Tuple[str, object]]] = None, + user_properties: Iterable[tuple[str, object]] | None = None, **extra, ) -> None: #: Normalized collection nodeid. @@ -277,7 +277,7 @@ def __init__( #: collected one e.g. if a method is inherited from a different module. #: The filesystempath may be relative to ``config.rootdir``. #: The line number is 0-based. - self.location: Tuple[str, Optional[int], str] = location + self.location: tuple[str, int | None, str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. @@ -318,7 +318,7 @@ def __repr__(self) -> str: ) @classmethod - def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": + def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: """Create and fill a TestReport with standard item and call info. :param item: The item. @@ -335,13 +335,13 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": sections = [] if not call.excinfo: outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: Union[ - None, - ExceptionInfo[BaseException], - Tuple[str, int, str], - str, - TerminalRepr, - ] = None + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ) = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" @@ -395,12 +395,16 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'failed', 'skipped']", - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], - result: Optional[List[Union[Item, Collector]]], - sections: Iterable[Tuple[str, str]] = (), + outcome: Literal["passed", "failed", "skipped"], + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ), + result: list[Item | Collector] | None, + sections: Iterable[tuple[str, str]] = (), **extra, ) -> None: #: Normalized collection nodeid. @@ -426,7 +430,7 @@ def __init__( @property def location( # type:ignore[override] self, - ) -> Optional[Tuple[str, Optional[int], str]]: + ) -> tuple[str, int | None, str] | None: return (self.fspath, None, self.fspath) def __repr__(self) -> str: @@ -444,8 +448,8 @@ def toterminal(self, out: TerminalWriter) -> None: def pytest_report_to_serializable( - report: Union[CollectReport, TestReport] -) -> Optional[Dict[str, Any]]: + report: CollectReport | TestReport, +) -> dict[str, Any] | None: if isinstance(report, (TestReport, CollectReport)): data = report._to_json() data["$report_type"] = report.__class__.__name__ @@ -455,8 +459,8 @@ def pytest_report_to_serializable( def pytest_report_from_serializable( - data: Dict[str, Any], -) -> Optional[Union[CollectReport, TestReport]]: + data: dict[str, Any], +) -> CollectReport | TestReport | None: if "$report_type" in data: if data["$report_type"] == "TestReport": return TestReport._from_json(data) @@ -468,16 +472,14 @@ def pytest_report_from_serializable( return None -def _report_to_json(report: BaseReport) -> Dict[str, Any]: +def _report_to_json(report: BaseReport) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. This was originally the serialize_report() function from xdist (ca03269). """ - def serialize_repr_entry( - entry: Union[ReprEntry, ReprEntryNative] - ) -> Dict[str, Any]: + def serialize_repr_entry(entry: ReprEntry | ReprEntryNative) -> dict[str, Any]: data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): @@ -485,7 +487,7 @@ def serialize_repr_entry( entry_data = {"type": type(entry).__name__, "data": data} return entry_data - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries @@ -493,18 +495,18 @@ def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: return result def serialize_repr_crash( - reprcrash: Optional[ReprFileLocation], - ) -> Optional[Dict[str, Any]]: + reprcrash: ReprFileLocation | None, + ) -> dict[str, Any] | None: if reprcrash is not None: return dataclasses.asdict(reprcrash) else: return None - def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: + def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: assert rep.longrepr is not None # TODO: Investigate whether the duck typing is really necessary here. longrepr = cast(ExceptionRepr, rep.longrepr) - result: Dict[str, Any] = { + result: dict[str, Any] = { "reprcrash": serialize_repr_crash(longrepr.reprcrash), "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), "sections": longrepr.sections, @@ -541,7 +543,7 @@ def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: return d -def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: +def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: """Return **kwargs that can be used to construct a TestReport or CollectReport instance. @@ -562,7 +564,7 @@ def deserialize_repr_entry(entry_data): if data["reprlocals"]: reprlocals = ReprLocals(data["reprlocals"]["lines"]) - reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry( + reprentry: ReprEntry | ReprEntryNative = ReprEntry( lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, @@ -581,7 +583,7 @@ def deserialize_repr_traceback(repr_traceback_dict): ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): + def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): if repr_crash_dict is not None: return ReprFileLocation(**repr_crash_dict) else: @@ -608,9 +610,9 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): description, ) ) - exception_info: Union[ - ExceptionChainRepr, ReprExceptionInfo - ] = ExceptionChainRepr(chain) + exception_info: ( + ExceptionChainRepr | ReprExceptionInfo + ) = ExceptionChainRepr(chain) else: exception_info = ReprExceptionInfo( reprtraceback=reprtraceback, diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 3e19f0de50c..b9e2f76db6f 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -1,21 +1,17 @@ """Basic collect and runtest protocol implementations.""" +from __future__ import annotations + import bdb import dataclasses import os import sys from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import Generic -from typing import List from typing import Literal -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from .reports import BaseReport from .reports import CollectErrorRepr @@ -68,7 +64,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: durations = terminalreporter.config.option.durations durations_min = terminalreporter.config.option.durations_min verbose = terminalreporter.config.getvalue("verbose") @@ -100,15 +96,15 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: session._setupstate.teardown_exact(None) -def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: ihook = item.ihook ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) runtestprotocol(item, nextitem=nextitem) @@ -117,8 +113,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: def runtestprotocol( - item: Item, log: bool = True, nextitem: Optional[Item] = None -) -> List[TestReport]: + item: Item, log: bool = True, nextitem: Item | None = None +) -> list[TestReport]: hasrequest = hasattr(item, "_request") if hasrequest and not item._request: # type: ignore[attr-defined] # This only happens if the item is re-run, as is done by @@ -181,14 +177,14 @@ def pytest_runtest_call(item: Item) -> None: raise e -def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: _update_current_test_var(item, "teardown") item.session._setupstate.teardown_exact(nextitem) _update_current_test_var(item, None) def _update_current_test_var( - item: Item, when: Optional[Literal["setup", "call", "teardown"]] + item: Item, when: Literal["setup", "call", "teardown"] | None ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -204,7 +200,7 @@ def _update_current_test_var( os.environ.pop(var_name) -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -233,7 +229,7 @@ def call_and_report( return report -def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: +def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: """Check whether the call raised an exception that should be reported as interactive.""" if call.excinfo is None: @@ -250,7 +246,7 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> def call_runtest_hook( item: Item, when: Literal["setup", "call", "teardown"], **kwds -) -> "CallInfo[None]": +) -> CallInfo[None]: if when == "setup": ihook: Callable[..., None] = item.ihook.pytest_runtest_setup elif when == "call": @@ -259,7 +255,7 @@ def call_runtest_hook( ihook = item.ihook.pytest_runtest_teardown else: assert False, f"Unhandled runtest hook case: {when}" - reraise: Tuple[Type[BaseException], ...] = (Exit,) + reraise: tuple[type[BaseException], ...] = (Exit,) if not item.config.getoption("usepdb", False): reraise += (KeyboardInterrupt,) return CallInfo.from_call( @@ -275,9 +271,9 @@ def call_runtest_hook( class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" - _result: Optional[TResult] + _result: TResult | None #: The captured exception of the call, if it raised. - excinfo: Optional[ExceptionInfo[BaseException]] + excinfo: ExceptionInfo[BaseException] | None #: The system time when the call started, in seconds since the epoch. start: float #: The system time when the call ended, in seconds since the epoch. @@ -289,8 +285,8 @@ class CallInfo(Generic[TResult]): def __init__( self, - result: Optional[TResult], - excinfo: Optional[ExceptionInfo[BaseException]], + result: TResult | None, + excinfo: ExceptionInfo[BaseException] | None, start: float, stop: float, duration: float, @@ -324,10 +320,8 @@ def from_call( cls, func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], - reraise: Optional[ - Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ] = None, - ) -> "CallInfo[TResult]": + reraise: None | (type[BaseException] | tuple[type[BaseException], ...]) = None, + ) -> CallInfo[TResult]: """Call func, wrapping the result in a CallInfo. :param func: @@ -342,7 +336,7 @@ def from_call( start = timing.time() precise_start = timing.perf_counter() try: - result: Optional[TResult] = func() + result: TResult | None = func() except BaseException: excinfo = ExceptionInfo.from_current() if reraise is not None and isinstance(excinfo.value, reraise): @@ -373,7 +367,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - def collect() -> List[Union[Item, Collector]]: + def collect() -> list[Item | Collector]: # Before collecting, if this is a Directory, load the conftests. # If a conftest import fails to load, it is considered a collection # error of the Directory collector. This is why it's done inside of the @@ -390,7 +384,7 @@ def collect() -> List[Union[Item, Collector]]: return list(collector.collect()) call = CallInfo.from_call(collect, "collect") - longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None + longrepr: None | tuple[str, int, str] | str | TerminalRepr = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" else: @@ -485,13 +479,13 @@ class SetupState: def __init__(self) -> None: # The stack is in the dict insertion order. - self.stack: Dict[ + self.stack: dict[ Node, - Tuple[ + tuple[ # Node's finalizers. - List[Callable[[], object]], + list[Callable[[], object]], # Node's exception, if its setup raised. - Optional[Union[OutcomeException, Exception]], + OutcomeException | Exception | None, ], ] = {} @@ -526,7 +520,7 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: assert node in self.stack, (node, self.stack) self.stack[node][0].append(finalizer) - def teardown_exact(self, nextitem: Optional[Item]) -> None: + def teardown_exact(self, nextitem: Item | None) -> None: """Teardown the current stack up until reaching nodes that nextitem also descends from. @@ -534,7 +528,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: stack is torn down. """ needed_collectors = nextitem and nextitem.listchain() or [] - exceptions: List[BaseException] = [] + exceptions: list[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break diff --git a/src/_pytest/scope.py b/src/_pytest/scope.py index 98edaf40225..292d37a9a10 100644 --- a/src/_pytest/scope.py +++ b/src/_pytest/scope.py @@ -7,10 +7,11 @@ Also this makes the module light to import, as it should. """ +from __future__ import annotations + from enum import Enum from functools import total_ordering from typing import Literal -from typing import Optional _ScopeName = Literal["session", "package", "module", "class", "function"] @@ -37,29 +38,29 @@ class Scope(Enum): Package: _ScopeName = "package" Session: _ScopeName = "session" - def next_lower(self) -> "Scope": + def next_lower(self) -> Scope: """Return the next lower scope.""" index = _SCOPE_INDICES[self] if index == 0: raise ValueError(f"{self} is the lower-most scope") return _ALL_SCOPES[index - 1] - def next_higher(self) -> "Scope": + def next_higher(self) -> Scope: """Return the next higher scope.""" index = _SCOPE_INDICES[self] if index == len(_SCOPE_INDICES) - 1: raise ValueError(f"{self} is the upper-most scope") return _ALL_SCOPES[index + 1] - def __lt__(self, other: "Scope") -> bool: + def __lt__(self, other: Scope) -> bool: self_index = _SCOPE_INDICES[self] other_index = _SCOPE_INDICES[other] return self_index < other_index @classmethod def from_user( - cls, scope_name: _ScopeName, descr: str, where: Optional[str] = None - ) -> "Scope": + cls, scope_name: _ScopeName, descr: str, where: str | None = None + ) -> Scope: """ Given a scope name from the user, return the equivalent Scope enum. Should be used whenever we want to convert a user provided scope name to its enum object. diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 0f8be899af2..435ff93d7c4 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -1,6 +1,6 @@ +from __future__ import annotations + from typing import Generator -from typing import Optional -from typing import Union import pytest from _pytest._io.saferepr import saferepr @@ -93,7 +93,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setuponly: config.option.setupshow = True return None diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py index 1a4ebdd99ca..bb7a27449a1 100644 --- a/src/_pytest/setupplan.py +++ b/src/_pytest/setupplan.py @@ -1,5 +1,4 @@ -from typing import Optional -from typing import Union +from __future__ import annotations import pytest from _pytest.config import Config @@ -23,7 +22,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Optional[object]: +) -> object | None: # Will return a dummy fixture if the setuponly option is provided. if request.config.option.setupplan: my_cache_key = fixturedef.cache_key(request) @@ -33,7 +32,7 @@ def pytest_fixture_setup( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 0c5c38f5f1a..3df0a26c4a9 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,4 +1,6 @@ """Support for skip/xfail functions and markers.""" +from __future__ import annotations + import dataclasses import os import platform @@ -7,8 +9,6 @@ from collections.abc import Mapping from typing import Generator from typing import Optional -from typing import Tuple -from typing import Type from _pytest.config import Config from _pytest.config import hookimpl @@ -82,7 +82,7 @@ def nop(*args, **kwargs): ) -def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: +def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: """Evaluate a single skipif/xfail condition. If an old-style string condition is given, it is eval()'d, otherwise the @@ -164,7 +164,7 @@ class Skip: reason: str = "unconditional skip" -def evaluate_skip_marks(item: Item) -> Optional[Skip]: +def evaluate_skip_marks(item: Item) -> Skip | None: """Evaluate skip and skipif marks on item, returning Skip if triggered.""" for mark in item.iter_markers(name="skipif"): if "condition" not in mark.kwargs: @@ -201,10 +201,10 @@ class Xfail: reason: str run: bool strict: bool - raises: Optional[Tuple[Type[BaseException], ...]] + raises: tuple[type[BaseException], ...] | None -def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: +def evaluate_xfail_marks(item: Item) -> Xfail | None: """Evaluate xfail marks on item, returning Xfail if triggered.""" for mark in item.iter_markers(name="xfail"): run = mark.kwargs.get("run", True) @@ -292,7 +292,7 @@ def pytest_runtest_makereport( return rep -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" diff --git a/src/_pytest/stash.py b/src/_pytest/stash.py index e61d75b95f7..0047add4cee 100644 --- a/src/_pytest/stash.py +++ b/src/_pytest/stash.py @@ -1,9 +1,9 @@ +from __future__ import annotations + from typing import Any from typing import cast -from typing import Dict from typing import Generic from typing import TypeVar -from typing import Union __all__ = ["Stash", "StashKey"] @@ -66,7 +66,7 @@ class Stash: __slots__ = ("_storage",) def __init__(self) -> None: - self._storage: Dict[StashKey[Any], object] = {} + self._storage: dict[StashKey[Any], object] = {} def __setitem__(self, key: StashKey[T], value: T) -> None: """Set a value for key.""" @@ -79,7 +79,7 @@ def __getitem__(self, key: StashKey[T]) -> T: """ return cast(T, self._storage[key]) - def get(self, key: StashKey[T], default: D) -> Union[T, D]: + def get(self, key: StashKey[T], default: D) -> T | D: """Get the value for key, or return default if the key wasn't set before.""" try: diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 74ad9dbd4dd..ca67eaa08de 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -1,5 +1,5 @@ -from typing import List -from typing import Optional +from __future__ import annotations + from typing import TYPE_CHECKING import pytest @@ -59,18 +59,18 @@ def pytest_sessionfinish(session: Session) -> None: class StepwisePlugin: def __init__(self, config: Config) -> None: self.config = config - self.session: Optional[Session] = None + self.session: Session | None = None self.report_status = "" assert config.cache is not None self.cache: Cache = config.cache - self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None) + self.lastfailed: str | None = self.cache.get(STEPWISE_CACHE_DIR, None) self.skip: bool = config.getoption("stepwise_skip") def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> None: if not self.lastfailed: self.report_status = "no previously failed tests, not skipping." @@ -117,7 +117,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if report.nodeid == self.lastfailed: self.lastfailed = None - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.config.getoption("verbose") >= 0 and self.report_status: return f"stepwise: {self.report_status}" return None diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index ea26d9368dc..e8a011b0fb5 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -2,6 +2,8 @@ This is a good source for looking at the various reporting hooks. """ +from __future__ import annotations + import argparse import dataclasses import datetime @@ -16,20 +18,14 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import final from typing import Generator -from typing import List from typing import Literal from typing import Mapping from typing import NamedTuple -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import pluggy @@ -87,7 +83,7 @@ def __init__( dest: str, default: object = None, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: super().__init__( option_strings=option_strings, @@ -102,8 +98,8 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[object], None], - option_string: Optional[str] = None, + values: str | Sequence[object] | None, + option_string: str | None = None, ) -> None: new_count = getattr(namespace, self.dest, 0) - 1 setattr(namespace, self.dest, new_count) @@ -128,7 +124,7 @@ class TestShortLogReport(NamedTuple): category: str letter: str - word: Union[str, Tuple[str, Mapping[str, bool]]] + word: str | tuple[str, Mapping[str, bool]] def pytest_addoption(parser: Parser) -> None: @@ -293,7 +289,7 @@ def getreportopt(config: Config) -> str: @hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: letter = "F" if report.passed: letter = "." @@ -321,12 +317,12 @@ class WarningReport: """ message: str - nodeid: Optional[str] = None - fslocation: Optional[Tuple[str, int]] = None + nodeid: str | None = None + fslocation: tuple[str, int] | None = None count_towards_summary: ClassVar = True - def get_location(self, config: Config) -> Optional[str]: + def get_location(self, config: Config) -> str | None: """Return the more user-friendly information about the location of a warning, or None.""" if self.nodeid: return self.nodeid @@ -339,31 +335,31 @@ def get_location(self, config: Config) -> Optional[str]: @final class TerminalReporter: - def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: + def __init__(self, config: Config, file: TextIO | None = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session: Optional[Session] = None - self._showfspath: Optional[bool] = None + self._session: Session | None = None + self._showfspath: bool | None = None - self.stats: Dict[str, List[Any]] = {} - self._main_color: Optional[str] = None - self._known_types: Optional[List[str]] = None + self.stats: dict[str, list[Any]] = {} + self._main_color: str | None = None + self._known_types: list[str] | None = None self.startpath = config.invocation_params.dir if file is None: file = sys.stdout self._tw = _pytest.config.create_terminal_writer(config, file) self._screen_width = self._tw.fullwidth - self.currentfspath: Union[None, Path, str, int] = None + self.currentfspath: None | Path | str | int = None self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() - self._progress_nodeids_reported: Set[str] = set() + self._progress_nodeids_reported: set[str] = set() self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write: Optional[float] = None - self._already_displayed_warnings: Optional[int] = None - self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None + self._collect_report_last_write: float | None = None + self._already_displayed_warnings: int | None = None + self._keyboardinterrupt_memo: ExceptionRepr | None = None def _determine_show_progress_info(self) -> Literal["progress", "count", False]: """Return whether we should display progress information based on the current config.""" @@ -410,7 +406,7 @@ def showfspath(self) -> bool: return self._showfspath @showfspath.setter - def showfspath(self, value: Optional[bool]) -> None: + def showfspath(self, value: bool | None) -> None: self._showfspath = value @property @@ -474,7 +470,7 @@ def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: def flush(self) -> None: self._tw.flush() - def write_line(self, line: Union[str, bytes], **markup: bool) -> None: + def write_line(self, line: str | bytes, **markup: bool) -> None: if not isinstance(line, str): line = str(line, errors="replace") self.ensure_newline() @@ -501,8 +497,8 @@ def rewrite(self, line: str, **markup: bool) -> None: def write_sep( self, sep: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: self.ensure_newline() @@ -552,7 +548,7 @@ def pytest_deselected(self, items: Sequence[Item]) -> None: self._add_stats("deselected", items) def pytest_runtest_logstart( - self, nodeid: str, location: Tuple[str, Optional[int], str] + self, nodeid: str, location: tuple[str, int | None, str] ) -> None: # Ensure that the path is printed before the # 1st test of a module starts running. @@ -743,7 +739,7 @@ def report_collect(self, final: bool = False) -> None: self.write_line(line) @hookimpl(trylast=True) - def pytest_sessionstart(self, session: "Session") -> None: + def pytest_sessionstart(self, session: Session) -> None: self._session = session self._sessionstarttime = timing.time() if not self.showheader: @@ -772,7 +768,7 @@ def pytest_sessionstart(self, session: "Session") -> None: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: Sequence[Union[str, Sequence[str]]] + self, lines: Sequence[str | Sequence[str]] ) -> None: for line_or_lines in reversed(lines): if isinstance(line_or_lines, str): @@ -781,14 +777,14 @@ def _write_report_lines_from_hooks( for line in line_or_lines: self.write_line(line) - def pytest_report_header(self, config: Config) -> List[str]: + def pytest_report_header(self, config: Config) -> list[str]: result = [f"rootdir: {config.rootpath}"] if config.inipath: result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: List[str] = config.getini("testpaths") + testpaths: list[str] = config.getini("testpaths") result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() @@ -796,7 +792,7 @@ def pytest_report_header(self, config: Config) -> List[str]: result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo))) return result - def pytest_collection_finish(self, session: "Session") -> None: + def pytest_collection_finish(self, session: Session) -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( @@ -828,7 +824,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: for item in items: self._tw.line(item.nodeid) return - stack: List[Node] = [] + stack: list[Node] = [] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -849,7 +845,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: @hookimpl(wrapper=True) def pytest_sessionfinish( - self, session: "Session", exitstatus: Union[int, ExitCode] + self, session: Session, exitstatus: int | ExitCode ) -> Generator[None, None, None]: result = yield self._tw.line("") @@ -911,7 +907,7 @@ def _report_keyboardinterrupt(self) -> None: ) def _locationline( - self, nodeid: str, fspath: str, lineno: Optional[int], domain: str + self, nodeid: str, fspath: str, lineno: int | None, domain: str ) -> str: def mkrel(nodeid: str) -> str: line = self.config.cwd_relative_nodeid(nodeid) @@ -956,7 +952,7 @@ def getreports(self, name: str): def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings") + all_warnings: list[WarningReport] | None = self.stats.get("warnings") if not all_warnings: return @@ -969,11 +965,11 @@ def summary_warnings(self) -> None: if not warning_reports: return - reports_grouped_by_message: Dict[str, List[WarningReport]] = {} + reports_grouped_by_message: dict[str, list[WarningReport]] = {} for wr in warning_reports: reports_grouped_by_message.setdefault(wr.message, []).append(wr) - def collapsed_location_report(reports: List[WarningReport]) -> str: + def collapsed_location_report(reports: list[WarningReport]) -> str: locations = [] for w in reports: location = w.get_location(self.config) @@ -1011,7 +1007,7 @@ def collapsed_location_report(reports: List[WarningReport]) -> str: def summary_passes(self) -> None: if self.config.option.tbstyle != "no": if self.hasopt("P"): - reports: List[TestReport] = self.getreports("passed") + reports: list[TestReport] = self.getreports("passed") if not reports: return self.write_sep("=", "PASSES") @@ -1022,7 +1018,7 @@ def summary_passes(self) -> None: self._outrep_summary(rep) self._handle_teardown_sections(rep.nodeid) - def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: + def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: reports = self.getreports("") return [ report @@ -1049,7 +1045,7 @@ def print_teardown_sections(self, rep: TestReport) -> None: def summary_failures(self) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("failed") + reports: list[BaseReport] = self.getreports("failed") if not reports: return self.write_sep("=", "FAILURES") @@ -1066,7 +1062,7 @@ def summary_failures(self) -> None: def summary_errors(self) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("error") + reports: list[BaseReport] = self.getreports("error") if not reports: return self.write_sep("=", "ERRORS") @@ -1133,7 +1129,7 @@ def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(lines: List[str], *, stat: str) -> None: + def show_simple(lines: list[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return @@ -1145,7 +1141,7 @@ def show_simple(lines: List[str], *, stat: str) -> None: ) lines.append(line) - def show_xfailed(lines: List[str]) -> None: + def show_xfailed(lines: list[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: verbose_word = rep._get_verbose_word(self.config) @@ -1160,7 +1156,7 @@ def show_xfailed(lines: List[str]) -> None: lines.append(line) - def show_xpassed(lines: List[str]) -> None: + def show_xpassed(lines: list[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: verbose_word = rep._get_verbose_word(self.config) @@ -1171,8 +1167,8 @@ def show_xpassed(lines: List[str]) -> None: reason = rep.wasxfail lines.append(f"{markup_word} {nodeid} {reason}") - def show_skipped(lines: List[str]) -> None: - skipped: List[CollectReport] = self.stats.get("skipped", []) + def show_skipped(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return @@ -1191,7 +1187,7 @@ def show_skipped(lines: List[str]) -> None: else: lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) - REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { + REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { "x": show_xfailed, "X": show_xpassed, "f": partial(show_simple, stat="failed"), @@ -1200,7 +1196,7 @@ def show_skipped(lines: List[str]) -> None: "E": partial(show_simple, stat="error"), } - lines: List[str] = [] + lines: list[str] = [] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -1211,7 +1207,7 @@ def show_skipped(lines: List[str]) -> None: for line in lines: self.write_line(line) - def _get_main_color(self) -> Tuple[str, List[str]]: + def _get_main_color(self) -> tuple[str, list[str]]: if self._main_color is None or self._known_types is None or self._is_last_item: self._set_main_color() assert self._main_color @@ -1231,7 +1227,7 @@ def _determine_main_color(self, unknown_type_seen: bool) -> str: return main_color def _set_main_color(self) -> None: - unknown_types: List[str] = [] + unknown_types: list[str] = [] for found_type in self.stats.keys(): if found_type: # setup/teardown reports have an empty key, ignore them if found_type not in KNOWN_TYPES and found_type not in unknown_types: @@ -1239,7 +1235,7 @@ def _set_main_color(self) -> None: self._known_types = list(KNOWN_TYPES) + unknown_types self._main_color = self._determine_main_color(bool(unknown_types)) - def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: """ Build the parts used in the last summary stats line. @@ -1264,14 +1260,14 @@ def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], s else: return self._build_normal_summary_stats_line() - def _get_reports_to_display(self, key: str) -> List[Any]: + def _get_reports_to_display(self, key: str) -> list[Any]: """Get test/collection reports for the given status key, such as `passed` or `error`.""" reports = self.stats.get(key, []) return [x for x in reports if getattr(x, "count_towards_summary", True)] def _build_normal_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: main_color, known_types = self._get_main_color() parts = [] @@ -1290,7 +1286,7 @@ def _build_normal_summary_stats_line( def _build_collect_only_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: deselected = len(self._get_reports_to_display("deselected")) errors = len(self._get_reports_to_display("error")) @@ -1331,7 +1327,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport return path -def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: +def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: """Format msg into format, ellipsizing it if doesn't fit in available_width. Returns None if even the ellipsis can't fit. @@ -1357,7 +1353,7 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" verbose_word = rep._get_verbose_word(config) @@ -1387,8 +1383,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, skipped: Sequence[CollectReport], -) -> List[Tuple[int, str, Optional[int], str]]: - d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {} +) -> list[tuple[int, str, int | None, str]]: + d: dict[tuple[str, int | None, str], list[CollectReport]] = {} for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) @@ -1405,11 +1401,11 @@ def _folded_skips( and "skip" in keywords and "pytestmark" not in keywords ): - key: Tuple[str, Optional[int], str] = (fspath, None, reason) + key: tuple[str, int | None, str] = (fspath, None, reason) else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: List[Tuple[int, str, Optional[int], str]] = [] + values: list[tuple[int, str, int | None, str]] = [] for key, events in d.items(): values.append((len(events), *key)) return values @@ -1424,7 +1420,7 @@ def _folded_skips( _color_for_type_default = "yellow" -def pluralize(count: int, noun: str) -> Tuple[int, str]: +def pluralize(count: int, noun: str) -> tuple[int, str]: # No need to pluralize words such as `failed` or `passed`. if noun not in ["error", "warnings", "test"]: return count, noun @@ -1437,8 +1433,8 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]: return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo) -> List[str]: - values: List[str] = [] +def _plugin_nameversions(plugininfo) -> list[str]: + values: list[str] = [] for plugin, dist in plugininfo: # Gets us name and version! name = "{dist.project_name}-{dist.version}".format(dist=dist) diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 0b5902d66a8..f2054c574bd 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import threading import traceback import warnings @@ -5,8 +7,6 @@ from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type import pytest @@ -34,22 +34,22 @@ class catch_threading_exception: """ def __init__(self) -> None: - self.args: Optional["threading.ExceptHookArgs"] = None - self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None + self.args: threading.ExceptHookArgs | None = None + self._old_hook: Callable[[threading.ExceptHookArgs], Any] | None = None - def _hook(self, args: "threading.ExceptHookArgs") -> None: + def _hook(self, args: threading.ExceptHookArgs) -> None: self.args = args - def __enter__(self) -> "catch_threading_exception": + def __enter__(self) -> catch_threading_exception: self._old_hook = threading.excepthook threading.excepthook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None threading.excepthook = self._old_hook diff --git a/src/_pytest/timing.py b/src/_pytest/timing.py index 925163a5858..8b814db997b 100644 --- a/src/_pytest/timing.py +++ b/src/_pytest/timing.py @@ -5,6 +5,8 @@ Fixture "mock_timing" also interacts with this module for pytest's own tests. """ +from __future__ import annotations + from time import perf_counter from time import sleep from time import time diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 6fa227760a8..993134eddb9 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,4 +1,6 @@ """Support for providing temporary directories to test functions.""" +from __future__ import annotations + import dataclasses import os import re @@ -10,8 +12,6 @@ from typing import final from typing import Generator from typing import Literal -from typing import Optional -from typing import Union from .pathlib import cleanup_dead_symlinks from .pathlib import LOCK_TIMEOUT @@ -43,20 +43,20 @@ class TempPathFactory: The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp: Optional[Path] + _given_basetemp: Path | None # pluggy TagTracerSub, not currently exposed, so Any. _trace: Any - _basetemp: Optional[Path] + _basetemp: Path | None _retention_count: int _retention_policy: RetentionType def __init__( self, - given_basetemp: Optional[Path], + given_basetemp: Path | None, retention_count: int, retention_policy: RetentionType, trace, - basetemp: Optional[Path] = None, + basetemp: Path | None = None, *, _ispytest: bool = False, ) -> None: @@ -79,7 +79,7 @@ def from_config( config: Config, *, _ispytest: bool = False, - ) -> "TempPathFactory": + ) -> TempPathFactory: """Create a factory according to pytest configuration. :meta private: @@ -195,7 +195,7 @@ def getbasetemp(self) -> Path: return basetemp -def get_user() -> Optional[str]: +def get_user() -> str | None: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" try: @@ -284,7 +284,7 @@ def tmp_path( del request.node.stash[tmppath_result_key] -def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): +def pytest_sessionfinish(session, exitstatus: int | ExitCode): """After each session, remove base directory if all the tests passed, the policy is "failed", and the basetemp is not specified by a user. """ @@ -315,6 +315,6 @@ def pytest_runtest_makereport( ) -> Generator[None, TestReport, TestReport]: rep = yield assert rep.when is not None - empty: Dict[str, bool] = {} + empty: dict[str, bool] = {} item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed return rep diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 34845cec1c7..3ddb9452423 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -1,4 +1,6 @@ """Discover and run std-library "unittest" style tests.""" +from __future__ import annotations + import sys import traceback import types @@ -6,8 +8,6 @@ from typing import Callable from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Tuple from typing import Type from typing import TYPE_CHECKING @@ -42,8 +42,8 @@ def pytest_pycollect_makeitem( - collector: Union[Module, Class], name: str, obj: object -) -> Optional["UnitTestCase"]: + collector: Module | Class, name: str, obj: object +) -> UnitTestCase | None: # Has unittest been imported and is obj a subclass of its TestCase? try: ut = sys.modules["unittest"] @@ -62,7 +62,7 @@ class UnitTestCase(Class): # to declare that our children do not support funcargs. nofuncargs = True - def collect(self) -> Iterable[Union[Item, Collector]]: + def collect(self) -> Iterable[Item | Collector]: from unittest import TestLoader cls = self.obj @@ -123,7 +123,7 @@ def _make_xunit_fixture( obj: type, setup_name: str, teardown_name: str, - cleanup_name: Optional[str], + cleanup_name: str | None, scope: Scope, pass_self: bool, ): @@ -182,8 +182,8 @@ def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: class TestCaseFunction(Function): nofuncargs = True - _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None - _testcase: Optional["unittest.TestCase"] = None + _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None + _testcase: unittest.TestCase | None = None def _getobj(self): assert self.parent is not None @@ -196,7 +196,7 @@ def _getobj(self): def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). - self._explicit_tearDown: Optional[Callable[[], None]] = None + self._explicit_tearDown: Callable[[], None] | None = None assert self.parent is not None self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] self._obj = getattr(self._testcase, self.name) @@ -210,10 +210,10 @@ def teardown(self) -> None: self._testcase = None self._obj = None - def startTest(self, testcase: "unittest.TestCase") -> None: + def startTest(self, testcase: unittest.TestCase) -> None: pass - def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: + def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: # Unwrap potential exception info (see twisted trial support below). rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: @@ -247,7 +247,7 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -257,11 +257,11 @@ def addError( self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: self._addexcinfo(rawexcinfo) - def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: + def addSkip(self, testcase: unittest.TestCase, reason: str) -> None: try: raise pytest.skip.Exception(reason, _use_item_location=True) except skip.Exception: @@ -269,8 +269,8 @@ def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: def addExpectedFailure( self, - testcase: "unittest.TestCase", - rawexcinfo: "_SysExcInfoType", + testcase: unittest.TestCase, + rawexcinfo: _SysExcInfoType, reason: str = "", ) -> None: try: @@ -280,8 +280,8 @@ def addExpectedFailure( def addUnexpectedSuccess( self, - testcase: "unittest.TestCase", - reason: Optional["twisted.trial.unittest.Todo"] = None, + testcase: unittest.TestCase, + reason: twisted.trial.unittest.Todo | None = None, ) -> None: msg = "Unexpected success" if reason: @@ -292,13 +292,13 @@ def addUnexpectedSuccess( except fail.Exception: self._addexcinfo(sys.exc_info()) - def addSuccess(self, testcase: "unittest.TestCase") -> None: + def addSuccess(self, testcase: unittest.TestCase) -> None: pass - def stopTest(self, testcase: "unittest.TestCase") -> None: + def stopTest(self, testcase: unittest.TestCase) -> None: pass - def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: pass def runtest(self) -> None: @@ -409,7 +409,7 @@ def excstore( return res -def check_testcase_implements_trial_reporter(done: List[int] = []) -> None: +def check_testcase_implements_trial_reporter(done: list[int] = []) -> None: if done: return from zope.interface import classImplements diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index 8c0a2d9ae95..072df398f77 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import traceback import warnings @@ -5,8 +7,6 @@ from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type import pytest @@ -34,24 +34,24 @@ class catch_unraisable_exception: """ def __init__(self) -> None: - self.unraisable: Optional["sys.UnraisableHookArgs"] = None - self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None + self.unraisable: sys.UnraisableHookArgs | None = None + self._old_hook: Callable[[sys.UnraisableHookArgs], Any] | None = None - def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None: + def _hook(self, unraisable: sys.UnraisableHookArgs) -> None: # Storing unraisable.object can resurrect an object which is being # finalized. Storing unraisable.exc_value creates a reference cycle. self.unraisable = unraisable - def __enter__(self) -> "catch_unraisable_exception": + def __enter__(self) -> catch_unraisable_exception: self._old_hook = sys.unraisablehook sys.unraisablehook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None sys.unraisablehook = self._old_hook diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 6c109b03f0a..878940e050e 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses import inspect import warnings @@ -5,7 +7,6 @@ from typing import Any from typing import final from typing import Generic -from typing import Type from typing import TypeVar @@ -72,7 +73,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): __module__ = "pytest" @classmethod - def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": + def simple(cls, apiname: str) -> PytestExperimentalApiWarning: return cls( "{apiname} is an experimental api that may change over time".format( apiname=apiname @@ -136,7 +137,7 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category: Type["_W"] + category: type[_W] template: str def format(self, **kwargs: Any) -> _W: diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 6ef4fafdce5..8cee64feeca 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import sys import warnings from contextlib import contextmanager from typing import Generator from typing import Literal -from typing import Optional import pytest from _pytest.config import apply_warning_filters @@ -27,7 +28,7 @@ def catch_warnings_for_item( config: Config, ihook, when: Literal["config", "collect", "runtest"], - item: Optional[Item], + item: Item | None, ) -> Generator[None, None, None]: """Context manager that catches warnings generated in the contained execution block. @@ -141,7 +142,7 @@ def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: @pytest.hookimpl(wrapper=True) def pytest_load_initial_conftests( - early_config: "Config", + early_config: Config, ) -> Generator[None, None, None]: with catch_warnings_for_item( config=early_config, ihook=early_config.hook, when="config", item=None diff --git a/src/py.py b/src/py.py index c997903363a..09be63b1d19 100644 --- a/src/py.py +++ b/src/py.py @@ -1,6 +1,8 @@ # shim for pylib going away # if pylib is installed this file will get skipped # (`py/__init__.py` has higher precedence) +from __future__ import annotations + import sys import _pytest._py.error as error diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index 449cb39b82d..a1432d7497b 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -1,5 +1,7 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" +from __future__ import annotations + from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo diff --git a/src/pytest/__main__.py b/src/pytest/__main__.py index b170152937b..e13a39d4a1f 100644 --- a/src/pytest/__main__.py +++ b/src/pytest/__main__.py @@ -1,4 +1,6 @@ """The pytest entry point.""" +from __future__ import annotations + import pytest if __name__ == "__main__": diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 77a9838cf11..e946c25c2fb 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import multiprocessing import os diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 43390ab83ae..d862b0b0f1b 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses import importlib.metadata import os diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 33809528a06..61317e8d77c 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import sys from types import FrameType diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 260c80299b4..216e27cb9e0 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -1,13 +1,14 @@ # flake8: noqa # disable flake check on this file because some constructs are strange # or redundant on purpose and can't be disable on a line-by-line basis +from __future__ import annotations + import inspect import linecache import sys import textwrap from pathlib import Path from typing import Any -from typing import Dict import pytest from _pytest._code import Code @@ -335,7 +336,7 @@ def test_findsource(monkeypatch) -> None: assert src is not None assert "if 1:" in str(src) - d: Dict[str, Any] = {} + d: dict[str, Any] = {} eval(co, d) src, lineno = findsource(d["x"]) assert src is not None diff --git a/testing/conftest.py b/testing/conftest.py index bcb05339b62..b88c84fd058 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import dataclasses import re import sys from typing import Generator -from typing import List import pytest from _pytest.monkeypatch import MonkeyPatch @@ -188,22 +189,22 @@ class ColorMapping: NO_COLORS = {k: "" for k in COLORS.keys()} @classmethod - def format(cls, lines: List[str]) -> List[str]: + def format(cls, lines: list[str]) -> list[str]: """Straightforward replacement of color names to their ASCII codes.""" return [line.format(**cls.COLORS) for line in lines] @classmethod - def format_for_fnmatch(cls, lines: List[str]) -> List[str]: + def format_for_fnmatch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.fnmatch_lines""" return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines] @classmethod - def format_for_rematch(cls, lines: List[str]) -> List[str]: + def format_for_rematch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.re_match_lines""" return [line.format(**cls.RE_COLORS) for line in lines] @classmethod - def strip_colors(cls, lines: List[str]) -> List[str]: + def strip_colors(cls, lines: list[str]) -> list[str]: """Entirely remove every color code""" return [line.format(**cls.NO_COLORS) for line in lines] diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index ebff49ce6d9..a4690db3215 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest import deprecated from _pytest.pytester import Pytester diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py index 5b00ac90e1b..b83c0622169 100644 --- a/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,4 +1,6 @@ """Reproduces issue #3774""" +from __future__ import annotations + from unittest import mock import pytest diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py index 9cd366295e7..0f198ccf671 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_init(): pass diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py index 8f2d73cfa4f..fe40c10a224 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/testing/example_scripts/collect/package_infinite_recursion/conftest.py index 973ccc0c030..8c9c7305954 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/conftest.py +++ b/testing/example_scripts/collect/package_infinite_recursion/conftest.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def pytest_ignore_collect(collection_path): return False diff --git a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py index f174823854e..c9eb7906a80 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +++ b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test(): pass diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py index 9cd366295e7..0f198ccf671 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_init(): pass diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py index 8f2d73cfa4f..fe40c10a224 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/testing/example_scripts/config/collect_pytest_prefix/conftest.py index 2da4ffe2fed..5e0ab54411b 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/conftest.py +++ b/testing/example_scripts/config/collect_pytest_prefix/conftest.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + class pytest_something: pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py index 8f2d73cfa4f..fe40c10a224 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py +++ b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/conftest_usageerror/conftest.py b/testing/example_scripts/conftest_usageerror/conftest.py index 8973e4252d3..3e7c1771802 100644 --- a/testing/example_scripts/conftest_usageerror/conftest.py +++ b/testing/example_scripts/conftest_usageerror/conftest.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def pytest_configure(config): import pytest diff --git a/testing/example_scripts/customdirectory/conftest.py b/testing/example_scripts/customdirectory/conftest.py index 5357014d7ab..0e59e0931c6 100644 --- a/testing/example_scripts/customdirectory/conftest.py +++ b/testing/example_scripts/customdirectory/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/testing/example_scripts/customdirectory/tests/test_first.py b/testing/example_scripts/customdirectory/tests/test_first.py index 0a78de59945..9953dd37785 100644 --- a/testing/example_scripts/customdirectory/tests/test_first.py +++ b/testing/example_scripts/customdirectory/tests/test_first.py @@ -1,3 +1,6 @@ # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_second.py b/testing/example_scripts/customdirectory/tests/test_second.py index eed724a7d96..df264f48b3b 100644 --- a/testing/example_scripts/customdirectory/tests/test_second.py +++ b/testing/example_scripts/customdirectory/tests/test_second.py @@ -1,3 +1,6 @@ # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_third.py b/testing/example_scripts/customdirectory/tests/test_third.py index 61cf59dc16c..b8b072dd770 100644 --- a/testing/example_scripts/customdirectory/tests/test_third.py +++ b/testing/example_scripts/customdirectory/tests/test_third.py @@ -1,3 +1,6 @@ # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py index d96c90a91bd..18180b99f2d 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py index 7479c66c1be..0dcc7ab2802 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py index 4737ef904e0..4985c69ff30 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py index e026fe3d192..b787cb39ee2 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_initvar.py b/testing/example_scripts/dataclasses/test_compare_initvar.py index d859634ddd5..dfebbf2bd20 100644 --- a/testing/example_scripts/dataclasses/test_compare_initvar.py +++ b/testing/example_scripts/dataclasses/test_compare_initvar.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import InitVar diff --git a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py index 0945790f004..97fd10ba5cf 100644 --- a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index 0a4820c69ba..b45a6772c59 100644 --- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/doctest/main_py/__main__.py b/testing/example_scripts/doctest/main_py/__main__.py index e471d06d643..edf13067cb4 100644 --- a/testing/example_scripts/doctest/main_py/__main__.py +++ b/testing/example_scripts/doctest/main_py/__main__.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_this_is_ignored(): assert True diff --git a/testing/example_scripts/doctest/main_py/test_normal_module.py b/testing/example_scripts/doctest/main_py/test_normal_module.py index 700cc9750cf..4e4941a44a7 100644 --- a/testing/example_scripts/doctest/main_py/test_normal_module.py +++ b/testing/example_scripts/doctest/main_py/test_normal_module.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def test_doc(): """ >>> 10 > 5 diff --git a/testing/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py index a7a5e9db80a..23393726341 100644 --- a/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py index f174823854e..c9eb7906a80 100644 --- a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py +++ b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test(): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py index be5adbeb6e5..addcbf16828 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py index df36da1369b..9345255e7b5 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_1(arg1): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index 00981c5dc12..9533889a726 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py index 1c34f94acc4..5d878cc9680 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_2(arg2): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py index d1efcbb338c..98a0bc8c1a1 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py index 5dfd2f77957..94652aa1937 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py index 4e22ce5a137..04907b4c126 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py index 0d891fbb503..8ce84c9820b 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_spam(spam): assert spam == "spamspam" diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py index 5dfd2f77957..94652aa1937 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py index 46d1446f470..426141c6e63 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py index 87a0c894111..cd56bb9411a 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py index 0661cb301fc..9932dffa1a7 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py index 256b92a17dd..09ae3930ef4 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py index e15dbd2ca45..22a02de20f3 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py index b775203231f..d46f24f164f 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_fixture_named_request.py b/testing/example_scripts/fixtures/test_fixture_named_request.py index 75514bf8b8c..07310538527 100644 --- a/testing/example_scripts/fixtures/test_fixture_named_request.py +++ b/testing/example_scripts/fixtures/test_fixture_named_request.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py index 055a1220b1c..25b6b41d23f 100644 --- a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +++ b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index 0598eb841a4..58b6c58d042 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py index 56444d14748..de6ba18aca2 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_hello(): pass diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py index 73437ef7bdb..108dbd92912 100644 --- a/testing/example_scripts/issue_519.py +++ b/testing/example_scripts/issue_519.py @@ -1,6 +1,6 @@ +from __future__ import annotations + import pprint -from typing import List -from typing import Tuple import pytest @@ -15,7 +15,7 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope="session") def checked_order(): - order: List[Tuple[str, str, str]] = [] + order: list[tuple[str, str, str]] = [] yield order pprint.pprint(order) diff --git a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py index 35a2c7b7628..8647b88f109 100644 --- a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +++ b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py index ff1eaf7d6bb..a25385aa175 100644 --- a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +++ b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import pathlib diff --git a/testing/example_scripts/perf_examples/collect_stats/template_test.py b/testing/example_scripts/perf_examples/collect_stats/template_test.py index 064ade190a1..c08e67c8fdf 100644 --- a/testing/example_scripts/perf_examples/collect_stats/template_test.py +++ b/testing/example_scripts/perf_examples/collect_stats/template_test.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_x(): pass diff --git a/testing/example_scripts/tmpdir/tmp_path_fixture.py b/testing/example_scripts/tmpdir/tmp_path_fixture.py index 8675eb2fa62..cffe0767cb4 100644 --- a/testing/example_scripts/tmpdir/tmp_path_fixture.py +++ b/testing/example_scripts/tmpdir/tmp_path_fixture.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py index d421ce927c9..79dd05d5c58 100644 --- a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +++ b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest import pytest diff --git a/testing/example_scripts/unittest/test_setup_skip.py b/testing/example_scripts/unittest/test_setup_skip.py index 93f79bb3b2e..7a7742c1c32 100644 --- a/testing/example_scripts/unittest/test_setup_skip.py +++ b/testing/example_scripts/unittest/test_setup_skip.py @@ -1,4 +1,6 @@ """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_class.py b/testing/example_scripts/unittest/test_setup_skip_class.py index 4f251dcba17..625654a7fa1 100644 --- a/testing/example_scripts/unittest/test_setup_skip_class.py +++ b/testing/example_scripts/unittest/test_setup_skip_class.py @@ -1,4 +1,6 @@ """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_module.py b/testing/example_scripts/unittest/test_setup_skip_module.py index 98befbe510f..97ff02f32a3 100644 --- a/testing/example_scripts/unittest/test_setup_skip_module.py +++ b/testing/example_scripts/unittest/test_setup_skip_module.py @@ -1,4 +1,6 @@ """setUpModule is always called, even if all tests in the module are skipped""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py index 1cd2168604c..2e00621c2b2 100644 --- a/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,8 +1,9 @@ -from typing import List +from __future__ import annotations + from unittest import IsolatedAsyncioTestCase -teardowns: List[None] = [] +teardowns: list[None] = [] class AsyncArguments(IsolatedAsyncioTestCase): diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py index fb26617067c..a30e84c11ce 100644 --- a/testing/example_scripts/unittest/test_unittest_asynctest.py +++ b/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -1,11 +1,12 @@ """Issue #7110""" +from __future__ import annotations + import asyncio -from typing import List import asynctest -teardowns: List[None] = [] +teardowns: list[None] = [] class Test(asynctest.TestCase): diff --git a/testing/example_scripts/unittest/test_unittest_plain_async.py b/testing/example_scripts/unittest/test_unittest_plain_async.py index 78dfece684e..1da6adc10ac 100644 --- a/testing/example_scripts/unittest/test_unittest_plain_async.py +++ b/testing/example_scripts/unittest/test_unittest_plain_async.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message.py b/testing/example_scripts/warnings/test_group_warnings_by_message.py index 6985caa4407..efbefb47a75 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py index b8c11cb71c9..88aa44255a4 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py index 636d04a5505..dab0fa3100d 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from test_1 import func diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index 7b9c109889e..80f78d843a2 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py index 998df7b1ca7..d49d0684a9b 100644 --- a/testing/freeze/create_executable.py +++ b/testing/freeze/create_executable.py @@ -1,4 +1,6 @@ """Generate an executable with pytest runner embedded using PyInstaller.""" +from __future__ import annotations + if __name__ == "__main__": import pytest import subprocess diff --git a/testing/freeze/runtests_script.py b/testing/freeze/runtests_script.py index 591863016ac..d722aca1051 100644 --- a/testing/freeze/runtests_script.py +++ b/testing/freeze/runtests_script.py @@ -2,6 +2,7 @@ This is the script that is actually frozen into an executable: simply executes pytest main(). """ +from __future__ import annotations if __name__ == "__main__": import sys diff --git a/testing/freeze/tests/test_trivial.py b/testing/freeze/tests/test_trivial.py index 08a55552abb..72253a54db4 100644 --- a/testing/freeze/tests/test_trivial.py +++ b/testing/freeze/tests/test_trivial.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def test_upper(): assert "foo".upper() == "FOO" diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py index 678a69c858a..19d97190494 100644 --- a/testing/freeze/tox_run.py +++ b/testing/freeze/tox_run.py @@ -2,6 +2,8 @@ Called by tox.ini: uses the generated executable to run the tests in ./tests/ directory. """ +from __future__ import annotations + if __name__ == "__main__": import os import sys diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 3432c63f688..901905d1a0d 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import textwrap from collections import ChainMap from collections import Counter diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index d94faa4f194..974bb0bf621 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index c7e63c67284..4dd7e72e4fa 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import os import re @@ -5,7 +7,6 @@ import sys from pathlib import Path from typing import Generator -from typing import Optional from unittest import mock import pytest @@ -165,7 +166,7 @@ def test_attr_hasmarkup() -> None: assert "\x1b[0m" in s -def assert_color(expected: bool, default: Optional[bool] = None) -> None: +def assert_color(expected: bool, default: bool | None = None) -> None: file = io.StringIO() if default is None: default = not expected diff --git a/testing/io/test_wcwidth.py b/testing/io/test_wcwidth.py index 7cc74df5d07..149dd328c11 100644 --- a/testing/io/test_wcwidth.py +++ b/testing/io/test_wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest._io.wcwidth import wcswidth from _pytest._io.wcwidth import wcwidth diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index f4912aecce4..708835944bb 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -1,5 +1,5 @@ -# mypy: disable-error-code="attr-defined" -# mypy: disallow-untyped-defs +from __future__ import annotations + import logging from typing import Iterator @@ -7,6 +7,9 @@ from _pytest.logging import caplog_records_key from _pytest.pytester import Pytester +# mypy: disable-error-code="attr-defined" +# mypy: disallow-untyped-defs + logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py index 37971293726..cfe3bee68c4 100644 --- a/testing/logging/test_formatter.py +++ b/testing/logging/test_formatter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Any diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 5d10688a00e..09b27828a36 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import os import re diff --git a/testing/plugins_integration/bdd_wallet.py b/testing/plugins_integration/bdd_wallet.py index 35927ea5875..79538b4a8c6 100644 --- a/testing/plugins_integration/bdd_wallet.py +++ b/testing/plugins_integration/bdd_wallet.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pytest_bdd import given from pytest_bdd import scenario from pytest_bdd import then diff --git a/testing/plugins_integration/django_settings.py b/testing/plugins_integration/django_settings.py index 0715f476531..250ce225355 100644 --- a/testing/plugins_integration/django_settings.py +++ b/testing/plugins_integration/django_settings.py @@ -1 +1,3 @@ +from __future__ import annotations + SECRET_KEY = "mysecret" diff --git a/testing/plugins_integration/pytest_anyio_integration.py b/testing/plugins_integration/pytest_anyio_integration.py index 65c2f593663..d0a10d00ae8 100644 --- a/testing/plugins_integration/pytest_anyio_integration.py +++ b/testing/plugins_integration/pytest_anyio_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import anyio import pytest diff --git a/testing/plugins_integration/pytest_asyncio_integration.py b/testing/plugins_integration/pytest_asyncio_integration.py index 5d2a3faccfc..838c3f2f7d6 100644 --- a/testing/plugins_integration/pytest_asyncio_integration.py +++ b/testing/plugins_integration/pytest_asyncio_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import pytest diff --git a/testing/plugins_integration/pytest_mock_integration.py b/testing/plugins_integration/pytest_mock_integration.py index 740469d00fb..14c4beda036 100644 --- a/testing/plugins_integration/pytest_mock_integration.py +++ b/testing/plugins_integration/pytest_mock_integration.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + def test_mocker(mocker): mocker.MagicMock() diff --git a/testing/plugins_integration/pytest_trio_integration.py b/testing/plugins_integration/pytest_trio_integration.py index 199f7850bc4..4d2a3214936 100644 --- a/testing/plugins_integration/pytest_trio_integration.py +++ b/testing/plugins_integration/pytest_trio_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import trio import pytest diff --git a/testing/plugins_integration/pytest_twisted_integration.py b/testing/plugins_integration/pytest_twisted_integration.py index 94748d036e5..2f8c96ac4e3 100644 --- a/testing/plugins_integration/pytest_twisted_integration.py +++ b/testing/plugins_integration/pytest_twisted_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest_twisted from twisted.internet.task import deferLater diff --git a/testing/plugins_integration/simple_integration.py b/testing/plugins_integration/simple_integration.py index 20b2fc4b5bb..1a51a7b683f 100644 --- a/testing/plugins_integration/simple_integration.py +++ b/testing/plugins_integration/simple_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/testing/python/approx.py b/testing/python/approx.py index 3b87e58f91a..d29174d22c7 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import operator from contextlib import contextmanager from decimal import Decimal @@ -5,7 +7,6 @@ from math import sqrt from operator import eq from operator import ne -from typing import Optional import pytest from _pytest.pytester import Pytester @@ -415,9 +416,7 @@ def test_zero_tolerance(self): (-1e100, -1e100), ], ) - def test_negative_tolerance( - self, rel: Optional[float], abs: Optional[float] - ) -> None: + def test_negative_tolerance(self, rel: float | None, abs: float | None) -> None: # Negative tolerances are not allowed. with pytest.raises(ValueError): 1.1 == approx(1, rel, abs) diff --git a/testing/python/collect.py b/testing/python/collect.py index da11dd34a9a..635f3eec2d8 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import os import sys import textwrap from typing import Any -from typing import Dict import _pytest._code import pytest @@ -1104,7 +1105,7 @@ def test_filter_traceback_generated_code(self) -> None: tb = None try: - ns: Dict[str, Any] = {} + ns: dict[str, Any] = {} exec("def foo(): raise ValueError", ns) ns["foo"]() except ValueError: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 775056a8e98..9f08ec22d68 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys import textwrap @@ -4403,7 +4405,7 @@ def test_fixture_named_request(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "*'request' is a reserved word for fixtures, use another name:", - " *test_fixture_named_request.py:5", + " *test_fixture_named_request.py:7", ] ) diff --git a/testing/python/integration.py b/testing/python/integration.py index 054c14a39e4..4de93afbc67 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest._code import getfslineno from _pytest.fixtures import getfixturemarker diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 9768c82ffa7..42f7c5e81ef 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses import itertools import re @@ -7,11 +9,7 @@ from typing import cast from typing import Dict from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import hypothesis from hypothesis import strategies @@ -34,7 +32,7 @@ def Metafunc(self, func, config=None) -> python.Metafunc: # on the funcarg level, so we don't need a full blown # initialization. class FuncFixtureInfoMock: - name2fixturedefs: Dict[str, List[fixtures.FixtureDef[object]]] = {} + name2fixturedefs: dict[str, list[fixtures.FixtureDef[object]]] = {} def __init__(self, names): self.names_closure = names @@ -100,7 +98,7 @@ class Exc(Exception): def __repr__(self): return "Exc(from_gen)" - def gen() -> Iterator[Union[int, None, Exc]]: + def gen() -> Iterator[int | None | Exc]: yield 0 yield None yield Exc() @@ -345,7 +343,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[str, Any, str]] = [ + values: list[tuple[str, Any, str]] = [ ("ação", MockConfig({option: True}), "ação"), ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -515,7 +513,7 @@ def test_idmaker_enum(self) -> None: def test_idmaker_idfn(self) -> None: """#351""" - def ids(val: object) -> Optional[str]: + def ids(val: object) -> str | None: if isinstance(val, Exception): return repr(val) return None @@ -578,7 +576,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -616,7 +614,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -1747,9 +1745,9 @@ def test_parametrize_some_arguments_auto_scope( self, pytester: Pytester, monkeypatch ) -> None: """Integration test for (#3941)""" - class_fix_setup: List[object] = [] + class_fix_setup: list[object] = [] monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False) - func_fix_setup: List[object] = [] + func_fix_setup: list[object] = [] monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False) pytester.makepyfile( diff --git a/testing/python/raises.py b/testing/python/raises.py index 3dcec31eb1f..62316486eff 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import sys diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py index f756dca41c7..c860b61e21b 100644 --- a/testing/python/show_fixtures_per_test.py +++ b/testing/python/show_fixtures_per_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 8c10e230b0c..8a1c78da45d 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import subprocess import sys from pathlib import Path diff --git a/testing/test_assertion.py b/testing/test_assertion.py index e55ec38e145..7f4e1774714 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1,10 +1,10 @@ +from __future__ import annotations + import collections import sys import textwrap from typing import Any -from typing import List from typing import MutableSequence -from typing import Optional import attr @@ -18,7 +18,7 @@ from _pytest.pytester import Pytester -def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): +def mock_config(verbose: int = 0, assertion_override: int | None = None): class TerminalWriter: def _highlight(self, source, lexer="python"): return source @@ -27,7 +27,7 @@ class Config: def get_terminal_writer(self): return TerminalWriter() - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: if verbosity_type is None: return verbose if verbosity_type == _Config.VERBOSITY_ASSERTIONS: @@ -370,12 +370,12 @@ def test_check(list): result.stdout.fnmatch_lines(["*test_hello*FAIL*", "*test_check*PASS*"]) -def callop(op: str, left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callop(op: str, left: Any, right: Any, verbose: int = 0) -> list[str] | None: config = mock_config(verbose=verbose) return plugin.pytest_assertrepr_compare(config, op, left, right) -def callequal(left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callequal(left: Any, right: Any, verbose: int = 0) -> list[str] | None: return callop("==", left, right, verbose) @@ -1312,7 +1312,7 @@ class TestTruncateExplanation: LINES_IN_TRUNCATION_MSG = 2 def test_doesnt_truncate_when_input_is_empty_list(self) -> None: - expl: List[str] = [] + expl: list[str] = [] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) assert result == expl diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index a4d48b6feeb..f4536b32603 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import ast import errno import glob @@ -12,12 +14,8 @@ from functools import partial from pathlib import Path from typing import cast -from typing import Dict from typing import Generator -from typing import List from typing import Mapping -from typing import Optional -from typing import Set from unittest import mock import _pytest._code @@ -44,13 +42,13 @@ def rewrite(src: str) -> ast.Module: def getmsg( - f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False -) -> Optional[str]: + f, extra_ns: Mapping[str, object] | None = None, *, must_pass: bool = False +) -> str | None: """Rewrite the assertions in f, run it, and get the failure message.""" src = "\n".join(_pytest._code.Code.from_function(f).source().lines) mod = rewrite(src) code = compile(mod, "", "exec") - ns: Dict[str, object] = {} + ns: dict[str, object] = {} if extra_ns is not None: ns.update(extra_ns) exec(code, ns) @@ -1638,8 +1636,8 @@ def hook( """ import importlib.machinery - self.find_spec_calls: List[str] = [] - self.initial_paths: Set[Path] = set() + self.find_spec_calls: list[str] = [] + self.initial_paths: set[Path] = set() class StubSession: _initialpaths = self.initial_paths @@ -2056,7 +2054,7 @@ class TestReprSizeVerbosity: ) def test_get_maxsize_for_saferepr(self, verbose: int, expected_size) -> None: class FakeConfig: - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: return verbose config = FakeConfig() diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 21c1957cfeb..24b57ee0c2e 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import os import shutil from pathlib import Path from typing import Generator -from typing import List import pytest from _pytest.config import ExitCode @@ -677,7 +678,7 @@ def test_lf_and_ff_prints_no_needless_message( else: assert "rerun previous" in result.stdout.str() - def get_cached_last_failed(self, pytester: Pytester) -> List[str]: + def get_cached_last_failed(self, pytester: Pytester) -> list[str]: config = pytester.parseconfigure() assert config.cache is not None return sorted(config.cache.get("cache/lastfailed", {})) diff --git a/testing/test_capture.py b/testing/test_capture.py index b6ea8161356..913dcfb8dfd 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import contextlib import io import os diff --git a/testing/test_collection.py b/testing/test_collection.py index be65169f75c..040a2977217 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import os import pprint import shutil import sys import textwrap from pathlib import Path -from typing import List import pytest from _pytest.config import ExitCode @@ -515,7 +516,7 @@ def test_collect_topdir(self, pytester: Pytester) -> None: assert len(colitems) == 1 assert colitems[0].path == topdir - def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: + def get_reported_items(self, hookrec: HookRecorder) -> list[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" calls = hookrec.getcalls("pytest_collectreport") return [ diff --git a/testing/test_compat.py b/testing/test_compat.py index 27c6db95bbf..c481c46e1c4 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import enum import sys from functools import cached_property from functools import partial from functools import wraps from typing import TYPE_CHECKING -from typing import Union import pytest from _pytest.compat import _PytestWrapper @@ -214,7 +215,7 @@ def prop(self) -> int: def test_assert_never_union() -> None: - x: Union[int, str] = 10 + x: int | str = 10 if isinstance(x, int): pass diff --git a/testing/test_config.py b/testing/test_config.py index 2d95fb4cc82..b3ce906a526 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses import importlib.metadata import os @@ -6,12 +8,7 @@ import textwrap from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Sequence -from typing import Tuple -from typing import Type -from typing import Union import _pytest._code import pytest @@ -607,7 +604,7 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None: class TestConfigAPI: def test_config_trace(self, pytester: Pytester) -> None: config = pytester.parseconfig() - values: List[str] = [] + values: list[str] = [] config.trace.root.setwriter(values.append) config.trace("hello") assert len(values) == 1 @@ -972,7 +969,7 @@ def test_basic_behavior(self, _sys_snapshot) -> None: def test_invocation_params_args(self, _sys_snapshot) -> None: """Show that fromdictargs can handle args in their "orig" format""" - option_dict: Dict[str, object] = {} + option_dict: dict[str, object] = {} args = ["-vvvv", "-s", "a", "b"] config = Config.fromdictargs(option_dict, args) @@ -1186,7 +1183,7 @@ def distributions(): def test_disable_plugin_autoload( pytester: Pytester, monkeypatch: MonkeyPatch, - parse_args: Union[Tuple[str, str], Tuple[()]], + parse_args: tuple[str, str] | tuple[()], should_load: bool, ) -> None: class DummyEntryPoint: @@ -1282,7 +1279,7 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None: ], ) def test_consider_args_after_options_for_rootdir( - pytester: Pytester, args: List[str] + pytester: Pytester, args: list[str] ) -> None: """ Consider all arguments in the command-line for rootdir @@ -2149,7 +2146,7 @@ def test_strtobool() -> None: ], ) def test_parse_warning_filter( - arg: str, escape: bool, expected: Tuple[str, str, Type[Warning], str, int] + arg: str, escape: bool, expected: tuple[str, str, type[Warning], str, int] ) -> None: assert parse_warning_filter(arg, escape=escape) == expected diff --git a/testing/test_conftest.py b/testing/test_conftest.py index cfc2d577b53..a8c1269d70c 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,13 +1,12 @@ +from __future__ import annotations + import os import textwrap from pathlib import Path from typing import cast -from typing import Dict from typing import Generator from typing import List -from typing import Optional from typing import Sequence -from typing import Union import pytest from _pytest.config import ExitCode @@ -26,8 +25,8 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( conftest: PytestPluginManager, - args: Sequence[Union[str, Path]], - confcutdir: Optional[Path] = None, + args: Sequence[str | Path], + confcutdir: Path | None = None, ) -> None: conftest._set_initial_conftests( args=args, @@ -484,7 +483,7 @@ def pytest_addoption(parser): class TestConftestVisibility: - def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]: # for issue616 + def _setup_tree(self, pytester: Pytester) -> dict[str, Path]: # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html runner = pytester.mkdir("empty") diff --git a/testing/test_debugging.py b/testing/test_debugging.py index eecc1e39fd3..73b0f0a1af8 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import os import sys -from typing import List import _pytest._code import pytest @@ -38,7 +39,7 @@ def runpdb_and_get_report(pytester: Pytester, source: str): @pytest.fixture -def custom_pdb_calls() -> List[str]: +def custom_pdb_calls() -> list[str]: called = [] # install dummy debugger class and track which methods were called on it @@ -857,7 +858,7 @@ def test_post_mortem(): self.flush(child) def test_pdb_custom_cls( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess( @@ -883,7 +884,7 @@ def test_pdb_validate_usepdb_cls(self): assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist") def test_pdb_custom_cls_without_pdb( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index f4d3155c435..3769acc9817 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import inspect import sys import textwrap from pathlib import Path from typing import Callable -from typing import Optional import pytest from _pytest.doctest import _get_checker @@ -1571,7 +1572,7 @@ def __getattr__(self, _): "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) def test_warning_on_unwrap_of_broken_object( - stop: Optional[Callable[[object], object]] + stop: Callable[[object], object] | None ) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" diff --git a/testing/test_entry_points.py b/testing/test_entry_points.py index dfb3d57d2d8..d1db02e35f5 100644 --- a/testing/test_entry_points.py +++ b/testing/test_entry_points.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import importlib.metadata diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index cad7a17c047..55b7e2a2eb6 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -4,6 +4,8 @@ See https://github.com/pytest-dev/pytest/issues/3333 for details. """ +from __future__ import annotations + import pytest from _pytest.pytester import Pytester diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index 5b7911f21f8..084e44cb34b 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import sys diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 8287de603ed..1c9d4a3f5a2 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os from pathlib import Path from textwrap import dedent diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index ba89d0c4acf..d396864ee95 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest.config import ExitCode from _pytest.pytester import Pytester diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 3f88c21e2b7..e48b40201a8 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,13 +1,11 @@ +from __future__ import annotations + import os import platform from datetime import datetime from pathlib import Path from typing import cast -from typing import List -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from xml.dom import minidom import xmlschema @@ -38,8 +36,8 @@ def __init__(self, pytester: Pytester, schema: xmlschema.XMLSchema) -> None: self.schema = schema def __call__( - self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" - ) -> Tuple[RunResult, "DomNode"]: + self, *args: str | os.PathLike[str], family: str | None = "xunit1" + ) -> tuple[RunResult, DomNode]: if family: args = ("-o", "junit_family=" + family) + args xml_path = self.pytester.path.joinpath("junit.xml") @@ -940,7 +938,7 @@ def test_mangle_test_address() -> None: def test_dont_configure_on_workers(tmp_path: Path) -> None: - gotten: List[object] = [] + gotten: list[object] = [] class FakeConfig: if TYPE_CHECKING: @@ -1183,7 +1181,7 @@ def test_unicode_issue368(pytester: Pytester) -> None: class Report(BaseReport): longrepr = ustr - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" when = "teardown" @@ -1496,7 +1494,7 @@ def test_global_properties(pytester: Pytester, xunit_family: str) -> None: log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "test_node_id" log.pytest_sessionstart() @@ -1532,7 +1530,7 @@ def test_url_property(pytester: Pytester) -> None: class Report(BaseReport): longrepr = "FooBarBaz" - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" url = test_url diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index 700499f24ea..46d347bb371 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py index 1ac3afd09e8..8ac3aa1f337 100644 --- a/testing/test_link_resolve.py +++ b/testing/test_link_resolve.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import subprocess import sys diff --git a/testing/test_main.py b/testing/test_main.py index 3c8998c1a35..8aa23c58a04 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import argparse import os import re import sys from pathlib import Path -from typing import Optional import pytest from _pytest.config import ExitCode @@ -81,7 +82,7 @@ def pytest_internalerror(excrepr, excinfo): @pytest.mark.parametrize("returncode", (None, 42)) def test_wrap_session_exit_sessionfinish( - returncode: Optional[int], pytester: Pytester + returncode: int | None, pytester: Pytester ) -> None: pytester.makeconftest( """ diff --git a/testing/test_mark.py b/testing/test_mark.py index 609f73d68eb..086c59b42d2 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1,7 +1,7 @@ +from __future__ import annotations + import os import sys -from typing import List -from typing import Optional from unittest import mock import pytest @@ -213,7 +213,7 @@ def test_hello(): ], ) def test_mark_option( - expr: str, expected_passed: List[Optional[str]], pytester: Pytester + expr: str, expected_passed: list[str | None], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -237,7 +237,7 @@ def test_two(): [("interface", ["test_interface"]), ("not interface", ["test_nointer"])], ) def test_mark_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makeconftest( """ @@ -275,7 +275,7 @@ def test_nointer(): ], ) def test_keyword_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -313,7 +313,7 @@ def test_keyword_option_considers_mark(pytester: Pytester) -> None: ], ) def test_keyword_option_parametrize( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -894,7 +894,7 @@ def test_ddd(): pass ) monkeypatch.chdir(pytester.path / "suite") - def get_collected_names(*args: str) -> List[str]: + def get_collected_names(*args: str) -> list[str]: _, rec = pytester.inline_genitems(*args) calls = rec.getcalls("pytest_collection_finish") assert len(calls) == 1 @@ -929,7 +929,7 @@ def test_aliases(self) -> None: @pytest.mark.parametrize("mark", [None, "", "skip", "xfail"]) def test_parameterset_for_parametrize_marks( - pytester: Pytester, mark: Optional[str] + pytester: Pytester, mark: str | None ) -> None: if mark is not None: pytester.makeini( diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index f3643e7b409..81d93be5608 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable import pytest diff --git a/testing/test_meta.py b/testing/test_meta.py index 9201bd21611..a80fd4d4dea 100644 --- a/testing/test_meta.py +++ b/testing/test_meta.py @@ -3,16 +3,17 @@ This ensures all internal packages can be imported without needing the pytest namespace being set, which is critical for the initialization of xdist. """ +from __future__ import annotations + import pkgutil import subprocess import sys -from typing import List import _pytest import pytest -def _modules() -> List[str]: +def _modules() -> list[str]: pytest_pkg: str = _pytest.__path__ # type: ignore return sorted( n diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 8175b5f0fad..5236af44232 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -1,11 +1,11 @@ +from __future__ import annotations + import os import re import sys import textwrap from pathlib import Path -from typing import Dict from typing import Generator -from typing import Type import pytest from _pytest.monkeypatch import MonkeyPatch @@ -134,7 +134,7 @@ def test_setitem() -> None: def test_setitem_deleted_meanwhile() -> None: - d: Dict[str, object] = {} + d: dict[str, object] = {} monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) del d["x"] @@ -159,7 +159,7 @@ def test_setenv_deleted_meanwhile(before: bool) -> None: def test_delitem() -> None: - d: Dict[str, object] = {"x": 1} + d: dict[str, object] = {"x": 1} monkeypatch = MonkeyPatch() monkeypatch.delitem(d, "x") assert "x" not in d @@ -359,7 +359,7 @@ class SampleInherit(Sample): [Sample, SampleInherit], ids=["new", "new-inherit"], ) -def test_issue156_undo_staticmethod(Sample: Type[Sample]) -> None: +def test_issue156_undo_staticmethod(Sample: type[Sample]) -> None: monkeypatch = MonkeyPatch() monkeypatch.setattr(Sample, "hello", None) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 880e2a44fae..bf4967da1e2 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,9 +1,9 @@ +from __future__ import annotations + import re import warnings from pathlib import Path from typing import cast -from typing import List -from typing import Type import pytest from _pytest import nodes @@ -30,7 +30,7 @@ ("a/b::c/d::e[/test]", ["", "a", "a/b", "a/b::c/d", "a/b::c/d::e[/test]"]), ), ) -def test_iterparentnodeids(nodeid: str, expected: List[str]) -> None: +def test_iterparentnodeids(nodeid: str, expected: list[str]) -> None: result = list(nodes.iterparentnodeids(nodeid)) assert result == expected @@ -94,7 +94,7 @@ def runtest(self): "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")] ) def test_node_warn_is_no_longer_only_pytest_warnings( - pytester: Pytester, warn_type: Type[Warning], msg: str + pytester: Pytester, warn_type: type[Warning], msg: str ) -> None: items = pytester.getitems( """ diff --git a/testing/test_nose.py b/testing/test_nose.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 2a629198438..ec3f1f652cb 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import argparse import locale import os diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 86b231f8b5e..da29f188e9d 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,7 +1,7 @@ +from __future__ import annotations + import email.message import io -from typing import List -from typing import Union import pytest from _pytest.monkeypatch import MonkeyPatch @@ -10,8 +10,8 @@ class TestPasteCapture: @pytest.fixture - def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]: - pastebinlist: List[Union[str, bytes]] = [] + def pastebinlist(self, monkeypatch, request) -> list[str | bytes]: + pastebinlist: list[str | bytes] = [] plugin = request.config.pluginmanager.getplugin("pastebin") monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append) return pastebinlist diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 3e1d2265bb7..60c101f9d91 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import errno import os.path import pickle diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index e5773412fbf..8e5bb071eb4 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,8 +1,9 @@ +from __future__ import annotations + import os import shutil import sys import types -from typing import List import pytest from _pytest.config import Config @@ -111,7 +112,7 @@ def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() - values: List[str] = [] + values: list[str] = [] pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 6fc6bd24519..87ab7c08f69 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import os import subprocess import sys import time from types import ModuleType -from typing import List import _pytest.pytester as pytester_mod import pytest @@ -226,7 +227,7 @@ def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None def spy_factory(self): class SysModulesSnapshotSpy: - instances: List["SysModulesSnapshotSpy"] = [] # noqa: F821 + instances: list[SysModulesSnapshotSpy] = [] # noqa: F821 def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) @@ -398,7 +399,7 @@ def test_preserve_container(self, monkeypatch: MonkeyPatch, path_type) -> None: original_data = list(getattr(sys, path_type)) original_other = getattr(sys, other_path_type) original_other_data = list(original_other) - new: List[object] = [] + new: list[object] = [] snapshot = SysPathsSnapshot() monkeypatch.setattr(sys, path_type, new) snapshot.restore() diff --git a/testing/test_python_path.py b/testing/test_python_path.py index dfef0f3fecf..2d1f2669da2 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -1,8 +1,8 @@ +from __future__ import annotations + import sys from textwrap import dedent from typing import Generator -from typing import List -from typing import Optional import pytest from _pytest.pytester import Pytester @@ -90,8 +90,8 @@ def test_clean_up(pytester: Pytester) -> None: pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n") pytester.makepyfile(test_foo="""def test_foo(): pass""") - before: Optional[List[str]] = None - after: Optional[List[str]] = None + before: list[str] | None = None + after: list[str] | None = None class Plugin: @pytest.hookimpl(wrapper=True, tryfirst=True) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 2508e22a2db..26b6897a824 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,7 +1,6 @@ +from __future__ import annotations + import warnings -from typing import List -from typing import Optional -from typing import Type import pytest from _pytest.pytester import Pytester @@ -50,7 +49,7 @@ class ChildOfChildWarning(ChildWarning): pass @staticmethod - def raise_warnings_from_list(_warnings: List[Type[Warning]]): + def raise_warnings_from_list(_warnings: list[type[Warning]]): for warn in _warnings: warnings.warn(f"Warning {warn().__repr__()}", warn) @@ -130,7 +129,7 @@ def test_invalid_enter_exit(self) -> None: class TestDeprecatedCall: """test pytest.deprecated_call()""" - def dep(self, i: int, j: Optional[int] = None) -> int: + def dep(self, i: int, j: int | None = None) -> int: if i == 0: warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) return 42 diff --git a/testing/test_reports.py b/testing/test_reports.py index 627ea1ed24f..757bfb1aa96 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from typing import Sequence -from typing import Union import pytest from _pytest._code.code import ExceptionChainRepr @@ -295,9 +296,9 @@ def test_a(): reprec = pytester.inline_run() if report_class is TestReport: - reports: Union[ - Sequence[TestReport], Sequence[CollectReport] - ] = reprec.getreports("pytest_runtest_logreport") + reports: ( + Sequence[TestReport] | Sequence[CollectReport] + ) = reprec.getreports("pytest_runtest_logreport") # we have 3 reports: setup/call/teardown assert len(reports) == 3 # get the call report diff --git a/testing/test_runner.py b/testing/test_runner.py index 26f5b9a0bc2..c68a81daddb 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,13 +1,11 @@ +from __future__ import annotations + import inspect import os import sys import types from functools import partial from pathlib import Path -from typing import Dict -from typing import List -from typing import Tuple -from typing import Type import pytest from _pytest import outcomes @@ -483,7 +481,7 @@ class TestClass(object): assert res[1].name == "TestClass" -reporttypes: List[Type[reports.BaseReport]] = [ +reporttypes: list[type[reports.BaseReport]] = [ reports.BaseReport, reports.TestReport, reports.CollectReport, @@ -493,9 +491,9 @@ class TestClass(object): @pytest.mark.parametrize( "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] ) -def test_report_extra_parameters(reporttype: Type[reports.BaseReport]) -> None: +def test_report_extra_parameters(reporttype: type[reports.BaseReport]) -> None: args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - basekw: Dict[str, List[object]] = dict.fromkeys(args, []) + basekw: dict[str, list[object]] = dict.fromkeys(args, []) report = reporttype(newthing=1, **basekw) assert report.newthing == 1 @@ -936,7 +934,7 @@ def runtest(self): def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: - pytest_current_test_vars: List[Tuple[str, str]] = [] + pytest_current_test_vars: list[tuple[str, str]] = [] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index e077ac41e2c..9a4641c3fab 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -1,5 +1,5 @@ """Test correct setup/teardowns at module, class, and instance level.""" -from typing import List +from __future__ import annotations import pytest from _pytest.pytester import Pytester @@ -249,7 +249,7 @@ def test_setup_teardown_function_level_with_optional_argument( """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys - trace_setups_teardowns: List[str] = [] + trace_setups_teardowns: list[str] = [] monkeypatch.setattr( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) diff --git a/testing/test_scope.py b/testing/test_scope.py index 09ee1343a80..97a2688c659 100644 --- a/testing/test_scope.py +++ b/testing/test_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re import pytest diff --git a/testing/test_session.py b/testing/test_session.py index 803bbed5434..ee9b20ad624 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch diff --git a/testing/test_setuponly.py b/testing/test_setuponly.py index fe4bdc514eb..a32ea00a4cc 100644 --- a/testing/test_setuponly.py +++ b/testing/test_setuponly.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import pytest diff --git a/testing/test_setupplan.py b/testing/test_setupplan.py index d51a1873959..5a9211d7806 100644 --- a/testing/test_setupplan.py +++ b/testing/test_setupplan.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_skipping.py b/testing/test_skipping.py index a002ba6e836..b4d845c479a 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import textwrap diff --git a/testing/test_stash.py b/testing/test_stash.py index 2c9df4832e4..5ae03d1fee8 100644 --- a/testing/test_stash.py +++ b/testing/test_stash.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest.stash import Stash from _pytest.stash import StashKey diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 85e38c7d568..3bcd948a85a 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path import pytest diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 20392096e40..204d923c506 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,4 +1,6 @@ """Terminal reporting of the full testing process.""" +from __future__ import annotations + import collections import os import sys @@ -7,9 +9,6 @@ from pathlib import Path from types import SimpleNamespace from typing import cast -from typing import Dict -from typing import List -from typing import Tuple import pluggy @@ -1943,9 +1942,9 @@ def tr() -> TerminalReporter: ) def test_summary_stats( tr: TerminalReporter, - exp_line: List[Tuple[str, Dict[str, bool]]], + exp_line: list[tuple[str, dict[str, bool]]], exp_color: str, - stats_arg: Dict[str, List[object]], + stats_arg: dict[str, list[object]], ) -> None: tr.stats = stats_arg diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py index fd9a091ccc9..d4ad23819f0 100644 --- a/testing/test_threadexception.py +++ b/testing/test_threadexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from _pytest.pytester import Pytester diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 2215e978a60..99a3d730707 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import dataclasses import os import stat @@ -6,8 +8,6 @@ from pathlib import Path from typing import Callable from typing import cast -from typing import List -from typing import Union import pytest from _pytest import pathlib @@ -33,7 +33,7 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: @dataclasses.dataclass class FakeConfig: - basetemp: Union[str, Path] + basetemp: str | Path @property def trace(self): @@ -396,7 +396,7 @@ def test_cleanup_lock_create(self, tmp_path): def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: lock = create_cleanup_lock(tmp_path) - registry: List[Callable[..., None]] = [] + registry: list[Callable[..., None]] = [] register_cleanup_lock_removal(lock, register=registry.append) (cleanup_func,) = registry diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 24f954051d6..2d18e1de42e 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import gc import sys -from typing import List import pytest from _pytest.config import ExitCode @@ -1211,7 +1212,7 @@ def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> No We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling tearDown() eventually to avoid memory leaks when using --pdb. """ - teardowns: List[str] = [] + teardowns: list[str] = [] monkeypatch.setattr( pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False ) @@ -1248,7 +1249,7 @@ def test_pdb_teardown_skipped_for_functions( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator (#7215). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( @@ -1285,7 +1286,7 @@ def test_pdb_teardown_skipped_for_classes( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator on the class (#10060). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( diff --git a/testing/test_unraisableexception.py b/testing/test_unraisableexception.py index d255adb2b91..627e6ced231 100644 --- a/testing/test_unraisableexception.py +++ b/testing/test_unraisableexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import pytest diff --git a/testing/test_warning_types.py b/testing/test_warning_types.py index 5f69439ef3e..067ad9ee7e9 100644 --- a/testing/test_warning_types.py +++ b/testing/test_warning_types.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import inspect import pytest diff --git a/testing/test_warnings.py b/testing/test_warnings.py index e7834dc4d4d..f70bd26b295 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,9 +1,8 @@ +from __future__ import annotations + import os import sys import warnings -from typing import List -from typing import Optional -from typing import Tuple import pytest from _pytest.fixtures import FixtureRequest @@ -623,11 +622,11 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None: "*== %s ==*" % WARNINGS_SUMMARY_HEADER, "test_1.py: 21 warnings", "test_2.py: 1 warning", - " */test_1.py:7: UserWarning: foo", + " */test_1.py:9: UserWarning: foo", " warnings.warn(UserWarning(msg))", "", "test_1.py: 20 warnings", - " */test_1.py:7: UserWarning: bar", + " */test_1.py:9: UserWarning: bar", " warnings.warn(UserWarning(msg))", "", "-- Docs: *", @@ -659,8 +658,8 @@ class TestStackLevel: @pytest.fixture def capwarn(self, pytester: Pytester): class CapturedWarnings: - captured: List[ - Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]] + captured: list[ + tuple[warnings.WarningMessage, tuple[str, int, str] | None] ] = [] @classmethod diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 57f2bae475f..c2796709160 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -3,6 +3,8 @@ This file is not executed, it is only checked by mypy to ensure that none of the code triggers any mypy errors. """ +from __future__ import annotations + import contextlib from typing import Optional