diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4e6f9d22..634fa745 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -58,20 +58,24 @@ jobs: test: name: ${{ matrix.os }} - Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest - + continue-on-error: ${{ !matrix.required }} strategy: matrix: os: [ubuntu, windows] python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + required: [true] + include: + - os: ubuntu + python-version: 3.14-dev + required: false + - os: windows + python-version: 3.14-dev + required: false + steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - if: "!endsWith(matrix.python-version, '-dev')" - with: - python-version: ${{ matrix.python-version }} - - uses: deadsnakes/action@v3.2.0 - if: endsWith(matrix.python-version, '-dev') with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/changelog.d/1025.added.rst b/changelog.d/1025.added.rst new file mode 100644 index 00000000..24977eda --- /dev/null +++ b/changelog.d/1025.added.rst @@ -0,0 +1 @@ +Prelimiary support for Python 3.14 diff --git a/docs/how-to-guides/multiple_loops_example.py b/docs/how-to-guides/multiple_loops_example.py index a4c7a01c..2083e8b6 100644 --- a/docs/how-to-guides/multiple_loops_example.py +++ b/docs/how-to-guides/multiple_loops_example.py @@ -1,5 +1,9 @@ import asyncio -from asyncio import DefaultEventLoopPolicy +import warnings + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + from asyncio import DefaultEventLoopPolicy import pytest @@ -20,5 +24,6 @@ def event_loop_policy(request): @pytest.mark.asyncio +@pytest.mark.filterwarnings("ignore::DeprecationWarning") async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) diff --git a/docs/reference/fixtures/event_loop_policy_example.py b/docs/reference/fixtures/event_loop_policy_example.py index 5fd87b73..e8642527 100644 --- a/docs/reference/fixtures/event_loop_policy_example.py +++ b/docs/reference/fixtures/event_loop_policy_example.py @@ -1,9 +1,14 @@ import asyncio +import warnings + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + from asyncio import DefaultEventLoopPolicy import pytest -class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): +class CustomEventLoopPolicy(DefaultEventLoopPolicy): pass @@ -13,5 +18,6 @@ def event_loop_policy(request): @pytest.mark.asyncio(loop_scope="module") +@pytest.mark.filterwarnings("ignore::DeprecationWarning") async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) diff --git a/docs/reference/fixtures/event_loop_policy_parametrized_example.py b/docs/reference/fixtures/event_loop_policy_parametrized_example.py index 1560889b..19552d81 100644 --- a/docs/reference/fixtures/event_loop_policy_parametrized_example.py +++ b/docs/reference/fixtures/event_loop_policy_parametrized_example.py @@ -1,10 +1,14 @@ import asyncio -from asyncio import DefaultEventLoopPolicy +import warnings + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + from asyncio import DefaultEventLoopPolicy import pytest -class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): +class CustomEventLoopPolicy(DefaultEventLoopPolicy): pass @@ -19,5 +23,6 @@ def event_loop_policy(request): @pytest.mark.asyncio +@pytest.mark.filterwarnings("ignore::DeprecationWarning") async def test_uses_custom_event_loop_policy(): assert isinstance(asyncio.get_event_loop_policy(), DefaultEventLoopPolicy) diff --git a/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py b/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py index afb4cc8a..5bb26247 100644 --- a/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py +++ b/docs/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py @@ -1,12 +1,16 @@ -import asyncio +import warnings + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + from asyncio import DefaultEventLoopPolicy import pytest @pytest.fixture( params=[ - asyncio.DefaultEventLoopPolicy(), - asyncio.DefaultEventLoopPolicy(), + DefaultEventLoopPolicy(), + DefaultEventLoopPolicy(), ] ) def event_loop_policy(request): diff --git a/pyproject.toml b/pyproject.toml index 9cdd7b3c..b55bc8e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,7 +121,6 @@ asyncio_default_fixture_loop_scope = "function" junit_family = "xunit2" filterwarnings = [ "error", - "ignore:The event_loop fixture provided by pytest-asyncio has been redefined.*:DeprecationWarning", ] [tool.coverage.run] diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index b4f4f637..aecf6e96 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -11,7 +11,7 @@ import socket import sys import warnings -from asyncio import AbstractEventLoopPolicy +from asyncio import AbstractEventLoop, AbstractEventLoopPolicy from collections.abc import ( AsyncIterator, Awaitable, @@ -632,17 +632,17 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass( @contextlib.contextmanager def _temporary_event_loop_policy(policy: AbstractEventLoopPolicy) -> Iterator[None]: - old_loop_policy = asyncio.get_event_loop_policy() + old_loop_policy = _get_event_loop_policy() try: old_loop = _get_event_loop_no_warn() except RuntimeError: old_loop = None - asyncio.set_event_loop_policy(policy) + _set_event_loop_policy(policy) try: yield finally: - asyncio.set_event_loop_policy(old_loop_policy) - asyncio.set_event_loop(old_loop) + _set_event_loop_policy(old_loop_policy) + _set_event_loop(old_loop) @pytest.hookimpl(tryfirst=True) @@ -671,6 +671,18 @@ def pytest_generate_tests(metafunc: Metafunc) -> None: ] +def _get_event_loop_policy() -> AbstractEventLoopPolicy: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + return asyncio.get_event_loop_policy() + + +def _set_event_loop_policy(policy: AbstractEventLoopPolicy) -> None: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + asyncio.set_event_loop_policy(policy) + + def _get_event_loop_no_warn( policy: AbstractEventLoopPolicy | None = None, ) -> asyncio.AbstractEventLoop: @@ -682,6 +694,12 @@ def _get_event_loop_no_warn( return asyncio.get_event_loop() +def _set_event_loop(loop: AbstractEventLoop | None) -> None: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + asyncio.set_event_loop(loop) + + @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: """ @@ -835,7 +853,7 @@ def _scoped_event_loop( _temporary_event_loop_policy(new_loop_policy), _provide_event_loop() as loop, ): - asyncio.set_event_loop(loop) + _set_event_loop(loop) yield loop return _scoped_event_loop @@ -849,7 +867,8 @@ def _scoped_event_loop( @contextlib.contextmanager def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]: - loop = asyncio.get_event_loop_policy().new_event_loop() + policy = _get_event_loop_policy() + loop = policy.new_event_loop() try: yield loop finally: @@ -866,7 +885,7 @@ def _provide_event_loop() -> Iterator[asyncio.AbstractEventLoop]: @pytest.fixture(scope="session", autouse=True) def event_loop_policy() -> AbstractEventLoopPolicy: """Return an instance of the policy used to create asyncio event loops.""" - return asyncio.get_event_loop_policy() + return _get_event_loop_policy() def is_async_test(item: Item) -> bool: diff --git a/tests/async_fixtures/test_async_fixtures_with_finalizer.py b/tests/async_fixtures/test_async_fixtures_with_finalizer.py deleted file mode 100644 index ac0629cf..00000000 --- a/tests/async_fixtures/test_async_fixtures_with_finalizer.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import annotations - -import asyncio -import functools - -import pytest - -import pytest_asyncio - - -@pytest.mark.asyncio(loop_scope="module") -async def test_module_with_event_loop_finalizer(port_with_event_loop_finalizer): - await asyncio.sleep(0.01) - assert port_with_event_loop_finalizer - - -@pytest.mark.asyncio(loop_scope="module") -async def test_module_with_get_event_loop_finalizer(port_with_get_event_loop_finalizer): - await asyncio.sleep(0.01) - assert port_with_get_event_loop_finalizer - - -@pytest_asyncio.fixture(loop_scope="module", scope="module") -async def port_with_event_loop_finalizer(request): - def port_finalizer(finalizer): - async def port_afinalizer(): - # await task using loop provided by event_loop fixture - # RuntimeError is raised if task is created on a different loop - await finalizer - - asyncio.run(port_afinalizer()) - - worker = asyncio.ensure_future(asyncio.sleep(0.2)) - request.addfinalizer(functools.partial(port_finalizer, worker)) - return True - - -@pytest_asyncio.fixture(loop_scope="module", scope="module") -async def port_with_get_event_loop_finalizer(request): - def port_finalizer(finalizer): - async def port_afinalizer(): - # await task using current loop retrieved from the event loop policy - # RuntimeError is raised if task is created on a different loop. - # This can happen when pytest_fixture_setup - # does not set up the loop correctly, - # for example when policy.set_event_loop() is called with a wrong argument - await finalizer - - current_loop = asyncio.get_event_loop_policy().get_event_loop() - current_loop.run_until_complete(port_afinalizer()) - - worker = asyncio.ensure_future(asyncio.sleep(0.2)) - request.addfinalizer(functools.partial(port_finalizer, worker)) - return True diff --git a/tox.ini b/tox.ini index 9a0cf93b..e6457f56 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.9.0 -envlist = py39, py310, py311, py312, py313, pytest-min, docs +envlist = py39, py310, py311, py312, py313, py314, pytest-min, docs isolated_build = true passenv = CI @@ -76,4 +76,5 @@ python = 3.11: py311 3.12: py312 3.13: py313 + 3.14-dev: py314 pypy3: pypy3