diff --git a/docs/topics/asyncio.rst b/docs/topics/asyncio.rst
index ad5c71fbf..ca5bc9a5c 100644
--- a/docs/topics/asyncio.rst
+++ b/docs/topics/asyncio.rst
@@ -49,6 +49,7 @@ You can usually fix the issue by moving those offending module-level Twisted
imports to the method or function definitions where they are used. For example,
if you have something like:
+.. skip: next
.. code-block:: python
from twisted.internet import reactor
diff --git a/docs/topics/settings.rst b/docs/topics/settings.rst
index db65fb993..4e9519eef 100644
--- a/docs/topics/settings.rst
+++ b/docs/topics/settings.rst
@@ -2003,6 +2003,7 @@ reactor is installed.
In order to use the reactor installed by Scrapy:
+.. skip: next
.. code-block:: python
import scrapy
diff --git a/tests/test_crawler.py b/tests/test_crawler.py
index e7e7440a8..70674c021 100644
--- a/tests/test_crawler.py
+++ b/tests/test_crawler.py
@@ -30,6 +30,7 @@ from scrapy.crawler import (
from scrapy.exceptions import ScrapyDeprecationWarning
from scrapy.extensions.throttle import AutoThrottle
from scrapy.settings import Settings, default_settings
+from scrapy.utils.asyncio import call_later
from scrapy.utils.defer import deferred_f_from_coro_f, deferred_from_coro
from scrapy.utils.log import configure_logging, get_scrapy_root_handler
from scrapy.utils.spider import DefaultSpider
@@ -932,8 +933,6 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
@inlineCallbacks
def test_shutdown_forced(self):
- from twisted.internet import reactor
-
sig = signal.SIGINT if sys.platform != "win32" else signal.SIGBREAK
args = self.get_script_args("sleeping.py", "10")
p = PopenSpawn(args, timeout=5)
@@ -943,7 +942,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
p.expect_exact("shutting down gracefully")
# sending the second signal too fast often causes problems
d = Deferred()
- reactor.callLater(0.01, d.callback, None)
+ call_later(0.01, d.callback, None)
yield d
p.kill(sig)
p.expect_exact("forcing unclean shutdown")
diff --git a/tests/test_downloader_handler_twisted_ftp.py b/tests/test_downloader_handler_twisted_ftp.py
new file mode 100644
index 000000000..a43fe318d
--- /dev/null
+++ b/tests/test_downloader_handler_twisted_ftp.py
@@ -0,0 +1,194 @@
+from __future__ import annotations
+
+import os
+import sys
+from pathlib import Path
+from tempfile import mkstemp
+from typing import TYPE_CHECKING, Any
+
+import pytest
+from pytest_twisted import async_yield_fixture
+from twisted.cred import checkers, credentials, portal
+
+from scrapy.core.downloader.handlers.ftp import FTPDownloadHandler
+from scrapy.http import HtmlResponse, Request, Response
+from scrapy.http.response.text import TextResponse
+from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
+from scrapy.utils.misc import build_from_crawler
+from scrapy.utils.python import to_bytes
+from scrapy.utils.spider import DefaultSpider
+from scrapy.utils.test import get_crawler
+
+if TYPE_CHECKING:
+ from collections.abc import AsyncGenerator, Generator
+
+
+class TestFTPBase:
+ username = "scrapy"
+ password = "passwd"
+ req_meta: dict[str, Any] = {"ftp_user": username, "ftp_password": password}
+
+ test_files = (
+ ("file.txt", b"I have the power!"),
+ ("file with spaces.txt", b"Moooooooooo power!"),
+ ("html-file-without-extension", b"\n
."),
+ )
+
+ def _create_files(self, root: Path) -> None:
+ userdir = root / self.username
+ userdir.mkdir()
+ for filename, content in self.test_files:
+ (userdir / filename).write_bytes(content)
+
+ def _get_factory(self, root):
+ from twisted.protocols.ftp import FTPFactory, FTPRealm
+
+ realm = FTPRealm(anonymousRoot=str(root), userHome=str(root))
+ p = portal.Portal(realm)
+ users_checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
+ users_checker.addUser(self.username, self.password)
+ p.registerChecker(users_checker, credentials.IUsernamePassword)
+ return FTPFactory(portal=p)
+
+ @async_yield_fixture
+ async def server_url(self, tmp_path: Path) -> AsyncGenerator[str]:
+ from twisted.internet import reactor
+
+ self._create_files(tmp_path)
+ factory = self._get_factory(tmp_path)
+ port = reactor.listenTCP(0, factory, interface="127.0.0.1")
+ portno = port.getHost().port
+
+ yield f"https://127.0.0.1:{portno}/"
+
+ await port.stopListening()
+
+ @staticmethod
+ @pytest.fixture
+ def dh() -> Generator[FTPDownloadHandler]:
+ crawler = get_crawler()
+ dh = build_from_crawler(FTPDownloadHandler, crawler)
+
+ yield dh
+
+ # if the test was skipped, there will be no client attribute
+ if hasattr(dh, "client"):
+ assert dh.client.transport
+ dh.client.transport.loseConnection()
+
+ @staticmethod
+ async def download_request(dh: FTPDownloadHandler, request: Request) -> Response:
+ return await maybe_deferred_to_future(
+ dh.download_request(request, DefaultSpider())
+ )
+
+ @deferred_f_from_coro_f
+ async def test_ftp_download_success(
+ self, server_url: str, dh: FTPDownloadHandler
+ ) -> None:
+ request = Request(url=server_url + "file.txt", meta=self.req_meta)
+ r = await self.download_request(dh, request)
+ assert r.status == 200
+ assert r.body == b"I have the power!"
+ assert r.headers == {b"Local Filename": [b""], b"Size": [b"17"]}
+ assert r.protocol is None
+
+ @deferred_f_from_coro_f
+ async def test_ftp_download_path_with_spaces(
+ self, server_url: str, dh: FTPDownloadHandler
+ ) -> None:
+ request = Request(
+ url=server_url + "file with spaces.txt",
+ meta=self.req_meta,
+ )
+ r = await self.download_request(dh, request)
+ assert r.status == 200
+ assert r.body == b"Moooooooooo power!"
+ assert r.headers == {b"Local Filename": [b""], b"Size": [b"18"]}
+
+ @deferred_f_from_coro_f
+ async def test_ftp_download_nonexistent(
+ self, server_url: str, dh: FTPDownloadHandler
+ ) -> None:
+ request = Request(url=server_url + "nonexistent.txt", meta=self.req_meta)
+ r = await self.download_request(dh, request)
+ assert r.status == 404
+
+ @deferred_f_from_coro_f
+ async def test_ftp_local_filename(
+ self, server_url: str, dh: FTPDownloadHandler
+ ) -> None:
+ f, local_fname = mkstemp()
+ fname_bytes = to_bytes(local_fname)
+ local_path = Path(local_fname)
+ os.close(f)
+ meta = {"ftp_local_filename": fname_bytes}
+ meta.update(self.req_meta)
+ request = Request(url=server_url + "file.txt", meta=meta)
+ r = await self.download_request(dh, request)
+ assert r.body == fname_bytes
+ assert r.headers == {b"Local Filename": [fname_bytes], b"Size": [b"17"]}
+ assert local_path.exists()
+ assert local_path.read_bytes() == b"I have the power!"
+ local_path.unlink()
+
+ @pytest.mark.parametrize(
+ ("filename", "response_class"),
+ [
+ ("file.txt", TextResponse),
+ ("html-file-without-extension", HtmlResponse),
+ ],
+ )
+ @deferred_f_from_coro_f
+ async def test_response_class(
+ self,
+ filename: str,
+ response_class: type[Response],
+ server_url: str,
+ dh: FTPDownloadHandler,
+ ) -> None:
+ f, local_fname = mkstemp()
+ local_fname_path = Path(local_fname)
+ os.close(f)
+ meta = {}
+ meta.update(self.req_meta)
+ request = Request(url=server_url + filename, meta=meta)
+ r = await self.download_request(dh, request)
+ assert type(r) is response_class # pylint: disable=unidiomatic-typecheck
+ local_fname_path.unlink()
+
+
+class TestFTP(TestFTPBase):
+ @deferred_f_from_coro_f
+ async def test_invalid_credentials(
+ self, server_url: str, dh: FTPDownloadHandler, reactor_pytest: str
+ ) -> None:
+ if reactor_pytest == "asyncio" and sys.platform == "win32":
+ pytest.skip(
+ "This test produces DirtyReactorAggregateError on Windows with asyncio"
+ )
+
+ from twisted.protocols.ftp import ConnectionLost
+
+ meta = dict(self.req_meta)
+ meta.update({"ftp_password": "invalid"})
+ request = Request(url=server_url + "file.txt", meta=meta)
+ with pytest.raises(ConnectionLost):
+ await self.download_request(dh, request)
+
+
+class TestAnonymousFTP(TestFTPBase):
+ username = "anonymous"
+ req_meta = {}
+
+ def _create_files(self, root: Path) -> None:
+ for filename, content in self.test_files:
+ (root / filename).write_bytes(content)
+
+ def _get_factory(self, tmp_path):
+ from twisted.protocols.ftp import FTPFactory, FTPRealm
+
+ realm = FTPRealm(anonymousRoot=str(tmp_path))
+ p = portal.Portal(realm)
+ p.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
+ return FTPFactory(portal=p, userAnonymous=self.username)
diff --git a/tests/test_downloader_handlers.py b/tests/test_downloader_handlers.py
index 9aa53edd9..e37932fbb 100644
--- a/tests/test_downloader_handlers.py
+++ b/tests/test_downloader_handlers.py
@@ -4,35 +4,25 @@ from __future__ import annotations
import contextlib
import os
-import sys
from pathlib import Path
from tempfile import mkdtemp, mkstemp
-from typing import TYPE_CHECKING, Any
from unittest import mock
import pytest
-from pytest_twisted import async_yield_fixture
-from twisted.cred import checkers, credentials, portal
from w3lib.url import path_to_file_uri
from scrapy.core.downloader.handlers import DownloadHandlers
from scrapy.core.downloader.handlers.datauri import DataURIDownloadHandler
from scrapy.core.downloader.handlers.file import FileDownloadHandler
-from scrapy.core.downloader.handlers.ftp import FTPDownloadHandler
from scrapy.core.downloader.handlers.s3 import S3DownloadHandler
from scrapy.exceptions import NotConfigured
-from scrapy.http import HtmlResponse, Request, Response
-from scrapy.http.response.text import TextResponse
+from scrapy.http import Request, Response
from scrapy.responsetypes import responsetypes
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
from scrapy.utils.misc import build_from_crawler
-from scrapy.utils.python import to_bytes
from scrapy.utils.spider import DefaultSpider
from scrapy.utils.test import get_crawler
-if TYPE_CHECKING:
- from collections.abc import AsyncGenerator, Generator
-
class DummyDH:
lazy = False
@@ -305,177 +295,6 @@ class TestS3:
)
-class TestFTPBase:
- username = "scrapy"
- password = "passwd"
- req_meta: dict[str, Any] = {"ftp_user": username, "ftp_password": password}
-
- test_files = (
- ("file.txt", b"I have the power!"),
- ("file with spaces.txt", b"Moooooooooo power!"),
- ("html-file-without-extension", b"\n."),
- )
-
- def _create_files(self, root: Path) -> None:
- userdir = root / self.username
- userdir.mkdir()
- for filename, content in self.test_files:
- (userdir / filename).write_bytes(content)
-
- def _get_factory(self, root):
- from twisted.protocols.ftp import FTPFactory, FTPRealm
-
- realm = FTPRealm(anonymousRoot=str(root), userHome=str(root))
- p = portal.Portal(realm)
- users_checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
- users_checker.addUser(self.username, self.password)
- p.registerChecker(users_checker, credentials.IUsernamePassword)
- return FTPFactory(portal=p)
-
- @async_yield_fixture
- async def server_url(self, tmp_path: Path) -> AsyncGenerator[str]:
- from twisted.internet import reactor
-
- self._create_files(tmp_path)
- factory = self._get_factory(tmp_path)
- port = reactor.listenTCP(0, factory, interface="127.0.0.1")
- portno = port.getHost().port
-
- yield f"https://127.0.0.1:{portno}/"
-
- await port.stopListening()
-
- @staticmethod
- @pytest.fixture
- def dh() -> Generator[FTPDownloadHandler]:
- crawler = get_crawler()
- dh = build_from_crawler(FTPDownloadHandler, crawler)
-
- yield dh
-
- # if the test was skipped, there will be no client attribute
- if hasattr(dh, "client"):
- assert dh.client.transport
- dh.client.transport.loseConnection()
-
- @staticmethod
- async def download_request(dh: FTPDownloadHandler, request: Request) -> Response:
- return await maybe_deferred_to_future(
- dh.download_request(request, DefaultSpider())
- )
-
- @deferred_f_from_coro_f
- async def test_ftp_download_success(
- self, server_url: str, dh: FTPDownloadHandler
- ) -> None:
- request = Request(url=server_url + "file.txt", meta=self.req_meta)
- r = await self.download_request(dh, request)
- assert r.status == 200
- assert r.body == b"I have the power!"
- assert r.headers == {b"Local Filename": [b""], b"Size": [b"17"]}
- assert r.protocol is None
-
- @deferred_f_from_coro_f
- async def test_ftp_download_path_with_spaces(
- self, server_url: str, dh: FTPDownloadHandler
- ) -> None:
- request = Request(
- url=server_url + "file with spaces.txt",
- meta=self.req_meta,
- )
- r = await self.download_request(dh, request)
- assert r.status == 200
- assert r.body == b"Moooooooooo power!"
- assert r.headers == {b"Local Filename": [b""], b"Size": [b"18"]}
-
- @deferred_f_from_coro_f
- async def test_ftp_download_nonexistent(
- self, server_url: str, dh: FTPDownloadHandler
- ) -> None:
- request = Request(url=server_url + "nonexistent.txt", meta=self.req_meta)
- r = await self.download_request(dh, request)
- assert r.status == 404
-
- @deferred_f_from_coro_f
- async def test_ftp_local_filename(
- self, server_url: str, dh: FTPDownloadHandler
- ) -> None:
- f, local_fname = mkstemp()
- fname_bytes = to_bytes(local_fname)
- local_path = Path(local_fname)
- os.close(f)
- meta = {"ftp_local_filename": fname_bytes}
- meta.update(self.req_meta)
- request = Request(url=server_url + "file.txt", meta=meta)
- r = await self.download_request(dh, request)
- assert r.body == fname_bytes
- assert r.headers == {b"Local Filename": [fname_bytes], b"Size": [b"17"]}
- assert local_path.exists()
- assert local_path.read_bytes() == b"I have the power!"
- local_path.unlink()
-
- @pytest.mark.parametrize(
- ("filename", "response_class"),
- [
- ("file.txt", TextResponse),
- ("html-file-without-extension", HtmlResponse),
- ],
- )
- @deferred_f_from_coro_f
- async def test_response_class(
- self,
- filename: str,
- response_class: type[Response],
- server_url: str,
- dh: FTPDownloadHandler,
- ) -> None:
- f, local_fname = mkstemp()
- local_fname_path = Path(local_fname)
- os.close(f)
- meta = {}
- meta.update(self.req_meta)
- request = Request(url=server_url + filename, meta=meta)
- r = await self.download_request(dh, request)
- assert type(r) is response_class # pylint: disable=unidiomatic-typecheck
- local_fname_path.unlink()
-
-
-class TestFTP(TestFTPBase):
- @deferred_f_from_coro_f
- async def test_invalid_credentials(
- self, server_url: str, dh: FTPDownloadHandler, reactor_pytest: str
- ) -> None:
- if reactor_pytest == "asyncio" and sys.platform == "win32":
- pytest.skip(
- "This test produces DirtyReactorAggregateError on Windows with asyncio"
- )
-
- from twisted.protocols.ftp import ConnectionLost
-
- meta = dict(self.req_meta)
- meta.update({"ftp_password": "invalid"})
- request = Request(url=server_url + "file.txt", meta=meta)
- with pytest.raises(ConnectionLost):
- await self.download_request(dh, request)
-
-
-class TestAnonymousFTP(TestFTPBase):
- username = "anonymous"
- req_meta = {}
-
- def _create_files(self, root: Path) -> None:
- for filename, content in self.test_files:
- (root / filename).write_bytes(content)
-
- def _get_factory(self, tmp_path):
- from twisted.protocols.ftp import FTPFactory, FTPRealm
-
- realm = FTPRealm(anonymousRoot=str(tmp_path))
- p = portal.Portal(realm)
- p.registerChecker(checkers.AllowAnonymousAccess(), credentials.IAnonymous)
- return FTPFactory(portal=p, userAnonymous=self.username)
-
-
class TestDataURI:
def setup_method(self):
crawler = get_crawler()
diff --git a/tests/test_downloader_handlers_http_base.py b/tests/test_downloader_handlers_http_base.py
index bbaf9696b..3784d0933 100644
--- a/tests/test_downloader_handlers_http_base.py
+++ b/tests/test_downloader_handlers_http_base.py
@@ -17,6 +17,7 @@ from twisted.web.http import _DataLoss
from scrapy.http import Headers, HtmlResponse, Request, Response, TextResponse
from scrapy.spiders import Spider
+from scrapy.utils.asyncio import call_later
from scrapy.utils.defer import (
deferred_f_from_coro_f,
deferred_from_coro,
@@ -305,8 +306,6 @@ class TestHttp11Base(TestHttpBase):
async def test_download_with_maxsize_very_large_file(
self, mockserver: MockServer, download_handler: DownloadHandlerProtocol
) -> None:
- from twisted.internet import reactor
-
# TODO: the logger check is specific to scrapy.core.downloader.handlers.http11
with mock.patch("scrapy.core.downloader.handlers.http11.logger") as logger:
request = Request(
@@ -326,7 +325,7 @@ class TestHttp11Base(TestHttpBase):
# after closing the connection.
d: defer.Deferred[mock.Mock] = defer.Deferred()
d.addCallback(check)
- reactor.callLater(0.1, d.callback, logger)
+ call_later(0.1, d.callback, logger)
await maybe_deferred_to_future(d)
@deferred_f_from_coro_f
diff --git a/tests/test_engine_loop.py b/tests/test_engine_loop.py
index 2bb5d2ff4..6915e1463 100644
--- a/tests/test_engine_loop.py
+++ b/tests/test_engine_loop.py
@@ -138,7 +138,6 @@ class TestRequestSendOrder:
def get_num(self, request_or_response: Request | Response):
return int(request_or_response.url.rsplit("&", maxsplit=1)[1])
- @deferred_f_from_coro_f
async def _test_request_order(
self,
start_nums,
@@ -218,14 +217,12 @@ class TestRequestSendOrder:
return
yield
- await maybe_deferred_to_future(
- self._test_request_order(
- start_nums=nums,
- settings={"CONCURRENT_REQUESTS": 1},
- response_seconds=response_seconds,
- start_fn=start,
- parse_fn=parse,
- )
+ await self._test_request_order(
+ start_nums=nums,
+ settings={"CONCURRENT_REQUESTS": 1},
+ response_seconds=response_seconds,
+ start_fn=start,
+ parse_fn=parse,
)
@deferred_f_from_coro_f
@@ -260,17 +257,15 @@ class TestRequestSendOrder:
return
yield
- await maybe_deferred_to_future(
- self._test_request_order(
- start_nums=nums,
- settings={
- "CONCURRENT_REQUESTS": 1,
- "SCHEDULER_START_MEMORY_QUEUE": "scrapy.squeues.LifoMemoryQueue",
- },
- response_seconds=response_seconds,
- start_fn=start,
- parse_fn=parse,
- )
+ await self._test_request_order(
+ start_nums=nums,
+ settings={
+ "CONCURRENT_REQUESTS": 1,
+ "SCHEDULER_START_MEMORY_QUEUE": "scrapy.squeues.LifoMemoryQueue",
+ },
+ response_seconds=response_seconds,
+ start_fn=start,
+ parse_fn=parse,
)
@deferred_f_from_coro_f
@@ -320,17 +315,15 @@ class TestRequestSendOrder:
return
yield
- await maybe_deferred_to_future(
- self._test_request_order(
- start_nums=nums,
- settings={
- "CONCURRENT_REQUESTS": 1,
- "SCHEDULER_START_MEMORY_QUEUE": None,
- },
- response_seconds=response_seconds,
- start_fn=start,
- parse_fn=parse,
- )
+ await self._test_request_order(
+ start_nums=nums,
+ settings={
+ "CONCURRENT_REQUESTS": 1,
+ "SCHEDULER_START_MEMORY_QUEUE": None,
+ },
+ response_seconds=response_seconds,
+ start_fn=start,
+ parse_fn=parse,
)
# Examples from the “Start requests” section of the documentation about
@@ -350,14 +343,12 @@ class TestRequestSendOrder:
request = self.request(num, response_seconds, download_slots)
yield request
- await maybe_deferred_to_future(
- self._test_request_order(
- start_nums=start_nums,
- cb_nums=cb_nums,
- settings={
- "CONCURRENT_REQUESTS": 1,
- },
- response_seconds=response_seconds,
- start_fn=start,
- )
+ await self._test_request_order(
+ start_nums=start_nums,
+ cb_nums=cb_nums,
+ settings={
+ "CONCURRENT_REQUESTS": 1,
+ },
+ response_seconds=response_seconds,
+ start_fn=start,
)
diff --git a/tests/test_http2_client_protocol.py b/tests/test_http2_client_protocol.py
index 3247da61b..d100d6b71 100644
--- a/tests/test_http2_client_protocol.py
+++ b/tests/test_http2_client_protocol.py
@@ -44,6 +44,11 @@ if TYPE_CHECKING:
from scrapy.core.http2.protocol import H2ClientProtocol
+pytestmark = pytest.mark.skipif(
+ not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled"
+)
+
+
def generate_random_string(size: int) -> str:
return "".join(random.choices(string.ascii_uppercase + string.digits, k=size))
@@ -187,7 +192,6 @@ async def make_request(client: H2ClientProtocol, request: Request) -> Response:
return await maybe_deferred_to_future(make_request_dfd(client, request))
-@pytest.mark.skipif(not H2_ENABLED, reason="HTTP/2 support in Twisted is not enabled")
class TestHttps2ClientProtocol:
scheme = "https"
host = "localhost"
diff --git a/tests/test_pipeline_media.py b/tests/test_pipeline_media.py
index b309c5d8e..42ae897e8 100644
--- a/tests/test_pipeline_media.py
+++ b/tests/test_pipeline_media.py
@@ -14,6 +14,7 @@ from scrapy.http.request import NO_CALLBACK
from scrapy.pipelines.files import FileException
from scrapy.pipelines.media import MediaPipeline
from scrapy.spiders import Spider
+from scrapy.utils.asyncio import call_later
from scrapy.utils.log import failure_to_exc_info
from scrapy.utils.signal import disconnect_all
from scrapy.utils.test import get_crawler
@@ -337,10 +338,8 @@ class TestMediaPipeline(TestBaseMediaPipeline):
rsp1 = Response("http://url")
def rsp1_func():
- from twisted.internet import reactor
-
dfd = Deferred().addCallback(_check_downloading)
- reactor.callLater(0.1, dfd.callback, rsp1)
+ call_later(0.1, dfd.callback, rsp1)
return dfd
def rsp2_func():
diff --git a/tests/test_pipelines.py b/tests/test_pipelines.py
index 064033f01..cba6a713c 100644
--- a/tests/test_pipelines.py
+++ b/tests/test_pipelines.py
@@ -4,6 +4,7 @@ import pytest
from twisted.internet.defer import Deferred, inlineCallbacks
from scrapy import Request, Spider, signals
+from scrapy.utils.asyncio import call_later
from scrapy.utils.defer import deferred_to_future, maybe_deferred_to_future
from scrapy.utils.test import get_crawler, get_from_asyncio_queue
from tests.mockserver.http import MockServer
@@ -30,9 +31,7 @@ class DeferredPipeline:
class AsyncDefPipeline:
async def process_item(self, item, spider):
d = Deferred()
- from twisted.internet import reactor
-
- reactor.callLater(0, d.callback, None)
+ call_later(0, d.callback, None)
await maybe_deferred_to_future(d)
item["pipeline_passed"] = True
return item
@@ -41,9 +40,8 @@ class AsyncDefPipeline:
class AsyncDefAsyncioPipeline:
async def process_item(self, item, spider):
d = Deferred()
- from twisted.internet import reactor
-
- reactor.callLater(0, d.callback, None)
+ loop = asyncio.get_event_loop()
+ loop.call_later(0, d.callback, None)
await deferred_to_future(d)
await asyncio.sleep(0.2)
item["pipeline_passed"] = await get_from_asyncio_queue(True)
diff --git a/tests/test_spidermiddleware.py b/tests/test_spidermiddleware.py
index b0f69c1ff..bc2404d8b 100644
--- a/tests/test_spidermiddleware.py
+++ b/tests/test_spidermiddleware.py
@@ -14,6 +14,7 @@ from scrapy.exceptions import _InvalidOutput
from scrapy.http import Request, Response
from scrapy.spiders import Spider
from scrapy.utils.asyncgen import collect_asyncgen
+from scrapy.utils.asyncio import call_later
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
from scrapy.utils.test import get_crawler
@@ -220,9 +221,7 @@ class ProcessSpiderExceptionAsyncIteratorMiddleware:
async def process_spider_exception(self, response, exception, spider):
yield {"foo": 1}
d = defer.Deferred()
- from twisted.internet import reactor
-
- reactor.callLater(0, d.callback, None)
+ call_later(0, d.callback, None)
await maybe_deferred_to_future(d)
yield {"foo": 2}
yield {"foo": 3}
diff --git a/tests/test_utils_signal.py b/tests/test_utils_signal.py
index d90d3ed95..6e4bdb49a 100644
--- a/tests/test_utils_signal.py
+++ b/tests/test_utils_signal.py
@@ -7,6 +7,7 @@ from twisted.internet import defer
from twisted.internet.defer import inlineCallbacks
from twisted.python.failure import Failure
+from scrapy.utils.asyncio import call_later
from scrapy.utils.defer import deferred_from_coro
from scrapy.utils.signal import (
send_catch_log,
@@ -65,12 +66,10 @@ class TestSendCatchLogDeferred(TestSendCatchLog):
class TestSendCatchLogDeferred2(TestSendCatchLogDeferred):
def ok_handler(self, arg, handlers_called):
- from twisted.internet import reactor
-
handlers_called.add(self.ok_handler)
assert arg == "test"
d = defer.Deferred()
- reactor.callLater(0, d.callback, "OK")
+ call_later(0, d.callback, "OK")
return d
@@ -98,12 +97,10 @@ class TestSendCatchLogAsync(TestSendCatchLog):
class TestSendCatchLogAsync2(TestSendCatchLogAsync):
def ok_handler(self, arg, handlers_called):
- from twisted.internet import reactor
-
handlers_called.add(self.ok_handler)
assert arg == "test"
d = defer.Deferred()
- reactor.callLater(0, d.callback, "OK")
+ call_later(0, d.callback, "OK")
return d