|
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