mirror of https://github.com/scrapy/scrapy.git
74 lines
2.2 KiB
Python
74 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from functools import wraps
|
|
from typing import TYPE_CHECKING, Any, ParamSpec
|
|
|
|
import pytest
|
|
from twisted.internet.defer import Deferred, inlineCallbacks
|
|
|
|
from scrapy.utils.defer import deferred_from_coro, deferred_to_future
|
|
from scrapy.utils.reactor import is_reactor_installed
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Awaitable, Callable, Generator
|
|
|
|
|
|
_P = ParamSpec("_P")
|
|
|
|
|
|
def inline_callbacks_test(
|
|
f: Callable[_P, Generator[Deferred[Any], Any, None]],
|
|
) -> Callable[_P, Awaitable[None]]:
|
|
"""Mark a test function written in a :func:`twisted.internet.defer.inlineCallbacks` style.
|
|
|
|
This calls :func:`twisted.internet.defer.inlineCallbacks` and then:
|
|
|
|
* with ``pytest-twisted`` this returns the resulting Deferred
|
|
* with ``pytest-asyncio`` this converts the resulting Deferred into a
|
|
coroutine
|
|
"""
|
|
|
|
if not is_reactor_installed():
|
|
|
|
@pytest.mark.asyncio
|
|
@wraps(f)
|
|
async def wrapper_coro(*args: _P.args, **kwargs: _P.kwargs) -> None:
|
|
await deferred_to_future(inlineCallbacks(f)(*args, **kwargs))
|
|
|
|
# Likely https://github.com/python/mypy/issues/17171
|
|
return wrapper_coro # type: ignore[no-any-return]
|
|
|
|
@wraps(f)
|
|
@inlineCallbacks
|
|
def wrapper_dfd(
|
|
*args: _P.args, **kwargs: _P.kwargs
|
|
) -> Generator[Deferred[Any], Any, None]:
|
|
return f(*args, **kwargs)
|
|
|
|
return wrapper_dfd
|
|
|
|
|
|
def coroutine_test(
|
|
coro_f: Callable[_P, Awaitable[None]],
|
|
) -> Callable[_P, Awaitable[None]]:
|
|
"""Mark a test function that returns a coroutine.
|
|
|
|
* with ``pytest-twisted`` this converts a coroutine into a
|
|
:class:`twisted.internet.defer.Deferred`
|
|
* with ``pytest-asyncio`` this is a no-op
|
|
|
|
In addition to handling asynchronous test functions this can also be used
|
|
to mark "synchronous" test functions (they still need to be made
|
|
``async def``) that call code that needs a reactor or a running event loop,
|
|
so that ``pytest-asyncio`` starts a loop for them too.
|
|
"""
|
|
|
|
if not is_reactor_installed():
|
|
return pytest.mark.asyncio(coro_f)
|
|
|
|
@wraps(coro_f)
|
|
def f(*coro_args: _P.args, **coro_kwargs: _P.kwargs) -> Deferred[None]:
|
|
return deferred_from_coro(coro_f(*coro_args, **coro_kwargs))
|
|
|
|
return f
|