mirror of https://github.com/scrapy/scrapy.git
Refactor test_crawl.py and test_crawler.py. (#7566)
This commit is contained in:
parent
90deebe75e
commit
fed75a6c76
|
|
@ -225,6 +225,7 @@ disable = [
|
|||
"too-many-positional-arguments",
|
||||
"too-many-public-methods",
|
||||
"too-many-return-statements",
|
||||
"undefined-variable",
|
||||
"unused-argument",
|
||||
"unused-variable",
|
||||
"useless-import-alias", # used as a hint to mypy
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,13 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
from collections.abc import Generator
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
from typing import Any, ClassVar
|
||||
|
||||
import pytest
|
||||
from twisted.internet.defer import Deferred
|
||||
from zope.interface.exceptions import MultipleInvalid
|
||||
|
||||
import scrapy
|
||||
|
|
@ -22,8 +22,8 @@ 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.defer import deferred_from_coro, maybe_deferred_to_future
|
||||
from scrapy.settings import Settings, _SettingsKey, default_settings
|
||||
from scrapy.utils.defer import ensure_awaitable, maybe_deferred_to_future
|
||||
from scrapy.utils.log import (
|
||||
_uninstall_scrapy_root_handler,
|
||||
configure_logging,
|
||||
|
|
@ -31,12 +31,14 @@ from scrapy.utils.log import (
|
|||
)
|
||||
from scrapy.utils.spider import DefaultSpider
|
||||
from scrapy.utils.test import get_crawler, get_reactor_settings
|
||||
from tests.utils.decorators import coroutine_test, inline_callbacks_test
|
||||
from tests.utils.decorators import coroutine_test
|
||||
|
||||
BASE_SETTINGS: dict[str, Any] = {}
|
||||
|
||||
|
||||
def get_raw_crawler(spidercls=None, settings_dict=None):
|
||||
def get_raw_crawler(
|
||||
spidercls: type[Spider] | None = None, settings_dict: dict[str, Any] | None = None
|
||||
) -> Crawler:
|
||||
"""get_crawler alternative that only calls the __init__ method of the
|
||||
crawler."""
|
||||
settings = Settings()
|
||||
|
|
@ -46,14 +48,18 @@ def get_raw_crawler(spidercls=None, settings_dict=None):
|
|||
|
||||
|
||||
class TestBaseCrawler:
|
||||
def assertOptionIsDefault(self, settings: Settings, key: str) -> None:
|
||||
@staticmethod
|
||||
def assertOptionIsDefault(settings: Settings, key: str) -> None:
|
||||
assert isinstance(settings, Settings)
|
||||
assert settings[key] == getattr(default_settings, key)
|
||||
|
||||
|
||||
class TestCrawler(TestBaseCrawler):
|
||||
def test_populate_spidercls_settings(self):
|
||||
spider_settings = {"TEST1": "spider", "TEST2": "spider"}
|
||||
def test_populate_spidercls_settings(self) -> None:
|
||||
spider_settings: dict[_SettingsKey, Any] = {
|
||||
"TEST1": "spider",
|
||||
"TEST2": "spider",
|
||||
}
|
||||
project_settings = {
|
||||
**BASE_SETTINGS,
|
||||
"TEST1": "project",
|
||||
|
|
@ -76,47 +82,47 @@ class TestCrawler(TestBaseCrawler):
|
|||
assert not settings.frozen
|
||||
assert crawler.settings.frozen
|
||||
|
||||
def test_crawler_accepts_dict(self):
|
||||
def test_crawler_accepts_dict(self) -> None:
|
||||
crawler = get_crawler(DefaultSpider, {"foo": "bar"})
|
||||
assert crawler.settings["foo"] == "bar"
|
||||
self.assertOptionIsDefault(crawler.settings, "RETRY_ENABLED")
|
||||
|
||||
def test_crawler_accepts_None(self):
|
||||
def test_crawler_accepts_None(self) -> None:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", ScrapyDeprecationWarning)
|
||||
crawler = Crawler(DefaultSpider)
|
||||
self.assertOptionIsDefault(crawler.settings, "RETRY_ENABLED")
|
||||
|
||||
def test_crawler_rejects_spider_objects(self):
|
||||
def test_crawler_rejects_spider_objects(self) -> None:
|
||||
with pytest.raises(ValueError, match="spidercls argument must be a class"):
|
||||
Crawler(DefaultSpider())
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_crawl_twice_seq_unsupported(self):
|
||||
crawler = get_raw_crawler(NoRequestsSpider, BASE_SETTINGS)
|
||||
yield crawler.crawl()
|
||||
with pytest.raises(RuntimeError, match="more than once on the same instance"):
|
||||
yield crawler.crawl()
|
||||
Crawler(DefaultSpider()) # type: ignore[arg-type]
|
||||
|
||||
@coroutine_test
|
||||
async def test_crawler_crawl_async_twice_seq_unsupported(self):
|
||||
async def test_crawler_crawl_twice_seq_unsupported(self) -> None:
|
||||
crawler = get_raw_crawler(NoRequestsSpider, BASE_SETTINGS)
|
||||
await maybe_deferred_to_future(crawler.crawl())
|
||||
with pytest.raises(RuntimeError, match="more than once on the same instance"):
|
||||
await maybe_deferred_to_future(crawler.crawl())
|
||||
|
||||
@coroutine_test
|
||||
async def test_crawler_crawl_async_twice_seq_unsupported(self) -> None:
|
||||
crawler = get_raw_crawler(NoRequestsSpider, BASE_SETTINGS)
|
||||
await crawler.crawl_async()
|
||||
with pytest.raises(RuntimeError, match="more than once on the same instance"):
|
||||
await crawler.crawl_async()
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_crawl_twice_parallel_unsupported(self):
|
||||
@coroutine_test
|
||||
async def test_crawler_crawl_twice_parallel_unsupported(self) -> None:
|
||||
crawler = get_raw_crawler(NoRequestsSpider, BASE_SETTINGS)
|
||||
d1 = crawler.crawl()
|
||||
d2 = crawler.crawl()
|
||||
yield d1
|
||||
await maybe_deferred_to_future(d1)
|
||||
with pytest.raises(RuntimeError, match="Crawling already taking place"):
|
||||
yield d2
|
||||
await maybe_deferred_to_future(d2)
|
||||
|
||||
@pytest.mark.only_asyncio
|
||||
@coroutine_test
|
||||
async def test_crawler_crawl_async_twice_parallel_unsupported(self):
|
||||
async def test_crawler_crawl_async_twice_parallel_unsupported(self) -> None:
|
||||
crawler = get_raw_crawler(NoRequestsSpider, BASE_SETTINGS)
|
||||
t1 = asyncio.create_task(crawler.crawl_async())
|
||||
t2 = asyncio.create_task(crawler.crawl_async())
|
||||
|
|
@ -124,12 +130,12 @@ class TestCrawler(TestBaseCrawler):
|
|||
with pytest.raises(RuntimeError, match="Crawling already taking place"):
|
||||
await t2
|
||||
|
||||
def test_get_addon(self):
|
||||
def test_get_addon(self) -> None:
|
||||
class ParentAddon:
|
||||
pass
|
||||
|
||||
class TrackingAddon(ParentAddon):
|
||||
instances = []
|
||||
instances: ClassVar[list[TrackingAddon]] = []
|
||||
|
||||
def __init__(self):
|
||||
TrackingAddon.instances.append(self)
|
||||
|
|
@ -150,7 +156,7 @@ class TestCrawler(TestBaseCrawler):
|
|||
addon = crawler.get_addon(TrackingAddon)
|
||||
assert addon == expected
|
||||
|
||||
addon = crawler.get_addon(DefaultSpider)
|
||||
addon = crawler.get_addon(DefaultSpider) # type: ignore[assignment]
|
||||
assert addon is None
|
||||
|
||||
addon = crawler.get_addon(ParentAddon)
|
||||
|
|
@ -162,19 +168,21 @@ class TestCrawler(TestBaseCrawler):
|
|||
addon = crawler.get_addon(ChildAddon)
|
||||
assert addon is None
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_downloader_middleware(self):
|
||||
@coroutine_test
|
||||
async def test_get_downloader_middleware(self) -> None:
|
||||
class ParentDownloaderMiddleware:
|
||||
pass
|
||||
|
||||
class TrackingDownloaderMiddleware(ParentDownloaderMiddleware):
|
||||
instances = []
|
||||
instances: ClassVar[list[TrackingDownloaderMiddleware]] = []
|
||||
|
||||
def __init__(self):
|
||||
TrackingDownloaderMiddleware.instances.append(self)
|
||||
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
cls: ClassVar[type[Any]]
|
||||
result: ClassVar[Any]
|
||||
|
||||
@classmethod
|
||||
def from_crawler(cls, crawler):
|
||||
|
|
@ -198,18 +206,18 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = TrackingDownloaderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert len(TrackingDownloaderMiddleware.instances) == 1
|
||||
assert MySpider.result == TrackingDownloaderMiddleware.instances[-1]
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = DefaultSpider
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ParentDownloaderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result == TrackingDownloaderMiddleware.instances[-1]
|
||||
|
||||
class ChildDownloaderMiddleware(TrackingDownloaderMiddleware):
|
||||
|
|
@ -217,16 +225,16 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ChildDownloaderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
def test_get_downloader_middleware_not_crawling(self):
|
||||
def test_get_downloader_middleware_not_crawling(self) -> None:
|
||||
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
crawler.get_downloader_middleware(DefaultSpider)
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_downloader_middleware_no_engine(self):
|
||||
@coroutine_test
|
||||
async def test_get_downloader_middleware_no_engine(self) -> None:
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
|
||||
|
|
@ -240,21 +248,23 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_extension(self):
|
||||
@coroutine_test
|
||||
async def test_get_extension(self) -> None:
|
||||
class ParentExtension:
|
||||
pass
|
||||
|
||||
class TrackingExtension(ParentExtension):
|
||||
instances = []
|
||||
instances: ClassVar[list[TrackingExtension]] = []
|
||||
|
||||
def __init__(self):
|
||||
TrackingExtension.instances.append(self)
|
||||
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
cls: ClassVar[type[Any]]
|
||||
result: ClassVar[Any]
|
||||
|
||||
@classmethod
|
||||
def from_crawler(cls, crawler):
|
||||
|
|
@ -278,18 +288,18 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = TrackingExtension
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert len(TrackingExtension.instances) == 1
|
||||
assert MySpider.result == TrackingExtension.instances[-1]
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = DefaultSpider
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ParentExtension
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result == TrackingExtension.instances[-1]
|
||||
|
||||
class ChildExtension(TrackingExtension):
|
||||
|
|
@ -297,16 +307,16 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ChildExtension
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
def test_get_extension_not_crawling(self):
|
||||
def test_get_extension_not_crawling(self) -> None:
|
||||
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
crawler.get_extension(DefaultSpider)
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_extension_no_engine(self):
|
||||
@coroutine_test
|
||||
async def test_get_extension_no_engine(self) -> None:
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
|
||||
|
|
@ -320,21 +330,23 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_item_pipeline(self):
|
||||
@coroutine_test
|
||||
async def test_get_item_pipeline(self) -> None:
|
||||
class ParentItemPipeline:
|
||||
pass
|
||||
|
||||
class TrackingItemPipeline(ParentItemPipeline):
|
||||
instances = []
|
||||
instances: ClassVar[list[TrackingItemPipeline]] = []
|
||||
|
||||
def __init__(self):
|
||||
TrackingItemPipeline.instances.append(self)
|
||||
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
cls: ClassVar[type[Any]]
|
||||
result: ClassVar[Any]
|
||||
|
||||
@classmethod
|
||||
def from_crawler(cls, crawler):
|
||||
|
|
@ -358,18 +370,18 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = TrackingItemPipeline
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert len(TrackingItemPipeline.instances) == 1
|
||||
assert MySpider.result == TrackingItemPipeline.instances[-1]
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = DefaultSpider
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ParentItemPipeline
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result == TrackingItemPipeline.instances[-1]
|
||||
|
||||
class ChildItemPipeline(TrackingItemPipeline):
|
||||
|
|
@ -377,16 +389,16 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ChildItemPipeline
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
def test_get_item_pipeline_not_crawling(self):
|
||||
def test_get_item_pipeline_not_crawling(self) -> None:
|
||||
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
crawler.get_item_pipeline(DefaultSpider)
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_item_pipeline_no_engine(self):
|
||||
@coroutine_test
|
||||
async def test_get_item_pipeline_no_engine(self) -> None:
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
|
||||
|
|
@ -400,21 +412,23 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_spider_middleware(self):
|
||||
@coroutine_test
|
||||
async def test_get_spider_middleware(self) -> None:
|
||||
class ParentSpiderMiddleware:
|
||||
pass
|
||||
|
||||
class TrackingSpiderMiddleware(ParentSpiderMiddleware):
|
||||
instances = []
|
||||
instances: ClassVar[list[TrackingSpiderMiddleware]] = []
|
||||
|
||||
def __init__(self):
|
||||
TrackingSpiderMiddleware.instances.append(self)
|
||||
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
cls: ClassVar[type[Any]]
|
||||
result: ClassVar[Any]
|
||||
|
||||
@classmethod
|
||||
def from_crawler(cls, crawler):
|
||||
|
|
@ -438,18 +452,18 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = TrackingSpiderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert len(TrackingSpiderMiddleware.instances) == 1
|
||||
assert MySpider.result == TrackingSpiderMiddleware.instances[-1]
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = DefaultSpider
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ParentSpiderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result == TrackingSpiderMiddleware.instances[-1]
|
||||
|
||||
class ChildSpiderMiddleware(TrackingSpiderMiddleware):
|
||||
|
|
@ -457,16 +471,16 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, settings)
|
||||
MySpider.cls = ChildSpiderMiddleware
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
assert MySpider.result is None
|
||||
|
||||
def test_get_spider_middleware_not_crawling(self):
|
||||
def test_get_spider_middleware_not_crawling(self) -> None:
|
||||
crawler = get_raw_crawler(settings_dict=BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
crawler.get_spider_middleware(DefaultSpider)
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_get_spider_middleware_no_engine(self):
|
||||
@coroutine_test
|
||||
async def test_get_spider_middleware_no_engine(self) -> None:
|
||||
class MySpider(Spider):
|
||||
name = "myspider"
|
||||
|
||||
|
|
@ -480,22 +494,23 @@ class TestCrawler(TestBaseCrawler):
|
|||
|
||||
crawler = get_raw_crawler(MySpider, BASE_SETTINGS)
|
||||
with pytest.raises(RuntimeError):
|
||||
yield crawler.crawl()
|
||||
await crawler.crawl_async()
|
||||
|
||||
|
||||
class TestSpiderSettings:
|
||||
def test_spider_custom_settings(self):
|
||||
def test_spider_custom_settings(self) -> None:
|
||||
class MySpider(scrapy.Spider):
|
||||
name = "spider"
|
||||
custom_settings = {"AUTOTHROTTLE_ENABLED": True}
|
||||
|
||||
crawler = get_crawler(MySpider)
|
||||
assert crawler.extensions
|
||||
enabled_exts = [e.__class__ for e in crawler.extensions.middlewares]
|
||||
assert AutoThrottle in enabled_exts
|
||||
|
||||
|
||||
class TestCrawlerLogging:
|
||||
def test_no_root_handler_installed(self):
|
||||
def test_no_root_handler_installed(self) -> None:
|
||||
handler = get_scrapy_root_handler()
|
||||
if handler is not None:
|
||||
logging.root.removeHandler(handler)
|
||||
|
|
@ -507,7 +522,7 @@ class TestCrawlerLogging:
|
|||
assert get_scrapy_root_handler() is None
|
||||
|
||||
@coroutine_test
|
||||
async def test_spider_custom_settings_log_level(self, tmp_path):
|
||||
async def test_spider_custom_settings_log_level(self, tmp_path: Path) -> None:
|
||||
log_file = Path(tmp_path, "log.txt")
|
||||
log_file.write_text("previous message\n", encoding="utf-8")
|
||||
|
||||
|
|
@ -535,9 +550,13 @@ class TestCrawlerLogging:
|
|||
|
||||
try:
|
||||
configure_logging()
|
||||
assert get_scrapy_root_handler().level == logging.DEBUG
|
||||
handler = get_scrapy_root_handler()
|
||||
assert handler is not None
|
||||
assert handler.level == logging.DEBUG
|
||||
crawler = get_crawler(MySpider)
|
||||
assert get_scrapy_root_handler().level == logging.INFO
|
||||
handler = get_scrapy_root_handler()
|
||||
assert handler is not None
|
||||
assert handler.level == logging.INFO
|
||||
await crawler.crawl_async()
|
||||
finally:
|
||||
_uninstall_scrapy_root_handler()
|
||||
|
|
@ -549,12 +568,13 @@ class TestCrawlerLogging:
|
|||
assert "info message" in logged
|
||||
assert "warning message" in logged
|
||||
assert "error message" in logged
|
||||
assert crawler.stats
|
||||
assert crawler.stats.get_value("log_count/ERROR") == 1
|
||||
assert crawler.stats.get_value("log_count/WARNING") == 1
|
||||
assert info_count == 1
|
||||
assert crawler.stats.get_value("log_count/DEBUG", 0) == 0
|
||||
|
||||
def test_spider_custom_settings_log_append(self, tmp_path):
|
||||
def test_spider_custom_settings_log_append(self, tmp_path: Path) -> None:
|
||||
log_file = Path(tmp_path, "log.txt")
|
||||
log_file.write_text("previous message\n", encoding="utf-8")
|
||||
|
||||
|
|
@ -579,12 +599,12 @@ class TestCrawlerLogging:
|
|||
|
||||
|
||||
class SpiderLoaderWithWrongInterface:
|
||||
def unneeded_method(self):
|
||||
def unneeded_method(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class TestCrawlerRunner(TestBaseCrawler):
|
||||
def test_spider_manager_verify_interface(self):
|
||||
def test_spider_manager_verify_interface(self) -> None:
|
||||
settings = Settings(
|
||||
{
|
||||
"SPIDER_LOADER_CLASS": SpiderLoaderWithWrongInterface,
|
||||
|
|
@ -593,18 +613,18 @@ class TestCrawlerRunner(TestBaseCrawler):
|
|||
with pytest.raises(MultipleInvalid):
|
||||
CrawlerRunner(settings)
|
||||
|
||||
def test_crawler_runner_accepts_dict(self):
|
||||
def test_crawler_runner_accepts_dict(self) -> None:
|
||||
runner = CrawlerRunner({"foo": "bar"})
|
||||
assert runner.settings["foo"] == "bar"
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
def test_crawler_runner_accepts_None(self):
|
||||
def test_crawler_runner_accepts_None(self) -> None:
|
||||
runner = CrawlerRunner()
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
|
||||
class TestAsyncCrawlerRunner(TestBaseCrawler):
|
||||
def test_spider_manager_verify_interface(self):
|
||||
def test_spider_manager_verify_interface(self) -> None:
|
||||
settings = Settings(
|
||||
{
|
||||
"SPIDER_LOADER_CLASS": SpiderLoaderWithWrongInterface,
|
||||
|
|
@ -613,23 +633,23 @@ class TestAsyncCrawlerRunner(TestBaseCrawler):
|
|||
with pytest.raises(MultipleInvalid):
|
||||
AsyncCrawlerRunner(settings)
|
||||
|
||||
def test_crawler_runner_accepts_dict(self):
|
||||
def test_crawler_runner_accepts_dict(self) -> None:
|
||||
runner = AsyncCrawlerRunner({"foo": "bar"})
|
||||
assert runner.settings["foo"] == "bar"
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
def test_crawler_runner_accepts_None(self):
|
||||
def test_crawler_runner_accepts_None(self) -> None:
|
||||
runner = AsyncCrawlerRunner()
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
|
||||
class TestCrawlerProcess(TestBaseCrawler):
|
||||
def test_crawler_process_accepts_dict(self):
|
||||
def test_crawler_process_accepts_dict(self) -> None:
|
||||
runner = CrawlerProcess({"foo": "bar"}, install_root_handler=False)
|
||||
assert runner.settings["foo"] == "bar"
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
def test_crawler_process_accepts_None(self):
|
||||
def test_crawler_process_accepts_None(self) -> None:
|
||||
runner = CrawlerProcess(install_root_handler=False)
|
||||
self.assertOptionIsDefault(runner.settings, "RETRY_ENABLED")
|
||||
|
||||
|
|
@ -668,33 +688,35 @@ class NoRequestsSpider(scrapy.Spider):
|
|||
|
||||
@pytest.mark.requires_reactor # CrawlerRunner requires a reactor
|
||||
class TestCrawlerRunnerHasSpider:
|
||||
@staticmethod
|
||||
def _runner() -> CrawlerRunnerBase:
|
||||
@pytest.fixture
|
||||
def runner(self) -> CrawlerRunnerBase:
|
||||
return CrawlerRunner(get_reactor_settings())
|
||||
|
||||
@staticmethod
|
||||
def _crawl(runner: CrawlerRunnerBase, spider: type[Spider]) -> Deferred[None]:
|
||||
return cast("Deferred[None]", runner.crawl(spider))
|
||||
async def _crawl(runner: CrawlerRunnerBase, spider: type[Spider]) -> None:
|
||||
await ensure_awaitable(runner.crawl(spider))
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_runner_bootstrap_successful(self):
|
||||
runner = self._runner()
|
||||
yield self._crawl(runner, NoRequestsSpider)
|
||||
@coroutine_test
|
||||
async def test_crawler_runner_bootstrap_successful(
|
||||
self, runner: CrawlerRunnerBase
|
||||
) -> None:
|
||||
await self._crawl(runner, NoRequestsSpider)
|
||||
assert not runner.bootstrap_failed
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_runner_bootstrap_successful_for_several(self):
|
||||
runner = self._runner()
|
||||
yield self._crawl(runner, NoRequestsSpider)
|
||||
yield self._crawl(runner, NoRequestsSpider)
|
||||
@coroutine_test
|
||||
async def test_crawler_runner_bootstrap_successful_for_several(
|
||||
self, runner: CrawlerRunnerBase
|
||||
) -> None:
|
||||
await self._crawl(runner, NoRequestsSpider)
|
||||
await self._crawl(runner, NoRequestsSpider)
|
||||
assert not runner.bootstrap_failed
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_runner_bootstrap_failed(self):
|
||||
runner = self._runner()
|
||||
|
||||
@coroutine_test
|
||||
async def test_crawler_runner_bootstrap_failed(
|
||||
self, runner: CrawlerRunnerBase
|
||||
) -> None:
|
||||
try:
|
||||
yield self._crawl(runner, ExceptionSpider)
|
||||
await self._crawl(runner, ExceptionSpider)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
|
|
@ -702,25 +724,25 @@ class TestCrawlerRunnerHasSpider:
|
|||
|
||||
assert runner.bootstrap_failed
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_runner_bootstrap_failed_for_several(self):
|
||||
runner = self._runner()
|
||||
|
||||
@coroutine_test
|
||||
async def test_crawler_runner_bootstrap_failed_for_several(
|
||||
self, runner: CrawlerRunnerBase
|
||||
) -> None:
|
||||
try:
|
||||
yield self._crawl(runner, ExceptionSpider)
|
||||
await self._crawl(runner, ExceptionSpider)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
pytest.fail("Exception should be raised from spider")
|
||||
|
||||
yield self._crawl(runner, NoRequestsSpider)
|
||||
await self._crawl(runner, NoRequestsSpider)
|
||||
|
||||
assert runner.bootstrap_failed
|
||||
|
||||
@inline_callbacks_test
|
||||
def test_crawler_runner_asyncio_enabled_true(
|
||||
@coroutine_test
|
||||
async def test_crawler_runner_asyncio_enabled_true(
|
||||
self, reactor_pytest: str
|
||||
) -> Generator[Deferred[Any], Any, None]:
|
||||
) -> None:
|
||||
if reactor_pytest != "asyncio":
|
||||
runner = CrawlerRunner(
|
||||
settings={
|
||||
|
|
@ -731,7 +753,7 @@ class TestCrawlerRunnerHasSpider:
|
|||
Exception,
|
||||
match=r"The installed reactor \(.*?\) does not match the requested one \(.*?\)",
|
||||
):
|
||||
yield self._crawl(runner, NoRequestsSpider)
|
||||
await self._crawl(runner, NoRequestsSpider)
|
||||
else:
|
||||
CrawlerRunner(
|
||||
settings={
|
||||
|
|
@ -746,11 +768,7 @@ class TestAsyncCrawlerRunnerHasSpider(TestCrawlerRunnerHasSpider):
|
|||
def _runner() -> CrawlerRunnerBase:
|
||||
return AsyncCrawlerRunner(get_reactor_settings())
|
||||
|
||||
@staticmethod
|
||||
def _crawl(runner: CrawlerRunnerBase, spider: type[Spider]) -> Deferred[None]:
|
||||
return deferred_from_coro(runner.crawl(spider))
|
||||
|
||||
def test_crawler_runner_asyncio_enabled_true(self):
|
||||
def test_crawler_runner_asyncio_enabled_true(self) -> None: # type: ignore[override]
|
||||
pytest.skip("This test is only for CrawlerRunner")
|
||||
|
||||
|
||||
|
|
@ -762,7 +780,9 @@ class TestAsyncCrawlerRunnerHasSpider(TestCrawlerRunnerHasSpider):
|
|||
({"LOG_VERSIONS": []}, None),
|
||||
],
|
||||
)
|
||||
def test_log_scrapy_info(settings, items, caplog):
|
||||
def test_log_scrapy_info(
|
||||
settings: dict[str, Any], items: list[str] | None, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
with caplog.at_level("INFO"):
|
||||
CrawlerProcess(settings, install_root_handler=False)
|
||||
assert (
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
with the same file names and expectations.
|
||||
"""
|
||||
|
||||
def test_simple(self):
|
||||
def test_simple(self) -> None:
|
||||
log = self.run_script("simple.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -61,7 +61,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
assert "is_reactorless(): False" in log
|
||||
|
||||
def test_multi(self):
|
||||
def test_multi(self) -> None:
|
||||
log = self.run_script("multi.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -70,7 +70,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
assert "ReactorAlreadyInstalledError" not in log
|
||||
|
||||
def test_reactor_default(self):
|
||||
def test_reactor_default(self) -> None:
|
||||
log = self.run_script("reactor_default.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -78,7 +78,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
"(twisted.internet.asyncioreactor.AsyncioSelectorReactor)"
|
||||
) in log
|
||||
|
||||
def test_asyncio_enabled_no_reactor(self):
|
||||
def test_asyncio_enabled_no_reactor(self) -> None:
|
||||
log = self.run_script("asyncio_enabled_no_reactor.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -87,7 +87,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
assert "RuntimeError" not in log
|
||||
|
||||
def test_asyncio_enabled_reactor(self):
|
||||
def test_asyncio_enabled_reactor(self) -> None:
|
||||
log = self.run_script("asyncio_enabled_reactor.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -100,7 +100,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
parse_version(w3lib_version) >= parse_version("2.0.0"),
|
||||
reason="w3lib 2.0.0 and later do not allow invalid domains.",
|
||||
)
|
||||
def test_ipv6_default_name_resolver(self):
|
||||
def test_ipv6_default_name_resolver(self) -> None:
|
||||
log = self.run_script("default_name_resolver.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -112,7 +112,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
in log
|
||||
)
|
||||
|
||||
def test_caching_hostname_resolver_ipv6(self):
|
||||
def test_caching_hostname_resolver_ipv6(self) -> None:
|
||||
log = self.run_script("caching_hostname_resolver_ipv6.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "scrapy.exceptions.CannotResolveHostError" not in log
|
||||
|
|
@ -126,7 +126,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
assert "TimeoutError" not in log
|
||||
assert "scrapy.exceptions.CannotResolveHostError" not in log
|
||||
|
||||
def test_twisted_reactor_asyncio(self):
|
||||
def test_twisted_reactor_asyncio(self) -> None:
|
||||
log = self.run_script("twisted_reactor_asyncio.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -134,7 +134,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
in log
|
||||
)
|
||||
|
||||
def test_twisted_reactor_asyncio_custom_settings(self):
|
||||
def test_twisted_reactor_asyncio_custom_settings(self) -> None:
|
||||
log = self.run_script("twisted_reactor_custom_settings.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -142,7 +142,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
in log
|
||||
)
|
||||
|
||||
def test_twisted_reactor_asyncio_custom_settings_same(self):
|
||||
def test_twisted_reactor_asyncio_custom_settings_same(self) -> None:
|
||||
log = self.run_script("twisted_reactor_custom_settings_same.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -151,7 +151,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_custom_loop_asyncio(self):
|
||||
def test_custom_loop_asyncio(self) -> None:
|
||||
log = self.run_script("asyncio_custom_loop.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -161,7 +161,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
assert "Using asyncio event loop: uvloop.Loop" in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_custom_loop_asyncio_deferred_signal(self):
|
||||
def test_custom_loop_asyncio_deferred_signal(self) -> None:
|
||||
log = self.run_script("asyncio_deferred_signal.py", "uvloop.Loop")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -172,7 +172,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
assert "async pipeline opened!" in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_asyncio_enabled_reactor_same_loop(self):
|
||||
def test_asyncio_enabled_reactor_same_loop(self) -> None:
|
||||
log = self.run_script("asyncio_enabled_reactor_same_loop.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -182,7 +182,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
assert "Using asyncio event loop: uvloop.Loop" in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_asyncio_enabled_reactor_different_loop(self):
|
||||
def test_asyncio_enabled_reactor_different_loop(self) -> None:
|
||||
log = self.run_script("asyncio_enabled_reactor_different_loop.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -190,7 +190,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
"setting (uvloop.Loop)"
|
||||
) in log
|
||||
|
||||
def test_default_loop_asyncio_deferred_signal(self):
|
||||
def test_default_loop_asyncio_deferred_signal(self) -> None:
|
||||
log = self.run_script("asyncio_deferred_signal.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -200,7 +200,7 @@ class TestCrawlerProcessSubprocessBase(ScriptRunnerMixin):
|
|||
assert "Using asyncio event loop: uvloop.Loop" not in log
|
||||
assert "async pipeline opened!" in log
|
||||
|
||||
def test_args_change_settings(self):
|
||||
def test_args_change_settings(self) -> None:
|
||||
log = self.run_script("args_settings.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "The value of FOO is 42" in log
|
||||
|
|
@ -243,7 +243,7 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
def script_dir(self) -> Path:
|
||||
return self.get_script_dir("CrawlerProcess")
|
||||
|
||||
def test_reactor_default_twisted_reactor_select(self):
|
||||
def test_reactor_default_twisted_reactor_select(self) -> None:
|
||||
log = self.run_script("reactor_default_twisted_reactor_select.py")
|
||||
if platform.system() in ["Windows", "Darwin"]:
|
||||
# The goal of this test function is to test that, when a reactor is
|
||||
|
|
@ -264,7 +264,7 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
"(twisted.internet.selectreactor.SelectReactor)"
|
||||
) in log
|
||||
|
||||
def test_reactor_select(self):
|
||||
def test_reactor_select(self) -> None:
|
||||
log = self.run_script("reactor_select.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -272,12 +272,12 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
"(twisted.internet.asyncioreactor.AsyncioSelectorReactor)"
|
||||
) in log
|
||||
|
||||
def test_reactor_select_twisted_reactor_select(self):
|
||||
def test_reactor_select_twisted_reactor_select(self) -> None:
|
||||
log = self.run_script("reactor_select_twisted_reactor_select.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "ReactorAlreadyInstalledError" not in log
|
||||
|
||||
def test_reactor_select_subclass_twisted_reactor_select(self):
|
||||
def test_reactor_select_subclass_twisted_reactor_select(self) -> None:
|
||||
log = self.run_script("reactor_select_subclass_twisted_reactor_select.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -285,7 +285,7 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
"(twisted.internet.selectreactor.SelectReactor)"
|
||||
) in log
|
||||
|
||||
def test_twisted_reactor_select(self):
|
||||
def test_twisted_reactor_select(self) -> None:
|
||||
log = self.run_script("twisted_reactor_select.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "Using reactor: twisted.internet.selectreactor.SelectReactor" in log
|
||||
|
|
@ -293,12 +293,12 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
@pytest.mark.skipif(
|
||||
platform.system() == "Windows", reason="PollReactor is not supported on Windows"
|
||||
)
|
||||
def test_twisted_reactor_poll(self):
|
||||
def test_twisted_reactor_poll(self) -> None:
|
||||
log = self.run_script("twisted_reactor_poll.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "Using reactor: twisted.internet.pollreactor.PollReactor" in log
|
||||
|
||||
def test_twisted_reactor_asyncio_custom_settings_conflict(self):
|
||||
def test_twisted_reactor_asyncio_custom_settings_conflict(self) -> None:
|
||||
log = self.run_script("twisted_reactor_custom_settings_conflict.py")
|
||||
assert "Using reactor: twisted.internet.selectreactor.SelectReactor" in log
|
||||
assert (
|
||||
|
|
@ -306,7 +306,7 @@ class TestCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
in log
|
||||
)
|
||||
|
||||
def test_reactorless(self):
|
||||
def test_reactorless(self) -> None:
|
||||
log = self.run_script("reactorless.py")
|
||||
assert (
|
||||
"RuntimeError: CrawlerProcess doesn't support TWISTED_REACTOR_ENABLED=False"
|
||||
|
|
@ -319,7 +319,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
def script_dir(self) -> Path:
|
||||
return self.get_script_dir("AsyncCrawlerProcess")
|
||||
|
||||
def test_twisted_reactor_custom_settings_select(self):
|
||||
def test_twisted_reactor_custom_settings_select(self) -> None:
|
||||
log = self.run_script("twisted_reactor_custom_settings_select.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -329,7 +329,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
) in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_asyncio_enabled_reactor_same_loop(self):
|
||||
def test_asyncio_enabled_reactor_same_loop(self) -> None:
|
||||
log = self.run_script("asyncio_custom_loop_custom_settings_same.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -339,7 +339,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
assert "Using asyncio event loop: uvloop.Loop" in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_asyncio_enabled_reactor_different_loop(self):
|
||||
def test_asyncio_enabled_reactor_different_loop(self) -> None:
|
||||
log = self.run_script("asyncio_custom_loop_custom_settings_different.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -347,7 +347,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
"setting (uvloop.Loop)"
|
||||
) in log
|
||||
|
||||
def test_reactorless_simple(self):
|
||||
def test_reactorless_simple(self) -> None:
|
||||
log = self.run_script("reactorless_simple.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
|
|
@ -356,7 +356,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
assert log.count("WARNING: HttpxDownloadHandler is experimental") == 2
|
||||
assert log.count("WARNING: ") == 2
|
||||
|
||||
def test_reactorless_custom_settings(self):
|
||||
def test_reactorless_custom_settings(self) -> None:
|
||||
"""Setting TWISTED_REACTOR_ENABLED=False in spider settings is not
|
||||
currently supported, AsyncCrawlerProcess will install a reactor in this
|
||||
case.
|
||||
|
|
@ -368,7 +368,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
in log
|
||||
)
|
||||
|
||||
def test_reactorless_datauri(self):
|
||||
def test_reactorless_datauri(self) -> None:
|
||||
log = self.run_script("reactorless_datauri.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
|
|
@ -378,13 +378,13 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
assert log.count("WARNING: HttpxDownloadHandler is experimental") == 2
|
||||
assert log.count("WARNING: ") == 2
|
||||
|
||||
def test_reactorless_import_hook(self):
|
||||
def test_reactorless_import_hook(self) -> None:
|
||||
log = self.run_script("reactorless_import_hook.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "ImportError: Import of twisted.internet.reactor is forbidden" in log
|
||||
|
||||
def test_reactorless_telnetconsole_default(self):
|
||||
def test_reactorless_telnetconsole_default(self) -> None:
|
||||
"""By default TWISTED_REACTOR_ENABLED=False silently sets TELNETCONSOLE_ENABLED=False."""
|
||||
log = self.run_script("reactorless_simple.py") # no need for a separate script
|
||||
assert "Not using a Twisted reactor" in log
|
||||
|
|
@ -392,7 +392,7 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
assert "The TelnetConsole extension requires a Twisted reactor" not in log
|
||||
assert "scrapy.extensions.telnet.TelnetConsole" not in log
|
||||
|
||||
def test_reactorless_telnetconsole_disabled(self):
|
||||
def test_reactorless_telnetconsole_disabled(self) -> None:
|
||||
"""Explicit TELNETCONSOLE_ENABLED=False, there are no warnings."""
|
||||
log = self.run_script("reactorless_telnetconsole_disabled.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
|
|
@ -400,14 +400,14 @@ class TestAsyncCrawlerProcessSubprocess(TestCrawlerProcessSubprocessBase):
|
|||
assert "The TelnetConsole extension requires a Twisted reactor" not in log
|
||||
assert "scrapy.extensions.telnet.TelnetConsole" not in log
|
||||
|
||||
def test_reactorless_telnetconsole_enabled(self):
|
||||
def test_reactorless_telnetconsole_enabled(self) -> None:
|
||||
"""Explicit TELNETCONSOLE_ENABLED=True, the user gets a warning."""
|
||||
log = self.run_script("reactorless_telnetconsole_enabled.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
assert "The TelnetConsole extension requires a Twisted reactor" in log
|
||||
|
||||
def test_reactorless_reactor(self):
|
||||
def test_reactorless_reactor(self) -> None:
|
||||
log = self.run_script("reactorless_reactor.py")
|
||||
assert (
|
||||
"RuntimeError: TWISTED_REACTOR_ENABLED is False but a Twisted reactor is installed"
|
||||
|
|
@ -427,7 +427,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
with the same file names and expectations.
|
||||
"""
|
||||
|
||||
def test_simple(self):
|
||||
def test_simple(self) -> None:
|
||||
log = self.run_script("simple.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -436,7 +436,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
assert "is_reactorless(): False" in log
|
||||
|
||||
def test_multi_parallel(self):
|
||||
def test_multi_parallel(self) -> None:
|
||||
log = self.run_script("multi_parallel.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -449,7 +449,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
re.DOTALL,
|
||||
)
|
||||
|
||||
def test_multi_seq(self):
|
||||
def test_multi_seq(self) -> None:
|
||||
log = self.run_script("multi_seq.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -463,7 +463,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
)
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_custom_loop_same(self):
|
||||
def test_custom_loop_same(self) -> None:
|
||||
log = self.run_script("custom_loop_same.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -473,7 +473,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
assert "Using asyncio event loop: uvloop.Loop" in log
|
||||
|
||||
@pytest.mark.requires_uvloop
|
||||
def test_custom_loop_different(self):
|
||||
def test_custom_loop_different(self) -> None:
|
||||
log = self.run_script("custom_loop_different.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -481,7 +481,7 @@ class TestCrawlerRunnerSubprocessBase(ScriptRunnerMixin):
|
|||
"setting (uvloop.Loop)"
|
||||
) in log
|
||||
|
||||
def test_no_reactor(self):
|
||||
def test_no_reactor(self) -> None:
|
||||
log = self.run_script("no_reactor.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -495,7 +495,7 @@ class TestCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
def script_dir(self) -> Path:
|
||||
return self.get_script_dir("CrawlerRunner")
|
||||
|
||||
def test_explicit_default_reactor(self):
|
||||
def test_explicit_default_reactor(self) -> None:
|
||||
log = self.run_script("explicit_default_reactor.py")
|
||||
assert "Spider closed (finished)" in log
|
||||
assert (
|
||||
|
|
@ -503,14 +503,14 @@ class TestCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
not in log
|
||||
)
|
||||
|
||||
def test_response_ip_address(self):
|
||||
def test_response_ip_address(self) -> None:
|
||||
log = self.run_script("ip_address.py")
|
||||
assert "INFO: Spider closed (finished)" in log
|
||||
assert "INFO: Host: not.a.real.domain" in log
|
||||
assert "INFO: Type: <class 'ipaddress.IPv4Address'>" in log
|
||||
assert "INFO: IP address: 127.0.0.1" in log
|
||||
|
||||
def test_change_default_reactor(self):
|
||||
def test_change_default_reactor(self) -> None:
|
||||
log = self.run_script("change_reactor.py")
|
||||
assert (
|
||||
"DEBUG: Using reactor: twisted.internet.asyncioreactor.AsyncioSelectorReactor"
|
||||
|
|
@ -518,7 +518,7 @@ class TestCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
)
|
||||
assert "DEBUG: Using asyncio event loop" in log
|
||||
|
||||
def test_reactorless(self):
|
||||
def test_reactorless(self) -> None:
|
||||
log = self.run_script("reactorless.py")
|
||||
assert (
|
||||
"RuntimeError: CrawlerRunner doesn't support TWISTED_REACTOR_ENABLED=False"
|
||||
|
|
@ -531,7 +531,7 @@ class TestAsyncCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
def script_dir(self) -> Path:
|
||||
return self.get_script_dir("AsyncCrawlerRunner")
|
||||
|
||||
def test_simple_default_reactor(self):
|
||||
def test_simple_default_reactor(self) -> None:
|
||||
log = self.run_script("simple_default_reactor.py")
|
||||
assert "Spider closed (finished)" not in log
|
||||
assert (
|
||||
|
|
@ -539,7 +539,7 @@ class TestAsyncCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
"AsyncCrawlerRunner requires that the installed Twisted reactor"
|
||||
) in log
|
||||
|
||||
def test_reactorless_simple(self):
|
||||
def test_reactorless_simple(self) -> None:
|
||||
log = self.run_script("reactorless_simple.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
|
|
@ -548,7 +548,7 @@ class TestAsyncCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
assert log.count("WARNING: HttpxDownloadHandler is experimental") == 2
|
||||
assert log.count("WARNING: ") == 2
|
||||
|
||||
def test_reactorless_custom_settings(self):
|
||||
def test_reactorless_custom_settings(self) -> None:
|
||||
"""Setting TWISTED_REACTOR_ENABLED=False in spider settings is not
|
||||
currently supported, AsyncCrawlerRunner will expect a reactor installed
|
||||
by the user.
|
||||
|
|
@ -557,7 +557,7 @@ class TestAsyncCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
assert "Spider closed (finished)" not in log
|
||||
assert "We expected a Twisted reactor to be installed but it isn't." in log
|
||||
|
||||
def test_reactorless_datauri(self):
|
||||
def test_reactorless_datauri(self) -> None:
|
||||
log = self.run_script("reactorless_datauri.py")
|
||||
assert "Not using a Twisted reactor" in log
|
||||
assert "Spider closed (finished)" in log
|
||||
|
|
@ -567,7 +567,7 @@ class TestAsyncCrawlerRunnerSubprocess(TestCrawlerRunnerSubprocessBase):
|
|||
assert log.count("WARNING: HttpxDownloadHandler is experimental") == 2
|
||||
assert log.count("WARNING: ") == 2
|
||||
|
||||
def test_reactorless_reactor(self):
|
||||
def test_reactorless_reactor(self) -> None:
|
||||
log = self.run_script("reactorless_reactor.py")
|
||||
assert (
|
||||
"RuntimeError: TWISTED_REACTOR_ENABLED is False but a Twisted reactor is installed"
|
||||
|
|
|
|||
Loading…
Reference in New Issue