More granular untyped defs checking in tests. (#7712)

This commit is contained in:
Andrey Rakhmatullin 2026-07-08 13:05:45 +05:00 committed by GitHub
parent bdf3067935
commit 61f99f2df1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 331 additions and 162 deletions

View File

@ -94,7 +94,82 @@ untyped_calls_exclude = [
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = "tests.*" module = "tests.*"
allow_untyped_defs = true allow_untyped_defs = true
allow_incomplete_defs = true # 48 errors allow_incomplete_defs = true # 59 errors
# TODO
[[tool.mypy.overrides]]
module = [
"tests.mockserver.*",
"tests.spiders",
"tests.test_closespider",
"tests.test_cmdline",
"tests.test_contracts",
"tests.test_core_downloader",
"tests.test_downloader_handler_twisted_ftp",
"tests.test_downloadermiddleware_cookies",
"tests.test_downloadermiddleware_httpauth",
"tests.test_downloadermiddleware_httpcache",
"tests.test_downloadermiddleware_httpcompression",
"tests.test_downloadermiddleware_httpproxy",
"tests.test_downloadermiddleware_offsite",
"tests.test_downloadermiddleware_redirect",
"tests.test_downloadermiddleware_redirect_base",
"tests.test_downloadermiddleware_redirect_metarefresh",
"tests.test_downloadermiddleware_retry",
"tests.test_downloadermiddleware_robotstxt",
"tests.test_downloadermiddleware_stats",
"tests.test_downloaderslotssettings",
"tests.test_dupefilters",
"tests.test_engine_loop",
"tests.test_exporters",
"tests.test_extension_statsmailer",
"tests.test_extension_throttle",
"tests.test_feedexport",
"tests.test_feedexport_postprocess",
"tests.test_feedexport_storages",
"tests.test_feedexport_uri_params",
"tests.test_http2_client_protocol",
"tests.test_http_headers",
"tests.test_http_request",
"tests.test_http_request_form",
"tests.test_http_response",
"tests.test_http_response_text",
"tests.test_item",
"tests.test_link",
"tests.test_linkextractors",
"tests.test_loader",
"tests.test_logformatter",
"tests.test_logstats",
"tests.test_mail",
"tests.test_pipeline_crawl",
"tests.test_pipeline_files",
"tests.test_pipeline_images",
"tests.test_pipeline_media",
"tests.test_pipelines",
"tests.test_pqueues",
"tests.test_request_attribute_binding",
"tests.test_request_cb_kwargs",
"tests.test_request_dict",
"tests.test_request_left",
"tests.test_robotstxt_interface",
"tests.test_scheduler_base",
"tests.test_settings",
"tests.test_spider",
"tests.test_spider_crawl",
"tests.test_spidermiddleware_output_chain",
"tests.test_spidermiddleware_process_start",
"tests.test_spider_sitemap",
"tests.test_squeues",
"tests.test_squeues_request",
"tests.test_stats",
"tests.test_utils_datatypes",
"tests.test_utils_decorators",
"tests.test_utils_defer",
"tests.test_utils_deprecate",
"tests.test_utils_misc.test_return_with_argument_inside_generator",
"tests.test_utils_python",
"tests.test_utils_request",
]
check_untyped_defs = false check_untyped_defs = false
# Interface classes are hard to support # Interface classes are hard to support

View File

@ -9,7 +9,7 @@ from scrapy.utils.defer import deferred_from_coro
class UppercasePipeline: class UppercasePipeline:
async def _open_spider(self, spider): async def _open_spider(self, spider: Spider) -> None:
spider.logger.info("async pipeline opened!") spider.logger.info("async pipeline opened!")
await asyncio.sleep(0.1) await asyncio.sleep(0.1)

View File

@ -10,6 +10,7 @@ class CachingHostnameResolverSpider(scrapy.Spider):
""" """
name = "caching_hostname_resolver_spider" name = "caching_hostname_resolver_spider"
url: str
async def start(self): async def start(self):
yield scrapy.Request(self.url) yield scrapy.Request(self.url)

View File

@ -9,7 +9,7 @@ from scrapy.utils.defer import deferred_from_coro
class UppercasePipeline: class UppercasePipeline:
async def _open_spider(self, spider): async def _open_spider(self, spider: Spider) -> None:
spider.logger.info("async pipeline opened!") spider.logger.info("async pipeline opened!")
await asyncio.sleep(0.1) await asyncio.sleep(0.1)

View File

@ -10,6 +10,7 @@ class CachingHostnameResolverSpider(scrapy.Spider):
""" """
name = "caching_hostname_resolver_spider" name = "caching_hostname_resolver_spider"
url: str
async def start(self): async def start(self):
yield scrapy.Request(self.url) yield scrapy.Request(self.url)

View File

@ -15,7 +15,7 @@ class SleepingSpider(scrapy.Spider):
async def parse(self, response): async def parse(self, response):
from twisted.internet import reactor from twisted.internet import reactor
d = Deferred() d: Deferred[None] = Deferred()
reactor.callLater(int(sys.argv[1]), d.callback, None) reactor.callLater(int(sys.argv[1]), d.callback, None)
await maybe_deferred_to_future(d) await maybe_deferred_to_future(d)

View File

@ -36,6 +36,7 @@ def createResolver(servers: list[tuple[str, int]]) -> ResolverBase:
class LocalhostSpider(Spider): class LocalhostSpider(Spider):
name = "localhost_spider" name = "localhost_spider"
url: str
async def start(self): async def start(self):
yield Request(self.url) yield Request(self.url)

View File

@ -107,11 +107,11 @@ class TestAddonManager:
addonlist = [] addonlist = []
for i in range(3): for i in range(3):
addon = get_addon_cls({"KEY1": i}) addon = get_addon_cls({"KEY1": i})
addon.number = i addon.number = i # type: ignore[attr-defined]
addonlist.append(addon) addonlist.append(addon)
# Test for every possible ordering # Test for every possible ordering
for ordered_addons in itertools.permutations(addonlist): for ordered_addons in itertools.permutations(addonlist):
expected_order = [a.number for a in ordered_addons] expected_order = [a.number for a in ordered_addons] # type: ignore[attr-defined]
settings = {"ADDONS": {a: i for i, a in enumerate(ordered_addons)}} settings = {"ADDONS": {a: i for i, a in enumerate(ordered_addons)}}
crawler = get_crawler(settings_dict=settings) crawler = get_crawler(settings_dict=settings)
manager = crawler.addons manager = crawler.addons
@ -177,7 +177,7 @@ class TestAddonManager:
) )
settings["SCHEDULER"] = "AddonScheduler" settings["SCHEDULER"] = "AddonScheduler"
settings_dict = { settings_dict: dict[str, Any] = {
"ADDONS": {AddonWithFallback: 1}, "ADDONS": {AddonWithFallback: 1},
} }
crawler = get_crawler(settings_dict=settings_dict) crawler = get_crawler(settings_dict=settings_dict)

View File

@ -1,21 +1,24 @@
from __future__ import annotations
import sys import sys
from pathlib import Path from pathlib import Path
from subprocess import PIPE, Popen from subprocess import PIPE, Popen
class TestCmdlineCrawlPipeline: def _execute(spname: str) -> int:
def _execute(self, spname): args = (sys.executable, "-m", "scrapy.cmdline", "crawl", spname)
args = (sys.executable, "-m", "scrapy.cmdline", "crawl", spname) cwd = Path(__file__).resolve().parent
cwd = Path(__file__).resolve().parent proc = Popen(args, stdout=PIPE, stderr=PIPE, cwd=cwd)
proc = Popen(args, stdout=PIPE, stderr=PIPE, cwd=cwd) proc.communicate()
_, stderr = proc.communicate() return proc.returncode
return proc.returncode, stderr
def test_open_spider_normally_in_pipeline(self):
returncode, _ = self._execute("normal")
assert returncode == 0
def test_exception_at_open_spider_in_pipeline(self): def test_open_spider_normally_in_pipeline():
returncode, _ = self._execute("exception") returncode = _execute("normal")
# An exception in pipeline's open_spider should result in a non-zero exit code assert returncode == 0
assert returncode == 1
def test_exception_at_open_spider_in_pipeline():
returncode = _execute("exception")
# An exception in pipeline's open_spider should result in a non-zero exit code
assert returncode == 1

View File

@ -89,6 +89,7 @@ class TestCommandSettings:
args=["-s", f"FEEDS={feeds_json}", "spider.py"] args=["-s", f"FEEDS={feeds_json}", "spider.py"]
) )
self.command.process_options(args, opts) self.command.process_options(args, opts)
assert self.command.settings is not None
assert isinstance(self.command.settings["FEEDS"], scrapy.settings.BaseSettings) assert isinstance(self.command.settings["FEEDS"], scrapy.settings.BaseSettings)
assert dict(self.command.settings["FEEDS"]) == json.loads(feeds_json) assert dict(self.command.settings["FEEDS"]) == json.loads(feeds_json)

View File

@ -356,9 +356,10 @@ with multiples lines
@coroutine_test @coroutine_test
async def test_engine_status(self, mockserver: MockServer) -> None: async def test_engine_status(self, mockserver: MockServer) -> None:
est = [] est: list[list[tuple[str, Any]]] = []
def cb(response): def cb(response):
assert crawler.engine
est.append(get_engine_status(crawler.engine)) est.append(get_engine_status(crawler.engine))
crawler = get_crawler(SingleRequestSpider) crawler = get_crawler(SingleRequestSpider)
@ -373,9 +374,10 @@ with multiples lines
@coroutine_test @coroutine_test
async def test_format_engine_status(self, mockserver: MockServer) -> None: async def test_format_engine_status(self, mockserver: MockServer) -> None:
est = [] est: list[str] = []
def cb(response): def cb(response):
assert crawler.engine
est.append(format_engine_status(crawler.engine)) est.append(format_engine_status(crawler.engine))
crawler = get_crawler(SingleRequestSpider) crawler = get_crawler(SingleRequestSpider)
@ -386,8 +388,8 @@ with multiples lines
assert len(est) == 1, est assert len(est) == 1, est
est = est[0].split("\n")[2:-2] # remove header & footer est = est[0].split("\n")[2:-2] # remove header & footer
# convert to dict # convert to dict
est = [x.split(":") for x in est] est_split = [x.split(":") for x in est]
est = [x for sublist in est for x in sublist] # flatten est = [x for sublist in est_split for x in sublist] # flatten
est = [x.lstrip().rstrip() for x in est] est = [x.lstrip().rstrip() for x in est]
it = iter(est) it = iter(est)
s = dict(zip(it, it, strict=True)) s = dict(zip(it, it, strict=True))

View File

@ -523,6 +523,7 @@ class TestCrawlerLogging:
} }
async def start(self): async def start(self):
assert crawler.stats
info_count_start = crawler.stats.get_value("log_count/INFO") info_count_start = crawler.stats.get_value("log_count/INFO")
logging.debug("debug message") # noqa: LOG015 logging.debug("debug message") # noqa: LOG015
logging.info("info message") # noqa: LOG015 logging.info("info message") # noqa: LOG015

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
import re import re
from configparser import ConfigParser from configparser import ConfigParser
@ -23,6 +25,7 @@ class TestScrapyUtils:
config_parser.read(tox_config_file_path) config_parser.read(tox_config_file_path)
pattern = r"Twisted==([\d.]+)" pattern = r"Twisted==([\d.]+)"
match = re.search(pattern, config_parser["min"]["deps"]) match = re.search(pattern, config_parser["min"]["deps"])
assert match
pinned_twisted_version_string = match[1] pinned_twisted_version_string = match[1]
assert twisted_version.short() == pinned_twisted_version_string assert twisted_version.short() == pinned_twisted_version_string

View File

@ -16,7 +16,7 @@ from scrapy.core.downloader.handlers.datauri import DataURIDownloadHandler
from scrapy.core.downloader.handlers.file import FileDownloadHandler from scrapy.core.downloader.handlers.file import FileDownloadHandler
from scrapy.core.downloader.handlers.s3 import S3DownloadHandler from scrapy.core.downloader.handlers.s3 import S3DownloadHandler
from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
from scrapy.http import Request from scrapy.http import Request, TextResponse
from scrapy.responsetypes import responsetypes from scrapy.responsetypes import responsetypes
from scrapy.utils.boto import is_botocore_available from scrapy.utils.boto import is_botocore_available
from scrapy.utils.misc import build_from_crawler from scrapy.utils.misc import build_from_crawler
@ -354,6 +354,7 @@ class TestDataURI:
response = await self.download_request(request) response = await self.download_request(request)
assert response.text == "A brief note" assert response.text == "A brief note"
assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck
assert isinstance(response, TextResponse)
assert response.encoding == "US-ASCII" assert response.encoding == "US-ASCII"
@coroutine_test @coroutine_test
@ -362,6 +363,7 @@ class TestDataURI:
response = await self.download_request(request) response = await self.download_request(request)
assert response.text == "\u038e\u03a3\u038e" assert response.text == "\u038e\u03a3\u038e"
assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck
assert isinstance(response, TextResponse)
assert response.encoding == "iso-8859-7" assert response.encoding == "iso-8859-7"
@coroutine_test @coroutine_test
@ -370,6 +372,7 @@ class TestDataURI:
response = await self.download_request(request) response = await self.download_request(request)
assert response.text == "\u038e\u03a3\u038e" assert response.text == "\u038e\u03a3\u038e"
assert response.body == b"\xbe\xd3\xbe" assert response.body == b"\xbe\xd3\xbe"
assert isinstance(response, TextResponse)
assert response.encoding == "iso-8859-7" assert response.encoding == "iso-8859-7"
@coroutine_test @coroutine_test
@ -382,6 +385,7 @@ class TestDataURI:
response = await self.download_request(request) response = await self.download_request(request)
assert response.text == "\u038e\u03a3\u038e" assert response.text == "\u038e\u03a3\u038e"
assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck assert type(response) is responsetypes.from_mimetype("text/plain") # pylint: disable=unidiomatic-typecheck
assert isinstance(response, TextResponse)
assert response.encoding == "utf-8" assert response.encoding == "utf-8"
@coroutine_test @coroutine_test

View File

@ -233,7 +233,7 @@ class TestMiddlewareUsingDeferreds(TestManagerBase):
return result return result
def process_request(self, request): def process_request(self, request):
d = Deferred() d: Deferred[Response] = Deferred()
d.addCallback(self.cb) d.addCallback(self.cb)
d.callback(resp) d.callback(resp)
return d return d
@ -298,6 +298,8 @@ class TestDownloadDeprecated(TestManagerBase):
return succeed(resp) return succeed(resp)
async with self.get_mwman() as mwman: async with self.get_mwman() as mwman:
assert mwman.crawler
assert mwman.crawler.spider
with pytest.warns( with pytest.warns(
ScrapyDeprecationWarning, ScrapyDeprecationWarning,
match=r"DownloaderMiddlewareManager.download\(\) is deprecated, use download_async\(\) instead", match=r"DownloaderMiddlewareManager.download\(\) is deprecated, use download_async\(\) instead",

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from scrapy.downloadermiddlewares.defaultheaders import DefaultHeadersMiddleware from scrapy.downloadermiddlewares.defaultheaders import DefaultHeadersMiddleware
from scrapy.http import Request from scrapy.http import Request
from scrapy.spiders import Spider from scrapy.spiders import Spider
@ -5,28 +7,29 @@ from scrapy.utils.python import to_bytes
from scrapy.utils.test import get_crawler from scrapy.utils.test import get_crawler
class TestDefaultHeadersMiddleware: def get_defaults_mw() -> tuple[dict[bytes, list[bytes]], DefaultHeadersMiddleware]:
def get_defaults_mw(self): crawler = get_crawler(Spider)
crawler = get_crawler(Spider) defaults = {
defaults = { to_bytes(k): [to_bytes(v)]
to_bytes(k): [to_bytes(v)] for k, v in crawler.settings.get("DEFAULT_REQUEST_HEADERS").items()
for k, v in crawler.settings.get("DEFAULT_REQUEST_HEADERS").items() }
} return defaults, DefaultHeadersMiddleware.from_crawler(crawler)
return defaults, DefaultHeadersMiddleware.from_crawler(crawler)
def test_process_request(self):
defaults, mw = self.get_defaults_mw()
req = Request("http://www.scrapytest.org")
mw.process_request(req)
assert req.headers == defaults
def test_update_headers(self): def test_process_request():
defaults, mw = self.get_defaults_mw() defaults, mw = get_defaults_mw()
headers = {"Accept-Language": ["es"], "Test-Header": ["test"]} req = Request("http://www.scrapytest.org")
bytes_headers = {b"Accept-Language": [b"es"], b"Test-Header": [b"test"]} mw.process_request(req)
req = Request("http://www.scrapytest.org", headers=headers) assert req.headers == defaults
assert req.headers == bytes_headers
mw.process_request(req)
defaults.update(bytes_headers) def test_update_headers():
assert req.headers == defaults defaults, mw = get_defaults_mw()
headers = {"Accept-Language": ["es"], "Test-Header": ["test"]}
bytes_headers = {b"Accept-Language": [b"es"], b"Test-Header": [b"test"]}
req = Request("http://www.scrapytest.org", headers=headers)
assert req.headers == bytes_headers
mw.process_request(req)
defaults.update(bytes_headers)
assert req.headers == defaults

View File

@ -1,43 +1,51 @@
from __future__ import annotations
from typing import Any
from scrapy.downloadermiddlewares.downloadtimeout import DownloadTimeoutMiddleware from scrapy.downloadermiddlewares.downloadtimeout import DownloadTimeoutMiddleware
from scrapy.http import Request from scrapy.http import Request
from scrapy.spiders import Spider from scrapy.spiders import Spider
from scrapy.utils.test import get_crawler from scrapy.utils.test import get_crawler
class TestDownloadTimeoutMiddleware: def get_request_spider_mw(settings: dict[str, Any] | None = None):
def get_request_spider_mw(self, settings=None): crawler = get_crawler(Spider, settings)
crawler = get_crawler(Spider, settings) spider = crawler._create_spider("foo")
spider = crawler._create_spider("foo") request = Request("http://scrapytest.org/")
request = Request("http://scrapytest.org/") return request, spider, DownloadTimeoutMiddleware.from_crawler(crawler)
return request, spider, DownloadTimeoutMiddleware.from_crawler(crawler)
def test_default_download_timeout(self):
req, spider, mw = self.get_request_spider_mw()
mw.spider_opened(spider)
assert mw.process_request(req) is None
assert req.meta.get("download_timeout") == 180
def test_string_download_timeout(self): def test_default_download_timeout():
req, spider, mw = self.get_request_spider_mw({"DOWNLOAD_TIMEOUT": "20.1"}) req, spider, mw = get_request_spider_mw()
mw.spider_opened(spider) mw.spider_opened(spider)
assert mw.process_request(req) is None assert mw.process_request(req) is None
assert req.meta.get("download_timeout") == 20.1 assert req.meta.get("download_timeout") == 180
def test_setting_has_download_timeout(self):
req, spider, mw = self.get_request_spider_mw({"DOWNLOAD_TIMEOUT": 2})
mw.spider_opened(spider)
assert mw.process_request(req) is None
assert req.meta.get("download_timeout") == 2
def test_request_has_download_timeout(self): def test_string_download_timeout():
req, spider, mw = self.get_request_spider_mw({"DOWNLOAD_TIMEOUT": 2}) req, spider, mw = get_request_spider_mw({"DOWNLOAD_TIMEOUT": "20.1"})
mw.spider_opened(spider) mw.spider_opened(spider)
req.meta["download_timeout"] = 1 assert mw.process_request(req) is None
assert mw.process_request(req) is None assert req.meta.get("download_timeout") == 20.1
assert req.meta.get("download_timeout") == 1
def test_zero_download_timeout(self):
req, spider, mw = self.get_request_spider_mw({"DOWNLOAD_TIMEOUT": 0}) def test_setting_has_download_timeout():
mw.spider_opened(spider) req, spider, mw = get_request_spider_mw({"DOWNLOAD_TIMEOUT": 2})
assert mw.process_request(req) is None mw.spider_opened(spider)
assert req.meta.get("download_timeout") is None assert mw.process_request(req) is None
assert req.meta.get("download_timeout") == 2
def test_request_has_download_timeout():
req, spider, mw = get_request_spider_mw({"DOWNLOAD_TIMEOUT": 2})
mw.spider_opened(spider)
req.meta["download_timeout"] = 1
assert mw.process_request(req) is None
assert req.meta.get("download_timeout") == 1
def test_zero_download_timeout():
req, spider, mw = get_request_spider_mw({"DOWNLOAD_TIMEOUT": 0})
mw.spider_opened(spider)
assert mw.process_request(req) is None
assert req.meta.get("download_timeout") is None

View File

@ -1,33 +1,37 @@
from __future__ import annotations
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
from scrapy.http import Request from scrapy.http import Request
from scrapy.spiders import Spider from scrapy.spiders import Spider
from scrapy.utils.test import get_crawler from scrapy.utils.test import get_crawler
class TestUserAgentMiddleware: def get_spider_and_mw(
def get_spider_and_mw(self, default_useragent): default_useragent: str | None,
crawler = get_crawler(Spider, {"USER_AGENT": default_useragent}) ) -> tuple[Spider, UserAgentMiddleware]:
spider = crawler._create_spider("foo") crawler = get_crawler(Spider, {"USER_AGENT": default_useragent})
return spider, UserAgentMiddleware.from_crawler(crawler) spider = crawler._create_spider("foo")
return spider, UserAgentMiddleware.from_crawler(crawler)
def test_default_agent(self):
_, mw = self.get_spider_and_mw("default_useragent")
req = Request("http://scrapytest.org/")
assert mw.process_request(req) is None
assert req.headers["User-Agent"] == b"default_useragent"
def test_header_agent(self): def test_default_agent():
spider, mw = self.get_spider_and_mw("default_useragent") _, mw = get_spider_and_mw("default_useragent")
mw.spider_opened(spider) req = Request("http://scrapytest.org/")
req = Request( assert mw.process_request(req) is None
"http://scrapytest.org/", headers={"User-Agent": "header_useragent"} assert req.headers["User-Agent"] == b"default_useragent"
)
assert mw.process_request(req) is None
assert req.headers["User-Agent"] == b"header_useragent"
def test_no_agent(self):
spider, mw = self.get_spider_and_mw(None) def test_header_agent():
mw.spider_opened(spider) spider, mw = get_spider_and_mw("default_useragent")
req = Request("http://scrapytest.org/") mw.spider_opened(spider)
assert mw.process_request(req) is None req = Request("http://scrapytest.org/", headers={"User-Agent": "header_useragent"})
assert "User-Agent" not in req.headers assert mw.process_request(req) is None
assert req.headers["User-Agent"] == b"header_useragent"
def test_no_agent():
spider, mw = get_spider_and_mw(None)
mw.spider_opened(spider)
req = Request("http://scrapytest.org/")
assert mw.process_request(req) is None
assert "User-Agent" not in req.headers

View File

@ -38,6 +38,8 @@ from tests import get_testdata
from tests.utils.decorators import coroutine_test, inline_callbacks_test from tests.utils.decorators import coroutine_test, inline_callbacks_test
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import AsyncIterator
from twisted.python.failure import Failure from twisted.python.failure import Failure
from scrapy.core.scheduler import Scheduler from scrapy.core.scheduler import Scheduler
@ -608,8 +610,8 @@ class TestEngineDownload(TestEngineDownloadAsync):
@coroutine_test @coroutine_test
async def test_request_scheduled_signal(): async def test_request_scheduled_signal():
class TestScheduler(BaseScheduler): class TestScheduler(BaseScheduler):
def __init__(self): def __init__(self) -> None:
self.enqueued = [] self.enqueued: list[Request] = []
def enqueue_request(self, request: Request) -> bool: def enqueue_request(self, request: Request) -> bool:
self.enqueued.append(request) self.enqueued.append(request)
@ -621,9 +623,9 @@ async def test_request_scheduled_signal():
crawler = get_crawler(MySpider) crawler = get_crawler(MySpider)
engine = ExecutionEngine(crawler, lambda _: None) engine = ExecutionEngine(crawler, lambda _: None)
scheduler = TestScheduler() scheduler = TestScheduler() # type: ignore[abstract]
async def start(): async def start() -> AsyncIterator[Any]:
return return
yield yield

View File

@ -102,7 +102,9 @@ class TestPeriodicLog:
ext.spider_closed(spider, reason="finished") ext.spider_closed(spider, reason="finished")
return ext, a, b return ext, a, b
def check(settings: dict[str, Any], condition: Callable) -> None: def check(
settings: dict[str, Any], condition: Callable[[str, Any], bool]
) -> None:
ext, a, b = emulate(settings) ext, a, b = emulate(settings)
assert list(a["delta"].keys()) == [ assert list(a["delta"].keys()) == [
k for k, v in ext.stats._stats.items() if condition(k, v) k for k, v in ext.stats._stats.items() if condition(k, v)
@ -168,7 +170,9 @@ class TestPeriodicLog:
ext.spider_closed(spider, reason="finished") ext.spider_closed(spider, reason="finished")
return ext, a, b return ext, a, b
def check(settings: dict[str, Any], condition: Callable) -> None: def check(
settings: dict[str, Any], condition: Callable[[str, Any], bool]
) -> None:
ext, a, b = emulate(settings) ext, a, b = emulate(settings)
assert list(a["stats"].keys()) == [ assert list(a["stats"].keys()) == [
k for k, v in ext.stats._stats.items() if condition(k, v) k for k, v in ext.stats._stats.items() if condition(k, v)

View File

@ -378,6 +378,7 @@ class TestBatchDeliveries(TestFeedExportBase):
} }
crawler = get_crawler(ItemSpider, settings) crawler = get_crawler(ItemSpider, settings)
yield crawler.crawl(total=2, mockserver=self.mockserver) yield crawler.crawl(total=2, mockserver=self.mockserver)
assert crawler.stats
assert "feedexport/success_count/FileFeedStorage" in crawler.stats.get_stats() assert "feedexport/success_count/FileFeedStorage" in crawler.stats.get_stats()
assert crawler.stats.get_value("feedexport/success_count/FileFeedStorage") == 12 assert crawler.stats.get_value("feedexport/success_count/FileFeedStorage") == 12
@ -392,7 +393,9 @@ class TestBatchDeliveries(TestFeedExportBase):
] ]
class CustomS3FeedStorage(S3FeedStorage): class CustomS3FeedStorage(S3FeedStorage):
stubs = [] from botocore.stub import Stubber # noqa: PLC0415
stubs: list[Stubber] = []
def open(self, *args, **kwargs): def open(self, *args, **kwargs):
from botocore import __version__ as botocore_version # noqa: PLC0415 from botocore import __version__ as botocore_version # noqa: PLC0415
@ -449,6 +452,7 @@ class TestBatchDeliveries(TestFeedExportBase):
assert len(CustomS3FeedStorage.stubs) == len(items) assert len(CustomS3FeedStorage.stubs) == len(items)
for stub in CustomS3FeedStorage.stubs: for stub in CustomS3FeedStorage.stubs:
stub.assert_no_pending_responses() stub.assert_no_pending_responses()
assert crawler.stats
assert ( assert (
"feedexport/success_count/CustomS3FeedStorage" in crawler.stats.get_stats() "feedexport/success_count/CustomS3FeedStorage" in crawler.stats.get_stats()
) )

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from http.cookiejar import DefaultCookiePolicy from http.cookiejar import DefaultCookiePolicy
from scrapy.http import Request, Response from scrapy.http import Request, Response
@ -46,7 +48,7 @@ class TestCookieJar:
def test_set_policy(self): def test_set_policy(self):
policy = DefaultCookiePolicy() policy = DefaultCookiePolicy()
self.jar.set_policy(policy) self.jar.set_policy(policy)
assert self.jar.jar._policy is policy assert self.jar.jar._policy is policy # type: ignore[attr-defined]
def test_check_expired_frequency(self): def test_check_expired_frequency(self):
jar = CookieJar(check_expired_frequency=1) jar = CookieJar(check_expired_frequency=1)

View File

@ -1,3 +1,5 @@
from typing import Any
from scrapy.http import ( from scrapy.http import (
Headers, Headers,
HtmlResponse, HtmlResponse,
@ -38,13 +40,13 @@ class TestResponseTypes:
] ]
for source, cls in mappings: for source, cls in mappings:
retcls = responsetypes.from_content_disposition(source) retcls = responsetypes.from_content_disposition(source)
assert retcls is cls, f"{source} ==> {retcls} != {cls}" assert retcls is cls, f"{source!r} ==> {retcls} != {cls}"
def test_from_content_disposition_no_filename(self): def test_from_content_disposition_no_filename(self):
assert responsetypes.from_content_disposition(b"attachment") is Response assert responsetypes.from_content_disposition(b"attachment") is Response
def test_from_content_type(self): def test_from_content_type(self):
mappings = [ mappings: list[tuple[str | bytes, type[Response]]] = [
("text/html; charset=UTF-8", HtmlResponse), ("text/html; charset=UTF-8", HtmlResponse),
("text/xml; charset=UTF-8", XmlResponse), ("text/xml; charset=UTF-8", XmlResponse),
("application/xhtml+xml; charset=UTF-8", HtmlResponse), ("application/xhtml+xml; charset=UTF-8", HtmlResponse),
@ -58,7 +60,7 @@ class TestResponseTypes:
] ]
for source, cls in mappings: for source, cls in mappings:
retcls = responsetypes.from_content_type(source) retcls = responsetypes.from_content_type(source)
assert retcls is cls, f"{source} ==> {retcls} != {cls}" assert retcls is cls, f"{source!r} ==> {retcls} != {cls}"
def test_from_body(self): def test_from_body(self):
mappings = [ mappings = [
@ -71,7 +73,7 @@ class TestResponseTypes:
] ]
for source, cls in mappings: for source, cls in mappings:
retcls = responsetypes.from_body(source) retcls = responsetypes.from_body(source)
assert retcls is cls, f"{source} ==> {retcls} != {cls}" assert retcls is cls, f"{source!r} ==> {retcls} != {cls}"
def test_from_headers(self): def test_from_headers(self):
mappings = [ mappings = [
@ -98,7 +100,7 @@ class TestResponseTypes:
def test_from_args(self): def test_from_args(self):
# TODO: add more tests that check precedence between the different arguments # TODO: add more tests that check precedence between the different arguments
mappings = [ mappings: list[tuple[dict[str, Any], type[Response]]] = [
({"url": "http://www.example.com/data.csv"}, TextResponse), ({"url": "http://www.example.com/data.csv"}, TextResponse),
# headers takes precedence over url # headers takes precedence over url
( (

View File

@ -379,13 +379,14 @@ class TestIntegrationWithDownloaderAwareInMemory:
url = mockserver.url("/status?n=200", is_secure=False) url = mockserver.url("/status?n=200", is_secure=False)
start_urls = [url] * 6 start_urls = [url] * 6
yield self.crawler.crawl(start_urls) yield self.crawler.crawl(start_urls)
assert self.crawler.stats
assert self.crawler.stats.get_value("downloader/response_count") == len( assert self.crawler.stats.get_value("downloader/response_count") == len(
start_urls start_urls
) )
class TestIncompatibility: class TestIncompatibility:
def _incompatible(self): def _incompatible(self) -> None:
settings = { settings = {
"SCHEDULER_PRIORITY_QUEUE": "scrapy.pqueues.DownloaderAwarePriorityQueue", "SCHEDULER_PRIORITY_QUEUE": "scrapy.pqueues.DownloaderAwarePriorityQueue",
"CONCURRENT_REQUESTS_PER_IP": 1, "CONCURRENT_REQUESTS_PER_IP": 1,

View File

@ -1,13 +1,20 @@
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest import pytest
from scrapy import Request, Spider, signals from scrapy import Request, Spider, signals
from scrapy.utils.test import get_crawler, get_from_asyncio_queue from scrapy.utils.test import get_crawler, get_from_asyncio_queue
from tests.mockserver.http import MockServer from tests.utils.decorators import coroutine_test
from tests.utils.decorators import coroutine_test, inline_callbacks_test
if TYPE_CHECKING:
from tests.mockserver.http import MockServer
class ItemSpider(Spider): class ItemSpider(Spider):
name = "itemspider" name = "itemspider"
mockserver: MockServer
async def start(self): async def start(self):
for index in range(10): for index in range(10):
@ -34,15 +41,6 @@ class TestMain:
class TestMockServer: class TestMockServer:
@classmethod
def setup_class(cls):
cls.mockserver = MockServer()
cls.mockserver.__enter__()
@classmethod
def teardown_class(cls):
cls.mockserver.__exit__(None, None, None)
def setup_method(self): def setup_method(self):
self.items = [] self.items = []
@ -51,11 +49,11 @@ class TestMockServer:
self.items.append(item) self.items.append(item)
@pytest.mark.only_asyncio @pytest.mark.only_asyncio
@inline_callbacks_test @coroutine_test
def test_simple_pipeline(self): async def test_simple_pipeline(self, mockserver: MockServer) -> None:
crawler = get_crawler(ItemSpider) crawler = get_crawler(ItemSpider)
crawler.signals.connect(self._on_item_scraped, signals.item_scraped) crawler.signals.connect(self._on_item_scraped, signals.item_scraped)
yield crawler.crawl(mockserver=self.mockserver) await crawler.crawl_async(mockserver=mockserver)
assert len(self.items) == 10 assert len(self.items) == 10
for index in range(10): for index in range(10):
assert {"index": index} in self.items assert {"index": index} in self.items

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from asyncio import sleep from asyncio import sleep
from typing import Any from typing import TYPE_CHECKING, Any
import pytest import pytest
@ -12,6 +12,9 @@ from scrapy.utils.test import get_crawler
from .utils import twisted_sleep from .utils import twisted_sleep
from .utils.decorators import coroutine_test from .utils.decorators import coroutine_test
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Callable
SLEEP_SECONDS = 0.1 SLEEP_SECONDS = 0.1
ITEM_A = {"id": "a"} ITEM_A = {"id": "a"}
@ -67,7 +70,11 @@ class TestMain:
await self._test_spider(TestSpider, [ITEM_A]) await self._test_spider(TestSpider, [ITEM_A])
async def _test_start(self, start_, expected_items=None): async def _test_start(
self,
start_: Callable[[Any], AsyncIterator[Any]],
expected_items: list[Any] | None = None,
) -> None:
class TestSpider(Spider): class TestSpider(Spider):
name = "test" name = "test"
start = start_ start = start_

View File

@ -186,7 +186,7 @@ class ProcessSpiderExceptionSimpleIterableMiddleware:
class ProcessSpiderExceptionAsyncIteratorMiddleware: class ProcessSpiderExceptionAsyncIteratorMiddleware:
async def process_spider_exception(self, response, exception): async def process_spider_exception(self, response, exception):
yield {"foo": 1} yield {"foo": 1}
d = defer.Deferred() d: defer.Deferred[None] = defer.Deferred()
call_later(0, d.callback, None) call_later(0, d.callback, None)
await maybe_deferred_to_future(d) await maybe_deferred_to_future(d)
yield {"foo": 2} yield {"foo": 2}

View File

@ -22,6 +22,7 @@ class _HttpErrorSpider(MockServerSpider):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
assert self.mockserver
self.start_urls = [ self.start_urls = [
self.mockserver.url("/status?n=200"), self.mockserver.url("/status?n=200"),
self.mockserver.url("/status?n=404"), self.mockserver.url("/status?n=404"),
@ -36,7 +37,7 @@ class _HttpErrorSpider(MockServerSpider):
for url in self.start_urls: for url in self.start_urls:
yield Request(url, self.parse, errback=self.on_error) yield Request(url, self.parse, errback=self.on_error)
def parse(self, response): def parse(self, response: Response) -> None:
self.parsed.add(response.url[-3:]) self.parsed.add(response.url[-3:])
def on_error(self, failure): def on_error(self, failure):

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import Any from typing import TYPE_CHECKING, Any
from urllib.parse import urlparse from urllib.parse import urlparse
import pytest import pytest
@ -35,6 +35,9 @@ from scrapy.utils.misc import build_from_crawler
from scrapy.utils.test import get_crawler from scrapy.utils.test import get_crawler
from tests.utils.decorators import coroutine_test from tests.utils.decorators import coroutine_test
if TYPE_CHECKING:
from collections.abc import AsyncIterator
class TestRefererMiddleware: class TestRefererMiddleware:
req_meta: dict[str, Any] = {} req_meta: dict[str, Any] = {}
@ -1031,7 +1034,7 @@ async def test_response_policy_only_supports_policy_names():
crawler = get_crawler(settings_dict={"REFERRER_POLICY": "no-referrer"}) crawler = get_crawler(settings_dict={"REFERRER_POLICY": "no-referrer"})
mw = build_from_crawler(RefererMiddleware, crawler) mw = build_from_crawler(RefererMiddleware, crawler)
async def input_result(): async def input_result() -> AsyncIterator[Any]:
yield Request("https://example.com/") yield Request("https://example.com/")
response = Response( response = Response(
@ -1079,7 +1082,7 @@ async def test_referer_policies_setting():
) )
mw = build_from_crawler(RefererMiddleware, crawler) mw = build_from_crawler(RefererMiddleware, crawler)
async def input_result(): async def input_result() -> AsyncIterator[Any]:
yield Request("https://example.com/") yield Request("https://example.com/")
# "no-referrer-when-downgrade": None, # "no-referrer-when-downgrade": None,

View File

@ -1,3 +1,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from scrapy.http import Request, Response from scrapy.http import Request, Response
from scrapy.spidermiddlewares.start import StartSpiderMiddleware from scrapy.spidermiddlewares.start import StartSpiderMiddleware
from scrapy.spiders import Spider from scrapy.spiders import Spider
@ -5,6 +9,9 @@ from scrapy.utils.misc import build_from_crawler
from scrapy.utils.test import get_crawler from scrapy.utils.test import get_crawler
from tests.utils.decorators import coroutine_test from tests.utils.decorators import coroutine_test
if TYPE_CHECKING:
from collections.abc import AsyncIterator
class TestMiddleware: class TestMiddleware:
@coroutine_test @coroutine_test
@ -12,7 +19,7 @@ class TestMiddleware:
crawler = get_crawler(Spider) crawler = get_crawler(Spider)
mw = build_from_crawler(StartSpiderMiddleware, crawler) mw = build_from_crawler(StartSpiderMiddleware, crawler)
async def start(): async def start() -> AsyncIterator[Request]:
yield Request("data:,1") yield Request("data:,1")
yield Request("data:,2", meta={"is_start_request": True}) yield Request("data:,2", meta={"is_start_request": True})
yield Request("data:,2", meta={"is_start_request": False}) yield Request("data:,2", meta={"is_start_request": False})

View File

@ -72,7 +72,7 @@ class TestParallelAsyncio:
for length in [20, 50, 100]: for length in [20, 50, 100]:
parallel_count = [0] parallel_count = [0]
max_parallel_count = [0] max_parallel_count = [0]
results = [] results: list[int] = []
ait = self.get_async_iterable(length) ait = self.get_async_iterable(length)
await _parallel_asyncio( await _parallel_asyncio(
ait, ait,
@ -91,7 +91,7 @@ class TestParallelAsyncio:
for length in [20, 50, 100]: for length in [20, 50, 100]:
parallel_count = [0] parallel_count = [0]
max_parallel_count = [0] max_parallel_count = [0]
results = [] results: list[int] = []
ait = self.get_async_iterable_with_delays(length) ait = self.get_async_iterable_with_delays(length)
await _parallel_asyncio( await _parallel_asyncio(
ait, ait,

View File

@ -1,3 +1,7 @@
from __future__ import annotations
from typing import Any
import pytest import pytest
from scrapy.exceptions import UsageError from scrapy.exceptions import UsageError
@ -106,7 +110,7 @@ class TestFeedExportConfig:
) )
def test_feed_complete_default_values_from_settings_empty(self): def test_feed_complete_default_values_from_settings_empty(self):
feed = {} feed: dict[str, Any] = {}
settings = Settings( settings = Settings(
{ {
"FEED_EXPORT_ENCODING": "custom encoding", "FEED_EXPORT_ENCODING": "custom encoding",

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import pytest import pytest
from scrapy.utils.console import get_shell_embed_func from scrapy.utils.console import get_shell_embed_func
@ -38,4 +40,5 @@ def test_get_shell_embed_func_bpython():
def test_get_shell_embed_func_ipython(): def test_get_shell_embed_func_ipython():
# default shell should be 'ipython' # default shell should be 'ipython'
shell = get_shell_embed_func() shell = get_shell_embed_func()
assert shell is not None
assert shell.__name__ == "_embed_ipython_shell" assert shell.__name__ == "_embed_ipython_shell"

View File

@ -1,9 +1,16 @@
from __future__ import annotations
import builtins import builtins
from io import StringIO from io import StringIO
from typing import TYPE_CHECKING
from unittest import mock from unittest import mock
from scrapy.utils.display import pformat, pprint from scrapy.utils.display import pformat, pprint
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
from types import ModuleType
value = {"a": 1} value = {"a": 1}
colorized_strings = { colorized_strings = {
( (
@ -77,14 +84,19 @@ def test_pformat_no_pygments(isatty):
real_import = builtins.__import__ real_import = builtins.__import__
def mock_import(name, globals_, locals_, fromlist, level): def mock_import(
name: str,
globals_: Mapping[str, object] | None = None,
locals_: Mapping[str, object] | None = None,
fromlist: Sequence[str] | None = (),
level: int = 0,
) -> ModuleType:
if "pygments" in name: if "pygments" in name:
raise ImportError raise ImportError
return real_import(name, globals_, locals_, fromlist, level) return real_import(name, globals_, locals_, fromlist, level)
builtins.__import__ = mock_import with mock.patch("builtins.__import__", mock_import):
assert pformat(value) == plain_string assert pformat(value) == plain_string
builtins.__import__ = real_import
def test_pprint(): def test_pprint():

View File

@ -238,7 +238,7 @@ class TestXmliterBase(ABC):
next(my_iter) next(my_iter)
def test_xmliter_objtype_exception(self): def test_xmliter_objtype_exception(self):
i = self.xmliter(42, "product") i = self.xmliter(42, "product") # type: ignore[arg-type]
with pytest.raises(TypeError): with pytest.raises(TypeError):
next(i) next(i)
@ -353,7 +353,7 @@ class TestLxmlXmliter(TestXmliterBase):
assert node.xpath("f:name/text()").getall() == ["African Coffee Table"] assert node.xpath("f:name/text()").getall() == ["African Coffee Table"]
def test_xmliter_objtype_exception(self): def test_xmliter_objtype_exception(self):
i = self.xmliter(42, "product") i = self.xmliter(42, "product") # type: ignore[arg-type]
with pytest.raises(TypeError): with pytest.raises(TypeError):
next(i) next(i)

View File

@ -38,7 +38,7 @@ class TestFailureToExcInfo:
assert exc_info == failure_to_exc_info(failure) assert exc_info == failure_to_exc_info(failure)
def test_non_failure(self): def test_non_failure(self):
assert failure_to_exc_info("test") is None assert failure_to_exc_info("test") is None # type: ignore[arg-type]
class TestTopLevelFormatter: class TestTopLevelFormatter:

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
@ -37,7 +39,7 @@ class TestUtilsMisc:
with pytest.raises(NameError): with pytest.raises(NameError):
load_object("scrapy.utils.misc.load_object999") load_object("scrapy.utils.misc.load_object999")
with pytest.raises(TypeError): with pytest.raises(TypeError):
load_object({}) load_object({}) # type: ignore[arg-type]
def test_walk_modules(self): def test_walk_modules(self):
mods = walk_modules_iter("tests.test_utils_misc.test_walk_modules") mods = walk_modules_iter("tests.test_utils_misc.test_walk_modules")
@ -114,7 +116,7 @@ class TestUtilsMisc:
args = (True, 100.0) args = (True, 100.0)
kwargs = {"key": "val"} kwargs = {"key": "val"}
def _test_with_crawler(mock, crawler): def _test_with_crawler(mock: mock.MagicMock, crawler: mock.MagicMock) -> None:
build_from_crawler(mock, crawler, *args, **kwargs) build_from_crawler(mock, crawler, *args, **kwargs)
if hasattr(mock, "from_crawler"): if hasattr(mock, "from_crawler"):
mock.from_crawler.assert_called_once_with(crawler, *args, **kwargs) mock.from_crawler.assert_called_once_with(crawler, *args, **kwargs)

View File

@ -38,7 +38,7 @@ def test_open_in_browser():
resp = Response(url, body=body) resp = Response(url, body=body)
with pytest.raises(TypeError): with pytest.raises(TypeError):
open_in_browser(resp, debug=True) # pylint: disable=unexpected-keyword-arg open_in_browser(resp, _openfunc=browser_open) # type: ignore[arg-type]
def test_get_meta_refresh(): def test_get_meta_refresh():
@ -320,4 +320,4 @@ def test_open_in_browser_text_response_uses_txt_extension():
def test_open_in_browser_raises_for_unsupported_response_type(): def test_open_in_browser_raises_for_unsupported_response_type():
response = Response("http://www.example.com", body=b"binary") response = Response("http://www.example.com", body=b"binary")
with pytest.raises(TypeError): with pytest.raises(TypeError):
open_in_browser(response, _openfunc=lambda _: True) open_in_browser(response, _openfunc=lambda _: True) # type: ignore[arg-type]

View File

@ -1,4 +1,7 @@
from __future__ import annotations
import asyncio import asyncio
from typing import TYPE_CHECKING
import pytest import pytest
from pydispatch import dispatcher from pydispatch import dispatcher
@ -16,6 +19,9 @@ from scrapy.utils.signal import (
from scrapy.utils.test import get_from_asyncio_queue from scrapy.utils.test import get_from_asyncio_queue
from tests.utils.decorators import inline_callbacks_test from tests.utils.decorators import inline_callbacks_test
if TYPE_CHECKING:
from collections.abc import Callable
class TestSendCatchLog: class TestSendCatchLog:
# whether the function being tested returns exceptions or failures # whether the function being tested returns exceptions or failures
@ -24,7 +30,7 @@ class TestSendCatchLog:
@inline_callbacks_test @inline_callbacks_test
def test_send_catch_log(self): def test_send_catch_log(self):
test_signal = object() test_signal = object()
handlers_called = set() handlers_called: set[Callable[..., None]] = set()
dispatcher.connect(self.error_handler, signal=test_signal) dispatcher.connect(self.error_handler, signal=test_signal)
dispatcher.connect(self.ok_handler, signal=test_signal) dispatcher.connect(self.ok_handler, signal=test_signal)
@ -74,7 +80,7 @@ class TestSendCatchLogDeferred2(TestSendCatchLogDeferred):
def ok_handler(self, arg, handlers_called): def ok_handler(self, arg, handlers_called):
handlers_called.add(self.ok_handler) handlers_called.add(self.ok_handler)
assert arg == "test" assert arg == "test"
d = defer.Deferred() d: defer.Deferred[str] = defer.Deferred()
call_later(0, d.callback, "OK") call_later(0, d.callback, "OK")
return d return d
@ -108,7 +114,7 @@ class TestSendCatchLogAsync2(TestSendCatchLogAsync):
def ok_handler(self, arg, handlers_called): def ok_handler(self, arg, handlers_called):
handlers_called.add(self.ok_handler) handlers_called.add(self.ok_handler)
assert arg == "test" assert arg == "test"
d = defer.Deferred() d: defer.Deferred[str] = defer.Deferred()
call_later(0, d.callback, "OK") call_later(0, d.callback, "OK")
return d return d

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from scrapy import Spider from scrapy import Spider
from scrapy.http import Request from scrapy.http import Request
from scrapy.item import Item from scrapy.item import Item
@ -17,7 +19,7 @@ def test_iterate_spider_output():
r = Request("http://scrapytest.org") r = Request("http://scrapytest.org")
o = object() o = object()
assert list(iterate_spider_output(i)) == [i] assert list(iterate_spider_output(i)) == [i] # type: ignore[call-overload]
assert list(iterate_spider_output(r)) == [r] assert list(iterate_spider_output(r)) == [r]
assert list(iterate_spider_output(o)) == [o] assert list(iterate_spider_output(o)) == [o]
assert list(iterate_spider_output([r, i, o])) == [r, i, o] assert list(iterate_spider_output([r, i, o])) == [r, i, o]

View File

@ -79,9 +79,9 @@ def test_get_oldest():
stale entries in `live_refs`, affecting results unless explicitly cleared. stale entries in `live_refs`, affecting results unless explicitly cleared.
""" """
def _delete_o1(): def _delete_o1() -> None:
"""Delete `o1` and ensure it is actually collected on PyPy.""" """Delete `o1` and ensure it is actually collected on PyPy."""
nonlocal o1 nonlocal o1 # type: ignore[misc]
del o1 del o1
if _IS_PYPY: if _IS_PYPY:
@ -89,7 +89,7 @@ def test_get_oldest():
# still exist until the GC runs, so we force a collection cycle. # still exist until the GC runs, so we force a collection cycle.
garbage_collect() garbage_collect()
def _do_asserts(): def _do_asserts() -> None:
assert trackref.get_oldest("Foo") is o1 assert trackref.get_oldest("Foo") is o1
assert trackref.get_oldest("Bar") is o2 assert trackref.get_oldest("Bar") is o2
# Ensure the newer Foo is not incorrectly considered the oldest # Ensure the newer Foo is not incorrectly considered the oldest