Skip to content

Commit 69c3024

Browse files
committed
mark: export pytest.MarkDecorator 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 2ec372d commit 69c3024

File tree

6 files changed

+28
-20
lines changed

6 files changed

+28
-20
lines changed

changelog/7469.deprecation.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Directly constructing the following classes is now deprecated:
22

33
- ``_pytest.mark.structures.Mark``
4+
- ``_pytest.mark.structures.MarkDecorator``
45

56
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ The types of objects used in pytest's API are now exported so they may be used i
33
The newly-exported types are:
44

55
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
6+
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
67

78
Constructing them directly is not supported; they are only meant for use in type annotations.
89
Doing so will emit a deprecation warning, and may become a hard-error in pytest 7.0.

doc/en/reference.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ Item
849849
MarkDecorator
850850
~~~~~~~~~~~~~
851851

852-
.. autoclass:: _pytest.mark.MarkDecorator
852+
.. autoclass:: pytest.MarkDecorator()
853853
:members:
854854

855855

src/_pytest/fixtures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
551551
on all function invocations.
552552
553553
:param marker:
554-
A :py:class:`_pytest.mark.MarkDecorator` object created by a call
554+
A :class:`pytest.MarkDecorator` object created by a call
555555
to ``pytest.mark.NAME(...)``.
556556
"""
557557
self.node.add_marker(marker)

src/_pytest/mark/structures.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,14 @@ def combined_with(self, other: "Mark") -> "Mark":
268268
# A generic parameter designating an object to which a Mark may
269269
# be applied -- a test function (callable) or class.
270270
# Note: a lambda is not allowed, but this can't be represented.
271-
_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type])
271+
Markable = TypeVar("Markable", bound=Union[Callable[..., object], type])
272272

273273

274-
@attr.s
274+
@attr.s(init=False, auto_attribs=True)
275275
class MarkDecorator:
276276
"""A decorator for applying a mark on test functions and classes.
277277
278-
MarkDecorators are created with ``pytest.mark``::
278+
``MarkDecorators`` are created with ``pytest.mark``::
279279
280280
mark1 = pytest.mark.NAME # Simple MarkDecorator
281281
mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
@@ -286,7 +286,7 @@ class MarkDecorator:
286286
def test_function():
287287
pass
288288
289-
When a MarkDecorator is called it does the following:
289+
When a ``MarkDecorator`` is called, it does the following:
290290
291291
1. If called with a single class as its only positional argument and no
292292
additional keyword arguments, it attaches the mark to the class so it
@@ -295,19 +295,24 @@ def test_function():
295295
2. If called with a single function as its only positional argument and
296296
no additional keyword arguments, it attaches the mark to the function,
297297
containing all the arguments already stored internally in the
298-
MarkDecorator.
298+
``MarkDecorator``.
299299
300-
3. When called in any other case, it returns a new MarkDecorator instance
301-
with the original MarkDecorator's content updated with the arguments
302-
passed to this call.
300+
3. When called in any other case, it returns a new ``MarkDecorator``
301+
instance with the original ``MarkDecorator``'s content updated with
302+
the arguments passed to this call.
303303
304-
Note: The rules above prevent MarkDecorators from storing only a single
305-
function or class reference as their positional argument with no
304+
Note: The rules above prevent a ``MarkDecorator`` from storing only a
305+
single function or class reference as its positional argument with no
306306
additional keyword or positional arguments. You can work around this by
307307
using `with_args()`.
308308
"""
309309

310-
mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark))
310+
mark: Mark
311+
312+
def __init__(self, mark: Mark, *, _ispytest: bool = False) -> None:
313+
""":meta private:"""
314+
check_ispytest(_ispytest)
315+
self.mark = mark
311316

312317
@property
313318
def name(self) -> str:
@@ -326,6 +331,7 @@ def kwargs(self) -> Mapping[str, Any]:
326331

327332
@property
328333
def markname(self) -> str:
334+
""":meta private:"""
329335
return self.name # for backward-compat (2.4.1 had this attr)
330336

331337
def __repr__(self) -> str:
@@ -336,17 +342,15 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
336342
337343
Unlike calling the MarkDecorator, with_args() can be used even
338344
if the sole argument is a callable/class.
339-
340-
:rtype: MarkDecorator
341345
"""
342346
mark = Mark(self.name, args, kwargs, _ispytest=True)
343-
return self.__class__(self.mark.combined_with(mark))
347+
return MarkDecorator(self.mark.combined_with(mark), _ispytest=True)
344348

345349
# Type ignored because the overloads overlap with an incompatible
346350
# return type. Not much we can do about that. Thankfully mypy picks
347351
# the first match so it works out even if we break the rules.
348352
@overload
349-
def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc]
353+
def __call__(self, arg: Markable) -> Markable: # type: ignore[misc]
350354
pass
351355

352356
@overload
@@ -405,7 +409,7 @@ def store_mark(obj, mark: Mark) -> None:
405409

406410
class _SkipMarkDecorator(MarkDecorator):
407411
@overload # type: ignore[override,misc]
408-
def __call__(self, arg: _Markable) -> _Markable:
412+
def __call__(self, arg: Markable) -> Markable:
409413
...
410414

411415
@overload
@@ -423,7 +427,7 @@ def __call__( # type: ignore[override]
423427

424428
class _XfailMarkDecorator(MarkDecorator):
425429
@overload # type: ignore[override,misc]
426-
def __call__(self, arg: _Markable) -> _Markable:
430+
def __call__(self, arg: Markable) -> Markable:
427431
...
428432

429433
@overload
@@ -534,7 +538,7 @@ def __getattr__(self, name: str) -> MarkDecorator:
534538
2,
535539
)
536540

537-
return MarkDecorator(Mark(name, (), {}, _ispytest=True))
541+
return MarkDecorator(Mark(name, (), {}, _ispytest=True), _ispytest=True)
538542

539543

540544
MARK_GEN = MarkGenerator()

src/pytest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from _pytest.main import Session
2424
from _pytest.mark import Mark
2525
from _pytest.mark import MARK_GEN as mark
26+
from _pytest.mark import MarkDecorator
2627
from _pytest.mark import param
2728
from _pytest.monkeypatch import MonkeyPatch
2829
from _pytest.nodes import Collector
@@ -91,6 +92,7 @@
9192
"main",
9293
"mark",
9394
"Mark",
95+
"MarkDecorator",
9496
"Module",
9597
"MonkeyPatch",
9698
"Package",

0 commit comments

Comments
 (0)