Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Aaron Coleman
Abdeali JK
Abdelrahman Elbehery
Abhijeet Kasurde
ace2016
Adam Johnson
Adam Stewart
Adam Uhlir
Expand Down
3 changes: 3 additions & 0 deletions changelog/13354.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:meth:`Node.get_closest_marker <_pytest.nodes.Node.get_closest_marker>` now also accepts a :class:`~pytest.MarkDecorator` as its ``default``, for example ``item.get_closest_marker("foo", pytest.mark.foo(1))``, and returns the wrapped :class:`~pytest.Mark`.

Previously the only way to build a ``default`` was to instantiate :class:`~pytest.Mark` directly, which is private and warns.
13 changes: 10 additions & 3 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,22 @@ def iter_markers_with_node(
def get_closest_marker(self, name: str) -> Mark | None: ...

@overload
def get_closest_marker(self, name: str, default: Mark) -> Mark: ...
def get_closest_marker(self, name: str, default: Mark | MarkDecorator) -> Mark: ...

def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None:
def get_closest_marker(
self, name: str, default: Mark | MarkDecorator | None = None
) -> Mark | None:
"""Return the first marker matching the name, from closest (for
example function) to farther level (for example module level).

:param default: Fallback return value if no marker was found.
:param default:
Fallback return value if no marker was found. A
:class:`~pytest.MarkDecorator` such as ``pytest.mark.foo(1)`` is
also accepted, in which case its :class:`~pytest.Mark` is returned.
:param name: Name to filter by.
"""
if isinstance(default, MarkDecorator):
default = default.mark
return next(self.iter_markers(name=name), default)

def listextrakeywords(self) -> set[str]:
Expand Down
12 changes: 12 additions & 0 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,18 @@ def test_has_inherited(self):
assert has_inherited_marker.kwargs == {"location": "class"}
assert has_own.get_closest_marker("missing") is None

def test_mark_closest_default_mark_decorator(self, pytester: Pytester) -> None:
p = pytester.makepyfile(
"""
def test_without_mark():
pass
"""
)
items, _rec = pytester.inline_genitems(p)
(item,) = items
default = pytest.mark.foo(location="default")
assert item.get_closest_marker("foo", default) is default.mark

def test_mark_with_wrong_marker(self, pytester: Pytester) -> None:
reprec = pytester.inline_runsource(
"""
Expand Down