Skip to content

Commit 1eee63a

Browse files
committed
fixtures: minor cleanups to reorder_items
This makes some minor clarity and performance improvements to the code.
1 parent 9802183 commit 1eee63a

File tree

1 file changed

+65
-60
lines changed

1 file changed

+65
-60
lines changed

Diff for: src/_pytest/fixtures.py

+65-60
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import Iterable
2222
from typing import Iterator
2323
from typing import List
24+
from typing import Mapping
2425
from typing import MutableMapping
2526
from typing import NoReturn
2627
from typing import Optional
@@ -161,6 +162,12 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
161162
)
162163

163164

165+
# Algorithm for sorting on a per-parametrized resource setup basis.
166+
# It is called for Session scope first and performs sorting
167+
# down to the lower scopes such as to minimize number of "high scope"
168+
# setups and teardowns.
169+
170+
164171
@dataclasses.dataclass(frozen=True)
165172
class FixtureArgKey:
166173
argname: str
@@ -169,97 +176,91 @@ class FixtureArgKey:
169176
item_cls: Optional[type]
170177

171178

172-
def get_parametrized_fixture_keys(
179+
_V = TypeVar("_V")
180+
OrderedSet = Dict[_V, None]
181+
182+
183+
def get_parametrized_fixture_argkeys(
173184
item: nodes.Item, scope: Scope
174185
) -> Iterator[FixtureArgKey]:
175186
"""Return list of keys for all parametrized arguments which match
176187
the specified scope."""
177188
assert scope is not Scope.Function
189+
178190
try:
179191
callspec: CallSpec2 = item.callspec # type: ignore[attr-defined]
180192
except AttributeError:
181193
return
194+
195+
item_cls = None
196+
if scope is Scope.Session:
197+
scoped_item_path = None
198+
elif scope is Scope.Package:
199+
# Package key = module's directory.
200+
scoped_item_path = item.path.parent
201+
elif scope is Scope.Module:
202+
scoped_item_path = item.path
203+
elif scope is Scope.Class:
204+
scoped_item_path = item.path
205+
item_cls = item.cls # type: ignore[attr-defined]
206+
else:
207+
assert_never(scope)
208+
182209
for argname in callspec.indices:
183210
if callspec._arg2scope[argname] != scope:
184211
continue
185-
186-
item_cls = None
187-
if scope is Scope.Session:
188-
scoped_item_path = None
189-
elif scope is Scope.Package:
190-
# Package key = module's directory.
191-
scoped_item_path = item.path.parent
192-
elif scope is Scope.Module:
193-
scoped_item_path = item.path
194-
elif scope is Scope.Class:
195-
scoped_item_path = item.path
196-
item_cls = item.cls # type: ignore[attr-defined]
197-
else:
198-
assert_never(scope)
199-
200212
param_index = callspec.indices[argname]
201213
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
202214

203215

204-
# Algorithm for sorting on a per-parametrized resource setup basis.
205-
# It is called for Session scope first and performs sorting
206-
# down to the lower scopes such as to minimize number of "high scope"
207-
# setups and teardowns.
208-
209-
210216
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
211-
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
217+
argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
212218
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
213219
for scope in HIGH_SCOPES:
214-
scoped_argkeys_cache = argkeys_cache[scope] = {}
220+
scoped_argkeys_by_item = argkeys_by_item[scope] = {}
215221
scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque)
216222
for item in items:
217-
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
218-
if keys:
219-
scoped_argkeys_cache[item] = keys
220-
for key in keys:
221-
scoped_items_by_argkey[key].append(item)
222-
items_dict = dict.fromkeys(items, None)
223+
argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope))
224+
if argkeys:
225+
scoped_argkeys_by_item[item] = argkeys
226+
for argkey in argkeys:
227+
scoped_items_by_argkey[argkey].append(item)
228+
229+
items_set = dict.fromkeys(items)
223230
return list(
224-
reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
231+
reorder_items_atscope(
232+
items_set, argkeys_by_item, items_by_argkey, Scope.Session
233+
)
225234
)
226235

227236

228-
def fix_cache_order(
229-
item: nodes.Item,
230-
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
231-
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
232-
) -> None:
233-
for scope in HIGH_SCOPES:
234-
for key in argkeys_cache[scope].get(item, []):
235-
items_by_argkey[scope][key].appendleft(item)
236-
237-
238237
def reorder_items_atscope(
239-
items: Dict[nodes.Item, None],
240-
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
241-
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
238+
items: OrderedSet[nodes.Item],
239+
argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]],
240+
items_by_argkey: Mapping[Scope, Mapping[FixtureArgKey, "Deque[nodes.Item]"]],
242241
scope: Scope,
243-
) -> Dict[nodes.Item, None]:
242+
) -> OrderedSet[nodes.Item]:
244243
if scope is Scope.Function or len(items) < 3:
245244
return items
246-
ignore: Set[Optional[FixtureArgKey]] = set()
247-
items_deque = deque(items)
248-
items_done: Dict[nodes.Item, None] = {}
245+
249246
scoped_items_by_argkey = items_by_argkey[scope]
250-
scoped_argkeys_cache = argkeys_cache[scope]
247+
scoped_argkeys_by_item = argkeys_by_item[scope]
248+
249+
ignore: Set[FixtureArgKey] = set()
250+
items_deque = deque(items)
251+
items_done: OrderedSet[nodes.Item] = {}
251252
while items_deque:
252-
no_argkey_group: Dict[nodes.Item, None] = {}
253+
no_argkey_items: OrderedSet[nodes.Item] = {}
253254
slicing_argkey = None
254255
while items_deque:
255256
item = items_deque.popleft()
256-
if item in items_done or item in no_argkey_group:
257+
if item in items_done or item in no_argkey_items:
257258
continue
258259
argkeys = dict.fromkeys(
259-
(k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None
260+
k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore
260261
)
261262
if not argkeys:
262-
no_argkey_group[item] = None
263+
no_argkey_items[item] = None
263264
else:
264265
slicing_argkey, _ = argkeys.popitem()
265266
# We don't have to remove relevant items from later in the
@@ -268,16 +269,20 @@ def reorder_items_atscope(
268269
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
269270
]
270271
for i in reversed(matching_items):
271-
fix_cache_order(i, argkeys_cache, items_by_argkey)
272272
items_deque.appendleft(i)
273+
# Fix items_by_argkey order.
274+
for other_scope in HIGH_SCOPES:
275+
other_scoped_items_by_argkey = items_by_argkey[other_scope]
276+
for argkey in argkeys_by_item[other_scope].get(i, ()):
277+
other_scoped_items_by_argkey[argkey].appendleft(i)
273278
break
274-
if no_argkey_group:
275-
no_argkey_group = reorder_items_atscope(
276-
no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
279+
if no_argkey_items:
280+
reordered_no_argkey_items = reorder_items_atscope(
281+
no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower()
277282
)
278-
for item in no_argkey_group:
279-
items_done[item] = None
280-
ignore.add(slicing_argkey)
283+
items_done.update(reordered_no_argkey_items)
284+
if slicing_argkey is not None:
285+
ignore.add(slicing_argkey)
281286
return items_done
282287

283288

0 commit comments

Comments
 (0)