userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
fixture_markers.py
1
"""
2
Utilities for attaching typed marks to pytest fixture functions
3
and querying those marks during a test session.
4
5
Use :func:`mark` to store an info object on the original function.
6
Use :func:`get_infos` to retrieve fixtures tagged with a particular
7
info type among those visible to a request.
8
9
Marks are independent of ``@pytest.fixture``: apply the mark to the
10
original function, then wrap with ``@pytest.fixture``.
11
"""
12
13
from
__future__
import
annotations
14
15
import
inspect
16
from
collections.abc
import
Callable, Iterator, Sequence
17
from
typing
import
Any, TypeVar, cast
18
19
import
pytest
20
21
__all__ = [
22
'get_infos'
,
23
'mark'
,
24
]
25
26
I = TypeVar(
'I'
)
# noqa: E741
27
28
_MARKS_ATTR =
'_testsuite_fixture_marks'
29
_SCOPE_RANK = {
30
'function'
: 0,
31
'class'
: 1,
32
'module'
: 2,
33
'package'
: 3,
34
'session'
: 4,
35
}
36
37
38
def
mark
(
39
func: Callable[..., object],
40
info: object,
41
/,
42
) -> Callable[..., object]:
43
"""
44
Attach *info* to the original function *func*.
45
46
Creates the marks dict if it is missing, otherwise updates it.
47
The *info* type is the lookup key for :func:`get_infos`. Each type
48
may be attached at most once.
49
50
*func* must be the original function, not a ``@pytest.fixture``
51
wrapper. Apply the mark under ``@pytest.fixture``.
52
53
See :doc:`fixture_markers` for usage examples.
54
55
:param func: The original fixture function.
56
:param info: Metadata instance to store.
57
:returns: *func*, unchanged.
58
"""
59
if
_is_pytest_fixture_wrapper(func):
60
name = getattr(func,
'__qualname__'
, func.__name__)
61
raise
ValueError(
62
f
'{name!r} is already wrapped by @pytest.fixture; '
63
'apply the mark under @pytest.fixture, not above it'
,
64
)
65
66
marks = getattr(func, _MARKS_ATTR,
None
)
67
if
marks
is
None
:
68
marks = {}
69
setattr(func, _MARKS_ATTR, marks)
70
info_type = type(info)
71
if
(existing := marks.get(info_type))
is
not
None
:
72
name = getattr(func,
'__qualname__'
, func.__name__)
73
raise
ValueError(
74
f
'{name!r} already has a {info_type.__name__} mark '
75
f
'({existing!r}); cannot attach another ({info!r})'
,
76
)
77
marks[info_type] = info
78
return
func
79
80
81
def
get_infos
(
82
request: pytest.FixtureRequest,
83
info_type: type[I],
84
/,
85
) -> dict[str, I]:
86
"""
87
Return marked fixtures of *info_type* that are visible to a request.
88
89
Walks fixture definitions known to pytest, keeps those applicable to
90
the requesting test, and skips fixtures whose scope is narrower than
91
``request.scope``.
92
93
Only the winning fixture definition is inspected. An override must
94
carry its own mark; a parent mark is not inherited.
95
96
See :doc:`fixture_markers` for usage examples.
97
98
:param request: The pytest fixture request object.
99
:param info_type: The info class whose tagged fixtures you want
100
to look up.
101
:returns: ``dict[str, I]`` mapping each tagged fixture's name to the
102
*info* object that was passed to :func:`mark`.
103
The dict is a fresh copy; mutating it has no effect on stored
104
data.
105
"""
106
collected: dict[str, I] = {}
107
for
fixturedef
in
_iter_visible_fixtures(request):
108
marks = getattr(fixturedef.func, _MARKS_ATTR,
None
)
109
if
not
marks:
110
continue
111
info = marks.get(info_type)
112
if
type(info)
is
info_type:
113
collected[fixturedef.argname] = cast(I, info)
114
return
collected
115
116
117
def
_is_pytest_fixture_wrapper(func: object) -> bool:
118
if
getattr(func,
'_pytestfixturefunction'
,
None
):
119
return
True
120
return
type(func).__name__ ==
'FixtureFunctionDefinition'
121
122
123
def
_get_fixturedefs(
124
fixture_manager: Any,
125
name: str,
126
request: pytest.FixtureRequest,
127
) -> Sequence[Any] |
None
:
128
item = request._pyfuncitem
129
params = inspect.signature(fixture_manager.getfixturedefs).parameters
130
key = item
if
list(params)[1] ==
'node'
else
item.nodeid
131
return
fixture_manager.getfixturedefs(name, key)
132
133
134
def
_iter_visible_fixtures(
135
request: pytest.FixtureRequest,
136
) -> Iterator[pytest.FixtureDef[Any]]:
137
fixture_manager = request.session._fixturemanager
138
invoking_rank = _SCOPE_RANK[request.scope]
139
140
for
name
in
fixture_manager._arg2fixturedefs:
141
matched = _get_fixturedefs(fixture_manager, name, request)
142
if
not
matched:
143
continue
144
winning = matched[-1]
145
if
_SCOPE_RANK[winning.scope] < invoking_rank:
146
continue
147
yield
winning
en
testsuite
fixture_markers.py
Generated on
for userver by
Doxygen
1.17.0