diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index bd2132a9a..7da5cb3d1 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,3 +1,4 @@ +import ast import inspect import re import sys @@ -12,6 +13,7 @@ from types import FrameType from types import TracebackType from typing import Any from typing import Callable +from typing import ClassVar from typing import Dict from typing import Generic from typing import Iterable @@ -238,7 +240,9 @@ class TracebackEntry: def getfirstlinesource(self) -> int: return self.frame.code.firstlineno - def getsource(self, astcache=None) -> Optional["Source"]: + def getsource( + self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None + ) -> Optional["Source"]: """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing @@ -258,7 +262,7 @@ class TracebackEntry: except SyntaxError: end = self.lineno + 1 else: - if key is not None: + if key is not None and astcache is not None: astcache[key] = astnode return source[start:end] @@ -435,15 +439,15 @@ E = TypeVar("E", bound=BaseException, covariant=True) @final -@attr.s(repr=False, init=False) +@attr.s(repr=False, init=False, auto_attribs=True) class ExceptionInfo(Generic[E]): """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" - _assert_start_repr = "AssertionError('assert " + _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo = attr.ib(type=Optional[Tuple[Type["E"], "E", TracebackType]]) - _striptext = attr.ib(type=str) - _traceback = attr.ib(type=Optional[Traceback]) + _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] + _striptext: str + _traceback: Optional[Traceback] def __init__( self, @@ -673,22 +677,24 @@ class ExceptionInfo(Generic[E]): return True -@attr.s +@attr.s(auto_attribs=True) class FormattedExcinfo: """Presenting information about failing Functions and Generators.""" # for traceback entries - flow_marker = ">" - fail_marker = "E" + flow_marker: ClassVar = ">" + fail_marker: ClassVar = "E" - showlocals = attr.ib(type=bool, default=False) - style = attr.ib(type="_TracebackStyle", default="long") - abspath = attr.ib(type=bool, default=True) - tbfilter = attr.ib(type=bool, default=True) - funcargs = attr.ib(type=bool, default=False) - truncate_locals = attr.ib(type=bool, default=True) - chain = attr.ib(type=bool, default=True) - astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) + showlocals: bool = False + style: "_TracebackStyle" = "long" + abspath: bool = True + tbfilter: bool = True + funcargs: bool = False + truncate_locals: bool = True + chain: bool = True + astcache: Dict[Union[str, Path], ast.AST] = attr.ib( + factory=dict, init=False, repr=False + ) def _getindent(self, source: "Source") -> int: # Figure out indent for the given source. @@ -951,7 +957,7 @@ class FormattedExcinfo: return ExceptionChainRepr(repr_chain) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class TerminalRepr: def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception @@ -987,13 +993,9 @@ class ExceptionRepr(TerminalRepr): tw.line(content) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ExceptionChainRepr(ExceptionRepr): - chain = attr.ib( - type=Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ] - ) + chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] def __attrs_post_init__(self) -> None: super().__attrs_post_init__() @@ -1011,23 +1013,23 @@ class ExceptionChainRepr(ExceptionRepr): super().toterminal(tw) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprExceptionInfo(ExceptionRepr): - reprtraceback = attr.ib(type="ReprTraceback") - reprcrash = attr.ib(type="ReprFileLocation") + reprtraceback: "ReprTraceback" + reprcrash: "ReprFileLocation" def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) super().toterminal(tw) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprTraceback(TerminalRepr): - reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) - extraline = attr.ib(type=Optional[str]) - style = attr.ib(type="_TracebackStyle") + reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] + extraline: Optional[str] + style: "_TracebackStyle" - entrysep = "_ " + entrysep: ClassVar = "_ " def toterminal(self, tw: TerminalWriter) -> None: # The entries might have different styles. @@ -1055,22 +1057,23 @@ class ReprTracebackNative(ReprTraceback): self.extraline = None -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprEntryNative(TerminalRepr): - lines = attr.ib(type=Sequence[str]) - style: "_TracebackStyle" = "native" + lines: Sequence[str] + + style: ClassVar["_TracebackStyle"] = "native" def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprEntry(TerminalRepr): - lines = attr.ib(type=Sequence[str]) - reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) - reprlocals = attr.ib(type=Optional["ReprLocals"]) - reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) - style = attr.ib(type="_TracebackStyle") + lines: Sequence[str] + reprfuncargs: Optional["ReprFuncArgs"] + reprlocals: Optional["ReprLocals"] + reprfileloc: Optional["ReprFileLocation"] + style: "_TracebackStyle" def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. @@ -1144,11 +1147,11 @@ class ReprEntry(TerminalRepr): ) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprFileLocation(TerminalRepr): - path = attr.ib(type=str, converter=str) - lineno = attr.ib(type=int) - message = attr.ib(type=str) + path: str = attr.ib(converter=str) + lineno: int + message: str def toterminal(self, tw: TerminalWriter) -> None: # Filename and lineno output for each entry, using an output format @@ -1161,18 +1164,18 @@ class ReprFileLocation(TerminalRepr): tw.line(f":{self.lineno}: {msg}") -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprLocals(TerminalRepr): - lines = attr.ib(type=Sequence[str]) + lines: Sequence[str] def toterminal(self, tw: TerminalWriter, indent="") -> None: for line in self.lines: tw.line(indent + line) -@attr.s(eq=False) +@attr.s(eq=False, auto_attribs=True) class ReprFuncArgs(TerminalRepr): - args = attr.ib(type=Sequence[Tuple[str, object]]) + args: Sequence[Tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index f75dfde45..b3a10882d 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -55,10 +55,10 @@ Signature: 8a477f597d28d172789f06886806bc55 @final -@attr.s(init=False) +@attr.s(init=False, auto_attribs=True) class Cache: - _cachedir = attr.ib(type=Path, repr=False) - _config = attr.ib(type=Config, repr=False) + _cachedir: Path = attr.ib(repr=False) + _config: Config = attr.ib(repr=False) # Sub-directory under cache-dir for directories created by `mkdir()`. _CACHE_PREFIX_DIRS = "d" diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index de5b80e30..1f98484d5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -858,7 +858,7 @@ class Config: """ @final - @attr.s(frozen=True) + @attr.s(frozen=True, auto_attribs=True) class InvocationParams: """Holds parameters passed during :func:`pytest.main`. @@ -874,21 +874,12 @@ class Config: Plugins accessing ``InvocationParams`` must be aware of that. """ - args = attr.ib(type=Tuple[str, ...], converter=_args_converter) - """The command-line arguments as passed to :func:`pytest.main`. - - :type: Tuple[str, ...] - """ - plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]]) - """Extra plugins, might be `None`. - - :type: Optional[Sequence[Union[str, plugin]]] - """ - dir = attr.ib(type=Path) - """The directory from which :func:`pytest.main` was invoked. - - :type: pathlib.Path - """ + args: Tuple[str, ...] = attr.ib(converter=_args_converter) + """The command-line arguments as passed to :func:`pytest.main`.""" + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] + """Extra plugins, might be `None`.""" + dir: Path + """The directory from which :func:`pytest.main` was invoked.""" def __init__( self, diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index de27abdb7..bcb53552a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -393,16 +393,16 @@ def get_direct_param_fixture_func(request): return request.param -@attr.s(slots=True) +@attr.s(slots=True, auto_attribs=True) class FuncFixtureInfo: # Original function argument names. - argnames = attr.ib(type=Tuple[str, ...]) + argnames: Tuple[str, ...] # Argnames that function immediately requires. These include argnames + # fixture names specified via usefixtures and via autouse=True in fixture # definitions. - initialnames = attr.ib(type=Tuple[str, ...]) - names_closure = attr.ib(type=List[str]) - name2fixturedefs = attr.ib(type=Dict[str, Sequence["FixtureDef[Any]"]]) + initialnames: Tuple[str, ...] + names_closure: List[str] + name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] def prune_dependency_tree(self) -> None: """Recompute names_closure from initialnames and name2fixturedefs. @@ -1187,11 +1187,11 @@ def wrap_function_to_error_out_if_called_directly( @final -@attr.s(frozen=True) +@attr.s(frozen=True, auto_attribs=True) class FixtureFunctionMarker: - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = attr.ib() + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) - autouse: bool = attr.ib(default=False) + autouse: bool = False ids: Union[ Tuple[Union[None, str, float, int, bool], ...], Callable[[Any], Optional[object]], @@ -1199,7 +1199,7 @@ class FixtureFunctionMarker: default=None, converter=_ensure_immutable_ids, ) - name: Optional[str] = attr.ib(default=None) + name: Optional[str] = None def __call__(self, function: FixtureFunction) -> FixtureFunction: if inspect.isclass(function): diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 6bfd54c39..cfdc091e1 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -442,9 +442,9 @@ class Failed(Exception): """Signals a stop as failed test run.""" -@attr.s +@attr.s(slots=True, auto_attribs=True) class _bestrelpath_cache(Dict[Path, str]): - path = attr.ib(type=Path) + path: Path def __missing__(self, path: Path) -> str: r = bestrelpath(self.path, path) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index a16654c78..34d7726e6 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -133,7 +133,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -@attr.s(slots=True) +@attr.s(slots=True, auto_attribs=True) class KeywordMatcher: """A matcher for keywords. @@ -148,7 +148,7 @@ class KeywordMatcher: any item, as well as names directly assigned to test functions. """ - _names = attr.ib(type=AbstractSet[str]) + _names: AbstractSet[str] @classmethod def from_item(cls, item: "Item") -> "KeywordMatcher": @@ -217,17 +217,17 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: items[:] = remaining -@attr.s(slots=True) +@attr.s(slots=True, auto_attribs=True) class MarkMatcher: """A matcher for markers which are present. Tries to match on any marker names, attached to the given colitem. """ - own_mark_names = attr.ib() + own_mark_names: AbstractSet[str] @classmethod - def from_item(cls, item) -> "MarkMatcher": + def from_item(cls, item: "Item") -> "MarkMatcher": mark_names = {mark.name for mark in item.iter_markers()} return cls(mark_names) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 20b424e16..836e1ebd4 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -47,11 +47,11 @@ class TokenType(enum.Enum): EOF = "end of input" -@attr.s(frozen=True, slots=True) +@attr.s(frozen=True, slots=True, auto_attribs=True) class Token: - type = attr.ib(type=TokenType) - value = attr.ib(type=str) - pos = attr.ib(type=int) + type: TokenType + value: str + pos: int class ParseError(Exception): diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index c8258d4b6..d902226bf 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -33,7 +33,6 @@ from typing import TYPE_CHECKING from typing import Union from weakref import WeakKeyDictionary -import attr from iniconfig import IniConfig from iniconfig import SectionWrapper @@ -1518,7 +1517,6 @@ class LineComp: @final -@attr.s(repr=False, str=False, init=False) class Testdir: """ Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead. diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index f18716b14..ab585095b 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -157,11 +157,11 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, return result, reason -@attr.s(slots=True, frozen=True) +@attr.s(slots=True, frozen=True, auto_attribs=True) class Skip: """The result of evaluate_skip_marks().""" - reason = attr.ib(type=str, default="unconditional skip") + reason: str = "unconditional skip" def evaluate_skip_marks(item: Item) -> Optional[Skip]: @@ -192,14 +192,14 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: return None -@attr.s(slots=True, frozen=True) +@attr.s(slots=True, frozen=True, auto_attribs=True) class Xfail: """The result of evaluate_xfail_marks().""" - reason = attr.ib(type=str) - run = attr.ib(type=bool) - strict = attr.ib(type=bool) - raises = attr.ib(type=Optional[Tuple[Type[BaseException], ...]]) + reason: str + run: bool + strict: bool + raises: Optional[Tuple[Type[BaseException], ...]] def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 0375c4d4e..b34e2e2a8 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -14,6 +14,7 @@ from pathlib import Path from typing import Any from typing import Callable from typing import cast +from typing import ClassVar from typing import Dict from typing import Generator from typing import List @@ -279,7 +280,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: return outcome, letter, outcome.upper() -@attr.s +@attr.s(auto_attribs=True) class WarningReport: """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. @@ -291,10 +292,11 @@ class WarningReport: File system location of the source of the warning (see ``get_location``). """ - message = attr.ib(type=str) - nodeid = attr.ib(type=Optional[str], default=None) - fslocation = attr.ib(type=Optional[Tuple[str, int]], default=None) - count_towards_summary = True + message: str + nodeid: Optional[str] = None + fslocation: Optional[Tuple[str, int]] = None + + count_towards_summary: ClassVar = True def get_location(self, config: Config) -> Optional[str]: """Return the more user-friendly information about the location of a warning, or None.""" diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 8c513ca46..ce9fa59c9 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -158,12 +158,12 @@ class TempPathFactory: @final -@attr.s(init=False) +@attr.s(init=False, auto_attribs=True) class TempdirFactory: """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH`` for :class:``TempPathFactory``.""" - _tmppath_factory = attr.ib(type=TempPathFactory) + _tmppath_factory: TempPathFactory def __init__( self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 2eadd9fe4..4139225d7 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -116,7 +116,7 @@ _W = TypeVar("_W", bound=PytestWarning) @final -@attr.s +@attr.s(auto_attribs=True) class UnformattedWarning(Generic[_W]): """A warning meant to be formatted during runtime. @@ -124,8 +124,8 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category = attr.ib(type=Type["_W"]) - template = attr.ib(type=str) + category: Type["_W"] + template: str def format(self, **kwargs: Any) -> _W: """Return an instance of the warning category, formatted with given kwargs.""" diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 9cd862ae4..61aa4406a 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -455,7 +455,7 @@ class TestFormattedExcinfo: pass """ ).strip() - pr.flow_marker = "|" + pr.flow_marker = "|" # type: ignore[misc] lines = pr.get_source(source, 0) assert len(lines) == 2 assert lines[0] == "| def f(x):"