Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix typing and update to mypy 1.8.0 #769

Merged
merged 5 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ repos:
- id: check-yaml
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.991
rev: v1.8.0
hooks:
- id: mypy
exclude: ^(docs|tests)/.*
additional_dependencies:
- pytest
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
Expand Down
1 change: 1 addition & 0 deletions docs/source/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog
0.23.5 (UNRELEASED)
===================
- Declare compatibility with pytest 8 `#737 <https://github.com/pytest-dev/pytest-asyncio/issues/737>`_
- Fix typing errors with recent versions of mypy `#769 <https://github.com/pytest-dev/pytest-asyncio/issues/769>`_

Known issues
------------
Expand Down
59 changes: 37 additions & 22 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
Awaitable,
Callable,
Dict,
Generator,
Iterable,
Iterator,
List,
Literal,
Mapping,
Optional,
Sequence,
Set,
Type,
TypeVar,
Expand All @@ -47,16 +50,14 @@
StashKey,
)

_R = TypeVar("_R")

_ScopeName = Literal["session", "package", "module", "class", "function"]
_T = TypeVar("_T")

SimpleFixtureFunction = TypeVar(
"SimpleFixtureFunction", bound=Callable[..., Awaitable[_R]]
"SimpleFixtureFunction", bound=Callable[..., Awaitable[object]]
seifertm marked this conversation as resolved.
Show resolved Hide resolved
)
FactoryFixtureFunction = TypeVar(
"FactoryFixtureFunction", bound=Callable[..., AsyncIterator[_R]]
"FactoryFixtureFunction", bound=Callable[..., AsyncIterator[object]]
)
FixtureFunction = Union[SimpleFixtureFunction, FactoryFixtureFunction]
FixtureFunctionMarker = Callable[[FixtureFunction], FixtureFunction]
Expand Down Expand Up @@ -204,6 +205,7 @@ def _preprocess_async_fixtures(
config = collector.config
asyncio_mode = _get_asyncio_mode(config)
fixturemanager = config.pluginmanager.get_plugin("funcmanage")
assert fixturemanager is not None
for fixtures in fixturemanager._arg2fixturedefs.values():
for fixturedef in fixtures:
func = fixturedef.func
Expand All @@ -217,11 +219,13 @@ def _preprocess_async_fixtures(
continue
scope = fixturedef.scope
if scope == "function":
event_loop_fixture_id = "event_loop"
event_loop_fixture_id: Optional[str] = "event_loop"
else:
event_loop_node = _retrieve_scope_root(collector, scope)
event_loop_fixture_id = event_loop_node.stash.get(
_event_loop_fixture_id, None
# Type ignored because of non-optimal mypy inference.
_event_loop_fixture_id, # type: ignore[arg-type]
None,
)
_make_asyncio_fixture_function(func)
function_signature = inspect.signature(func)
Expand All @@ -234,8 +238,15 @@ def _preprocess_async_fixtures(
f"instead."
)
)
_inject_fixture_argnames(fixturedef, event_loop_fixture_id)
_synchronize_async_fixture(fixturedef, event_loop_fixture_id)
assert event_loop_fixture_id
_inject_fixture_argnames(
fixturedef,
event_loop_fixture_id,
)
_synchronize_async_fixture(
fixturedef,
event_loop_fixture_id,
)
assert _is_asyncio_fixture_function(fixturedef.func)
processed_fixturedefs.add(fixturedef)

Expand Down Expand Up @@ -512,25 +523,26 @@ def pytest_pycollect_makeitem_preprocess_async_fixtures(
return None


# TODO: #778 Narrow down return type of function when dropping support for pytest 7
# The function name needs to start with "pytest_"
# see https://github.com/pytest-dev/pytest/issues/11307
@pytest.hookimpl(specname="pytest_pycollect_makeitem", hookwrapper=True)
def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
collector: Union[pytest.Module, pytest.Class], name: str, obj: object
) -> Union[
pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None
]:
) -> Generator[None, Any, None]:
seifertm marked this conversation as resolved.
Show resolved Hide resolved
"""
Converts coroutines and async generators collected as pytest.Functions
to AsyncFunction items.
"""
hook_result = yield
node_or_list_of_nodes = hook_result.get_result()
node_or_list_of_nodes: Union[
pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None
] = hook_result.get_result()
if not node_or_list_of_nodes:
return
try:
if isinstance(node_or_list_of_nodes, Sequence):
node_iterator = iter(node_or_list_of_nodes)
except TypeError:
else:
# Treat single node as a single-element iterable
node_iterator = iter((node_or_list_of_nodes,))
updated_node_collection = []
Expand All @@ -549,8 +561,8 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(
hook_result.force_result(updated_node_collection)


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


@pytest.hookimpl
def pytest_collectstart(collector: pytest.Collector):
def pytest_collectstart(collector: pytest.Collector) -> None:
try:
collector_scope = next(
scope
Expand Down Expand Up @@ -639,8 +651,8 @@ def _patched_collect():
pass
return collector.__original_collect()

collector.__original_collect = collector.collect
collector.collect = _patched_collect
collector.__original_collect = collector.collect # type: ignore[attr-defined]
collector.collect = _patched_collect # type: ignore[method-assign]
elif isinstance(collector, Class):
collector.obj.__pytest_asyncio_scoped_event_loop = scoped_event_loop

Expand Down Expand Up @@ -708,6 +720,7 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
if event_loop_fixture_id in metafunc.fixturenames:
return
fixturemanager = metafunc.config.pluginmanager.get_plugin("funcmanage")
assert fixturemanager is not None
if "event_loop" in metafunc.fixturenames:
raise MultipleEventLoopsRequestedError(
_MULTIPLE_LOOPS_REQUESTED_ERROR.format(
Expand All @@ -726,10 +739,11 @@ def pytest_generate_tests(metafunc: Metafunc) -> None:
)


# TODO: #778 Narrow down return type of function when dropping support for pytest 7
@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(
fixturedef: FixtureDef, request: SubRequest
) -> Optional[object]:
fixturedef: FixtureDef,
) -> Generator[None, Any, None]:
"""Adjust the event loop policy when an event loop is produced."""
if fixturedef.argname == "event_loop":
# The use of a fixture finalizer is preferred over the
Expand All @@ -744,7 +758,7 @@ def pytest_fixture_setup(
_provide_clean_event_loop,
)
outcome = yield
loop = outcome.get_result()
loop: asyncio.AbstractEventLoop = outcome.get_result()
# Weird behavior was observed when checking for an attribute of FixtureDef.func
# Instead, we now check for a special attribute of the returned event loop
fixture_filename = inspect.getsourcefile(fixturedef.func)
Expand Down Expand Up @@ -946,6 +960,7 @@ def _retrieve_scope_root(item: Union[Collector, Item], scope: str) -> Collector:
scope_root_type = node_type_by_scope[scope]
for node in reversed(item.listchain()):
if isinstance(node, scope_root_type):
assert isinstance(node, pytest.Collector)
return node
error_message = (
f"{item.name} is marked to be run in an event loop with scope {scope}, "
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ classifiers =

[options]
python_requires = >=3.8
packages = find:
packages = pytest_asyncio
include_package_data = True

# Always adjust requirements.txt and pytest-min-requirements.txt when changing runtime dependencies
Expand Down
Empty file added tests/__init__.py
Empty file.
Empty file added tests/hypothesis/__init__.py
Empty file.
Empty file.
Empty file added tests/markers/__init__.py
Empty file.
Empty file added tests/modes/__init__.py
Empty file.