From bde242fdaf097e26dd0caa7faf79b6998b21f939 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Fri, 14 Jan 2022 15:55:10 +0200 Subject: [PATCH 01/11] Relax asyncio_mode type definition; it allows to support pytest 5.4+ --- README.rst | 1 + pytest_asyncio/plugin.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9d2257a5..81a81d0a 100644 --- a/README.rst +++ b/README.rst @@ -259,6 +259,7 @@ Changelog 0.17.1 (UNRELEASED) ~~~~~~~~~~~~~~~~~~~ - Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_ +- Relax ``asyncio_mode`` type definition; it allows to support pytest 5.4+. `#263 `_ 0.17.0 (22-01-13) ~~~~~~~~~~~~~~~~~~~ diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 04b5e139..92b22e8a 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -53,7 +53,6 @@ def pytest_addoption(parser, pluginmanager): parser.addini( "asyncio_mode", help="default value for --asyncio-mode", - type="string", default="legacy", ) From 7cd0e6c924996aa39c407ef473560a0276da18f7 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Fri, 14 Jan 2022 15:56:24 +0200 Subject: [PATCH 02/11] Fix issue number --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 81a81d0a..bb708bc2 100644 --- a/README.rst +++ b/README.rst @@ -259,7 +259,7 @@ Changelog 0.17.1 (UNRELEASED) ~~~~~~~~~~~~~~~~~~~ - Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_ -- Relax ``asyncio_mode`` type definition; it allows to support pytest 5.4+. `#263 `_ +- Relax ``asyncio_mode`` type definition; it allows to support pytest 5.4+. `#262 `_ 0.17.0 (22-01-13) ~~~~~~~~~~~~~~~~~~~ From 1b18f0307f30a2b0cb894304520a66d879d09f39 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 13:33:24 +0200 Subject: [PATCH 03/11] Bump minimal supported pytest version --- .github/workflows/main.yml | 1 + setup.cfg | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1728d40f..a2e6d0b4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,6 +56,7 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] + pytest-version: [6.1.0, 7.0.0rc1] steps: - uses: actions/checkout@v2 diff --git a/setup.cfg b/setup.cfg index 7be86f36..96662047 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,7 @@ setup_requires = setuptools_scm >= 6.2 install_requires = - pytest >= 5.4.0 + pytest >= 6.1.0 [options.extras_require] testing = From cea853a443a20fa57ee9f42ae5e11fd007913865 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:00:54 +0200 Subject: [PATCH 04/11] Add pytest-min config --- .github/workflows/main.yml | 1 - tox.ini | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a2e6d0b4..1728d40f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,7 +56,6 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] - pytest-version: [6.1.0, 7.0.0rc1] steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index 0092b03e..f150b0a9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.14.0 -envlist = py37, py38, py39, py310, lint, version-info +envlist = py37, py38, py39, py310, lint, version-info, pytest-min skip_missing_interpreters = true passenv = CI @@ -11,6 +11,14 @@ commands = make test allowlist_externals = make +[testenv:pytest-min] +extras = testing +deps = + pytest == 6.1.0 +commands = make test +allowlist_externals = + make + [testenv:lint] skip_install = true basepython = python3.9 @@ -37,7 +45,7 @@ commands = [gh-actions] python = - 3.7: py37 + 3.7: py37, pytest-min 3.8: py38 3.9: py39, lint 3.10: py310 From 9a85da946beb79b0e7efd4ae47b2cccc8ee8606b Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:09:56 +0200 Subject: [PATCH 05/11] Replace pytester with older testdir fixture --- tests/hypothesis/test_base.py | 14 ++++++++------ tests/modes/test_auto_mode.py | 28 ++++++++++++++-------------- tests/modes/test_legacy_mode.py | 28 ++++++++++++++-------------- tests/modes/test_strict_mode.py | 22 +++++++++++----------- tests/test_flaky_integration.py | 8 ++++---- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/tests/hypothesis/test_base.py b/tests/hypothesis/test_base.py index fbee75bf..959b7483 100644 --- a/tests/hypothesis/test_base.py +++ b/tests/hypothesis/test_base.py @@ -7,6 +7,8 @@ import pytest from hypothesis import given, strategies as st +pytest_plugins = "testdir" + @pytest.fixture(scope="module") def event_loop(): @@ -43,8 +45,8 @@ async def test_can_use_fixture_provided_event_loop(event_loop, n): await semaphore.acquire() -def test_async_auto_marked(pytester): - pytester.makepyfile( +def test_async_auto_marked(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -60,13 +62,13 @@ async def test_hypothesis(n: int): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) -def test_sync_not_auto_marked(pytester): +def test_sync_not_auto_marked(testdir): """Assert that synchronous Hypothesis functions are not marked with asyncio""" - pytester.makepyfile( + testdir.makepyfile( dedent( """\ import asyncio @@ -84,5 +86,5 @@ def test_hypothesis(request, n: int): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) diff --git a/tests/modes/test_auto_mode.py b/tests/modes/test_auto_mode.py index 980b0b04..64723576 100644 --- a/tests/modes/test_auto_mode.py +++ b/tests/modes/test_auto_mode.py @@ -1,10 +1,10 @@ from textwrap import dedent -pytest_plugins = "pytester" +pytest_plugins = "testdir" -def test_auto_mode_cmdline(pytester): - pytester.makepyfile( +def test_auto_mode_cmdline(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -17,12 +17,12 @@ async def test_a(): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) -def test_auto_mode_cfg(pytester): - pytester.makepyfile( +def test_auto_mode_cfg(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -35,13 +35,13 @@ async def test_a(): """ ) ) - pytester.makefile(".ini", pytest="[pytest]\nasyncio_mode = auto\n") - result = pytester.runpytest() + testdir.makefile(".ini", pytest="[pytest]\nasyncio_mode = auto\n") + result = testdir.runpytest() result.assert_outcomes(passed=1) -def test_auto_mode_async_fixture(pytester): - pytester.makepyfile( +def test_auto_mode_async_fixture(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -60,12 +60,12 @@ async def test_a(fixture_a): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) -def test_auto_mode_method_fixture(pytester): - pytester.makepyfile( +def test_auto_mode_method_fixture(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -87,5 +87,5 @@ async def test_a(self, fixture_a): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) diff --git a/tests/modes/test_legacy_mode.py b/tests/modes/test_legacy_mode.py index df9c2cb6..ca423a08 100644 --- a/tests/modes/test_legacy_mode.py +++ b/tests/modes/test_legacy_mode.py @@ -1,6 +1,6 @@ from textwrap import dedent -pytest_plugins = "pytester" +pytest_plugins = "testdir" LEGACY_MODE = ( @@ -18,8 +18,8 @@ ).format(name="*") -def test_warning_for_legacy_mode_cmdline(pytester): - pytester.makepyfile( +def test_warning_for_legacy_mode_cmdline(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -33,13 +33,13 @@ async def test_a(): """ ) ) - result = pytester.runpytest("--asyncio-mode=legacy") + result = testdir.runpytest("--asyncio-mode=legacy") assert result.parseoutcomes()["warnings"] == 1 result.stdout.fnmatch_lines(["*" + LEGACY_MODE + "*"]) -def test_warning_for_legacy_mode_cfg(pytester): - pytester.makepyfile( +def test_warning_for_legacy_mode_cfg(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -53,15 +53,15 @@ async def test_a(): """ ) ) - pytester.makefile(".ini", pytest="[pytest]\nasyncio_mode = legacy\n") - result = pytester.runpytest() + testdir.makefile(".ini", pytest="[pytest]\nasyncio_mode = legacy\n") + result = testdir.runpytest() assert result.parseoutcomes()["warnings"] == 1 result.stdout.fnmatch_lines(["*" + LEGACY_MODE + "*"]) result.stdout.no_fnmatch_line("*" + LEGACY_ASYNCIO_FIXTURE + "*") -def test_warning_for_legacy_fixture(pytester): - pytester.makepyfile( +def test_warning_for_legacy_fixture(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -81,13 +81,13 @@ async def test_a(fixture_a): """ ) ) - result = pytester.runpytest("--asyncio-mode=legacy") + result = testdir.runpytest("--asyncio-mode=legacy") assert result.parseoutcomes()["warnings"] == 2 result.stdout.fnmatch_lines(["*" + LEGACY_ASYNCIO_FIXTURE + "*"]) -def test_warning_for_legacy_method_fixture(pytester): - pytester.makepyfile( +def test_warning_for_legacy_method_fixture(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -110,6 +110,6 @@ async def test_a(self, fixture_a): """ ) ) - result = pytester.runpytest("--asyncio-mode=legacy") + result = testdir.runpytest("--asyncio-mode=legacy") assert result.parseoutcomes()["warnings"] == 2 result.stdout.fnmatch_lines(["*" + LEGACY_ASYNCIO_FIXTURE + "*"]) diff --git a/tests/modes/test_strict_mode.py b/tests/modes/test_strict_mode.py index 7b574012..1b59b298 100644 --- a/tests/modes/test_strict_mode.py +++ b/tests/modes/test_strict_mode.py @@ -1,10 +1,10 @@ from textwrap import dedent -pytest_plugins = "pytester" +pytest_plugins = "testdir" -def test_strict_mode_cmdline(pytester): - pytester.makepyfile( +def test_strict_mode_cmdline(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -18,12 +18,12 @@ async def test_a(): """ ) ) - result = pytester.runpytest("--asyncio-mode=strict") + result = testdir.runpytest("--asyncio-mode=strict") result.assert_outcomes(passed=1) -def test_strict_mode_cfg(pytester): - pytester.makepyfile( +def test_strict_mode_cfg(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -37,13 +37,13 @@ async def test_a(): """ ) ) - pytester.makefile(".ini", pytest="[pytest]\nasyncio_mode = strict\n") - result = pytester.runpytest() + testdir.makefile(".ini", pytest="[pytest]\nasyncio_mode = strict\n") + result = testdir.runpytest() result.assert_outcomes(passed=1) -def test_strict_mode_method_fixture(pytester): - pytester.makepyfile( +def test_strict_mode_method_fixture(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -66,5 +66,5 @@ async def test_a(self, fixture_a): """ ) ) - result = pytester.runpytest("--asyncio-mode=auto") + result = testdir.runpytest("--asyncio-mode=auto") result.assert_outcomes(passed=1) diff --git a/tests/test_flaky_integration.py b/tests/test_flaky_integration.py index 2e551aad..b5abd91e 100644 --- a/tests/test_flaky_integration.py +++ b/tests/test_flaky_integration.py @@ -4,11 +4,11 @@ from textwrap import dedent -pytest_plugins = "pytester" +pytest_plugins = "testdir" -def test_auto_mode_cmdline(pytester): - pytester.makepyfile( +def test_auto_mode_cmdline(testdir): + testdir.makepyfile( dedent( """\ import asyncio @@ -29,7 +29,7 @@ async def test_asyncio_flaky_thing_that_fails_then_succeeds(): ) # runpytest_subprocess() is required to don't pollute the output # with flaky restart information - result = pytester.runpytest_subprocess() + result = testdir.runpytest_subprocess("--asyncio-mode=strict") result.assert_outcomes(passed=1) result.stdout.fnmatch_lines( [ From 10c547ef1d53195d1582b91225df4b156841b65b Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:14:56 +0200 Subject: [PATCH 06/11] Fix --- tests/hypothesis/test_base.py | 2 -- tests/modes/test_auto_mode.py | 2 -- tests/modes/test_legacy_mode.py | 3 --- tests/modes/test_strict_mode.py | 2 -- tests/test_flaky_integration.py | 4 ---- 5 files changed, 13 deletions(-) diff --git a/tests/hypothesis/test_base.py b/tests/hypothesis/test_base.py index 959b7483..e6da3427 100644 --- a/tests/hypothesis/test_base.py +++ b/tests/hypothesis/test_base.py @@ -7,8 +7,6 @@ import pytest from hypothesis import given, strategies as st -pytest_plugins = "testdir" - @pytest.fixture(scope="module") def event_loop(): diff --git a/tests/modes/test_auto_mode.py b/tests/modes/test_auto_mode.py index 64723576..157ffded 100644 --- a/tests/modes/test_auto_mode.py +++ b/tests/modes/test_auto_mode.py @@ -1,7 +1,5 @@ from textwrap import dedent -pytest_plugins = "testdir" - def test_auto_mode_cmdline(testdir): testdir.makepyfile( diff --git a/tests/modes/test_legacy_mode.py b/tests/modes/test_legacy_mode.py index ca423a08..12d4afe1 100644 --- a/tests/modes/test_legacy_mode.py +++ b/tests/modes/test_legacy_mode.py @@ -1,8 +1,5 @@ from textwrap import dedent -pytest_plugins = "testdir" - - LEGACY_MODE = ( "The 'asyncio_mode' default value will change to 'strict' in future, " "please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' " diff --git a/tests/modes/test_strict_mode.py b/tests/modes/test_strict_mode.py index 1b59b298..3b6487c7 100644 --- a/tests/modes/test_strict_mode.py +++ b/tests/modes/test_strict_mode.py @@ -1,7 +1,5 @@ from textwrap import dedent -pytest_plugins = "testdir" - def test_strict_mode_cmdline(testdir): testdir.makepyfile( diff --git a/tests/test_flaky_integration.py b/tests/test_flaky_integration.py index b5abd91e..54c9d2ea 100644 --- a/tests/test_flaky_integration.py +++ b/tests/test_flaky_integration.py @@ -1,11 +1,7 @@ """Tests for the Flaky integration, which retries failed tests. """ - - from textwrap import dedent -pytest_plugins = "testdir" - def test_auto_mode_cmdline(testdir): testdir.makepyfile( From a81f87c1774f327d9ad9e5206109b6e1125b1a01 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:19:28 +0200 Subject: [PATCH 07/11] Fix conftest --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 03fd33f2..4aa8c89a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,8 @@ import pytest +pytest_plugins = "pytester" + @pytest.fixture def dependent_fixture(event_loop): From a92dd7319a9be007b39f803bd90c2f8760077de7 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:20:44 +0200 Subject: [PATCH 08/11] Fix required pytest version --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index f150b0a9..b2bec5ba 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,8 @@ passenv = [testenv] extras = testing +deps = + pytest == 6.2.5 # required for Python 3.10, not bad for others commands = make test allowlist_externals = make From c150f80e3343138ccb71eed05063fb59c222c14a Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 15:12:26 +0200 Subject: [PATCH 09/11] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2497af54..2b8ee961 100644 --- a/README.rst +++ b/README.rst @@ -260,7 +260,7 @@ Changelog ~~~~~~~~~~~~~~~~~~~ - Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_ - Fixed a bug that closes the default event loop if the loop doesn't exist `#257 `_ -- Relax ``asyncio_mode`` type definition; it allows to support pytest 5.4+. `#262 `_ +- Relax ``asyncio_mode`` type definition; it allows to support pytest 6.1+. `#262 `_ 0.17.0 (22-01-13) ~~~~~~~~~~~~~~~~~~~ From 7d09fd9cd6c6e103f6b727563005ef3da83eed4d Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sat, 15 Jan 2022 14:59:09 +0200 Subject: [PATCH 10/11] Provide typing info (#260) --- Makefile | 5 +- README.rst | 1 + pytest_asyncio/plugin.py | 166 ++++++++++++++++++++++++++++++--------- pytest_asyncio/py.typed | 0 setup.cfg | 3 + tox.ini | 3 +- 6 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 pytest_asyncio/py.typed diff --git a/Makefile b/Makefile index 0817a0e7..2b0216f9 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,11 @@ clean-test: ## remove test and coverage artifacts lint: # CI env-var is set by GitHub actions ifdef CI - pre-commit run --all-files --show-diff-on-failure + python -m pre_commit run --all-files --show-diff-on-failure else - pre-commit run --all-files + python -m pre_commit run --all-files endif + python -m mypy pytest_asyncio --show-error-codes test: coverage run -m pytest tests diff --git a/README.rst b/README.rst index 2b8ee961..7f9df6f6 100644 --- a/README.rst +++ b/README.rst @@ -261,6 +261,7 @@ Changelog - Fixes a bug that prevents async Hypothesis tests from working without explicit ``asyncio`` marker when ``--asyncio-mode=auto`` is set. `#258 `_ - Fixed a bug that closes the default event loop if the loop doesn't exist `#257 `_ - Relax ``asyncio_mode`` type definition; it allows to support pytest 6.1+. `#262 `_ +- Added type annotations. `#198 `_ 0.17.0 (22-01-13) ~~~~~~~~~~~~~~~~~~~ diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 8c6fa4fc..a32dd6f4 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -6,8 +6,45 @@ import inspect import socket import warnings +from typing import ( + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + TypeVar, + Union, + cast, + overload, +) import pytest +from typing_extensions import Literal + +_R = TypeVar("_R") + +_ScopeName = Literal["session", "package", "module", "class", "function"] +_T = TypeVar("_T") + +SimpleFixtureFunction = TypeVar( + "SimpleFixtureFunction", bound=Callable[..., Awaitable[_R]] +) +FactoryFixtureFunction = TypeVar( + "FactoryFixtureFunction", bound=Callable[..., AsyncIterator[_R]] +) +FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction] +FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction] + +Config = Any # pytest < 7.0 +PytestPluginManager = Any # pytest < 7.0 +FixtureDef = Any # pytest < 7.0 +Parser = Any # pytest < 7.0 +SubRequest = Any # pytest < 7.0 class Mode(str, enum.Enum): @@ -41,7 +78,7 @@ class Mode(str, enum.Enum): """ -def pytest_addoption(parser, pluginmanager): +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: group = parser.getgroup("asyncio") group.addoption( "--asyncio-mode", @@ -57,7 +94,45 @@ def pytest_addoption(parser, pluginmanager): ) -def fixture(fixture_function=None, **kwargs): +@overload +def fixture( + fixture_function: FixtureFunction, + *, + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + name: Optional[str] = ..., +) -> FixtureFunction: + ... + + +@overload +def fixture( + fixture_function: None = ..., + *, + scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + name: Optional[str] = None, +) -> FixtureFunctionMarker: + ... + + +def fixture( + fixture_function: Optional[FixtureFunction] = None, **kwargs: Any +) -> Union[FixtureFunction, FixtureFunctionMarker]: if fixture_function is not None: _set_explicit_asyncio_mark(fixture_function) return pytest.fixture(fixture_function, **kwargs) @@ -65,41 +140,41 @@ def fixture(fixture_function=None, **kwargs): else: @functools.wraps(fixture) - def inner(fixture_function): + def inner(fixture_function: FixtureFunction) -> FixtureFunction: return fixture(fixture_function, **kwargs) return inner -def _has_explicit_asyncio_mark(obj): +def _has_explicit_asyncio_mark(obj: Any) -> bool: obj = getattr(obj, "__func__", obj) # instance method maybe? return getattr(obj, "_force_asyncio_fixture", False) -def _set_explicit_asyncio_mark(obj): +def _set_explicit_asyncio_mark(obj: Any) -> None: if hasattr(obj, "__func__"): # instance method, check the function object obj = obj.__func__ obj._force_asyncio_fixture = True -def _is_coroutine(obj): +def _is_coroutine(obj: Any) -> bool: """Check to see if an object is really an asyncio coroutine.""" return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj) -def _is_coroutine_or_asyncgen(obj): +def _is_coroutine_or_asyncgen(obj: Any) -> bool: return _is_coroutine(obj) or inspect.isasyncgenfunction(obj) -def _get_asyncio_mode(config): +def _get_asyncio_mode(config: Config) -> Mode: val = config.getoption("asyncio_mode") if val is None: val = config.getini("asyncio_mode") return Mode(val) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: """Inject documentation.""" config.addinivalue_line( "markers", @@ -112,10 +187,14 @@ def pytest_configure(config): @pytest.mark.tryfirst -def pytest_pycollect_makeitem(collector, name, obj): +def pytest_pycollect_makeitem( + collector: Union[pytest.Module, pytest.Class], name: str, obj: object +) -> Union[ + None, pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]] +]: """A pytest hook to collect asyncio coroutines.""" if not collector.funcnamefilter(name): - return + return None if ( _is_coroutine(obj) or _is_hypothesis_test(obj) @@ -130,10 +209,11 @@ def pytest_pycollect_makeitem(collector, name, obj): ret = list(collector._genfunctions(name, obj)) for elem in ret: elem.add_marker("asyncio") - return ret + return ret # type: ignore[return-value] + return None -def _hypothesis_test_wraps_coroutine(function): +def _hypothesis_test_wraps_coroutine(function: Any) -> bool: return _is_coroutine(function.hypothesis.inner_test) @@ -143,11 +223,11 @@ class FixtureStripper: REQUEST = "request" EVENT_LOOP = "event_loop" - def __init__(self, fixturedef): + def __init__(self, fixturedef: FixtureDef) -> None: self.fixturedef = fixturedef - self.to_strip = set() + self.to_strip: Set[str] = set() - def add(self, name): + def add(self, name: str) -> None: """Add fixture name to fixturedef and record in to_strip list (If not previously included)""" if name in self.fixturedef.argnames: @@ -155,7 +235,7 @@ def add(self, name): self.fixturedef.argnames += (name,) self.to_strip.add(name) - def get_and_strip_from(self, name, data_dict): + def get_and_strip_from(self, name: str, data_dict: Dict[str, _T]) -> _T: """Strip name from data, and return value""" result = data_dict[name] if name in self.to_strip: @@ -164,7 +244,7 @@ def get_and_strip_from(self, name, data_dict): @pytest.hookimpl(trylast=True) -def pytest_fixture_post_finalizer(fixturedef, request): +def pytest_fixture_post_finalizer(fixturedef: FixtureDef, request: SubRequest) -> None: """Called after fixture teardown""" if fixturedef.argname == "event_loop": policy = asyncio.get_event_loop_policy() @@ -181,7 +261,9 @@ def pytest_fixture_post_finalizer(fixturedef, request): @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef, request): +def pytest_fixture_setup( + fixturedef: FixtureDef, request: SubRequest +) -> Optional[object]: """Adjust the event loop policy when an event loop is produced.""" if fixturedef.argname == "event_loop": outcome = yield @@ -294,7 +376,7 @@ async def setup(): @pytest.hookimpl(tryfirst=True, hookwrapper=True) -def pytest_pyfunc_call(pyfuncitem): +def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Optional[object]: """ Pytest hook called before a test case is run. @@ -302,31 +384,35 @@ def pytest_pyfunc_call(pyfuncitem): where the wrapped test coroutine is executed in an event loop. """ if "asyncio" in pyfuncitem.keywords: + funcargs: Dict[str, object] = pyfuncitem.funcargs # type: ignore[name-defined] + loop = cast(asyncio.AbstractEventLoop, funcargs["event_loop"]) if _is_hypothesis_test(pyfuncitem.obj): pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( pyfuncitem.obj.hypothesis.inner_test, - _loop=pyfuncitem.funcargs["event_loop"], + _loop=loop, ) else: pyfuncitem.obj = wrap_in_sync( - pyfuncitem.obj, _loop=pyfuncitem.funcargs["event_loop"] + pyfuncitem.obj, + _loop=loop, ) yield -def _is_hypothesis_test(function) -> bool: +def _is_hypothesis_test(function: Any) -> bool: return getattr(function, "is_hypothesis_test", False) -def wrap_in_sync(func, _loop): +def wrap_in_sync(func: Callable[..., Awaitable[Any]], _loop: asyncio.AbstractEventLoop): """Return a sync wrapper around an async function executing it in the current event loop.""" # if the function is already wrapped, we rewrap using the original one # not using __wrapped__ because the original function may already be # a wrapped one - if hasattr(func, "_raw_test_func"): - func = func._raw_test_func + raw_func = getattr(func, "_raw_test_func", None) + if raw_func is not None: + func = raw_func @functools.wraps(func) def inner(**kwargs): @@ -343,20 +429,22 @@ def inner(**kwargs): task.exception() raise - inner._raw_test_func = func + inner._raw_test_func = func # type: ignore[attr-defined] return inner -def pytest_runtest_setup(item): +def pytest_runtest_setup(item: pytest.Item) -> None: if "asyncio" in item.keywords: + fixturenames = item.fixturenames # type: ignore[attr-defined] # inject an event loop fixture for all async tests - if "event_loop" in item.fixturenames: - item.fixturenames.remove("event_loop") - item.fixturenames.insert(0, "event_loop") + if "event_loop" in fixturenames: + fixturenames.remove("event_loop") + fixturenames.insert(0, "event_loop") + obj = item.obj # type: ignore[attr-defined] if ( item.get_closest_marker("asyncio") is not None - and not getattr(item.obj, "hypothesis", False) - and getattr(item.obj, "is_hypothesis_test", False) + and not getattr(obj, "hypothesis", False) + and getattr(obj, "is_hypothesis_test", False) ): pytest.fail( "test function `%r` is using Hypothesis, but pytest-asyncio " @@ -365,14 +453,14 @@ def pytest_runtest_setup(item): @pytest.fixture -def event_loop(request): +def event_loop(request: pytest.FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: """Create an instance of the default event loop for each test case.""" loop = asyncio.get_event_loop_policy().new_event_loop() yield loop loop.close() -def _unused_port(socket_type): +def _unused_port(socket_type: int) -> int: """Find an unused localhost port from 1024-65535 and return it.""" with contextlib.closing(socket.socket(type=socket_type)) as sock: sock.bind(("127.0.0.1", 0)) @@ -380,17 +468,17 @@ def _unused_port(socket_type): @pytest.fixture -def unused_tcp_port(): +def unused_tcp_port() -> int: return _unused_port(socket.SOCK_STREAM) @pytest.fixture -def unused_udp_port(): +def unused_udp_port() -> int: return _unused_port(socket.SOCK_DGRAM) @pytest.fixture(scope="session") -def unused_tcp_port_factory(): +def unused_tcp_port_factory() -> Callable[[], int]: """A factory function, producing different unused TCP ports.""" produced = set() @@ -409,7 +497,7 @@ def factory(): @pytest.fixture(scope="session") -def unused_udp_port_factory(): +def unused_udp_port_factory() -> Callable[[], int]: """A factory function, producing different unused UDP ports.""" produced = set() diff --git a/pytest_asyncio/py.typed b/pytest_asyncio/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/setup.cfg b/setup.cfg index 96662047..f05526bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ classifiers = Framework :: AsyncIO Framework :: Pytest + Typing :: Typed [options] python_requires = >=3.7 @@ -38,12 +39,14 @@ setup_requires = install_requires = pytest >= 6.1.0 + typing-extensions >= 4.0 [options.extras_require] testing = coverage==6.2 hypothesis >= 5.7.1 flaky >= 3.5.0 + mypy == 0.931 [options.entry_points] pytest11 = diff --git a/tox.ini b/tox.ini index b2bec5ba..574ea6d4 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,8 @@ allowlist_externals = [testenv:lint] skip_install = true -basepython = python3.9 +basepython = python3.10 +extras = testing deps = pre-commit == 2.16.0 commands = From 2cd678c780efccb187a761edfd6667748b8d1f12 Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 16 Jan 2022 14:30:28 +0200 Subject: [PATCH 11/11] Restore working on pytest 6.1 --- pytest_asyncio/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index dfcf471c..7a7c8dd5 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -460,7 +460,7 @@ def pytest_runtest_setup(item: pytest.Item) -> None: @pytest.fixture -def event_loop(request: pytest.FixtureRequest) -> Iterator[asyncio.AbstractEventLoop]: +def event_loop(request: "pytest.FixtureRequest") -> Iterator[asyncio.AbstractEventLoop]: """Create an instance of the default event loop for each test case.""" loop = asyncio.get_event_loop_policy().new_event_loop() yield loop