From 941e8b5104d86bb1b06b15fd3395f23120203c09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:26:54 +0000 Subject: [PATCH 1/5] Build(deps): Bump pygments from 2.18.0 to 2.19.1 in /dependencies/docs Bumps [pygments](https://github.com/pygments/pygments) from 2.18.0 to 2.19.1. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.18.0...2.19.1) --- updated-dependencies: - dependency-name: pygments dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies/docs/constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/docs/constraints.txt b/dependencies/docs/constraints.txt index 530d174e..607c1632 100644 --- a/dependencies/docs/constraints.txt +++ b/dependencies/docs/constraints.txt @@ -8,7 +8,7 @@ imagesize==1.4.1 Jinja2==3.1.5 MarkupSafe==3.0.2 packaging==24.2 -Pygments==2.18.0 +Pygments==2.19.1 requests==2.32.3 snowballstemmer==2.2.0 Sphinx==8.0.2 From aae43d4f766dfd8f9a643faad41be411888a5d16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:05:44 +0000 Subject: [PATCH 2/5] Build(deps): Bump hypothesis in /dependencies/default Bumps [hypothesis](https://github.com/HypothesisWorks/hypothesis) from 6.123.2 to 6.123.4. - [Release notes](https://github.com/HypothesisWorks/hypothesis/releases) - [Commits](https://github.com/HypothesisWorks/hypothesis/compare/hypothesis-python-6.123.2...hypothesis-python-6.123.4) --- updated-dependencies: - dependency-name: hypothesis dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- dependencies/default/constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/default/constraints.txt b/dependencies/default/constraints.txt index 934ec98c..816c4639 100644 --- a/dependencies/default/constraints.txt +++ b/dependencies/default/constraints.txt @@ -1,7 +1,7 @@ attrs==24.3.0 coverage==7.6.10 exceptiongroup==1.2.2 -hypothesis==6.123.2 +hypothesis==6.123.4 iniconfig==2.0.0 packaging==24.2 pluggy==1.5.0 From e8ffb10528aaf578dc9ccc7ddbb1fadab56be8ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 00:27:18 +0000 Subject: [PATCH 3/5] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6) - [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.0...v1.14.1) - [github.com/rhysd/actionlint: v1.7.5 → v1.7.6](https://github.com/rhysd/actionlint/compare/v1.7.5...v1.7.6) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c79734ca..c1943f60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: - id: check-merge-conflict exclude: rst$ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.8.6 hooks: - id: ruff args: [--fix] @@ -42,7 +42,7 @@ repos: - id: check-yaml - id: debug-statements - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.0 + rev: v1.14.1 hooks: - id: mypy exclude: ^(docs|tests)/.* @@ -53,7 +53,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/rhysd/actionlint - rev: v1.7.5 + rev: v1.7.6 hooks: - id: actionlint-docker args: From c3ad6340b8ad251f3c03d6bbe399f08434059bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20Nouvertn=C3=A9?= <25355197+provinzkraut@users.noreply.github.com> Date: Sun, 5 Jan 2025 14:04:36 +0100 Subject: [PATCH 4/5] fix: Shutdown generators before closing event loops. Add a comment --- docs/reference/changelog.rst | 5 ++++ pytest_asyncio/plugin.py | 39 ++++++++++++++++++++------------ tests/test_event_loop_fixture.py | 27 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index aca0ea0f..dfd3ee9a 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -2,6 +2,11 @@ Changelog ========= +0.25.2 (UNRELEASED) +=================== + +- Call ``loop.shutdown_asyncgens()`` before closing the event loop to ensure async generators are closed in the same manner as ``asyncio.run`` does `#1034 `_ + 0.25.1 (2025-01-02) =================== - Fixes an issue that caused a broken event loop when a function-scoped test was executed in between two tests with wider loop scope `#950 `_ diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 8ad17c0a..2f028ae1 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -708,11 +708,12 @@ def scoped_event_loop( event_loop_policy, ) -> Iterator[asyncio.AbstractEventLoop]: new_loop_policy = event_loop_policy - with _temporary_event_loop_policy(new_loop_policy): - loop = _make_pytest_asyncio_loop(asyncio.new_event_loop()) + with ( + _temporary_event_loop_policy(new_loop_policy), + _provide_event_loop() as loop, + ): asyncio.set_event_loop(loop) yield loop - loop.close() # @pytest.fixture does not register the fixture anywhere, so pytest doesn't # know it exists. We work around this by attaching the fixture function to the @@ -1147,16 +1148,26 @@ def _retrieve_scope_root(item: Collector | Item, scope: str) -> Collector: def event_loop(request: FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: """Create an instance of the default event loop for each test case.""" new_loop_policy = request.getfixturevalue(event_loop_policy.__name__) - with _temporary_event_loop_policy(new_loop_policy): - loop = asyncio.get_event_loop_policy().new_event_loop() - # Add a magic value to the event loop, so pytest-asyncio can determine if the - # event_loop fixture was overridden. Other implementations of event_loop don't - # set this value. - # The magic value must be set as part of the function definition, because pytest - # seems to have multiple instances of the same FixtureDef or fixture function - loop = _make_pytest_asyncio_loop(loop) + with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop: yield loop - loop.close() + + +@contextlib.contextmanager +def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]: + loop = asyncio.get_event_loop_policy().new_event_loop() + # Add a magic value to the event loop, so pytest-asyncio can determine if the + # event_loop fixture was overridden. Other implementations of event_loop don't + # set this value. + # The magic value must be set as part of the function definition, because pytest + # seems to have multiple instances of the same FixtureDef or fixture function + loop = _make_pytest_asyncio_loop(loop) + try: + yield loop + finally: + try: + loop.run_until_complete(loop.shutdown_asyncgens()) + finally: + loop.close() @pytest.fixture(scope="session") @@ -1164,11 +1175,9 @@ def _session_event_loop( request: FixtureRequest, event_loop_policy: AbstractEventLoopPolicy ) -> Iterator[asyncio.AbstractEventLoop]: new_loop_policy = event_loop_policy - with _temporary_event_loop_policy(new_loop_policy): - loop = _make_pytest_asyncio_loop(asyncio.new_event_loop()) + with _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop: asyncio.set_event_loop(loop) yield loop - loop.close() @pytest.fixture(scope="session", autouse=True) diff --git a/tests/test_event_loop_fixture.py b/tests/test_event_loop_fixture.py index 21785075..447d15d5 100644 --- a/tests/test_event_loop_fixture.py +++ b/tests/test_event_loop_fixture.py @@ -53,3 +53,30 @@ async def test_custom_policy_is_not_overwritten(): ) result = pytester.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=2) + + +def test_event_loop_fixture_handles_unclosed_async_gen( + pytester: Pytester, +): + pytester.makeini("[pytest]\nasyncio_default_fixture_loop_scope = function") + pytester.makepyfile( + dedent( + """\ + import asyncio + import pytest + + pytest_plugins = 'pytest_asyncio' + + @pytest.mark.asyncio + async def test_something(): + async def generator_fn(): + yield + yield + + gen = generator_fn() + await gen.__anext__() + """ + ) + ) + result = pytester.runpytest_subprocess("--asyncio-mode=strict", "-W", "default") + result.assert_outcomes(passed=1, warnings=0) From 2188cdbbf7b47e653957d3f39aeb7d4d0de80bf4 Mon Sep 17 00:00:00 2001 From: Michael Seifert Date: Wed, 8 Jan 2025 07:05:05 +0100 Subject: [PATCH 5/5] build: Prepare release of v0.25.2. --- docs/reference/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index dfd3ee9a..a28c9fd3 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -2,7 +2,7 @@ Changelog ========= -0.25.2 (UNRELEASED) +0.25.2 (2025-01-08) =================== - Call ``loop.shutdown_asyncgens()`` before closing the event loop to ensure async generators are closed in the same manner as ``asyncio.run`` does `#1034 `_