Skip to content

Commit fc6d6cf

Browse files
bluetechseifertm
andauthored
Fix typing and update to mypy 1.8.0 (#769)
* [refactor] Fix typing and update to mypy 1.8.0 * [tests] Change tests/ into a package This allows mypy to distinguish between the different conftests.py files when running `mypy tests/`, and it's also preferable in general. * [refactor] Added assert statement instead of "type ignore" pragmas. Signed-off-by: Michael Seifert <[email protected]> * [docs] Add TODO for more specific return types once support for pytest 7 has been dropped. Signed-off-by: Michael Seifert <[email protected]> * [docs] Add changelog entry. Signed-off-by: Michael Seifert <[email protected]> --------- Signed-off-by: Michael Seifert <[email protected]> Co-authored-by: Michael Seifert <[email protected]>
1 parent 6008cf1 commit fc6d6cf

File tree

9 files changed

+42
-24
lines changed

9 files changed

+42
-24
lines changed

.pre-commit-config.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ repos:
3737
- id: check-yaml
3838
- id: debug-statements
3939
- repo: https://github.com/pre-commit/mirrors-mypy
40-
rev: v0.991
40+
rev: v1.8.0
4141
hooks:
4242
- id: mypy
4343
exclude: ^(docs|tests)/.*
44+
additional_dependencies:
45+
- pytest
4446
- repo: https://github.com/pycqa/flake8
4547
rev: 6.1.0
4648
hooks:

docs/source/reference/changelog.rst

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Changelog
55
0.23.5 (UNRELEASED)
66
===================
77
- Declare compatibility with pytest 8 `#737 <https://github.com/pytest-dev/pytest-asyncio/issues/737>`_
8+
- Fix typing errors with recent versions of mypy `#769 <https://github.com/pytest-dev/pytest-asyncio/issues/769>`_
89

910
Known issues
1011
------------

pytest_asyncio/plugin.py

+37-22
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616
Awaitable,
1717
Callable,
1818
Dict,
19+
Generator,
1920
Iterable,
2021
Iterator,
2122
List,
2223
Literal,
24+
Mapping,
2325
Optional,
26+
Sequence,
2427
Set,
2528
Type,
2629
TypeVar,
@@ -47,16 +50,14 @@
4750
StashKey,
4851
)
4952

50-
_R = TypeVar("_R")
51-
5253
_ScopeName = Literal["session", "package", "module", "class", "function"]
5354
_T = TypeVar("_T")
5455

5556
SimpleFixtureFunction = TypeVar(
56-
"SimpleFixtureFunction", bound=Callable[..., Awaitable[_R]]
57+
"SimpleFixtureFunction", bound=Callable[..., Awaitable[object]]
5758
)
5859
FactoryFixtureFunction = TypeVar(
59-
"FactoryFixtureFunction", bound=Callable[..., AsyncIterator[_R]]
60+
"FactoryFixtureFunction", bound=Callable[..., AsyncIterator[object]]
6061
)
6162
FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction]
6263
FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction]
@@ -204,6 +205,7 @@ def _preprocess_async_fixtures(
204205
config = collector.config
205206
asyncio_mode = _get_asyncio_mode(config)
206207
fixturemanager = config.pluginmanager.get_plugin("funcmanage")
208+
assert fixturemanager is not None
207209
for fixtures in fixturemanager._arg2fixturedefs.values():
208210
for fixturedef in fixtures:
209211
func = fixturedef.func
@@ -217,11 +219,13 @@ def _preprocess_async_fixtures(
217219
continue
218220
scope = fixturedef.scope
219221
if scope == "function":
220-
event_loop_fixture_id = "event_loop"
222+
event_loop_fixture_id: Optional[str] = "event_loop"
221223
else:
222224
event_loop_node = _retrieve_scope_root(collector, scope)
223225
event_loop_fixture_id = event_loop_node.stash.get(
224-
_event_loop_fixture_id, None
226+
# Type ignored because of non-optimal mypy inference.
227+
_event_loop_fixture_id, # type: ignore[arg-type]
228+
None,
225229
)
226230
_make_asyncio_fixture_function(func)
227231
function_signature = inspect.signature(func)
@@ -234,8 +238,15 @@ def _preprocess_async_fixtures(
234238
f"instead."
235239
)
236240
)
237-
_inject_fixture_argnames(fixturedef, event_loop_fixture_id)
238-
_synchronize_async_fixture(fixturedef, event_loop_fixture_id)
241+
assert event_loop_fixture_id
242+
_inject_fixture_argnames(
243+
fixturedef,
244+
event_loop_fixture_id,
245+
)
246+
_synchronize_async_fixture(
247+
fixturedef,
248+
event_loop_fixture_id,
249+
)
239250
assert _is_asyncio_fixture_function(fixturedef.func)
240251
processed_fixturedefs.add(fixturedef)
241252

@@ -512,25 +523,26 @@ def pytest_pycollect_makeitem_preprocess_async_fixtures(
512523
return None
513524

514525

526+
# TODO: #778 Narrow down return type of function when dropping support for pytest 7
515527
# The function name needs to start with "pytest_"
516528
# see https://github.com/pytest-dev/pytest/issues/11307
517529
@pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True)
518530
def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
519531
collector: Union[pytest.Module, pytest.Class], name: str, obj: object
520-
) -> Union[
521-
pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None
522-
]:
532+
) -> Generator[None, Any, None]:
523533
"""
524534
Converts coroutines and async generators collected as pytest.Functions
525535
to AsyncFunction items.
526536
"""
527537
hook_result = yield
528-
node_or_list_of_nodes = hook_result.get_result()
538+
node_or_list_of_nodes: Union[
539+
pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None
540+
] = hook_result.get_result()
529541
if not node_or_list_of_nodes:
530542
return
531-
try:
543+
if isinstance(node_or_list_of_nodes, Sequence):
532544
node_iterator = iter(node_or_list_of_nodes)
533-
except TypeError:
545+
else:
534546
# Treat single node as a single-element iterable
535547
node_iterator = iter((node_or_list_of_nodes,))
536548
updated_node_collection = []
@@ -549,8 +561,8 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
549561
hook_result.force_result(updated_node_collection)
550562

551563

552-
_event_loop_fixture_id = StashKey[str]
553-
_fixture_scope_by_collector_type = {
564+
_event_loop_fixture_id = StashKey[str]()
565+
_fixture_scope_by_collector_type: Mapping[Type[pytest.Collector], _ScopeName] = {
554566
Class: "class",
555567
# Package is a subclass of module and the dict is used in isinstance checks
556568
# Therefore, the order matters and Package needs to appear before Module
@@ -565,7 +577,7 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
565577

566578

567579
@pytest.hookimpl
568-
def pytest_collectstart(collector: pytest.Collector):
580+
def pytest_collectstart(collector: pytest.Collector) -> None:
569581
try:
570582
collector_scope = next(
571583
scope
@@ -639,8 +651,8 @@ def _patched_collect():
639651
pass
640652
return collector.__original_collect()
641653

642-
collector.__original_collect = collector.collect
643-
collector.collect = _patched_collect
654+
collector.__original_collect = collector.collect # type: ignore[attr-defined]
655+
collector.collect = _patched_collect # type: ignore[method-assign]
644656
elif isinstance(collector, Class):
645657
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop
646658

@@ -708,6 +720,7 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
708720
if event_loop_fixture_id in metafunc.fixturenames:
709721
return
710722
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
723+
assert fixturemanager is not None
711724
if "event_loop" in metafunc.fixturenames:
712725
raise MultipleEventLoopsRequestedError(
713726
_MULTIPLE_LOOPS_REQUESTED_ERROR.format(
@@ -726,10 +739,11 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
726739
)
727740

728741

742+
# TODO: #778 Narrow down return type of function when dropping support for pytest 7
729743
@pytest.hookimpl(hookwrapper=True)
730744
def pytest_fixture_setup(
731-
fixturedef: FixtureDef, request: SubRequest
732-
) -> Optional[object]:
745+
fixturedef: FixtureDef,
746+
) -> Generator[None, Any, None]:
733747
"""Adjust the event loop policy when an event loop is produced."""
734748
if fixturedef.argname == "event_loop":
735749
# The use of a fixture finalizer is preferred over the
@@ -744,7 +758,7 @@ def pytest_fixture_setup(
744758
_provide_clean_event_loop,
745759
)
746760
outcome = yield
747-
loop = outcome.get_result()
761+
loop: asyncio.AbstractEventLoop = outcome.get_result()
748762
# Weird behavior was observed when checking for an attribute of FixtureDef.func
749763
# Instead, we now check for a special attribute of the returned event loop
750764
fixture_filename = inspect.getsourcefile(fixturedef.func)
@@ -946,6 +960,7 @@ def _retrieve_scope_root(item: Union[Collector, Item], scope: str) -> Collector:
946960
scope_root_type = node_type_by_scope[scope]
947961
for node in reversed(item.listchain()):
948962
if isinstance(node, scope_root_type):
963+
assert isinstance(node, pytest.Collector)
949964
return node
950965
error_message = (
951966
f"{item.name} is marked to be run in an event loop with scope {scope}, "

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ classifiers =
3535

3636
[options]
3737
python_requires = >=3.8
38-
packages = find:
38+
packages = pytest_asyncio
3939
include_package_data = True
4040

4141
# Always adjust requirements.txt and pytest-min-requirements.txt when changing runtime dependencies

tests/__init__.py

Whitespace-only changes.

tests/hypothesis/__init__.py

Whitespace-only changes.

tests/loop_fixture_scope/__init__.py

Whitespace-only changes.

tests/markers/__init__.py

Whitespace-only changes.

tests/modes/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)