Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop attrs dependency, use dataclasses instead #10669

Merged
merged 2 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/10669.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest no longer depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported).
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ packages =
pytest
py_modules = py
install_requires =
attrs>=19.2.0
iniconfig
packaging
pluggy>=0.12,<2.0
Expand All @@ -68,6 +67,7 @@ console_scripts =
[options.extras_require]
testing =
argcomplete
attrs>=19.2.0
hypothesis>=3.56
mock
nose
Expand Down
64 changes: 37 additions & 27 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ast
import dataclasses
import inspect
import os
import re
Expand Down Expand Up @@ -32,7 +33,6 @@
from typing import Union
from weakref import ref

import attr
import pluggy

import _pytest
Expand Down Expand Up @@ -445,7 +445,7 @@ def recursionindex(self) -> Optional[int]:


@final
@attr.s(repr=False, init=False, auto_attribs=True)
@dataclasses.dataclass
class ExceptionInfo(Generic[E]):
"""Wraps sys.exc_info() objects and offers help for navigating the traceback."""

Expand Down Expand Up @@ -649,12 +649,12 @@ def getrepr(
"""
if style == "native":
return ReprExceptionInfo(
ReprTracebackNative(
reprtraceback=ReprTracebackNative(
traceback.format_exception(
self.type, self.value, self.traceback[0]._rawentry
)
),
self._getreprcrash(),
reprcrash=self._getreprcrash(),
)

fmt = FormattedExcinfo(
Expand Down Expand Up @@ -684,7 +684,7 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
return True


@attr.s(auto_attribs=True)
@dataclasses.dataclass
class FormattedExcinfo:
"""Presenting information about failing Functions and Generators."""

Expand All @@ -699,8 +699,8 @@ class FormattedExcinfo:
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
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
default_factory=dict, init=False, repr=False
)

def _getindent(self, source: "Source") -> int:
Expand Down Expand Up @@ -978,7 +978,7 @@ def repr_excinfo(
return ExceptionChainRepr(repr_chain)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class TerminalRepr:
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
Expand All @@ -996,14 +996,14 @@ def toterminal(self, tw: TerminalWriter) -> None:


# This class is abstract -- only subclasses are instantiated.
@attr.s(eq=False)
@dataclasses.dataclass(eq=False)
class ExceptionRepr(TerminalRepr):
# Provided by subclasses.
reprcrash: Optional["ReprFileLocation"]
reprtraceback: "ReprTraceback"

def __attrs_post_init__(self) -> None:
self.sections: List[Tuple[str, str, str]] = []
reprcrash: Optional["ReprFileLocation"]
sections: List[Tuple[str, str, str]] = dataclasses.field(
init=False, default_factory=list
)

def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep))
Expand All @@ -1014,16 +1014,23 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.line(content)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ExceptionChainRepr(ExceptionRepr):
chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]

def __attrs_post_init__(self) -> None:
super().__attrs_post_init__()
def __init__(
self,
chain: Sequence[
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
],
) -> None:
# reprcrash and reprtraceback of the outermost (the newest) exception
# in the chain.
self.reprtraceback = self.chain[-1][0]
self.reprcrash = self.chain[-1][1]
super().__init__(
reprtraceback=chain[-1][0],
reprcrash=chain[-1][1],
)
self.chain = chain

def toterminal(self, tw: TerminalWriter) -> None:
for element in self.chain:
Expand All @@ -1034,7 +1041,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
super().toterminal(tw)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprExceptionInfo(ExceptionRepr):
reprtraceback: "ReprTraceback"
reprcrash: "ReprFileLocation"
Expand All @@ -1044,7 +1051,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
super().toterminal(tw)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprTraceback(TerminalRepr):
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
extraline: Optional[str]
Expand Down Expand Up @@ -1073,12 +1080,12 @@ def toterminal(self, tw: TerminalWriter) -> None:

class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines: Sequence[str]) -> None:
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None
self.style = "native"


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprEntryNative(TerminalRepr):
lines: Sequence[str]

Expand All @@ -1088,7 +1095,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprEntry(TerminalRepr):
lines: Sequence[str]
reprfuncargs: Optional["ReprFuncArgs"]
Expand Down Expand Up @@ -1168,12 +1175,15 @@ def __str__(self) -> str:
)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprFileLocation(TerminalRepr):
path: str = attr.ib(converter=str)
path: str
lineno: int
message: str

def __post_init__(self) -> None:
self.path = str(self.path)

def toterminal(self, tw: TerminalWriter) -> None:
# Filename and lineno output for each entry, using an output format
# that most editors understand.
Expand All @@ -1185,7 +1195,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.line(f":{self.lineno}: {msg}")


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprLocals(TerminalRepr):
lines: Sequence[str]

Expand All @@ -1194,7 +1204,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None:
tw.line(indent + line)


@attr.s(eq=False, auto_attribs=True)
@dataclasses.dataclass(eq=False)
class ReprFuncArgs(TerminalRepr):
args: Sequence[Tuple[str, object]]

Expand Down
11 changes: 6 additions & 5 deletions src/_pytest/cacheprovider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Implementation of the cache provider."""
# This plugin was not named "cache" to avoid conflicts with the external
# pytest-cache version.
import dataclasses
import json
import os
from pathlib import Path
Expand All @@ -12,8 +13,6 @@
from typing import Set
from typing import Union

import attr

from .pathlib import resolve_from_str
from .pathlib import rm_rf
from .reports import CollectReport
Expand Down Expand Up @@ -53,10 +52,12 @@


@final
@attr.s(init=False, auto_attribs=True)
@dataclasses.dataclass
class Cache:
_cachedir: Path = attr.ib(repr=False)
_config: Config = attr.ib(repr=False)
"""Instance of the `cache` fixture."""

_cachedir: Path = dataclasses.field(repr=False)
_config: Config = dataclasses.field(repr=False)

# Sub-directory under cache-dir for directories created by `mkdir()`.
_CACHE_PREFIX_DIRS = "d"
Expand Down
7 changes: 3 additions & 4 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Python version compatibility code."""
import dataclasses
import enum
import functools
import inspect
Expand All @@ -17,8 +18,6 @@
from typing import TypeVar
from typing import Union

import attr

import py

# fmt: off
Expand Down Expand Up @@ -253,7 +252,7 @@ def ascii_escaped(val: Union[bytes, str]) -> str:
return _translate_non_printable(ret)


@attr.s
@dataclasses.dataclass
class _PytestWrapper:
"""Dummy wrapper around a function object for internal use only.

Expand All @@ -262,7 +261,7 @@ class _PytestWrapper:
decorator to issue warnings when the fixture function is called directly.
"""

obj = attr.ib()
obj: Any


def get_real_func(obj):
Expand Down
21 changes: 14 additions & 7 deletions src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import argparse
import collections.abc
import copy
import dataclasses
import enum
import glob
import inspect
Expand Down Expand Up @@ -34,7 +35,6 @@
from typing import TYPE_CHECKING
from typing import Union

import attr
from pluggy import HookimplMarker
from pluggy import HookspecMarker
from pluggy import PluginManager
Expand Down Expand Up @@ -886,10 +886,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
yield from _iter_rewritable_modules(new_package_files)


def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
return tuple(args)


@final
class Config:
"""Access to configuration values, pluginmanager and plugin hooks.
Expand All @@ -903,7 +899,7 @@ class Config:
"""

@final
@attr.s(frozen=True, auto_attribs=True)
@dataclasses.dataclass(frozen=True)
class InvocationParams:
"""Holds parameters passed during :func:`pytest.main`.

Expand All @@ -919,13 +915,24 @@ class InvocationParams:
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`."""
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,
*,
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):
"""Indicates the source of the test arguments.

Expand Down