mirror of https://github.com/scrapy/scrapy.git
401 lines
13 KiB
Python
401 lines
13 KiB
Python
import re
|
|
import warnings
|
|
from asyncio import sleep
|
|
from logging import ERROR
|
|
|
|
import pytest
|
|
from testfixtures import LogCapture
|
|
from twisted.trial.unittest import TestCase
|
|
|
|
from scrapy import Spider, signals
|
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
|
from scrapy.utils.defer import deferred_f_from_coro_f, maybe_deferred_to_future
|
|
from scrapy.utils.test import get_crawler
|
|
from tests.test_spider_start import SLEEP_SECONDS
|
|
|
|
from .utils import twisted_sleep
|
|
|
|
ITEM_A = {"id": "a"}
|
|
ITEM_B = {"id": "b"}
|
|
ITEM_C = {"id": "c"}
|
|
ITEM_D = {"id": "d"}
|
|
|
|
|
|
class AsyncioSleepSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
await sleep(SLEEP_SECONDS)
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
|
|
|
|
class NoOpSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
|
|
|
|
class TwistedSleepSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
await maybe_deferred_to_future(twisted_sleep(SLEEP_SECONDS))
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
|
|
|
|
class UniversalSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
|
|
def process_start_requests(self, start_requests, spider):
|
|
raise NotImplementedError
|
|
|
|
|
|
# Spiders and spider middlewares for MainTestCase._test_wrap
|
|
|
|
|
|
class ModernWrapSpider(Spider):
|
|
name = "test"
|
|
|
|
async def start(self):
|
|
yield ITEM_B
|
|
|
|
|
|
class ModernWrapSpiderSubclass(ModernWrapSpider):
|
|
name = "test"
|
|
|
|
|
|
class UniversalWrapSpider(Spider):
|
|
name = "test"
|
|
|
|
async def start(self):
|
|
yield ITEM_B
|
|
|
|
def start_requests(self):
|
|
yield ITEM_D
|
|
|
|
|
|
class DeprecatedWrapSpider(Spider):
|
|
name = "test"
|
|
|
|
def start_requests(self):
|
|
yield ITEM_B
|
|
|
|
|
|
class ModernWrapSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
yield ITEM_A
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
yield ITEM_C
|
|
|
|
|
|
class UniversalWrapSpiderMiddleware:
|
|
async def process_start(self, start):
|
|
yield ITEM_A
|
|
async for item_or_request in start:
|
|
yield item_or_request
|
|
yield ITEM_C
|
|
|
|
def process_start_requests(self, start, spider):
|
|
yield ITEM_A
|
|
yield from start
|
|
yield ITEM_C
|
|
|
|
|
|
class DeprecatedWrapSpiderMiddleware:
|
|
def process_start_requests(self, start, spider):
|
|
yield ITEM_A
|
|
yield from start
|
|
yield ITEM_C
|
|
|
|
|
|
class MainTestCase(TestCase):
|
|
# Helper methods.
|
|
|
|
async def _test(self, spider_middlewares, spider_cls, expected_items):
|
|
actual_items = []
|
|
|
|
def track_item(item, response, spider):
|
|
actual_items.append(item)
|
|
|
|
settings = {
|
|
"SPIDER_MIDDLEWARES": {cls: n for n, cls in enumerate(spider_middlewares)},
|
|
}
|
|
crawler = get_crawler(spider_cls, settings_dict=settings)
|
|
crawler.signals.connect(track_item, signals.item_scraped)
|
|
await maybe_deferred_to_future(crawler.crawl())
|
|
assert crawler.stats.get_value("finish_reason") == "finished"
|
|
assert actual_items == expected_items, f"{actual_items=} != {expected_items=}"
|
|
|
|
async def _test_wrap(self, spider_middleware, spider_cls, expected_items=None):
|
|
expected_items = (
|
|
expected_items if expected_items is not None else [ITEM_A, ITEM_B, ITEM_C]
|
|
)
|
|
await self._test([spider_middleware], spider_cls, expected_items)
|
|
|
|
async def _test_douple_wrap(self, smw1, smw2, spider_cls, expected_items=None):
|
|
expected_items = (
|
|
expected_items
|
|
if expected_items is not None
|
|
else [ITEM_A, ITEM_A, ITEM_B, ITEM_C, ITEM_C]
|
|
)
|
|
await self._test([smw1, smw2], spider_cls, expected_items)
|
|
|
|
async def _test_process_start(self, process_start_fn, expected_items=None):
|
|
class TestSpiderMiddleware:
|
|
process_start = process_start_fn
|
|
|
|
class TestSpider(Spider):
|
|
name = "test"
|
|
|
|
await self._test([TestSpiderMiddleware], TestSpider, expected_items)
|
|
|
|
# Deprecation and universal.
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_modern_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_wrap(ModernWrapSpiderMiddleware, ModernWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_universal_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_wrap(ModernWrapSpiderMiddleware, UniversalWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_deprecated_spider(self):
|
|
with pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated start_requests\(\)"
|
|
):
|
|
await self._test_wrap(ModernWrapSpiderMiddleware, DeprecatedWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_modern_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_wrap(UniversalWrapSpiderMiddleware, ModernWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_universal_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_wrap(UniversalWrapSpiderMiddleware, UniversalWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_deprecated_spider(self):
|
|
with pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated start_requests\(\)"
|
|
):
|
|
await self._test_wrap(UniversalWrapSpiderMiddleware, DeprecatedWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_deprecated_mw_modern_spider(self):
|
|
with (
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
),
|
|
LogCapture(level=ERROR) as log,
|
|
):
|
|
await self._test_wrap(DeprecatedWrapSpiderMiddleware, ModernWrapSpider, [])
|
|
assert "To solve this issue" in str(log), log
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_deprecated_mw_modern_spider_subclass(self):
|
|
with (
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
),
|
|
LogCapture(level=ERROR) as log,
|
|
):
|
|
await self._test_wrap(
|
|
DeprecatedWrapSpiderMiddleware, ModernWrapSpiderSubclass, []
|
|
)
|
|
assert re.search(
|
|
r"\S+?\.ModernWrapSpider \(inherited by \S+?.ModernWrapSpiderSubclass\) .*? only compatible with \(deprecated\) spiders",
|
|
str(log),
|
|
), log
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_deprecated_mw_universal_spider(self):
|
|
with pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
):
|
|
await self._test_wrap(
|
|
DeprecatedWrapSpiderMiddleware,
|
|
UniversalWrapSpider,
|
|
[ITEM_A, ITEM_D, ITEM_C],
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_deprecated_mw_deprecated_spider(self):
|
|
with (
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
),
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated start_requests\(\)"
|
|
),
|
|
):
|
|
await self._test_wrap(DeprecatedWrapSpiderMiddleware, DeprecatedWrapSpider)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_universal_mw_modern_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
UniversalWrapSpiderMiddleware,
|
|
ModernWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_deprecated_mw_modern_spider(self):
|
|
with pytest.raises(ValueError, match=r"trying to combine spider middlewares"):
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
ModernWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_deprecated_mw_modern_spider(self):
|
|
with (
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
),
|
|
LogCapture(level=ERROR) as log,
|
|
):
|
|
await self._test_douple_wrap(
|
|
UniversalWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
ModernWrapSpider,
|
|
[],
|
|
)
|
|
assert re.search(r"only compatible with \(deprecated\) spiders", str(log)), log
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_universal_mw_universal_spider(self):
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
UniversalWrapSpiderMiddleware,
|
|
UniversalWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_deprecated_mw_universal_spider(self):
|
|
with pytest.raises(ValueError, match=r"trying to combine spider middlewares"):
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
UniversalWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_deprecated_mw_universal_spider(self):
|
|
with pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
):
|
|
await self._test_douple_wrap(
|
|
UniversalWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
UniversalWrapSpider,
|
|
[ITEM_A, ITEM_A, ITEM_D, ITEM_C, ITEM_C],
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_universal_mw_deprecated_spider(self):
|
|
with pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated start_requests\(\)"
|
|
):
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
UniversalWrapSpiderMiddleware,
|
|
DeprecatedWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_modern_mw_deprecated_mw_deprecated_spider(self):
|
|
with pytest.raises(ValueError, match=r"trying to combine spider middlewares"):
|
|
await self._test_douple_wrap(
|
|
ModernWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
DeprecatedWrapSpider,
|
|
)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_universal_mw_deprecated_mw_deprecated_spider(self):
|
|
with (
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated process_start_requests\(\)"
|
|
),
|
|
pytest.warns(
|
|
ScrapyDeprecationWarning, match=r"deprecated start_requests\(\)"
|
|
),
|
|
):
|
|
await self._test_douple_wrap(
|
|
UniversalWrapSpiderMiddleware,
|
|
DeprecatedWrapSpiderMiddleware,
|
|
DeprecatedWrapSpider,
|
|
)
|
|
|
|
# Bad definitions.
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_async_function(self):
|
|
async def process_start(mw, start):
|
|
return
|
|
|
|
with LogCapture() as log:
|
|
await self._test_process_start(process_start, [])
|
|
|
|
assert ".process_start must be an asynchronous generator" in str(log), log
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_sync_function(self):
|
|
def process_start(mw, spider):
|
|
return []
|
|
|
|
with LogCapture() as log:
|
|
await self._test_process_start(process_start, [])
|
|
|
|
assert ".process_start must be an asynchronous generator" in str(log)
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_sync_generator(self):
|
|
def process_start(mw, spider):
|
|
return
|
|
yield
|
|
|
|
with LogCapture() as log:
|
|
await self._test_process_start(process_start, [])
|
|
|
|
assert ".process_start must be an asynchronous generator" in str(log)
|
|
|
|
# Exceptions during iteration.
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_exception_before_yield(self):
|
|
async def process_start(mw, start):
|
|
raise RuntimeError
|
|
yield # pylint: disable=unreachable
|
|
|
|
with LogCapture() as log:
|
|
await self._test_process_start(process_start, [])
|
|
|
|
assert "in process_start\n raise RuntimeError" in str(log), log
|
|
|
|
@deferred_f_from_coro_f
|
|
async def test_exception_after_yield(self):
|
|
async def process_start(mw, spider):
|
|
yield ITEM_A
|
|
raise RuntimeError
|
|
|
|
with LogCapture() as log:
|
|
await self._test_process_start(process_start, [ITEM_A])
|
|
|
|
assert "in process_start\n raise RuntimeError" in str(log), log
|