Drop attrs dependency, use dataclasses instead (#10669)

Since pytest now requires Python>=3.7, we can use the stdlib attrs
clone, dataclasses, instead of the OG package.

attrs is still somewhat nicer than dataclasses and has some extra
functionality, but for pytest usage there's not really a justification
IMO to impose the extra dependency on users when a standard alternative
exists.
This commit is contained in:
Ran Benita 2023-01-20 11:13:36 +02:00 committed by GitHub
parent 4d4ed42c34
commit 310b67b227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 199 additions and 159 deletions

View File

@ -0,0 +1 @@
pytest no longer depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported).

View File

@ -44,7 +44,6 @@ packages =
pytest pytest
py_modules = py py_modules = py
install_requires = install_requires =
attrs>=19.2.0
iniconfig iniconfig
packaging packaging
pluggy>=0.12,<2.0 pluggy>=0.12,<2.0
@ -68,6 +67,7 @@ console_scripts =
[options.extras_require] [options.extras_require]
testing = testing =
argcomplete argcomplete
attrs>=19.2.0
hypothesis>=3.56 hypothesis>=3.56
mock mock
nose nose

View File

@ -1,4 +1,5 @@
import ast import ast
import dataclasses
import inspect import inspect
import os import os
import re import re
@ -32,7 +33,6 @@ from typing import TypeVar
from typing import Union from typing import Union
from weakref import ref from weakref import ref
import attr
import pluggy import pluggy
import _pytest import _pytest
@ -445,7 +445,7 @@ E = TypeVar("E", bound=BaseException, covariant=True)
@final @final
@attr.s(repr=False, init=False, auto_attribs=True) @dataclasses.dataclass
class ExceptionInfo(Generic[E]): class ExceptionInfo(Generic[E]):
"""Wraps sys.exc_info() objects and offers help for navigating the traceback.""" """Wraps sys.exc_info() objects and offers help for navigating the traceback."""
@ -649,12 +649,12 @@ class ExceptionInfo(Generic[E]):
""" """
if style == "native": if style == "native":
return ReprExceptionInfo( return ReprExceptionInfo(
ReprTracebackNative( reprtraceback=ReprTracebackNative(
traceback.format_exception( traceback.format_exception(
self.type, self.value, self.traceback[0]._rawentry self.type, self.value, self.traceback[0]._rawentry
) )
), ),
self._getreprcrash(), reprcrash=self._getreprcrash(),
) )
fmt = FormattedExcinfo( fmt = FormattedExcinfo(
@ -684,7 +684,7 @@ class ExceptionInfo(Generic[E]):
return True return True
@attr.s(auto_attribs=True) @dataclasses.dataclass
class FormattedExcinfo: class FormattedExcinfo:
"""Presenting information about failing Functions and Generators.""" """Presenting information about failing Functions and Generators."""
@ -699,8 +699,8 @@ class FormattedExcinfo:
funcargs: bool = False funcargs: bool = False
truncate_locals: bool = True truncate_locals: bool = True
chain: bool = True chain: bool = True
astcache: Dict[Union[str, Path], ast.AST] = attr.ib( astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
factory=dict, init=False, repr=False default_factory=dict, init=False, repr=False
) )
def _getindent(self, source: "Source") -> int: def _getindent(self, source: "Source") -> int:
@ -978,7 +978,7 @@ class FormattedExcinfo:
return ExceptionChainRepr(repr_chain) return ExceptionChainRepr(repr_chain)
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class TerminalRepr: class TerminalRepr:
def __str__(self) -> str: def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception # FYI this is called from pytest-xdist's serialization of exception
@ -996,14 +996,14 @@ class TerminalRepr:
# This class is abstract -- only subclasses are instantiated. # This class is abstract -- only subclasses are instantiated.
@attr.s(eq=False) @dataclasses.dataclass(eq=False)
class ExceptionRepr(TerminalRepr): class ExceptionRepr(TerminalRepr):
# Provided by subclasses. # Provided by subclasses.
reprcrash: Optional["ReprFileLocation"]
reprtraceback: "ReprTraceback" reprtraceback: "ReprTraceback"
reprcrash: Optional["ReprFileLocation"]
def __attrs_post_init__(self) -> None: sections: List[Tuple[str, str, str]] = dataclasses.field(
self.sections: List[Tuple[str, str, str]] = [] init=False, default_factory=list
)
def addsection(self, name: str, content: str, sep: str = "-") -> None: def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep)) self.sections.append((name, content, sep))
@ -1014,16 +1014,23 @@ class ExceptionRepr(TerminalRepr):
tw.line(content) tw.line(content)
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ExceptionChainRepr(ExceptionRepr): class ExceptionChainRepr(ExceptionRepr):
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
def __attrs_post_init__(self) -> None: def __init__(
super().__attrs_post_init__() self,
chain: Sequence[
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
],
) -> None:
# reprcrash and reprtraceback of the outermost (the newest) exception # reprcrash and reprtraceback of the outermost (the newest) exception
# in the chain. # in the chain.
self.reprtraceback = self.chain[-1][0] super().__init__(
self.reprcrash = self.chain[-1][1] reprtraceback=chain[-1][0],
reprcrash=chain[-1][1],
)
self.chain = chain
def toterminal(self, tw: TerminalWriter) -> None: def toterminal(self, tw: TerminalWriter) -> None:
for element in self.chain: for element in self.chain:
@ -1034,7 +1041,7 @@ class ExceptionChainRepr(ExceptionRepr):
super().toterminal(tw) super().toterminal(tw)
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprExceptionInfo(ExceptionRepr): class ReprExceptionInfo(ExceptionRepr):
reprtraceback: "ReprTraceback" reprtraceback: "ReprTraceback"
reprcrash: "ReprFileLocation" reprcrash: "ReprFileLocation"
@ -1044,7 +1051,7 @@ class ReprExceptionInfo(ExceptionRepr):
super().toterminal(tw) super().toterminal(tw)
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprTraceback(TerminalRepr): class ReprTraceback(TerminalRepr):
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
extraline: Optional[str] extraline: Optional[str]
@ -1073,12 +1080,12 @@ class ReprTraceback(TerminalRepr):
class ReprTracebackNative(ReprTraceback): class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines: Sequence[str]) -> None: def __init__(self, tblines: Sequence[str]) -> None:
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)] self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None self.extraline = None
self.style = "native"
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprEntryNative(TerminalRepr): class ReprEntryNative(TerminalRepr):
lines: Sequence[str] lines: Sequence[str]
@ -1088,7 +1095,7 @@ class ReprEntryNative(TerminalRepr):
tw.write("".join(self.lines)) tw.write("".join(self.lines))
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprEntry(TerminalRepr): class ReprEntry(TerminalRepr):
lines: Sequence[str] lines: Sequence[str]
reprfuncargs: Optional["ReprFuncArgs"] reprfuncargs: Optional["ReprFuncArgs"]
@ -1168,12 +1175,15 @@ class ReprEntry(TerminalRepr):
) )
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprFileLocation(TerminalRepr): class ReprFileLocation(TerminalRepr):
path: str = attr.ib(converter=str) path: str
lineno: int lineno: int
message: str message: str
def __post_init__(self) -> None:
self.path = str(self.path)
def toterminal(self, tw: TerminalWriter) -> None: def toterminal(self, tw: TerminalWriter) -> None:
# Filename and lineno output for each entry, using an output format # Filename and lineno output for each entry, using an output format
# that most editors understand. # that most editors understand.
@ -1185,7 +1195,7 @@ class ReprFileLocation(TerminalRepr):
tw.line(f":{self.lineno}: {msg}") tw.line(f":{self.lineno}: {msg}")
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprLocals(TerminalRepr): class ReprLocals(TerminalRepr):
lines: Sequence[str] lines: Sequence[str]
@ -1194,7 +1204,7 @@ class ReprLocals(TerminalRepr):
tw.line(indent + line) tw.line(indent + line)
@attr.s(eq=False, auto_attribs=True) @dataclasses.dataclass(eq=False)
class ReprFuncArgs(TerminalRepr): class ReprFuncArgs(TerminalRepr):
args: Sequence[Tuple[str, object]] args: Sequence[Tuple[str, object]]

View File

@ -1,6 +1,7 @@
"""Implementation of the cache provider.""" """Implementation of the cache provider."""
# This plugin was not named "cache" to avoid conflicts with the external # This plugin was not named "cache" to avoid conflicts with the external
# pytest-cache version. # pytest-cache version.
import dataclasses
import json import json
import os import os
from pathlib import Path from pathlib import Path
@ -12,8 +13,6 @@ from typing import Optional
from typing import Set from typing import Set
from typing import Union from typing import Union
import attr
from .pathlib import resolve_from_str from .pathlib import resolve_from_str
from .pathlib import rm_rf from .pathlib import rm_rf
from .reports import CollectReport from .reports import CollectReport
@ -52,10 +51,12 @@ Signature: 8a477f597d28d172789f06886806bc55
@final @final
@attr.s(init=False, auto_attribs=True) @dataclasses.dataclass
class Cache: class Cache:
_cachedir: Path = attr.ib(repr=False) """Instance of the `cache` fixture."""
_config: Config = attr.ib(repr=False)
_cachedir: Path = dataclasses.field(repr=False)
_config: Config = dataclasses.field(repr=False)
# Sub-directory under cache-dir for directories created by `mkdir()`. # Sub-directory under cache-dir for directories created by `mkdir()`.
_CACHE_PREFIX_DIRS = "d" _CACHE_PREFIX_DIRS = "d"

View File

@ -1,4 +1,5 @@
"""Python version compatibility code.""" """Python version compatibility code."""
import dataclasses
import enum import enum
import functools import functools
import inspect import inspect
@ -17,8 +18,6 @@ from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union from typing import Union
import attr
import py import py
# fmt: off # fmt: off
@ -253,7 +252,7 @@ def ascii_escaped(val: Union[bytes, str]) -> str:
return _translate_non_printable(ret) return _translate_non_printable(ret)
@attr.s @dataclasses.dataclass
class _PytestWrapper: class _PytestWrapper:
"""Dummy wrapper around a function object for internal use only. """Dummy wrapper around a function object for internal use only.
@ -262,7 +261,7 @@ class _PytestWrapper:
decorator to issue warnings when the fixture function is called directly. decorator to issue warnings when the fixture function is called directly.
""" """
obj = attr.ib() obj: Any
def get_real_func(obj): def get_real_func(obj):

View File

@ -2,6 +2,7 @@
import argparse import argparse
import collections.abc import collections.abc
import copy import copy
import dataclasses
import enum import enum
import glob import glob
import inspect import inspect
@ -34,7 +35,6 @@ from typing import Type
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
from pluggy import HookimplMarker from pluggy import HookimplMarker
from pluggy import HookspecMarker from pluggy import HookspecMarker
from pluggy import PluginManager from pluggy import PluginManager
@ -886,10 +886,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
yield from _iter_rewritable_modules(new_package_files) yield from _iter_rewritable_modules(new_package_files)
def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
return tuple(args)
@final @final
class Config: class Config:
"""Access to configuration values, pluginmanager and plugin hooks. """Access to configuration values, pluginmanager and plugin hooks.
@ -903,7 +899,7 @@ class Config:
""" """
@final @final
@attr.s(frozen=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class InvocationParams: class InvocationParams:
"""Holds parameters passed during :func:`pytest.main`. """Holds parameters passed during :func:`pytest.main`.
@ -919,13 +915,24 @@ class Config:
Plugins accessing ``InvocationParams`` must be aware of that. Plugins accessing ``InvocationParams`` must be aware of that.
""" """
args: Tuple[str, ...] = attr.ib(converter=_args_converter) args: Tuple[str, ...]
"""The command-line arguments as passed to :func:`pytest.main`.""" """The command-line arguments as passed to :func:`pytest.main`."""
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] plugins: Optional[Sequence[Union[str, _PluggyPlugin]]]
"""Extra plugins, might be `None`.""" """Extra plugins, might be `None`."""
dir: Path dir: Path
"""The directory from which :func:`pytest.main` was invoked.""" """The directory from which :func:`pytest.main` was invoked."""
def __init__(
self,
*,
args: Iterable[str],
plugins: Optional[Sequence[Union[str, _PluggyPlugin]]],
dir: Path,
) -> None:
object.__setattr__(self, "args", tuple(args))
object.__setattr__(self, "plugins", plugins)
object.__setattr__(self, "dir", dir)
class ArgsSource(enum.Enum): class ArgsSource(enum.Enum):
"""Indicates the source of the test arguments. """Indicates the source of the test arguments.

View File

@ -1,3 +1,4 @@
import dataclasses
import functools import functools
import inspect import inspect
import os import os
@ -28,8 +29,6 @@ from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union from typing import Union
import attr
import _pytest import _pytest
from _pytest import nodes from _pytest import nodes
from _pytest._code import getfslineno from _pytest._code import getfslineno
@ -103,7 +102,7 @@ _FixtureCachedResult = Union[
] ]
@attr.s(frozen=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class PseudoFixtureDef(Generic[FixtureValue]): class PseudoFixtureDef(Generic[FixtureValue]):
cached_result: "_FixtureCachedResult[FixtureValue]" cached_result: "_FixtureCachedResult[FixtureValue]"
_scope: Scope _scope: Scope
@ -350,8 +349,10 @@ def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
return request.param return request.param
@attr.s(slots=True, auto_attribs=True) @dataclasses.dataclass
class FuncFixtureInfo: class FuncFixtureInfo:
__slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs")
# Original function argument names. # Original function argument names.
argnames: Tuple[str, ...] argnames: Tuple[str, ...]
# Argnames that function immediately requires. These include argnames + # Argnames that function immediately requires. These include argnames +
@ -1181,19 +1182,21 @@ def wrap_function_to_error_out_if_called_directly(
@final @final
@attr.s(frozen=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class FixtureFunctionMarker: class FixtureFunctionMarker:
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) params: Optional[Tuple[object, ...]]
autouse: bool = False autouse: bool = False
ids: Optional[ ids: Optional[
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
] = attr.ib( ] = None
default=None,
converter=_ensure_immutable_ids,
)
name: Optional[str] = None name: Optional[str] = None
_ispytest: dataclasses.InitVar[bool] = False
def __post_init__(self, _ispytest: bool) -> None:
check_ispytest(_ispytest)
def __call__(self, function: FixtureFunction) -> FixtureFunction: def __call__(self, function: FixtureFunction) -> FixtureFunction:
if inspect.isclass(function): if inspect.isclass(function):
raise ValueError("class fixtures not supported (maybe in the future)") raise ValueError("class fixtures not supported (maybe in the future)")
@ -1313,10 +1316,11 @@ def fixture( # noqa: F811
""" """
fixture_marker = FixtureFunctionMarker( fixture_marker = FixtureFunctionMarker(
scope=scope, scope=scope,
params=params, params=tuple(params) if params is not None else None,
autouse=autouse, autouse=autouse,
ids=ids, ids=None if ids is None else ids if callable(ids) else tuple(ids),
name=name, name=name,
_ispytest=True,
) )
# Direct decoration. # Direct decoration.

View File

@ -1,4 +1,5 @@
"""Add backward compatibility support for the legacy py path type.""" """Add backward compatibility support for the legacy py path type."""
import dataclasses
import shlex import shlex
import subprocess import subprocess
from pathlib import Path from pathlib import Path
@ -7,7 +8,6 @@ from typing import Optional
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
from iniconfig import SectionWrapper from iniconfig import SectionWrapper
from _pytest.cacheprovider import Cache from _pytest.cacheprovider import Cache
@ -268,7 +268,7 @@ class LegacyTestdirPlugin:
@final @final
@attr.s(init=False, auto_attribs=True) @dataclasses.dataclass
class TempdirFactory: class TempdirFactory:
"""Backward compatibility wrapper that implements :class:`py.path.local` """Backward compatibility wrapper that implements :class:`py.path.local`
for :class:`TempPathFactory`. for :class:`TempPathFactory`.

View File

@ -1,5 +1,6 @@
"""Core implementation of the testing process: init, session, runtest loop.""" """Core implementation of the testing process: init, session, runtest loop."""
import argparse import argparse
import dataclasses
import fnmatch import fnmatch
import functools import functools
import importlib import importlib
@ -19,8 +20,6 @@ from typing import Type
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
import _pytest._code import _pytest._code
from _pytest import nodes from _pytest import nodes
from _pytest.compat import final from _pytest.compat import final
@ -442,8 +441,10 @@ class Failed(Exception):
"""Signals a stop as failed test run.""" """Signals a stop as failed test run."""
@attr.s(slots=True, auto_attribs=True) @dataclasses.dataclass
class _bestrelpath_cache(Dict[Path, str]): class _bestrelpath_cache(Dict[Path, str]):
__slots__ = ("path",)
path: Path path: Path
def __missing__(self, path: Path) -> str: def __missing__(self, path: Path) -> str:

View File

@ -1,4 +1,5 @@
"""Generic mechanism for marking and selecting python functions.""" """Generic mechanism for marking and selecting python functions."""
import dataclasses
from typing import AbstractSet from typing import AbstractSet
from typing import Collection from typing import Collection
from typing import List from typing import List
@ -6,8 +7,6 @@ from typing import Optional
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
from .expression import Expression from .expression import Expression
from .expression import ParseError from .expression import ParseError
from .structures import EMPTY_PARAMETERSET_OPTION from .structures import EMPTY_PARAMETERSET_OPTION
@ -130,7 +129,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
return None return None
@attr.s(slots=True, auto_attribs=True) @dataclasses.dataclass
class KeywordMatcher: class KeywordMatcher:
"""A matcher for keywords. """A matcher for keywords.
@ -145,6 +144,8 @@ class KeywordMatcher:
any item, as well as names directly assigned to test functions. any item, as well as names directly assigned to test functions.
""" """
__slots__ = ("_names",)
_names: AbstractSet[str] _names: AbstractSet[str]
@classmethod @classmethod
@ -201,13 +202,15 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
items[:] = remaining items[:] = remaining
@attr.s(slots=True, auto_attribs=True) @dataclasses.dataclass
class MarkMatcher: class MarkMatcher:
"""A matcher for markers which are present. """A matcher for markers which are present.
Tries to match on any marker names, attached to the given colitem. Tries to match on any marker names, attached to the given colitem.
""" """
__slots__ = ("own_mark_names",)
own_mark_names: AbstractSet[str] own_mark_names: AbstractSet[str]
@classmethod @classmethod

View File

@ -15,6 +15,7 @@ The semantics are:
- or/and/not evaluate according to the usual boolean semantics. - or/and/not evaluate according to the usual boolean semantics.
""" """
import ast import ast
import dataclasses
import enum import enum
import re import re
import types import types
@ -25,8 +26,6 @@ from typing import NoReturn
from typing import Optional from typing import Optional
from typing import Sequence from typing import Sequence
import attr
__all__ = [ __all__ = [
"Expression", "Expression",
@ -44,8 +43,9 @@ class TokenType(enum.Enum):
EOF = "end of input" EOF = "end of input"
@attr.s(frozen=True, slots=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class Token: class Token:
__slots__ = ("type", "value", "pos")
type: TokenType type: TokenType
value: str value: str
pos: int pos: int

View File

@ -1,4 +1,5 @@
import collections.abc import collections.abc
import dataclasses
import inspect import inspect
import warnings import warnings
from typing import Any from typing import Any
@ -20,8 +21,6 @@ from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union from typing import Union
import attr
from .._code import getfslineno from .._code import getfslineno
from ..compat import ascii_escaped from ..compat import ascii_escaped
from ..compat import final from ..compat import final
@ -191,8 +190,10 @@ class ParameterSet(NamedTuple):
@final @final
@attr.s(frozen=True, init=False, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class Mark: class Mark:
"""A pytest mark."""
#: Name of the mark. #: Name of the mark.
name: str name: str
#: Positional arguments of the mark decorator. #: Positional arguments of the mark decorator.
@ -201,9 +202,11 @@ class Mark:
kwargs: Mapping[str, Any] kwargs: Mapping[str, Any]
#: Source Mark for ids with parametrize Marks. #: Source Mark for ids with parametrize Marks.
_param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False) _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False)
#: Resolved/generated ids with parametrize Marks. #: Resolved/generated ids with parametrize Marks.
_param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False) _param_ids_generated: Optional[Sequence[str]] = dataclasses.field(
default=None, repr=False
)
def __init__( def __init__(
self, self,
@ -261,7 +264,7 @@ class Mark:
Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) Markable = TypeVar("Markable", bound=Union[Callable[..., object], type])
@attr.s(init=False, auto_attribs=True) @dataclasses.dataclass
class MarkDecorator: class MarkDecorator:
"""A decorator for applying a mark on test functions and classes. """A decorator for applying a mark on test functions and classes.

View File

@ -1,4 +1,5 @@
"""Python test discovery, setup and run of test functions.""" """Python test discovery, setup and run of test functions."""
import dataclasses
import enum import enum
import fnmatch import fnmatch
import inspect import inspect
@ -27,8 +28,6 @@ from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
import _pytest import _pytest
from _pytest import fixtures from _pytest import fixtures
from _pytest import nodes from _pytest import nodes
@ -956,10 +955,20 @@ def hasnew(obj: object) -> bool:
@final @final
@attr.s(frozen=True, auto_attribs=True, slots=True) @dataclasses.dataclass(frozen=True)
class IdMaker: class IdMaker:
"""Make IDs for a parametrization.""" """Make IDs for a parametrization."""
__slots__ = (
"argnames",
"parametersets",
"idfn",
"ids",
"config",
"nodeid",
"func_name",
)
# The argnames of the parametrization. # The argnames of the parametrization.
argnames: Sequence[str] argnames: Sequence[str]
# The ParameterSets of the parametrization. # The ParameterSets of the parametrization.
@ -1109,7 +1118,7 @@ class IdMaker:
@final @final
@attr.s(frozen=True, slots=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class CallSpec2: class CallSpec2:
"""A planned parameterized invocation of a test function. """A planned parameterized invocation of a test function.
@ -1120,18 +1129,18 @@ class CallSpec2:
# arg name -> arg value which will be passed to the parametrized test # arg name -> arg value which will be passed to the parametrized test
# function (direct parameterization). # function (direct parameterization).
funcargs: Dict[str, object] = attr.Factory(dict) funcargs: Dict[str, object] = dataclasses.field(default_factory=dict)
# arg name -> arg value which will be passed to a fixture of the same name # arg name -> arg value which will be passed to a fixture of the same name
# (indirect parametrization). # (indirect parametrization).
params: Dict[str, object] = attr.Factory(dict) params: Dict[str, object] = dataclasses.field(default_factory=dict)
# arg name -> arg index. # arg name -> arg index.
indices: Dict[str, int] = attr.Factory(dict) indices: Dict[str, int] = dataclasses.field(default_factory=dict)
# Used for sorting parametrized resources. # Used for sorting parametrized resources.
_arg2scope: Dict[str, Scope] = attr.Factory(dict) _arg2scope: Dict[str, Scope] = dataclasses.field(default_factory=dict)
# Parts which will be added to the item's name in `[..]` separated by "-". # Parts which will be added to the item's name in `[..]` separated by "-".
_idlist: List[str] = attr.Factory(list) _idlist: List[str] = dataclasses.field(default_factory=list)
# Marks which will be applied to the item. # Marks which will be applied to the item.
marks: List[Mark] = attr.Factory(list) marks: List[Mark] = dataclasses.field(default_factory=list)
def setmulti( def setmulti(
self, self,
@ -1163,9 +1172,9 @@ class CallSpec2:
return CallSpec2( return CallSpec2(
funcargs=funcargs, funcargs=funcargs,
params=params, params=params,
arg2scope=arg2scope,
indices=indices, indices=indices,
idlist=[*self._idlist, id], _arg2scope=arg2scope,
_idlist=[*self._idlist, id],
marks=[*self.marks, *normalize_mark_list(marks)], marks=[*self.marks, *normalize_mark_list(marks)],
) )

View File

@ -1,3 +1,4 @@
import dataclasses
import os import os
from io import StringIO from io import StringIO
from pprint import pprint from pprint import pprint
@ -16,8 +17,6 @@ from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union from typing import Union
import attr
from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ExceptionRepr from _pytest._code.code import ExceptionRepr
@ -459,15 +458,15 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
def serialize_repr_entry( def serialize_repr_entry(
entry: Union[ReprEntry, ReprEntryNative] entry: Union[ReprEntry, ReprEntryNative]
) -> Dict[str, Any]: ) -> Dict[str, Any]:
data = attr.asdict(entry) data = dataclasses.asdict(entry)
for key, value in data.items(): for key, value in data.items():
if hasattr(value, "__dict__"): if hasattr(value, "__dict__"):
data[key] = attr.asdict(value) data[key] = dataclasses.asdict(value)
entry_data = {"type": type(entry).__name__, "data": data} entry_data = {"type": type(entry).__name__, "data": data}
return entry_data return entry_data
def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]:
result = attr.asdict(reprtraceback) result = dataclasses.asdict(reprtraceback)
result["reprentries"] = [ result["reprentries"] = [
serialize_repr_entry(x) for x in reprtraceback.reprentries serialize_repr_entry(x) for x in reprtraceback.reprentries
] ]
@ -477,7 +476,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
reprcrash: Optional[ReprFileLocation], reprcrash: Optional[ReprFileLocation],
) -> Optional[Dict[str, Any]]: ) -> Optional[Dict[str, Any]]:
if reprcrash is not None: if reprcrash is not None:
return attr.asdict(reprcrash) return dataclasses.asdict(reprcrash)
else: else:
return None return None
@ -594,7 +593,10 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
ExceptionChainRepr, ReprExceptionInfo ExceptionChainRepr, ReprExceptionInfo
] = ExceptionChainRepr(chain) ] = ExceptionChainRepr(chain)
else: else:
exception_info = ReprExceptionInfo(reprtraceback, reprcrash) exception_info = ReprExceptionInfo(
reprtraceback=reprtraceback,
reprcrash=reprcrash,
)
for section in reportdict["longrepr"]["sections"]: for section in reportdict["longrepr"]["sections"]:
exception_info.addsection(*section) exception_info.addsection(*section)

View File

@ -1,5 +1,6 @@
"""Basic collect and runtest protocol implementations.""" """Basic collect and runtest protocol implementations."""
import bdb import bdb
import dataclasses
import os import os
import sys import sys
from typing import Callable from typing import Callable
@ -14,8 +15,6 @@ from typing import TYPE_CHECKING
from typing import TypeVar from typing import TypeVar
from typing import Union from typing import Union
import attr
from .reports import BaseReport from .reports import BaseReport
from .reports import CollectErrorRepr from .reports import CollectErrorRepr
from .reports import CollectReport from .reports import CollectReport
@ -268,7 +267,7 @@ TResult = TypeVar("TResult", covariant=True)
@final @final
@attr.s(repr=False, init=False, auto_attribs=True) @dataclasses.dataclass
class CallInfo(Generic[TResult]): class CallInfo(Generic[TResult]):
"""Result/Exception info of a function invocation.""" """Result/Exception info of a function invocation."""

View File

@ -1,4 +1,5 @@
"""Support for skip/xfail functions and markers.""" """Support for skip/xfail functions and markers."""
import dataclasses
import os import os
import platform import platform
import sys import sys
@ -9,8 +10,6 @@ from typing import Optional
from typing import Tuple from typing import Tuple
from typing import Type from typing import Type
import attr
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
@ -157,7 +156,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
return result, reason return result, reason
@attr.s(slots=True, frozen=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class Skip: class Skip:
"""The result of evaluate_skip_marks().""" """The result of evaluate_skip_marks()."""
@ -192,10 +191,12 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]:
return None return None
@attr.s(slots=True, frozen=True, auto_attribs=True) @dataclasses.dataclass(frozen=True)
class Xfail: class Xfail:
"""The result of evaluate_xfail_marks().""" """The result of evaluate_xfail_marks()."""
__slots__ = ("reason", "run", "strict", "raises")
reason: str reason: str
run: bool run: bool
strict: bool strict: bool

View File

@ -3,6 +3,7 @@
This is a good source for looking at the various reporting hooks. This is a good source for looking at the various reporting hooks.
""" """
import argparse import argparse
import dataclasses
import datetime import datetime
import inspect import inspect
import platform import platform
@ -27,7 +28,6 @@ from typing import Tuple
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Union from typing import Union
import attr
import pluggy import pluggy
import _pytest._version import _pytest._version
@ -287,7 +287,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]:
return outcome, letter, outcome.upper() return outcome, letter, outcome.upper()
@attr.s(auto_attribs=True) @dataclasses.dataclass
class WarningReport: class WarningReport:
"""Simple structure to hold warnings information captured by ``pytest_warning_recorded``. """Simple structure to hold warnings information captured by ``pytest_warning_recorded``.

View File

@ -1,10 +1,12 @@
"""Support for providing temporary directories to test functions.""" """Support for providing temporary directories to test functions."""
import dataclasses
import os import os
import re import re
import sys import sys
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from shutil import rmtree from shutil import rmtree
from typing import Any
from typing import Dict from typing import Dict
from typing import Generator from typing import Generator
from typing import Optional from typing import Optional
@ -21,7 +23,6 @@ if TYPE_CHECKING:
RetentionType = Literal["all", "failed", "none"] RetentionType = Literal["all", "failed", "none"]
import attr
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from .pathlib import LOCK_TIMEOUT from .pathlib import LOCK_TIMEOUT
@ -42,18 +43,19 @@ tmppath_result_key = StashKey[Dict[str, bool]]()
@final @final
@attr.s(init=False) @dataclasses.dataclass
class TempPathFactory: class TempPathFactory:
"""Factory for temporary directories under the common base temp directory. """Factory for temporary directories under the common base temp directory.
The base directory can be configured using the ``--basetemp`` option. The base directory can be configured using the ``--basetemp`` option.
""" """
_given_basetemp = attr.ib(type=Optional[Path]) _given_basetemp: Optional[Path]
_trace = attr.ib() # pluggy TagTracerSub, not currently exposed, so Any.
_basetemp = attr.ib(type=Optional[Path]) _trace: Any
_retention_count = attr.ib(type=int) _basetemp: Optional[Path]
_retention_policy = attr.ib(type="RetentionType") _retention_count: int
_retention_policy: "RetentionType"
def __init__( def __init__(
self, self,

View File

@ -1,3 +1,4 @@
import dataclasses
import inspect import inspect
import warnings import warnings
from types import FunctionType from types import FunctionType
@ -6,8 +7,6 @@ from typing import Generic
from typing import Type from typing import Type
from typing import TypeVar from typing import TypeVar
import attr
from _pytest.compat import final from _pytest.compat import final
@ -130,7 +129,7 @@ _W = TypeVar("_W", bound=PytestWarning)
@final @final
@attr.s(auto_attribs=True) @dataclasses.dataclass
class UnformattedWarning(Generic[_W]): class UnformattedWarning(Generic[_W]):
"""A warning meant to be formatted during runtime. """A warning meant to be formatted during runtime.

View File

@ -1,9 +1,8 @@
import dataclasses
import os import os
import sys import sys
import types import types
import attr
import pytest import pytest
from _pytest.compat import importlib_metadata from _pytest.compat import importlib_metadata
from _pytest.config import ExitCode from _pytest.config import ExitCode
@ -115,11 +114,11 @@ class TestGeneralUsage:
loaded = [] loaded = []
@attr.s @dataclasses.dataclass
class DummyEntryPoint: class DummyEntryPoint:
name = attr.ib() name: str
module = attr.ib() module: str
group = "pytest11" group: str = "pytest11"
def load(self): def load(self):
__import__(self.module) __import__(self.module)
@ -132,10 +131,10 @@ class TestGeneralUsage:
DummyEntryPoint("mycov", "mycov_module"), DummyEntryPoint("mycov", "mycov_module"),
] ]
@attr.s @dataclasses.dataclass
class DummyDist: class DummyDist:
entry_points = attr.ib() entry_points: object
files = () files: object = ()
def my_dists(): def my_dists():
return (DummyDist(entry_points),) return (DummyDist(entry_points),)
@ -1037,14 +1036,14 @@ def test_fixture_values_leak(pytester: Pytester) -> None:
""" """
pytester.makepyfile( pytester.makepyfile(
""" """
import attr import dataclasses
import gc import gc
import pytest import pytest
import weakref import weakref
@attr.s @dataclasses.dataclass
class SomeObj(object): class SomeObj:
name = attr.ib() name: str
fix_of_test1_ref = None fix_of_test1_ref = None
session_ref = None session_ref = None

View File

@ -1,3 +1,4 @@
import dataclasses
import re import re
import sys import sys
from typing import List from typing import List
@ -192,20 +193,18 @@ def mock_timing(monkeypatch: MonkeyPatch):
Time is static, and only advances through `sleep` calls, thus tests might sleep over large Time is static, and only advances through `sleep` calls, thus tests might sleep over large
numbers and obtain accurate time() calls at the end, making tests reliable and instant. numbers and obtain accurate time() calls at the end, making tests reliable and instant.
""" """
import attr
@attr.s @dataclasses.dataclass
class MockTiming: class MockTiming:
_current_time: float = 1590150050.0
_current_time = attr.ib(default=1590150050.0) def sleep(self, seconds: float) -> None:
def sleep(self, seconds):
self._current_time += seconds self._current_time += seconds
def time(self): def time(self) -> float:
return self._current_time return self._current_time
def patch(self): def patch(self) -> None:
from _pytest import timing from _pytest import timing
monkeypatch.setattr(timing, "sleep", self.sleep) monkeypatch.setattr(timing, "sleep", self.sleep)

View File

@ -1,3 +1,4 @@
import dataclasses
import itertools import itertools
import re import re
import sys import sys
@ -12,7 +13,6 @@ from typing import Sequence
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
import attr
import hypothesis import hypothesis
from hypothesis import strategies from hypothesis import strategies
@ -39,14 +39,14 @@ class TestMetafunc:
def __init__(self, names): def __init__(self, names):
self.names_closure = names self.names_closure = names
@attr.s @dataclasses.dataclass
class DefinitionMock(python.FunctionDefinition): class DefinitionMock(python.FunctionDefinition):
obj = attr.ib() _nodeid: str
_nodeid = attr.ib() obj: object
names = getfuncargnames(func) names = getfuncargnames(func)
fixtureinfo: Any = FuncFixtureInfoMock(names) fixtureinfo: Any = FuncFixtureInfoMock(names)
definition: Any = DefinitionMock._create(func, "mock::nodeid") definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid")
return python.Metafunc(definition, fixtureinfo, config, _ispytest=True) return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
def test_no_funcargs(self) -> None: def test_no_funcargs(self) -> None:
@ -140,9 +140,9 @@ class TestMetafunc:
"""Unit test for _find_parametrized_scope (#3941).""" """Unit test for _find_parametrized_scope (#3941)."""
from _pytest.python import _find_parametrized_scope from _pytest.python import _find_parametrized_scope
@attr.s @dataclasses.dataclass
class DummyFixtureDef: class DummyFixtureDef:
_scope = attr.ib() _scope: Scope
fixtures_defs = cast( fixtures_defs = cast(
Dict[str, Sequence[fixtures.FixtureDef[object]]], Dict[str, Sequence[fixtures.FixtureDef[object]]],

View File

@ -1,3 +1,4 @@
import dataclasses
import os import os
import re import re
import sys import sys
@ -10,8 +11,6 @@ from typing import Tuple
from typing import Type from typing import Type
from typing import Union from typing import Union
import attr
import _pytest._code import _pytest._code
import pytest import pytest
from _pytest.compat import importlib_metadata from _pytest.compat import importlib_metadata
@ -423,11 +422,11 @@ class TestParseIni:
This test installs a mock "myplugin-1.5" which is used in the parametrized test cases. This test installs a mock "myplugin-1.5" which is used in the parametrized test cases.
""" """
@attr.s @dataclasses.dataclass
class DummyEntryPoint: class DummyEntryPoint:
name = attr.ib() name: str
module = attr.ib() module: str
group = "pytest11" group: str = "pytest11"
def load(self): def load(self):
__import__(self.module) __import__(self.module)
@ -437,11 +436,11 @@ class TestParseIni:
DummyEntryPoint("myplugin1", "myplugin1_module"), DummyEntryPoint("myplugin1", "myplugin1_module"),
] ]
@attr.s @dataclasses.dataclass
class DummyDist: class DummyDist:
entry_points = attr.ib() entry_points: object
files = () files: object = ()
version = plugin_version version: str = plugin_version
@property @property
def metadata(self): def metadata(self):

View File

@ -518,10 +518,10 @@ class TestImportLibMode:
fn1.write_text( fn1.write_text(
dedent( dedent(
""" """
import attr import dataclasses
import pickle import pickle
@attr.s(auto_attribs=True) @dataclasses.dataclass
class Data: class Data:
x: int = 42 x: int = 42
""" """
@ -533,10 +533,10 @@ class TestImportLibMode:
fn2.write_text( fn2.write_text(
dedent( dedent(
""" """
import attr import dataclasses
import pickle import pickle
@attr.s(auto_attribs=True) @dataclasses.dataclass
class Data: class Data:
x: str = "" x: str = ""
""" """

View File

@ -1,3 +1,4 @@
import dataclasses
import os import os
import stat import stat
import sys import sys
@ -6,8 +7,7 @@ from pathlib import Path
from typing import Callable from typing import Callable
from typing import cast from typing import cast
from typing import List from typing import List
from typing import Union
import attr
import pytest import pytest
from _pytest import pathlib from _pytest import pathlib
@ -31,9 +31,9 @@ def test_tmp_path_fixture(pytester: Pytester) -> None:
results.stdout.fnmatch_lines(["*1 passed*"]) results.stdout.fnmatch_lines(["*1 passed*"])
@attr.s @dataclasses.dataclass
class FakeConfig: class FakeConfig:
basetemp = attr.ib() basetemp: Union[str, Path]
@property @property
def trace(self): def trace(self):
@ -56,7 +56,7 @@ class FakeConfig:
class TestTmpPathHandler: class TestTmpPathHandler:
def test_mktemp(self, tmp_path): def test_mktemp(self, tmp_path: Path) -> None:
config = cast(Config, FakeConfig(tmp_path)) config = cast(Config, FakeConfig(tmp_path))
t = TempPathFactory.from_config(config, _ispytest=True) t = TempPathFactory.from_config(config, _ispytest=True)
tmp = t.mktemp("world") tmp = t.mktemp("world")
@ -67,7 +67,9 @@ class TestTmpPathHandler:
assert str(tmp2.relative_to(t.getbasetemp())).startswith("this") assert str(tmp2.relative_to(t.getbasetemp())).startswith("this")
assert tmp2 != tmp assert tmp2 != tmp
def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): def test_tmppath_relative_basetemp_absolute(
self, tmp_path: Path, monkeypatch: MonkeyPatch
) -> None:
"""#4425""" """#4425"""
monkeypatch.chdir(tmp_path) monkeypatch.chdir(tmp_path)
config = cast(Config, FakeConfig("hello")) config = cast(Config, FakeConfig("hello"))