Skip to content

Commit 2ec372d

Browse files
committed
mark: export pytest.Mark for typing purposes
The type cannot be constructed directly, but is exported for use in type annotations, since it is reachable through existing public API.
1 parent 89dcfbf commit 2ec372d

File tree

5 files changed

+48
-12
lines changed

5 files changed

+48
-12
lines changed

changelog/7469.deprecation.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Directly constructing the following classes is now deprecated:
2+
3+
- ``_pytest.mark.structures.Mark``
4+
5+
These have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 7.0.0.

changelog/7469.feature.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
The types of objects used in pytest's API are now exported so they may be used in type annotations.
2+
3+
The newly-exported types are:
4+
5+
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
6+
7+
Constructing them directly is not supported; they are only meant for use in type annotations.
8+
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.
9+
10+
Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy.

doc/en/reference.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ For example:
239239
def test_function():
240240
...
241241
242-
Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to the collected
242+
Will create and attach a :class:`Mark <pytest.Mark>` object to the collected
243243
:class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with
244244
:meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`. The ``mark`` object will have the following attributes:
245245

@@ -863,7 +863,7 @@ MarkGenerator
863863
Mark
864864
~~~~
865865

866-
.. autoclass:: _pytest.mark.structures.Mark
866+
.. autoclass:: pytest.Mark()
867867
:members:
868868

869869

src/_pytest/mark/structures.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ..compat import NOTSET
2929
from ..compat import NotSetType
3030
from _pytest.config import Config
31+
from _pytest.deprecated import check_ispytest
3132
from _pytest.outcomes import fail
3233
from _pytest.warning_types import PytestUnknownMarkWarning
3334

@@ -200,21 +201,38 @@ def _for_parametrize(
200201

201202

202203
@final
203-
@attr.s(frozen=True)
204+
@attr.s(frozen=True, init=False, auto_attribs=True)
204205
class Mark:
205206
#: Name of the mark.
206-
name = attr.ib(type=str)
207+
name: str
207208
#: Positional arguments of the mark decorator.
208-
args = attr.ib(type=Tuple[Any, ...])
209+
args: Tuple[Any, ...]
209210
#: Keyword arguments of the mark decorator.
210-
kwargs = attr.ib(type=Mapping[str, Any])
211+
kwargs: Mapping[str, Any]
211212

212213
#: Source Mark for ids with parametrize Marks.
213-
_param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False)
214+
_param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False)
214215
#: Resolved/generated ids with parametrize Marks.
215-
_param_ids_generated = attr.ib(
216-
type=Optional[Sequence[str]], default=None, repr=False
217-
)
216+
_param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False)
217+
218+
def __init__(
219+
self,
220+
name: str,
221+
args: Tuple[Any, ...],
222+
kwargs: Mapping[str, Any],
223+
param_ids_from: Optional["Mark"] = None,
224+
param_ids_generated: Optional[Sequence[str]] = None,
225+
*,
226+
_ispytest: bool = False,
227+
) -> None:
228+
""":meta private:"""
229+
check_ispytest(_ispytest)
230+
# Weirdness to bypass frozen=True.
231+
object.__setattr__(self, "name", name)
232+
object.__setattr__(self, "args", args)
233+
object.__setattr__(self, "kwargs", kwargs)
234+
object.__setattr__(self, "_param_ids_from", param_ids_from)
235+
object.__setattr__(self, "_param_ids_generated", param_ids_generated)
218236

219237
def _has_param_ids(self) -> bool:
220238
return "ids" in self.kwargs or len(self.args) >= 4
@@ -243,6 +261,7 @@ def combined_with(self, other: "Mark") -> "Mark":
243261
self.args + other.args,
244262
dict(self.kwargs, **other.kwargs),
245263
param_ids_from=param_ids_from,
264+
_ispytest=True,
246265
)
247266

248267

@@ -320,7 +339,7 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
320339
321340
:rtype: MarkDecorator
322341
"""
323-
mark = Mark(self.name, args, kwargs)
342+
mark = Mark(self.name, args, kwargs, _ispytest=True)
324343
return self.__class__(self.mark.combined_with(mark))
325344

326345
# Type ignored because the overloads overlap with an incompatible
@@ -515,7 +534,7 @@ def __getattr__(self, name: str) -> MarkDecorator:
515534
2,
516535
)
517536

518-
return MarkDecorator(Mark(name, (), {}))
537+
return MarkDecorator(Mark(name, (), {}, _ispytest=True))
519538

520539

521540
MARK_GEN = MarkGenerator()

src/pytest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from _pytest.freeze_support import freeze_includes
2222
from _pytest.logging import LogCaptureFixture
2323
from _pytest.main import Session
24+
from _pytest.mark import Mark
2425
from _pytest.mark import MARK_GEN as mark
2526
from _pytest.mark import param
2627
from _pytest.monkeypatch import MonkeyPatch
@@ -89,6 +90,7 @@
8990
"LogCaptureFixture",
9091
"main",
9192
"mark",
93+
"Mark",
9294
"Module",
9395
"MonkeyPatch",
9496
"Package",

0 commit comments

Comments
 (0)