|
36 | 36 | import pytest
|
37 | 37 | from _pytest.scope import Scope
|
38 | 38 | from pytest import (
|
39 |
| - Collector, |
40 | 39 | Config,
|
41 | 40 | FixtureDef,
|
42 | 41 | FixtureRequest,
|
43 | 42 | Function,
|
44 | 43 | Item,
|
45 | 44 | Mark,
|
46 | 45 | Metafunc,
|
| 46 | + MonkeyPatch, |
47 | 47 | Parser,
|
48 | 48 | PytestCollectionWarning,
|
49 | 49 | PytestDeprecationWarning,
|
@@ -231,39 +231,6 @@ def pytest_report_header(config: Config) -> list[str]:
|
231 | 231 | ]
|
232 | 232 |
|
233 | 233 |
|
234 |
| -def _preprocess_async_fixtures( |
235 |
| - collector: Collector, |
236 |
| - processed_fixturedefs: set[FixtureDef], |
237 |
| -) -> None: |
238 |
| - config = collector.config |
239 |
| - default_loop_scope = config.getini("asyncio_default_fixture_loop_scope") |
240 |
| - asyncio_mode = _get_asyncio_mode(config) |
241 |
| - fixturemanager = config.pluginmanager.get_plugin("funcmanage") |
242 |
| - assert fixturemanager is not None |
243 |
| - for fixtures in fixturemanager._arg2fixturedefs.values(): |
244 |
| - for fixturedef in fixtures: |
245 |
| - func = fixturedef.func |
246 |
| - if fixturedef in processed_fixturedefs or not _is_coroutine_or_asyncgen( |
247 |
| - func |
248 |
| - ): |
249 |
| - continue |
250 |
| - if asyncio_mode == Mode.STRICT and not _is_asyncio_fixture_function(func): |
251 |
| - # Ignore async fixtures without explicit asyncio mark in strict mode |
252 |
| - # This applies to pytest_trio fixtures, for example |
253 |
| - continue |
254 |
| - loop_scope = ( |
255 |
| - getattr(func, "_loop_scope", None) |
256 |
| - or default_loop_scope |
257 |
| - or fixturedef.scope |
258 |
| - ) |
259 |
| - _make_asyncio_fixture_function(func, loop_scope) |
260 |
| - if "request" not in fixturedef.argnames: |
261 |
| - fixturedef.argnames += ("request",) |
262 |
| - fixturedef.func = _fixture_synchronizer(fixturedef) # type: ignore[misc] |
263 |
| - assert _is_asyncio_fixture_function(fixturedef.func) |
264 |
| - processed_fixturedefs.add(fixturedef) |
265 |
| - |
266 |
| - |
267 | 234 | def _fixture_synchronizer(fixturedef: FixtureDef) -> Callable:
|
268 | 235 | """Returns a synchronous function evaluating the specified fixture."""
|
269 | 236 | if inspect.isasyncgenfunction(fixturedef.func):
|
@@ -599,22 +566,6 @@ def runtest(self) -> None:
|
599 | 566 | super().runtest()
|
600 | 567 |
|
601 | 568 |
|
602 |
| -_HOLDER: set[FixtureDef] = set() |
603 |
| - |
604 |
| - |
605 |
| -# The function name needs to start with "pytest_" |
606 |
| -# see https://github.com/pytest-dev/pytest/issues/11307 |
607 |
| -@pytest.hookimpl(specname="pytest_pycollect_makeitem", tryfirst=True) |
608 |
| -def pytest_pycollect_makeitem_preprocess_async_fixtures( |
609 |
| - collector: pytest.Module | pytest.Class, name: str, obj: object |
610 |
| -) -> pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None: |
611 |
| - """A pytest hook to collect asyncio coroutines.""" |
612 |
| - if not collector.funcnamefilter(name): |
613 |
| - return None |
614 |
| - _preprocess_async_fixtures(collector, _HOLDER) |
615 |
| - return None |
616 |
| - |
617 |
| - |
618 | 569 | # The function name needs to start with "pytest_"
|
619 | 570 | # see https://github.com/pytest-dev/pytest/issues/11307
|
620 | 571 | @pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True)
|
@@ -829,6 +780,32 @@ def pytest_runtest_setup(item: pytest.Item) -> None:
|
829 | 780 | )
|
830 | 781 |
|
831 | 782 |
|
| 783 | +@pytest.hookimpl(wrapper=True) |
| 784 | +def pytest_fixture_setup(fixturedef: FixtureDef, request) -> object | None: |
| 785 | + asyncio_mode = _get_asyncio_mode(request.config) |
| 786 | + if not _is_asyncio_fixture_function(fixturedef.func): |
| 787 | + if asyncio_mode == Mode.STRICT: |
| 788 | + # Ignore async fixtures without explicit asyncio mark in strict mode |
| 789 | + # This applies to pytest_trio fixtures, for example |
| 790 | + return (yield) |
| 791 | + if not _is_coroutine_or_asyncgen(fixturedef.func): |
| 792 | + return (yield) |
| 793 | + default_loop_scope = request.config.getini("asyncio_default_fixture_loop_scope") |
| 794 | + loop_scope = ( |
| 795 | + getattr(fixturedef.func, "_loop_scope", None) |
| 796 | + or default_loop_scope |
| 797 | + or fixturedef.scope |
| 798 | + ) |
| 799 | + synchronizer = _fixture_synchronizer(fixturedef) |
| 800 | + _make_asyncio_fixture_function(synchronizer, loop_scope) |
| 801 | + with MonkeyPatch.context() as c: |
| 802 | + if "request" not in fixturedef.argnames: |
| 803 | + c.setattr(fixturedef, "argnames", (*fixturedef.argnames, "request")) |
| 804 | + c.setattr(fixturedef, "func", synchronizer) |
| 805 | + hook_result = yield |
| 806 | + return hook_result |
| 807 | + |
| 808 | + |
832 | 809 | _DUPLICATE_LOOP_SCOPE_DEFINITION_ERROR = """\
|
833 | 810 | An asyncio pytest marker defines both "scope" and "loop_scope", \
|
834 | 811 | but it should only use "loop_scope".
|
|
0 commit comments