mirror of https://github.com/scrapy/scrapy.git
Merge 3ea86df16f into c9446931a8
This commit is contained in:
commit
246f964e6c
|
|
@ -3,6 +3,28 @@
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Scrapy VERSION (unreleased)
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Backward-incompatible changes
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- The following runtime usage of zope.interface_ interfaces is removed:
|
||||||
|
|
||||||
|
- :class:`~scrapy.spiderloader.SpiderLoader` and
|
||||||
|
:class:`~scrapy.spiderloader.DummySpiderLoader` are no longer marked
|
||||||
|
as implementing the ``ISpiderLoader`` interface.
|
||||||
|
|
||||||
|
- :func:`~scrapy.spiderloader.get_spider_loader` no longer checks that the
|
||||||
|
configured spider loader implements the ``ISpiderLoader`` interface.
|
||||||
|
|
||||||
|
- :class:`~scrapy.extensions.feedexport.BlockingFeedStorage`,
|
||||||
|
- :class:`~scrapy.extensions.feedexport.FileFeedStorage` and
|
||||||
|
- :class:`~scrapy.extensions.feedexport.StdoutFeedStorage` are no longer
|
||||||
|
marked as implementing the ``IFeedStorage`` interface.
|
||||||
|
|
||||||
|
(:issue:`6585`, :issue:`TBD`)
|
||||||
|
|
||||||
.. _release-2.17.0:
|
.. _release-2.17.0:
|
||||||
|
|
||||||
Scrapy 2.17.0 (2026-07-07)
|
Scrapy 2.17.0 (2026-07-07)
|
||||||
|
|
@ -2643,7 +2665,7 @@ Deprecation removals
|
||||||
(:issue:`6109`, :issue:`6116`)
|
(:issue:`6109`, :issue:`6116`)
|
||||||
|
|
||||||
- A custom class assigned to the :setting:`SPIDER_LOADER_CLASS` setting that
|
- A custom class assigned to the :setting:`SPIDER_LOADER_CLASS` setting that
|
||||||
does not implement the :class:`~scrapy.interfaces.ISpiderLoader` interface
|
does not implement the ``ISpiderLoader`` interface
|
||||||
will now raise a :exc:`zope.interface.verify.DoesNotImplement` exception at
|
will now raise a :exc:`zope.interface.verify.DoesNotImplement` exception at
|
||||||
run time. Non-compliant classes have been triggering a deprecation warning
|
run time. Non-compliant classes have been triggering a deprecation warning
|
||||||
since Scrapy 1.0.0.
|
since Scrapy 1.0.0.
|
||||||
|
|
|
||||||
|
|
@ -178,9 +178,7 @@ SpiderLoader API
|
||||||
defined across the project.
|
defined across the project.
|
||||||
|
|
||||||
Custom spider loaders can be employed by specifying their path in the
|
Custom spider loaders can be employed by specifying their path in the
|
||||||
:setting:`SPIDER_LOADER_CLASS` project setting. They must fully implement
|
:setting:`SPIDER_LOADER_CLASS` project setting.
|
||||||
the :class:`scrapy.interfaces.ISpiderLoader` interface to guarantee an
|
|
||||||
errorless execution.
|
|
||||||
|
|
||||||
.. method:: from_settings(settings)
|
.. method:: from_settings(settings)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from twisted.internet.ssl import (
|
||||||
from twisted.web.client import BrowserLikePolicyForHTTPS
|
from twisted.web.client import BrowserLikePolicyForHTTPS
|
||||||
from twisted.web.iweb import IPolicyForHTTPS
|
from twisted.web.iweb import IPolicyForHTTPS
|
||||||
from zope.interface.declarations import implementer
|
from zope.interface.declarations import implementer
|
||||||
from zope.interface.verify import verifyObject
|
|
||||||
|
|
||||||
from scrapy.core.downloader.tls import (
|
from scrapy.core.downloader.tls import (
|
||||||
_TWISTED_VERSION_MAP,
|
_TWISTED_VERSION_MAP,
|
||||||
|
|
@ -232,7 +231,6 @@ class _AcceptableProtocolsContextFactory:
|
||||||
# all of this with _ScrapyClientContextFactory.acceptableProtocols.
|
# all of this with _ScrapyClientContextFactory.acceptableProtocols.
|
||||||
|
|
||||||
def __init__(self, context_factory: Any, acceptable_protocols: list[bytes]):
|
def __init__(self, context_factory: Any, acceptable_protocols: list[bytes]):
|
||||||
verifyObject(IPolicyForHTTPS, context_factory)
|
|
||||||
self._wrapped_context_factory: Any = context_factory
|
self._wrapped_context_factory: Any = context_factory
|
||||||
self._acceptable_protocols: list[bytes] = acceptable_protocols
|
self._acceptable_protocols: list[bytes] = acceptable_protocols
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
from twisted.internet.defer import Deferred, DeferredList
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
from w3lib.url import file_uri_to_path
|
from w3lib.url import file_uri_to_path
|
||||||
from zope.interface import Interface, implementer
|
from zope.interface import Interface
|
||||||
|
|
||||||
from scrapy import Spider, signals
|
from scrapy import Spider, signals
|
||||||
from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
|
from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning
|
||||||
|
|
@ -88,25 +88,18 @@ class ItemFilter:
|
||||||
return True # accept all items by default
|
return True # accept all items by default
|
||||||
|
|
||||||
|
|
||||||
class IFeedStorage(Interface): # type: ignore[misc]
|
class _IFeedStorage(Interface): # type: ignore[misc] # pragma: no cover
|
||||||
"""Interface that all Feed Storages must implement"""
|
|
||||||
|
|
||||||
# pylint: disable=no-self-argument
|
# pylint: disable=no-self-argument
|
||||||
|
|
||||||
def __init__(uri, *, feed_options=None): # type: ignore[no-untyped-def] # pylint: disable=super-init-not-called
|
def __init__(uri, *, feed_options=None): ... # type: ignore[no-untyped-def] # pylint: disable=super-init-not-called
|
||||||
"""Initialize the storage with the parameters given in the URI and the
|
|
||||||
feed-specific options (see :setting:`FEEDS`)"""
|
|
||||||
|
|
||||||
def open(spider): # type: ignore[no-untyped-def]
|
def open(spider): ... # type: ignore[no-untyped-def]
|
||||||
"""Open the storage for the given spider. It must return a file-like
|
|
||||||
object that will be used for the exporters"""
|
|
||||||
|
|
||||||
def store(file): # type: ignore[no-untyped-def]
|
def store(file): ... # type: ignore[no-untyped-def]
|
||||||
"""Store the given file stream"""
|
|
||||||
|
|
||||||
|
|
||||||
class FeedStorageProtocol(Protocol):
|
class FeedStorageProtocol(Protocol):
|
||||||
"""Reimplementation of ``IFeedStorage`` that can be used in type hints."""
|
"""Protocol that all Feed Storages must follow."""
|
||||||
|
|
||||||
def __init__(self, uri: str, *, feed_options: dict[str, Any] | None = None):
|
def __init__(self, uri: str, *, feed_options: dict[str, Any] | None = None):
|
||||||
"""Initialize the storage with the parameters given in the URI and the
|
"""Initialize the storage with the parameters given in the URI and the
|
||||||
|
|
@ -120,7 +113,6 @@ class FeedStorageProtocol(Protocol):
|
||||||
"""Store the given file stream"""
|
"""Store the given file stream"""
|
||||||
|
|
||||||
|
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class BlockingFeedStorage(ABC):
|
class BlockingFeedStorage(ABC):
|
||||||
def open(self, spider: Spider) -> IO[bytes]:
|
def open(self, spider: Spider) -> IO[bytes]:
|
||||||
path = spider.crawler.settings["FEED_TEMPDIR"]
|
path = spider.crawler.settings["FEED_TEMPDIR"]
|
||||||
|
|
@ -137,7 +129,6 @@ class BlockingFeedStorage(ABC):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class StdoutFeedStorage:
|
class StdoutFeedStorage:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -164,7 +155,6 @@ class StdoutFeedStorage:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class FileFeedStorage:
|
class FileFeedStorage:
|
||||||
def __init__(self, uri: str, *, feed_options: dict[str, Any] | None = None):
|
def __init__(self, uri: str, *, feed_options: dict[str, Any] | None = None):
|
||||||
self.path: str = file_uri_to_path(uri) if uri.startswith("file:") else uri
|
self.path: str = file_uri_to_path(uri) if uri.startswith("file:") else uri
|
||||||
|
|
@ -731,3 +721,14 @@ class FeedExporter:
|
||||||
feed_options.get("item_filter", ItemFilter)
|
feed_options.get("item_filter", ItemFilter)
|
||||||
)
|
)
|
||||||
return item_filter_class(feed_options)
|
return item_filter_class(feed_options)
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(name: str) -> Any: # pragma: no cover
|
||||||
|
if name == "IFeedStorage":
|
||||||
|
warnings.warn(
|
||||||
|
"scrapy.extensions.feedexport.IFeedStorage is deprecated.",
|
||||||
|
ScrapyDeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return _IFeedStorage
|
||||||
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
|
# pragma: no file cover
|
||||||
# pylint: disable=no-method-argument,no-self-argument
|
# pylint: disable=no-method-argument,no-self-argument
|
||||||
|
import warnings
|
||||||
|
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
|
|
||||||
|
from scrapy.exceptions import ScrapyDeprecationWarning
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"The scrapy.interfaces module is deprecated.",
|
||||||
|
ScrapyDeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ISpiderLoader(Interface):
|
class ISpiderLoader(Interface):
|
||||||
def from_settings(settings):
|
def from_settings(settings): ...
|
||||||
"""Return an instance of the class for the given settings"""
|
|
||||||
|
|
||||||
def load(spider_name):
|
def load(spider_name): ...
|
||||||
"""Return the Spider class for the given spider name. If the spider
|
|
||||||
name is not found, it must raise a KeyError."""
|
|
||||||
|
|
||||||
def list():
|
def list(): ...
|
||||||
"""Return a list with the names of all spiders available in the
|
|
||||||
project"""
|
|
||||||
|
|
||||||
def find_by_request(request):
|
def find_by_request(request): ...
|
||||||
"""Return the list of spiders names that can handle the given request"""
|
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@ import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING, Protocol, cast
|
from typing import TYPE_CHECKING, Protocol, cast
|
||||||
|
|
||||||
from zope.interface import implementer
|
|
||||||
from zope.interface.verify import verifyClass
|
|
||||||
|
|
||||||
from scrapy.interfaces import ISpiderLoader
|
|
||||||
from scrapy.utils.misc import load_object, walk_modules_iter
|
from scrapy.utils.misc import load_object, walk_modules_iter
|
||||||
from scrapy.utils.spider import iter_spider_classes
|
from scrapy.utils.spider import iter_spider_classes
|
||||||
|
|
||||||
|
|
@ -26,7 +22,6 @@ def get_spider_loader(settings: BaseSettings) -> SpiderLoaderProtocol:
|
||||||
"""Get SpiderLoader instance from settings"""
|
"""Get SpiderLoader instance from settings"""
|
||||||
cls_path = settings.get("SPIDER_LOADER_CLASS")
|
cls_path = settings.get("SPIDER_LOADER_CLASS")
|
||||||
loader_cls = load_object(cls_path)
|
loader_cls = load_object(cls_path)
|
||||||
verifyClass(ISpiderLoader, loader_cls)
|
|
||||||
return cast("SpiderLoaderProtocol", loader_cls.from_settings(settings.frozencopy()))
|
return cast("SpiderLoaderProtocol", loader_cls.from_settings(settings.frozencopy()))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,7 +42,6 @@ class SpiderLoaderProtocol(Protocol):
|
||||||
"""Return the list of spiders names that can handle the given request"""
|
"""Return the list of spiders names that can handle the given request"""
|
||||||
|
|
||||||
|
|
||||||
@implementer(ISpiderLoader)
|
|
||||||
class SpiderLoader:
|
class SpiderLoader:
|
||||||
"""
|
"""
|
||||||
SpiderLoader is a class which locates and loads spiders
|
SpiderLoader is a class which locates and loads spiders
|
||||||
|
|
@ -133,7 +127,6 @@ class SpiderLoader:
|
||||||
return list(self._spiders.keys())
|
return list(self._spiders.keys())
|
||||||
|
|
||||||
|
|
||||||
@implementer(ISpiderLoader)
|
|
||||||
class DummySpiderLoader:
|
class DummySpiderLoader:
|
||||||
"""A dummy spider loader that does not load any spiders."""
|
"""A dummy spider loader that does not load any spiders."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Any, ClassVar
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zope.interface.exceptions import MultipleInvalid
|
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy import Spider
|
from scrapy import Spider
|
||||||
|
|
@ -586,21 +585,7 @@ class TestCrawlerLogging:
|
||||||
assert "debug message" in logged
|
assert "debug message" in logged
|
||||||
|
|
||||||
|
|
||||||
class SpiderLoaderWithWrongInterface:
|
|
||||||
def unneeded_method(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TestCrawlerRunner(TestBaseCrawler):
|
class TestCrawlerRunner(TestBaseCrawler):
|
||||||
def test_spider_manager_verify_interface(self) -> None:
|
|
||||||
settings = Settings(
|
|
||||||
{
|
|
||||||
"SPIDER_LOADER_CLASS": SpiderLoaderWithWrongInterface,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
with pytest.raises(MultipleInvalid):
|
|
||||||
CrawlerRunner(settings)
|
|
||||||
|
|
||||||
def test_crawler_runner_accepts_dict(self) -> None:
|
def test_crawler_runner_accepts_dict(self) -> None:
|
||||||
runner = CrawlerRunner({"foo": "bar"})
|
runner = CrawlerRunner({"foo": "bar"})
|
||||||
assert runner.settings["foo"] == "bar"
|
assert runner.settings["foo"] == "bar"
|
||||||
|
|
@ -612,15 +597,6 @@ class TestCrawlerRunner(TestBaseCrawler):
|
||||||
|
|
||||||
|
|
||||||
class TestAsyncCrawlerRunner(TestBaseCrawler):
|
class TestAsyncCrawlerRunner(TestBaseCrawler):
|
||||||
def test_spider_manager_verify_interface(self) -> None:
|
|
||||||
settings = Settings(
|
|
||||||
{
|
|
||||||
"SPIDER_LOADER_CLASS": SpiderLoaderWithWrongInterface,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
with pytest.raises(MultipleInvalid):
|
|
||||||
AsyncCrawlerRunner(settings)
|
|
||||||
|
|
||||||
def test_crawler_runner_accepts_dict(self) -> None:
|
def test_crawler_runner_accepts_dict(self) -> None:
|
||||||
runner = AsyncCrawlerRunner({"foo": "bar"})
|
runner = AsyncCrawlerRunner({"foo": "bar"})
|
||||||
assert runner.settings["foo"] == "bar"
|
assert runner.settings["foo"] == "bar"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import lxml.etree
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from w3lib.url import file_uri_to_path
|
from w3lib.url import file_uri_to_path
|
||||||
from zope.interface import implementer
|
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy import Spider, signals
|
from scrapy import Spider, signals
|
||||||
|
|
@ -31,7 +30,6 @@ from scrapy.extensions.feedexport import (
|
||||||
FeedExporter,
|
FeedExporter,
|
||||||
FeedSlot,
|
FeedSlot,
|
||||||
FileFeedStorage,
|
FileFeedStorage,
|
||||||
IFeedStorage,
|
|
||||||
)
|
)
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.utils.python import to_unicode
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
@ -91,7 +89,6 @@ class FailingBlockingFeedStorage(DummyBlockingFeedStorage):
|
||||||
raise OSError("Cannot store")
|
raise OSError("Cannot store")
|
||||||
|
|
||||||
|
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class LogOnStoreFileStorage:
|
class LogOnStoreFileStorage:
|
||||||
"""
|
"""
|
||||||
This storage logs inside `store` method.
|
This storage logs inside `store` method.
|
||||||
|
|
@ -1234,7 +1231,6 @@ class TestFeedExport(TestFeedExportBase):
|
||||||
|
|
||||||
@coroutine_test
|
@coroutine_test
|
||||||
async def test_storage_file_no_postprocessing(self):
|
async def test_storage_file_no_postprocessing(self):
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class Storage:
|
class Storage:
|
||||||
def __init__(self, uri, *, feed_options=None):
|
def __init__(self, uri, *, feed_options=None):
|
||||||
pass
|
pass
|
||||||
|
|
@ -1256,7 +1252,6 @@ class TestFeedExport(TestFeedExportBase):
|
||||||
|
|
||||||
@coroutine_test
|
@coroutine_test
|
||||||
async def test_storage_file_postprocessing(self):
|
async def test_storage_file_postprocessing(self):
|
||||||
@implementer(IFeedStorage)
|
|
||||||
class Storage:
|
class Storage:
|
||||||
def __init__(self, uri, *, feed_options=None):
|
def __init__(self, uri, *, feed_options=None):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ from urllib.parse import urljoin
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
from zope.interface.verify import verifyObject
|
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy import Spider
|
from scrapy import Spider
|
||||||
from scrapy.exceptions import NotConfigured
|
from scrapy.exceptions import NotConfigured
|
||||||
from scrapy.extensions.feedexport import FeedExporter, IFeedStorage, S3FeedStorage
|
from scrapy.extensions.feedexport import FeedExporter, S3FeedStorage
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.utils.python import to_unicode
|
from scrapy.utils.python import to_unicode
|
||||||
from scrapy.utils.test import get_crawler
|
from scrapy.utils.test import get_crawler
|
||||||
|
|
@ -435,9 +434,6 @@ class TestBatchDeliveries(TestFeedExportBase):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
crawler = get_crawler(settings_dict=settings)
|
|
||||||
storage = S3FeedStorage.from_crawler(crawler, uri)
|
|
||||||
verifyObject(IFeedStorage, storage)
|
|
||||||
|
|
||||||
class TestSpider(scrapy.Spider):
|
class TestSpider(scrapy.Spider):
|
||||||
name = "testspider"
|
name = "testspider"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ from urllib.parse import quote
|
||||||
import pytest
|
import pytest
|
||||||
from testfixtures import LogCapture
|
from testfixtures import LogCapture
|
||||||
from w3lib.url import path_to_file_uri
|
from w3lib.url import path_to_file_uri
|
||||||
from zope.interface.verify import verifyObject
|
|
||||||
|
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy.extensions.feedexport import (
|
from scrapy.extensions.feedexport import (
|
||||||
|
|
@ -20,7 +19,6 @@ from scrapy.extensions.feedexport import (
|
||||||
FileFeedStorage,
|
FileFeedStorage,
|
||||||
FTPFeedStorage,
|
FTPFeedStorage,
|
||||||
GCSFeedStorage,
|
GCSFeedStorage,
|
||||||
IFeedStorage,
|
|
||||||
S3FeedStorage,
|
S3FeedStorage,
|
||||||
StdoutFeedStorage,
|
StdoutFeedStorage,
|
||||||
)
|
)
|
||||||
|
|
@ -71,11 +69,6 @@ class TestFileFeedStorage:
|
||||||
finally:
|
finally:
|
||||||
os.chdir(old_cwd)
|
os.chdir(old_cwd)
|
||||||
|
|
||||||
def test_interface(self, tmp_path):
|
|
||||||
path = tmp_path / "file.txt"
|
|
||||||
st = FileFeedStorage(str(path))
|
|
||||||
verifyObject(IFeedStorage, st)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _store(path: Path, feed_options: dict[str, Any] | None = None) -> None:
|
def _store(path: Path, feed_options: dict[str, Any] | None = None) -> None:
|
||||||
storage = FileFeedStorage(str(path), feed_options=feed_options)
|
storage = FileFeedStorage(str(path), feed_options=feed_options)
|
||||||
|
|
@ -131,7 +124,6 @@ class TestFTPFeedStorage:
|
||||||
uri,
|
uri,
|
||||||
feed_options=feed_options,
|
feed_options=feed_options,
|
||||||
)
|
)
|
||||||
verifyObject(IFeedStorage, storage)
|
|
||||||
spider = self.get_test_spider()
|
spider = self.get_test_spider()
|
||||||
file = storage.open(spider)
|
file = storage.open(spider)
|
||||||
file.write(content)
|
file.write(content)
|
||||||
|
|
@ -275,7 +267,6 @@ class TestS3FeedStorage:
|
||||||
bucket = "mybucket"
|
bucket = "mybucket"
|
||||||
key = "export.csv"
|
key = "export.csv"
|
||||||
storage = S3FeedStorage.from_crawler(crawler, f"s3://{bucket}/{key}")
|
storage = S3FeedStorage.from_crawler(crawler, f"s3://{bucket}/{key}")
|
||||||
verifyObject(IFeedStorage, storage)
|
|
||||||
|
|
||||||
file = mock.MagicMock()
|
file = mock.MagicMock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,12 @@ from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from zope.interface.verify import verifyObject
|
|
||||||
|
|
||||||
# ugly hack to avoid cyclic imports of scrapy.spiders when running this test
|
# ugly hack to avoid cyclic imports of scrapy.spiders when running this test
|
||||||
# alone
|
# alone
|
||||||
import scrapy
|
import scrapy
|
||||||
from scrapy.crawler import CrawlerRunner
|
from scrapy.crawler import CrawlerRunner
|
||||||
from scrapy.http import Request
|
from scrapy.http import Request
|
||||||
from scrapy.interfaces import ISpiderLoader
|
|
||||||
from scrapy.settings import Settings
|
from scrapy.settings import Settings
|
||||||
from scrapy.spiderloader import DummySpiderLoader, SpiderLoader, get_spider_loader
|
from scrapy.spiderloader import DummySpiderLoader, SpiderLoader, get_spider_loader
|
||||||
|
|
||||||
|
|
@ -45,9 +43,6 @@ def spider_loader(spider_loader_env):
|
||||||
|
|
||||||
|
|
||||||
class TestSpiderLoader:
|
class TestSpiderLoader:
|
||||||
def test_interface(self, spider_loader):
|
|
||||||
verifyObject(ISpiderLoader, spider_loader)
|
|
||||||
|
|
||||||
def test_list(self, spider_loader):
|
def test_list(self, spider_loader):
|
||||||
assert set(spider_loader.list()) == {
|
assert set(spider_loader.list()) == {
|
||||||
"spider1",
|
"spider1",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue