Skip to content

Commit 272be7e

Browse files
authored
Merge pull request #7039 from bluetech/markdecorator-doc
Slightly improve Mark and MarkDecorator documentation
2 parents 7048d5b + 4344c61 commit 272be7e

File tree

1 file changed

+69
-56
lines changed

1 file changed

+69
-56
lines changed

src/_pytest/mark/structures.py

Lines changed: 69 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
import warnings
33
from collections import namedtuple
44
from collections.abc import MutableMapping
5+
from typing import Any
56
from typing import Iterable
67
from typing import List
8+
from typing import Mapping
79
from typing import Optional
10+
from typing import Sequence
811
from typing import Set
12+
from typing import Tuple
913
from typing import Union
1014

1115
import attr
@@ -140,28 +144,32 @@ def _for_parametrize(cls, argnames, argvalues, func, config, function_definition
140144

141145
@attr.s(frozen=True)
142146
class Mark:
143-
#: name of the mark
147+
#: Name of the mark.
144148
name = attr.ib(type=str)
145-
#: positional arguments of the mark decorator
146-
args = attr.ib() # List[object]
147-
#: keyword arguments of the mark decorator
148-
kwargs = attr.ib() # Dict[str, object]
149+
#: Positional arguments of the mark decorator.
150+
args = attr.ib(type=Tuple[Any, ...])
151+
#: Keyword arguments of the mark decorator.
152+
kwargs = attr.ib(type=Mapping[str, Any])
149153

150-
#: source Mark for ids with parametrize Marks
154+
#: Source Mark for ids with parametrize Marks.
151155
_param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False)
152-
#: resolved/generated ids with parametrize Marks
153-
_param_ids_generated = attr.ib(type=Optional[List[str]], default=None, repr=False)
156+
#: Resolved/generated ids with parametrize Marks.
157+
_param_ids_generated = attr.ib(
158+
type=Optional[Sequence[str]], default=None, repr=False
159+
)
154160

155-
def _has_param_ids(self):
161+
def _has_param_ids(self) -> bool:
156162
return "ids" in self.kwargs or len(self.args) >= 4
157163

158164
def combined_with(self, other: "Mark") -> "Mark":
159-
"""
160-
:param other: the mark to combine with
165+
"""Return a new Mark which is a combination of this
166+
Mark and another Mark.
167+
168+
Combines by appending args and merging kwargs.
169+
170+
:param other: The mark to combine with.
161171
:type other: Mark
162172
:rtype: Mark
163-
164-
combines by appending args and merging the mappings
165173
"""
166174
assert self.name == other.name
167175

@@ -183,76 +191,77 @@ def combined_with(self, other: "Mark") -> "Mark":
183191

184192
@attr.s
185193
class MarkDecorator:
186-
""" A decorator for test functions and test classes. When applied
187-
it will create :class:`Mark` objects which are often created like this::
194+
"""A decorator for applying a mark on test functions and classes.
195+
196+
MarkDecorators are created with ``pytest.mark``::
188197
189-
mark1 = pytest.mark.NAME # simple MarkDecorator
190-
mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
198+
mark1 = pytest.mark.NAME # Simple MarkDecorator
199+
mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
191200
192201
and can then be applied as decorators to test functions::
193202
194203
@mark2
195204
def test_function():
196205
pass
197206
198-
When a MarkDecorator instance is called it does the following:
207+
When a MarkDecorator is called it does the following:
199208
200209
1. If called with a single class as its only positional argument and no
201-
additional keyword arguments, it attaches itself to the class so it
210+
additional keyword arguments, it attaches the mark to the class so it
202211
gets applied automatically to all test cases found in that class.
203-
2. If called with a single function as its only positional argument and
204-
no additional keyword arguments, it attaches a MarkInfo object to the
205-
function, containing all the arguments already stored internally in
206-
the MarkDecorator.
207-
3. When called in any other case, it performs a 'fake construction' call,
208-
i.e. it returns a new MarkDecorator instance with the original
209-
MarkDecorator's content updated with the arguments passed to this
210-
call.
211-
212-
Note: The rules above prevent MarkDecorator objects from storing only a
213-
single function or class reference as their positional argument with no
214-
additional keyword or positional arguments.
215212
213+
2. If called with a single function as its only positional argument and
214+
no additional keyword arguments, it attaches the mark to the function,
215+
containing all the arguments already stored internally in the
216+
MarkDecorator.
217+
218+
3. When called in any other case, it returns a new MarkDecorator instance
219+
with the original MarkDecorator's content updated with the arguments
220+
passed to this call.
221+
222+
Note: The rules above prevent MarkDecorators from storing only a single
223+
function or class reference as their positional argument with no
224+
additional keyword or positional arguments. You can work around this by
225+
using `with_args()`.
216226
"""
217227

218-
mark = attr.ib(validator=attr.validators.instance_of(Mark))
228+
mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark))
219229

220230
@property
221-
def name(self):
222-
"""alias for mark.name"""
231+
def name(self) -> str:
232+
"""Alias for mark.name."""
223233
return self.mark.name
224234

225235
@property
226-
def args(self):
227-
"""alias for mark.args"""
236+
def args(self) -> Tuple[Any, ...]:
237+
"""Alias for mark.args."""
228238
return self.mark.args
229239

230240
@property
231-
def kwargs(self):
232-
"""alias for mark.kwargs"""
241+
def kwargs(self) -> Mapping[str, Any]:
242+
"""Alias for mark.kwargs."""
233243
return self.mark.kwargs
234244

235245
@property
236-
def markname(self):
246+
def markname(self) -> str:
237247
return self.name # for backward-compat (2.4.1 had this attr)
238248

239-
def __repr__(self):
249+
def __repr__(self) -> str:
240250
return "<MarkDecorator {!r}>".format(self.mark)
241251

242-
def with_args(self, *args, **kwargs):
243-
""" return a MarkDecorator with extra arguments added
252+
def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
253+
"""Return a MarkDecorator with extra arguments added.
244254
245-
unlike call this can be used even if the sole argument is a callable/class
255+
Unlike calling the MarkDecorator, with_args() can be used even
256+
if the sole argument is a callable/class.
246257
247258
:return: MarkDecorator
248259
"""
249-
250260
mark = Mark(self.name, args, kwargs)
251261
return self.__class__(self.mark.combined_with(mark))
252262

253-
def __call__(self, *args, **kwargs):
254-
""" if passed a single callable argument: decorate it with mark info.
255-
otherwise add *args/**kwargs in-place to mark information. """
263+
def __call__(self, *args: object, **kwargs: object):
264+
"""Call the MarkDecorator."""
256265
if args and not kwargs:
257266
func = args[0]
258267
is_class = inspect.isclass(func)
@@ -288,27 +297,31 @@ def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List
288297
return [x for x in extracted if isinstance(x, Mark)]
289298

290299

291-
def store_mark(obj, mark):
292-
"""store a Mark on an object
293-
this is used to implement the Mark declarations/decorators correctly
300+
def store_mark(obj, mark: Mark) -> None:
301+
"""Store a Mark on an object.
302+
303+
This is used to implement the Mark declarations/decorators correctly.
294304
"""
295305
assert isinstance(mark, Mark), mark
296-
# always reassign name to avoid updating pytestmark
297-
# in a reference that was only borrowed
306+
# Always reassign name to avoid updating pytestmark in a reference that
307+
# was only borrowed.
298308
obj.pytestmark = get_unpacked_marks(obj) + [mark]
299309

300310

301311
class MarkGenerator:
302-
""" Factory for :class:`MarkDecorator` objects - exposed as
303-
a ``pytest.mark`` singleton instance. Example::
312+
"""Factory for :class:`MarkDecorator` objects - exposed as
313+
a ``pytest.mark`` singleton instance.
314+
315+
Example::
304316
305317
import pytest
318+
306319
@pytest.mark.slowtest
307320
def test_function():
308321
pass
309322
310-
will set a 'slowtest' :class:`MarkInfo` object
311-
on the ``test_function`` object. """
323+
applies a 'slowtest' :class:`Mark` on ``test_function``.
324+
"""
312325

313326
_config = None
314327
_markers = set() # type: Set[str]

0 commit comments

Comments
 (0)