RFC: from __future__ import annotations + pyupgrade
This commit is contained in:
parent
c2a4a8d518
commit
f784b97fca
|
@ -57,3 +57,4 @@ pip-wheel-metadata/
|
|||
|
||||
# pytest debug logs generated via --debug
|
||||
pytestdebug.log
|
||||
/.dmypy.json
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
from __future__ import annotations
|
||||
|
||||
for i in range(1000):
|
||||
exec("def test_func_%d(): pass" % i)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
SKIP = True
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from unittest import TestCase # noqa: F401
|
||||
|
||||
for i in range(15000):
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
for i in range(5000):
|
||||
exec(
|
||||
f"""
|
||||
|
|
|
@ -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 @@ intersphinx_mapping = {
|
|||
}
|
||||
|
||||
|
||||
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 configure_logging(app: "sphinx.application.Sphinx") -> None:
|
|||
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",
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
collect_ignore = ["conf.py"]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
module.TestStateFullThing.classcount = 0
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from __future__ import annotations
|
||||
|
||||
collect_ignore = ["nonpython", "customdirectory"]
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_first.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_1():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_second.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_2():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# content of test_third.py
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_3():
|
||||
pass
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# content of conftest.py
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# run this with $ pytest --collect-only test_collectonly.py
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_function():
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
xfail = pytest.mark.xfail
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
|||
|
||||
Requires Python3.6+.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
|
@ -13,6 +13,8 @@ After that, it will create a release using the `release` tox environment, and pu
|
|||
**Token**: currently the token from the GitHub Actions is used, pushed with
|
||||
`pytest bot <pytestbot@gmail.com>` commit author.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
"""Invoke development tasks."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# mypy: disallow-untyped-defs
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import pathlib
|
||||
import re
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
__all__ = ["__version__", "version_tuple"]
|
||||
|
||||
try:
|
||||
|
|
|
@ -61,13 +61,13 @@ If things do not work right away:
|
|||
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 @@ if os.environ.get("_ARGCOMPLETE"):
|
|||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import builtins
|
||||
import dataclasses
|
||||
import inspect
|
||||
import os
|
||||
|
@ -16,7 +19,6 @@ from types import TracebackType
|
|||
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 Iterable
|
|||
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 @@ class Code:
|
|||
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 @@ class Code:
|
|||
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 @@ class Code:
|
|||
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 @@ class Frame:
|
|||
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 @@ class Frame:
|
|||
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 @@ class TracebackEntry:
|
|||
return "<TracebackEntry %s:%d>" % (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 @@ class TracebackEntry:
|
|||
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 @@ class TracebackEntry:
|
|||
|
||||
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 @@ class TracebackEntry:
|
|||
|
||||
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 @@ class Traceback(List[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 @@ class Traceback(List[TracebackEntry]):
|
|||
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 @@ class Traceback(List[TracebackEntry]):
|
|||
|
||||
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 @@ class Traceback(List[TracebackEntry]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
# 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 @@ class ExceptionInfo(Generic[E]):
|
|||
@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 @@ class ExceptionInfo(Generic[E]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
"""
|
||||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
]
|
||||
)
|
||||
|
||||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
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 @@ class ExceptionInfo(Generic[E]):
|
|||
|
||||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
|
||||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
|
||||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
|
||||
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 @@ class FormattedExcinfo:
|
|||
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 @@ class FormattedExcinfo:
|
|||
|
||||
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 @@ class FormattedExcinfo:
|
|||
# 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 @@ class TerminalRepr:
|
|||
@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 @@ class ExceptionRepr(TerminalRepr):
|
|||
|
||||
@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 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
|
||||
@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 @@ class ReprExceptionInfo(ExceptionRepr):
|
|||
|
||||
@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 @@ class ReprEntryNative(TerminalRepr):
|
|||
@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 @@ class ReprEntry(TerminalRepr):
|
|||
# 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 @@ class ReprLocals(TerminalRepr):
|
|||
|
||||
@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 @@ class ReprFuncArgs(TerminalRepr):
|
|||
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).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import textwrap
|
||||
|
@ -7,11 +9,7 @@ import warnings
|
|||
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 @@ class Source:
|
|||
...
|
||||
|
||||
@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 @@ class Source:
|
|||
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 @@ class 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 @@ class Source:
|
|||
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 @@ class Source:
|
|||
#
|
||||
|
||||
|
||||
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:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from .terminalwriter import get_terminal_width
|
||||
from .terminalwriter import TerminalWriter
|
||||
|
||||
|
|
|
@ -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 @@ import types as _types
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
objid = id(object)
|
||||
|
@ -135,7 +132,7 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -176,7 +173,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -195,7 +192,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("[")
|
||||
|
@ -210,7 +207,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("(")
|
||||
|
@ -225,7 +222,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
if not len(object):
|
||||
|
@ -251,7 +248,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -310,7 +307,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -339,7 +336,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
write = stream.write
|
||||
|
@ -357,7 +354,7 @@ class PrettyPrinter:
|
|||
stream: IO[str],
|
||||
indent: int,
|
||||
allowance: int,
|
||||
context: Set[int],
|
||||
context: set[int],
|
||||
level: int,
|
||||
) -> None:
|
||||
stream.write("mappingproxy(")
|
||||
|
@ -372,7 +369,7 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
|
||||
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 @@ class PrettyPrinter:
|
|||
|
||||
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 @@ class PrettyPrinter:
|
|||
|
||||
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 @@ class PrettyPrinter:
|
|||
|
||||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
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 @@ class PrettyPrinter:
|
|||
_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 @@ class PrettyPrinter:
|
|||
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):
|
||||
|
|
|
@ -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 @@ DEFAULT_REPR_MAX_SIZE = 240
|
|||
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -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 @@ class TerminalWriter:
|
|||
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 @@ class TerminalWriter:
|
|||
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:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
from functools import lru_cache
|
||||
|
||||
|
|
|
@ -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 install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
|
|||
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 pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
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)
|
||||
|
|
|
@ -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 collections import defaultdict
|
|||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
|
||||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
|
||||
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 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
|
||||
# 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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# 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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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(
|
||||
|
|
|
@ -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 @@ USAGE_MSG = "use '-vv' to show"
|
|||
|
||||
|
||||
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):
|
||||
|
|
|
@ -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 AbstractSet
|
|||
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 @@ from _pytest.config import Config
|
|||
# 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) :]
|
||||
|
|
|
@ -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 @@ class Cache:
|
|||
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 @@ class Cache:
|
|||
|
||||
|
||||
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 @@ class LFPluginCollWrapper:
|
|||
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 @@ class LFPluginCollWrapper:
|
|||
|
||||
|
||||
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 @@ class LFPlugin:
|
|||
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 @@ class LFPlugin:
|
|||
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 @@ class LFPlugin:
|
|||
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 @@ class LFPlugin:
|
|||
|
||||
@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 @@ class NFPlugin:
|
|||
|
||||
@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 @@ class NFPlugin:
|
|||
|
||||
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
|
||||
|
|
|
@ -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 Generator
|
|||
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 @@ class DontReadFromInput(TextIO):
|
|||
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 @@ class DontReadFromInput(TextIO):
|
|||
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 @@ class DontReadFromInput(TextIO):
|
|||
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 @@ class NoCapture(CaptureBase[str]):
|
|||
|
||||
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 @@ class SysCaptureBase(CaptureBase[AnyStr]):
|
|||
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 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
|||
# 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 @@ class FDCaptureBase(CaptureBase[AnyStr]):
|
|||
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 "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
|
||||
|
@ -635,7 +632,7 @@ class MultiCapture(Generic[AnyStr]):
|
|||
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 "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".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 @@ class CaptureManager:
|
|||
|
||||
# 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
|
||||
|
||||
|
|
|
@ -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 types import TracebackType
|
|||
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 @@ builtin_plugins.add("pytester_assertions")
|
|||
|
||||
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
# -- 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 @@ class PytestPluginManager(PluginManager):
|
|||
# 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 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
)
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
#
|
||||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
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 @@ class PytestPluginManager(PluginManager):
|
|||
|
||||
|
||||
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 Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
|
||||
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 @@ class Config:
|
|||
return self._rootpath
|
||||
|
||||
@property
|
||||
def inipath(self) -> Optional[Path]:
|
||||
def inipath(self) -> Path | None:
|
||||
"""The path to the :ref:`configfile <configfiles>`.
|
||||
|
||||
:type: Optional[pathlib.Path]
|
||||
|
@ -1066,15 +1060,15 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class 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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
|
||||
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 @@ class Config:
|
|||
|
||||
# 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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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 @@ class Config:
|
|||
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).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
@ -5,16 +7,12 @@ from gettext import gettext
|
|||
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 @@ class Parser:
|
|||
|
||||
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 @@ class Parser:
|
|||
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 @@ class Parser:
|
|||
|
||||
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 @@ class Parser:
|
|||
|
||||
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 @@ class Parser:
|
|||
|
||||
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 @@ class Parser:
|
|||
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 @@ class Parser:
|
|||
|
||||
|
||||
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 @@ class Argument:
|
|||
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 @@ class Argument:
|
|||
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 @@ class Argument:
|
|||
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 @@ class OptionGroup:
|
|||
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 @@ class OptionGroup:
|
|||
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 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# 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 @@ class MyOptionParser(argparse.ArgumentParser):
|
|||
# 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 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
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 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
|||
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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import final
|
||||
|
||||
|
||||
|
|
|
@ -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 load_config_dict_from_file(
|
|||
|
||||
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 @@ CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supporte
|
|||
|
||||
|
||||
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
|
||||
|
|
|
@ -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 @@ import unittest
|
|||
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 @@ if TYPE_CHECKING:
|
|||
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 pytest_configure(config: Config) -> 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 @@ class pytestPDB:
|
|||
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 @@ class pytestPDB:
|
|||
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 @@ class pytestPDB:
|
|||
|
||||
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:
|
||||
|
|
|
@ -8,6 +8,8 @@ All constants defined in this module should be either instances of
|
|||
: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
|
||||
|
|
|
@ -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 contextlib import contextmanager
|
|||
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 @@ DOCTEST_REPORT_CHOICES = (
|
|||
# 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 @@ class ReprFailDoctest(TerminalRepr):
|
|||
|
||||
|
||||
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 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
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_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
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 _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
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 _init_runner_class() -> Type["doctest.DocTestRunner"]:
|
|||
|
||||
|
||||
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 @@ class DoctestItem(Item):
|
|||
@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 @@ class DoctestItem(Item):
|
|||
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 @@ class DoctestItem(Item):
|
|||
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 @@ class DoctestItem(Item):
|
|||
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 @@ class DoctestTextfile(Module):
|
|||
)
|
||||
|
||||
|
||||
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 @@ class DoctestModule(Module):
|
|||
)
|
||||
|
||||
|
||||
def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
||||
def _init_checker_class() -> type[doctest.OutputChecker]:
|
||||
import doctest
|
||||
import re
|
||||
|
||||
|
@ -661,8 +657,8 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
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 _init_checker_class() -> Type["doctest.OutputChecker"]:
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import dataclasses
|
||||
import functools
|
||||
|
@ -12,20 +14,17 @@ from typing import AbstractSet
|
|||
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 @@ _FixtureCachedResult = Union[
|
|||
|
||||
@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 @@ class FuncFixtureInfo:
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
self.param: Any
|
||||
|
||||
@property
|
||||
def _fixturemanager(self) -> "FixtureManager":
|
||||
def _fixturemanager(self) -> FixtureManager:
|
||||
return self._pyfuncitem.session._fixturemanager
|
||||
|
||||
@property
|
||||
|
@ -406,7 +405,7 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
"""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 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
"""
|
||||
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 @@ class FixtureRequest(abc.ABC):
|
|||
|
||||
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 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class FixtureRequest(abc.ABC):
|
|||
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 @@ class SubRequest(FixtureRequest):
|
|||
scope: Scope,
|
||||
param: Any,
|
||||
param_index: int,
|
||||
fixturedef: "FixtureDef[object]",
|
||||
fixturedef: FixtureDef[object],
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> None:
|
||||
|
@ -738,7 +737,7 @@ class SubRequest(FixtureRequest):
|
|||
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 @@ class SubRequest(FixtureRequest):
|
|||
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 @@ class SubRequest(FixtureRequest):
|
|||
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 @@ class FixtureLookupError(LookupError):
|
|||
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 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
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 @@ class FixtureDef(Generic[FixtureValue]):
|
|||
|
||||
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 wrap_function_to_error_out_if_called_directly(
|
|||
@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 @@ class FixtureFunctionMarker:
|
|||
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 @@ class FixtureManager:
|
|||
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 @@ class FixtureManager:
|
|||
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 @@ class FixtureManager:
|
|||
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 @@ class FixtureManager:
|
|||
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 @@ class FixtureManager:
|
|||
|
||||
# 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 @@ class FixtureManager:
|
|||
def parsefactories( # noqa: F811
|
||||
self,
|
||||
node_or_obj: object,
|
||||
nodeid: Optional[str],
|
||||
nodeid: str | None,
|
||||
*,
|
||||
unittest: bool = ...,
|
||||
) -> None:
|
||||
|
@ -1642,8 +1631,8 @@ class FixtureManager:
|
|||
|
||||
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 @@ class FixtureManager:
|
|||
|
||||
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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__}")
|
||||
|
|
|
@ -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 = HookspecMarker("pytest")
|
|||
|
||||
|
||||
@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) <pytest.PytestPluginManager.add_hookspecs>`.
|
||||
|
||||
|
@ -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() <pytest.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
|
||||
|
|
|
@ -6,6 +6,8 @@ Based on initial code from Ross Lawley.
|
|||
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 re
|
|||
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 @@ families["xunit2"] = families["_base"]
|
|||
|
||||
|
||||
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 @@ class _NodeReporter:
|
|||
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 @@ class _NodeReporter:
|
|||
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 @@ class _NodeReporter:
|
|||
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 @@ class _NodeReporter:
|
|||
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 @@ class _NodeReporter:
|
|||
|
||||
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 @@ class LogXML:
|
|||
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 @@ class LogXML:
|
|||
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 @@ class LogXML:
|
|||
_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")
|
||||
|
|
|
@ -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 @@ import subprocess
|
|||
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 @@ LEGACY_PATH = py.path. local
|
|||
# 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 @@ class Testdir:
|
|||
"""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 @@ class Testdir:
|
|||
"""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 @@ class Testdir:
|
|||
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 @@ class Testdir:
|
|||
"""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 <configfiles>`.
|
||||
|
||||
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
|
||||
|
|
|
@ -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 Generic
|
|||
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 @@ class ColoredLevelFormatter(DatetimeFormatter):
|
|||
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 @@ class catching_logs(Generic[_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 @@ class LogCaptureFixture:
|
|||
|
||||
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 @@ class LogCaptureFixture:
|
|||
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 @@ class LogCaptureFixture:
|
|||
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 @@ class LogCaptureFixture:
|
|||
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 @@ class LogCaptureFixture:
|
|||
|
||||
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 @@ class LogCaptureFixture:
|
|||
|
||||
@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 @@ class LoggingPlugin:
|
|||
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 @@ class LoggingPlugin:
|
|||
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 @@ class LoggingPlugin:
|
|||
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 @@ class _LiveLoggingStreamHandler(logging_StreamHandler):
|
|||
"""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
|
||||
|
|
|
@ -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 AbstractSet
|
|||
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 validate_basetemp(path: str) -> str:
|
|||
|
||||
|
||||
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 @@ class Dir(nodes.Directory):
|
|||
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 @@ class Dir(nodes.Directory):
|
|||
"""
|
||||
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 @@ class Session(nodes.Collector):
|
|||
)
|
||||
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 @@ class Session(nodes.Collector):
|
|||
)
|
||||
|
||||
@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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
|
||||
def isinitpath(
|
||||
self,
|
||||
path: Union[str, "os.PathLike[str]"],
|
||||
path: str | os.PathLike[str],
|
||||
*,
|
||||
with_parents: bool = False,
|
||||
) -> bool:
|
||||
|
@ -669,7 +664,7 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
|
||||
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 @@ class Session(nodes.Collector):
|
|||
|
||||
@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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(nodes.Collector):
|
|||
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 @@ class Session(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 @@ class Session(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 @@ class Session(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
|
||||
|
|
|
@ -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 @@ old_mark_config_key = StashKey[Optional[Config]]()
|
|||
|
||||
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 <fixture-parametrize-marks>`.
|
||||
|
@ -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 @@ class KeywordMatcher:
|
|||
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 @@ class MarkMatcher:
|
|||
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)
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ The semantics are:
|
|||
- 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 Callable
|
|||
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 @@ class Scanner:
|
|||
)
|
||||
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 @@ class Expression:
|
|||
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.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import dataclasses
|
||||
import inspect
|
||||
|
@ -8,16 +10,11 @@ from typing import Collection
|
|||
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 @@ class ParameterSet(NamedTuple):
|
|||
@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 @@ class ParameterSet(NamedTuple):
|
|||
|
||||
@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 @@ class ParameterSet(NamedTuple):
|
|||
|
||||
@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 @@ class ParameterSet(NamedTuple):
|
|||
@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 @@ class Mark:
|
|||
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 @@ class 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 @@ class MarkDecorator:
|
|||
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 @@ class MarkDecorator:
|
|||
""":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 @@ class MarkDecorator:
|
|||
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 @@ class MarkDecorator:
|
|||
|
||||
|
||||
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 @@ if TYPE_CHECKING:
|
|||
...
|
||||
|
||||
@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 @@ if TYPE_CHECKING:
|
|||
@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 @@ if TYPE_CHECKING:
|
|||
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 @@ class MarkGenerator:
|
|||
|
||||
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 @@ MARK_GEN = MarkGenerator(_ispytest=True)
|
|||
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 @@ class NodeKeywords(MutableMapping[str, Any]):
|
|||
|
||||
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)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Monkeypatching and mocking functionality."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -7,14 +9,10 @@ from contextlib import contextmanager
|
|||
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 @@ V = TypeVar("V")
|
|||
|
||||
|
||||
@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 @@ class MonkeyPatch:
|
|||
|
||||
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 @@ class MonkeyPatch:
|
|||
|
||||
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 @@ class MonkeyPatch:
|
|||
# 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 @@ class MonkeyPatch:
|
|||
|
||||
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:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import os
|
||||
import warnings
|
||||
|
@ -9,16 +11,10 @@ from typing import Callable
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
"""
|
||||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
"""
|
||||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
|||
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 Collector(Node, abc.ABC):
|
|||
"""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 @@ class Collector(Node, abc.ABC):
|
|||
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 @@ class FSCollector(Collector, abc.ABC):
|
|||
cls,
|
||||
parent,
|
||||
*,
|
||||
path: Optional[Path] = None,
|
||||
path: Path | None = None,
|
||||
**kw,
|
||||
):
|
||||
"""The public constructor."""
|
||||
|
@ -662,9 +654,9 @@ class Item(Node, abc.ABC):
|
|||
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 @@ class Item(Node, abc.ABC):
|
|||
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 @@ class Item(Node, abc.ABC):
|
|||
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 @@ class Item(Node, abc.ABC):
|
|||
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``
|
||||
|
|
|
@ -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 _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _E
|
|||
@_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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import contextlib
|
||||
import fnmatch
|
||||
|
@ -24,16 +26,9 @@ from pathlib import PurePath
|
|||
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.
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
PYTEST_DONT_REWRITE
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import gc
|
||||
|
@ -19,22 +21,16 @@ from io import StringIO
|
|||
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 @@ class LsofFdLeakChecker:
|
|||
|
||||
|
||||
@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 @@ class HookRecorder:
|
|||
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 @@ class HookRecorder:
|
|||
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 @@ class HookRecorder:
|
|||
@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 @@ class HookRecorder:
|
|||
@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 @@ class HookRecorder:
|
|||
|
||||
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 @@ class HookRecorder:
|
|||
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 @@ class HookRecorder:
|
|||
|
||||
|
||||
@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 @@ class RunResult:
|
|||
% (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 @@ class RunResult:
|
|||
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 @@ class RunResult:
|
|||
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 @@ class RunResult:
|
|||
|
||||
|
||||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
#: :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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class 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 @@ class Pytester:
|
|||
|
||||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
|
||||
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 Pytester:
|
|||
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 Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
|
||||
def getmodulecol(
|
||||
self,
|
||||
source: Union[str, "os.PathLike[str]"],
|
||||
source: str | os.PathLike[str],
|
||||
configargs=(),
|
||||
*,
|
||||
withinit: bool = False,
|
||||
|
@ -1318,9 +1311,7 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
|
||||
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 @@ class Pytester:
|
|||
|
||||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class Pytester:
|
|||
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 @@ class LineMatcher:
|
|||
"""
|
||||
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):
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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 final
|
|||
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 @@ class PyobjMixin(nodes.Node):
|
|||
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 @@ class PyobjMixin(nodes.Node):
|
|||
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 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
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 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
|
||||
# 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 @@ class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
|
|||
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 @@ class Package(nodes.Directory):
|
|||
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 @@ class Package(nodes.Directory):
|
|||
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 @@ class Class(PyCollector):
|
|||
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 @@ class IdMaker:
|
|||
# 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 @@ class IdMaker:
|
|||
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 @@ class IdMaker:
|
|||
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 @@ class CallSpec2:
|
|||
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 @@ class Metafunc:
|
|||
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 @@ class Metafunc:
|
|||
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 @@ class Metafunc:
|
|||
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 @@ class Metafunc:
|
|||
|
||||
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 @@ class Metafunc:
|
|||
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 @@ class Metafunc:
|
|||
: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 @@ class Metafunc:
|
|||
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 @@ class Metafunc:
|
|||
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 _show_fixtures_per_test(config: Config, session: Session) -> 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 _show_fixtures_per_test(config: Config, session: Session) -> 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 @@ class Function(PyobjMixin, nodes.Item):
|
|||
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 @@ class Function(PyobjMixin, nodes.Item):
|
|||
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 @@ class Function(PyobjMixin, nodes.Item):
|
|||
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"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import pprint
|
||||
from collections.abc import Collection
|
||||
|
@ -10,9 +12,7 @@ from typing import Callable
|
|||
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 Tuple
|
|||
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 @@ if TYPE_CHECKING:
|
|||
|
||||
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 @@ class ApproxBase:
|
|||
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 @@ class ApproxBase:
|
|||
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 @@ class ApproxNumpy(ApproxBase):
|
|||
)
|
||||
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 @@ class ApproxMapping(ApproxBase):
|
|||
{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 @@ class ApproxSequenceLike(ApproxBase):
|
|||
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 @@ E = TypeVar("E", bound=BaseException)
|
|||
|
||||
@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 @@ raises.Exception = fail.Exception # type: ignore
|
|||
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 @@ class RaisesContext(ContextManager[_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:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
|
|
@ -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 Callable
|
|||
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 @@ T = TypeVar("T")
|
|||
|
||||
|
||||
@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 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
# 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 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
"""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 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
|
||||
# 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 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
|
||||
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 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
|
|||
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 @@ class WarningsChecker(WarningsRecorder):
|
|||
|
||||
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)
|
||||
|
||||
|
|
|
@ -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 @@ _R = TypeVar("_R", bound="BaseReport")
|
|||
|
||||
|
||||
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 @@ class BaseReport:
|
|||
s = "<unprintable longrepr>"
|
||||
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 @@ class BaseReport:
|
|||
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 @@ class BaseReport:
|
|||
)
|
||||
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 @@ class BaseReport:
|
|||
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 @@ class BaseReport:
|
|||
|
||||
|
||||
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 @@ class TestReport(BaseReport):
|
|||
#: 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 @@ class TestReport(BaseReport):
|
|||
)
|
||||
|
||||
@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 @@ class TestReport(BaseReport):
|
|||
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 @@ class CollectReport(BaseReport):
|
|||
@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 @@ class CollectErrorRepr(TerminalRepr):
|
|||
|
||||
|
||||
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 _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
|||
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 _report_to_json(report: BaseReport) -> 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 _report_to_json(report: 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 _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
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 _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
]
|
||||
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 _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
|||
description,
|
||||
)
|
||||
)
|
||||
exception_info: Union[
|
||||
ExceptionChainRepr, ReprExceptionInfo
|
||||
] = ExceptionChainRepr(chain)
|
||||
exception_info: (
|
||||
ExceptionChainRepr | ReprExceptionInfo
|
||||
) = ExceptionChainRepr(chain)
|
||||
else:
|
||||
exception_info = ReprExceptionInfo(
|
||||
reprtraceback=reprtraceback,
|
||||
|
|
|
@ -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 @@ TResult = TypeVar("TResult", covariant=True)
|
|||
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 @@ class CallInfo(Generic[TResult]):
|
|||
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 @@ class CallInfo(Generic[TResult]):
|
|||
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 pytest_make_collect_report(collector: Collector) -> CollectReport:
|
|||
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 @@ class SetupState:
|
|||
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 @@ class SetupState:
|
|||
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
|
||||
|
|
|
@ -7,10 +7,11 @@ would cause circular references.
|
|||
|
||||
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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 @@ import traceback
|
|||
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 pytest_configure(config: Config) -> None:
|
|||
)
|
||||
|
||||
|
||||
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"
|
||||
|
|
|
@ -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 @@ class Stash:
|
|||
"""
|
||||
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:
|
||||
|
|
|
@ -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 @@ class StepwisePlugin:
|
|||
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
|
||||
|
|
|
@ -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 pathlib import Path
|
|||
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 @@ class MoreQuietAction(argparse.Action):
|
|||
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 @@ class MoreQuietAction(argparse.Action):
|
|||
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 @@ class WarningReport:
|
|||
|
||||
@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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
|
||||
@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 @@ class TerminalReporter:
|
|||
)
|
||||
|
||||
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 @@ class TerminalReporter:
|
|||
|
||||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
|
||||
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 @@ class TerminalReporter:
|
|||
|
||||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
)
|
||||
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 @@ class TerminalReporter:
|
|||
|
||||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
"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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
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 @@ class TerminalReporter:
|
|||
|
||||
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 @@ _color_for_type = {
|
|||
_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)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import traceback
|
||||
import warnings
|
||||
|
@ -5,8 +7,6 @@ from types import TracebackType
|
|||
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
|
||||
|
|
|
@ -5,6 +5,8 @@ pytest runtime information (issue #185).
|
|||
|
||||
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
|
||||
|
|
|
@ -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 Dict
|
|||
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 @@ class TempPathFactory:
|
|||
config: Config,
|
||||
*,
|
||||
_ispytest: bool = False,
|
||||
) -> "TempPathFactory":
|
||||
) -> TempPathFactory:
|
||||
"""Create a factory according to pytest configuration.
|
||||
|
||||
:meta private:
|
||||
|
@ -195,7 +195,7 @@ class TempPathFactory:
|
|||
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
|
||||
|
|
|
@ -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 Any
|
|||
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 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
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 _make_xunit_fixture(
|
|||
|
||||
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 @@ class TestCaseFunction(Function):
|
|||
|
||||
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 @@ class TestCaseFunction(Function):
|
|||
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 @@ class TestCaseFunction(Function):
|
|||
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 @@ class TestCaseFunction(Function):
|
|||
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 @@ class TestCaseFunction(Function):
|
|||
|
||||
def addExpectedFailure(
|
||||
self,
|
||||
testcase: "unittest.TestCase",
|
||||
rawexcinfo: "_SysExcInfoType",
|
||||
testcase: unittest.TestCase,
|
||||
rawexcinfo: _SysExcInfoType,
|
||||
reason: str = "",
|
||||
) -> None:
|
||||
try:
|
||||
|
@ -280,8 +280,8 @@ class TestCaseFunction(Function):
|
|||
|
||||
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 @@ class TestCaseFunction(Function):
|
|||
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 pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
|
|||
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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
import warnings
|
||||
|
@ -5,8 +7,6 @@ from types import TracebackType
|
|||
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
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue