Skip to content

[pre-commit] Add flake8-bugbear, pyupgrade pydocstyle to ruff #969

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

Merged
merged 4 commits into from
Oct 28, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,44 @@ write_to = "pytest_asyncio/_version.py"

[tool.ruff]
line-length = 88
format.docstring-code-format = true
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"W", # pycodestyle
"B", # bugbear
"D", # pydocstyle
"E", # pycodestyle
"F", # pyflakes
"FA100", # add future annotations
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
"PIE", # flake8-pie
"PLE", # pylint error
"PYI", # flake8-pyi
"RUF", # ruff
"T100", # flake8-debugger
"UP", # pyupgrade
"W", # pycodestyle
]

lint.ignore = [
# bugbear ignore
"B028", # No explicit `stacklevel` keyword argument found
# pydocstyle ignore
"D100", # Missing docstring in public module
"D101", # Missing docstring in public class
"D102", # Missing docstring in public method
"D103", # Missing docstring in public function
"D104", # Missing docstring in public package
"D105", # Missing docstring in magic method
"D106", # Missing docstring in public nested class
"D107", # Missing docstring in `__init__`
"D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible
"D205", # 1 blank line required between summary line and description
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
"D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible.
"D400", # First line should end with a period
"D401", # First line of docstring should be in imperative mood
"D402", # First line should not be the function's signature
"D404", # First word of the docstring should not be "This"
"D415", # First line should end with a period, question mark, or exclamation point
]

[tool.pytest.ini_options]
Expand Down
4 changes: 3 additions & 1 deletion pytest_asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""The main point for importing pytest-asyncio items."""

from ._version import version as __version__ # noqa
from __future__ import annotations

from ._version import version as __version__ # noqa: F401
from .plugin import fixture, is_async_test

__all__ = ("fixture", "is_async_test")
123 changes: 56 additions & 67 deletions pytest_asyncio/plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""pytest-asyncio implementation."""

from __future__ import annotations

import asyncio
import contextlib
import enum
Expand All @@ -8,23 +10,20 @@
import socket
import warnings
from asyncio import AbstractEventLoopPolicy
from textwrap import dedent
from typing import (
Any,
from collections.abc import (
AsyncIterator,
Awaitable,
Callable,
Dict,
Generator,
Iterable,
Iterator,
List,
Literal,
Mapping,
Optional,
Sequence,
Set,
Type,
)
from textwrap import dedent
from typing import (
Any,
Callable,
Literal,
TypeVar,
Union,
overload,
Expand Down Expand Up @@ -112,41 +111,41 @@ def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None
def fixture(
fixture_function: FixtureFunction,
*,
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
loop_scope: Union[_ScopeName, None] = ...,
params: Optional[Iterable[object]] = ...,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
loop_scope: _ScopeName | None = ...,
params: Iterable[object] | None = ...,
autouse: bool = ...,
ids: Union[
Iterable[Union[str, float, int, bool, None]],
Callable[[Any], Optional[object]],
None,
] = ...,
name: Optional[str] = ...,
ids: (
Iterable[str | float | int | bool | None]
| Callable[[Any], object | None]
| None
) = ...,
name: str | None = ...,
) -> FixtureFunction: ...


@overload
def fixture(
fixture_function: None = ...,
*,
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
loop_scope: Union[_ScopeName, None] = ...,
params: Optional[Iterable[object]] = ...,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
loop_scope: _ScopeName | None = ...,
params: Iterable[object] | None = ...,
autouse: bool = ...,
ids: Union[
Iterable[Union[str, float, int, bool, None]],
Callable[[Any], Optional[object]],
None,
] = ...,
name: Optional[str] = None,
ids: (
Iterable[str | float | int | bool | None]
| Callable[[Any], object | None]
| None
) = ...,
name: str | None = None,
) -> FixtureFunctionMarker: ...


def fixture(
fixture_function: Optional[FixtureFunction] = None,
loop_scope: Union[_ScopeName, None] = None,
fixture_function: FixtureFunction | None = None,
loop_scope: _ScopeName | None = None,
**kwargs: Any,
) -> Union[FixtureFunction, FixtureFunctionMarker]:
) -> FixtureFunction | FixtureFunctionMarker:
if fixture_function is not None:
_make_asyncio_fixture_function(fixture_function, loop_scope)
return pytest.fixture(fixture_function, **kwargs)
Expand All @@ -165,9 +164,7 @@ def _is_asyncio_fixture_function(obj: Any) -> bool:
return getattr(obj, "_force_asyncio_fixture", False)


def _make_asyncio_fixture_function(
obj: Any, loop_scope: Union[_ScopeName, None]
) -> None:
def _make_asyncio_fixture_function(obj: Any, loop_scope: _ScopeName | None) -> None:
if hasattr(obj, "__func__"):
# instance method, check the function object
obj = obj.__func__
Expand All @@ -185,11 +182,11 @@ def _get_asyncio_mode(config: Config) -> Mode:
val = config.getini("asyncio_mode")
try:
return Mode(val)
except ValueError:
except ValueError as e:
modes = ", ".join(m.value for m in Mode)
raise pytest.UsageError(
f"{val!r} is not a valid asyncio_mode. Valid modes: {modes}."
)
) from e


_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET = """\
Expand All @@ -215,7 +212,7 @@ def pytest_configure(config: Config) -> None:


@pytest.hookimpl(tryfirst=True)
def pytest_report_header(config: Config) -> List[str]:
def pytest_report_header(config: Config) -> list[str]:
"""Add asyncio config to pytest header."""
mode = _get_asyncio_mode(config)
default_loop_scope = config.getini("asyncio_default_fixture_loop_scope")
Expand All @@ -224,7 +221,7 @@ def pytest_report_header(config: Config) -> List[str]:

def _preprocess_async_fixtures(
collector: Collector,
processed_fixturedefs: Set[FixtureDef],
processed_fixturedefs: set[FixtureDef],
) -> None:
config = collector.config
default_loop_scope = config.getini("asyncio_default_fixture_loop_scope")
Expand Down Expand Up @@ -268,9 +265,7 @@ def _preprocess_async_fixtures(


def _synchronize_async_fixture(fixturedef: FixtureDef) -> None:
"""
Wraps the fixture function of an async fixture in a synchronous function.
"""
"""Wraps the fixture function of an async fixture in a synchronous function."""
if inspect.isasyncgenfunction(fixturedef.func):
_wrap_asyncgen_fixture(fixturedef)
elif inspect.iscoroutinefunction(fixturedef.func):
Expand All @@ -279,10 +274,10 @@ def _synchronize_async_fixture(fixturedef: FixtureDef) -> None:

def _add_kwargs(
func: Callable[..., Any],
kwargs: Dict[str, Any],
kwargs: dict[str, Any],
event_loop: asyncio.AbstractEventLoop,
request: FixtureRequest,
) -> Dict[str, Any]:
) -> dict[str, Any]:
sig = inspect.signature(func)
ret = kwargs.copy()
if "request" in sig.parameters:
Expand All @@ -292,7 +287,7 @@ def _add_kwargs(
return ret


def _perhaps_rebind_fixture_func(func: _T, instance: Optional[Any]) -> _T:
def _perhaps_rebind_fixture_func(func: _T, instance: Any | None) -> _T:
if instance is not None:
# The fixture needs to be bound to the actual request.instance
# so it is bound to the same object as the test method.
Expand Down Expand Up @@ -392,9 +387,7 @@ class PytestAsyncioFunction(Function):
"""Base class for all test functions managed by pytest-asyncio."""

@classmethod
def item_subclass_for(
cls, item: Function, /
) -> Union[Type["PytestAsyncioFunction"], None]:
def item_subclass_for(cls, item: Function, /) -> type[PytestAsyncioFunction] | None:
"""
Returns a subclass of PytestAsyncioFunction if there is a specialized subclass
for the specified function item.
Expand Down Expand Up @@ -522,17 +515,15 @@ def runtest(self) -> None:
super().runtest()


_HOLDER: Set[FixtureDef] = set()
_HOLDER: set[FixtureDef] = set()


# The function name needs to start with "pytest_"
# see https://github.com/pytest-dev/pytest/issues/11307
@pytest.hookimpl(specname="pytest_pycollect_makeitem", tryfirst=True)
def pytest_pycollect_makeitem_preprocess_async_fixtures(
collector: Union[pytest.Module, pytest.Class], name: str, obj: object
) -> Union[
pytest.Item, pytest.Collector, List[Union[pytest.Item, pytest.Collector]], None
]:
collector: pytest.Module | pytest.Class, name: str, obj: object
) -> pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None:
"""A pytest hook to collect asyncio coroutines."""
if not collector.funcnamefilter(name):
return None
Expand All @@ -544,20 +535,17 @@ def pytest_pycollect_makeitem_preprocess_async_fixtures(
# 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
collector: pytest.Module | pytest.Class, name: str, obj: object
) -> Generator[None, pluggy.Result, None]:
"""
Converts coroutines and async generators collected as pytest.Functions
to AsyncFunction items.
"""
hook_result = yield
try:
node_or_list_of_nodes: Union[
pytest.Item,
pytest.Collector,
List[Union[pytest.Item, pytest.Collector]],
None,
] = hook_result.get_result()
node_or_list_of_nodes: (
pytest.Item | pytest.Collector | list[pytest.Item | pytest.Collector] | None
) = hook_result.get_result()
except BaseException as e:
hook_result.force_exception(e)
return
Expand Down Expand Up @@ -585,7 +573,7 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(


_event_loop_fixture_id = StashKey[str]()
_fixture_scope_by_collector_type: Mapping[Type[pytest.Collector], _ScopeName] = {
_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 @@ -596,7 +584,7 @@ def pytest_pycollect_makeitem_convert_async_functions_to_subclass(

# A stack used to push package-scoped loops during collection of a package
# and pop those loops during collection of a Module
__package_loop_stack: List[Union[FixtureFunctionMarker, FixtureFunction]] = []
__package_loop_stack: list[FixtureFunctionMarker | FixtureFunction] = []


@pytest.hookimpl
Expand Down Expand Up @@ -872,7 +860,7 @@ def _provide_clean_event_loop() -> None:


def _get_event_loop_no_warn(
policy: Optional[AbstractEventLoopPolicy] = None,
policy: AbstractEventLoopPolicy | None = None,
) -> asyncio.AbstractEventLoop:
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
Expand All @@ -883,7 +871,7 @@ def _get_event_loop_no_warn(


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem: Function) -> Optional[object]:
def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
"""
Pytest hook called before a test case is run.

Expand All @@ -910,9 +898,10 @@ def pytest_pyfunc_call(pyfuncitem: Function) -> Optional[object]:
def wrap_in_sync(
func: Callable[..., Awaitable[Any]],
):
"""Return a sync wrapper around an async function executing it in the
current event loop."""

"""
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
Expand Down Expand Up @@ -1002,7 +991,7 @@ def _get_marked_loop_scope(asyncio_marker: Mark) -> _ScopeName:
return scope


def _retrieve_scope_root(item: Union[Collector, Item], scope: str) -> Collector:
def _retrieve_scope_root(item: Collector | Item, scope: str) -> Collector:
node_type_by_scope = {
"class": Class,
"module": Module,
Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_async_fixtures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import asyncio
import unittest.mock

Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_async_fixtures_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module-scoped too.
"""

from __future__ import annotations

import asyncio

import pytest
Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_async_fixtures_with_finalizer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import asyncio
import functools

Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_async_gen_fixtures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import unittest.mock

import pytest
Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_nested.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import asyncio

import pytest
Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_parametrized_loop.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from textwrap import dedent

from pytest import Pytester
Expand Down
2 changes: 2 additions & 0 deletions tests/async_fixtures/test_shared_module_fixture.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from textwrap import dedent

from pytest import Pytester
Expand Down
Loading