From cf6ef74c7949abe5446e1d48f512d5544f869744 Mon Sep 17 00:00:00 2001 From: Adrian Date: Mon, 10 Aug 2026 20:19:46 +0200 Subject: [PATCH] Deprecate the old import path of FileException (#7973) --- scrapy/pipelines/files.py | 26 ++++++++++++++++++-------- scrapy/pipelines/images.py | 9 ++------- tests/test_pipeline_files.py | 14 +++++++++++--- tests/test_pipeline_media.py | 2 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/scrapy/pipelines/files.py b/scrapy/pipelines/files.py index e666f4ddd..9d67e98bf 100644 --- a/scrapy/pipelines/files.py +++ b/scrapy/pipelines/files.py @@ -27,9 +27,7 @@ from twisted.internet.defer import Deferred, maybeDeferred from scrapy.exceptions import IgnoreRequest, NotConfigured, ScrapyDeprecationWarning from scrapy.http import Request, Response from scrapy.http.request import NO_CALLBACK -from scrapy.pipelines.media import ( - FileException as FileException, # noqa: PLC0414 # re-exported for backward compatibility -) +from scrapy.pipelines.media import FileException as _FileException from scrapy.pipelines.media import ( FileInfo, FileInfoOrError, @@ -626,7 +624,7 @@ class FilesPipeline(MediaPipeline): f"{request} referred in <{referer}>: {failure.value}", extra={"spider": info.spider}, ) - raise FileException + raise _FileException async def media_downloaded( self, @@ -645,7 +643,7 @@ class FilesPipeline(MediaPipeline): {"status": response.status, "request": request, "referer": referer}, extra={"spider": info.spider}, ) - raise FileException("download-error") + raise _FileException("download-error") if not response.body: logger.warning( @@ -654,7 +652,7 @@ class FilesPipeline(MediaPipeline): {"request": request, "referer": referer}, extra={"spider": info.spider}, ) - raise FileException("empty-content") + raise _FileException("empty-content") status = "cached" if "cached" in response.flags else "downloaded" logger.debug( @@ -670,7 +668,7 @@ class FilesPipeline(MediaPipeline): checksum: str = await ensure_awaitable( self.file_downloaded(response, request, info, item=item) ) - except FileException as exc: + except _FileException as exc: logger.warning( "File (error): Error processing file from %(request)s " "referred in <%(referer)s>: %(errormsg)s", @@ -687,7 +685,7 @@ class FilesPipeline(MediaPipeline): exc_info=True, extra={"spider": info.spider}, ) - raise FileException(str(exc)) from exc + raise _FileException(str(exc)) from exc return { "url": request.url, @@ -770,3 +768,15 @@ class FilesPipeline(MediaPipeline): if media_type: media_ext = cast("str", mimetypes.guess_extension(media_type)) return f"full/{media_guid}{media_ext}" + + +def __getattr__(name: str) -> Any: + if name == "FileException": + warnings.warn( + "scrapy.pipelines.files.FileException is deprecated, use " + "scrapy.pipelines.media.FileException instead.", + ScrapyDeprecationWarning, + stacklevel=2, + ) + return _FileException + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index 5e7a4b409..7186fc8de 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -18,13 +18,8 @@ from itemadapter import ItemAdapter from scrapy.exceptions import NotConfigured, ScrapyDeprecationWarning from scrapy.http import Request, Response from scrapy.http.request import NO_CALLBACK -from scrapy.pipelines.files import ( - FileException, - FilesPipeline, - GCSFilesStore, - S3FilesStore, - _md5sum, -) +from scrapy.pipelines.files import FilesPipeline, GCSFilesStore, S3FilesStore, _md5sum +from scrapy.pipelines.media import FileException from scrapy.utils.defer import ensure_awaitable from scrapy.utils.python import to_bytes diff --git a/tests/test_pipeline_files.py b/tests/test_pipeline_files.py index 53f998762..928f4e2c2 100644 --- a/tests/test_pipeline_files.py +++ b/tests/test_pipeline_files.py @@ -24,18 +24,18 @@ from twisted.internet.defer import Deferred from twisted.python.failure import Failure from scrapy.crawler import Crawler -from scrapy.exceptions import IgnoreRequest, NotConfigured +from scrapy.exceptions import IgnoreRequest, NotConfigured, ScrapyDeprecationWarning from scrapy.http import Request, Response from scrapy.item import Field, Item +from scrapy.pipelines import files from scrapy.pipelines.files import ( - FileException, FilesPipeline, FSFilesStore, FTPFilesStore, GCSFilesStore, S3FilesStore, ) -from scrapy.pipelines.media import _MediaRequestFiltered +from scrapy.pipelines.media import FileException, _MediaRequestFiltered from scrapy.settings import Settings from scrapy.utils.asyncio import call_later from scrapy.utils.defer import maybe_deferred_to_future @@ -1249,3 +1249,11 @@ def test_files_pipeline_raises_notconfigured_when_files_store_invalid(store): with pytest.raises(NotConfigured): build_from_crawler(FilesPipeline, crawler) + + +def test_file_exception_deprecated_import(): + with pytest.warns(ScrapyDeprecationWarning, match="FileException"): + assert files.FileException is FileException + + with pytest.raises(AttributeError): + files.nonexistent diff --git a/tests/test_pipeline_media.py b/tests/test_pipeline_media.py index 305837f57..231f6748f 100644 --- a/tests/test_pipeline_media.py +++ b/tests/test_pipeline_media.py @@ -10,8 +10,8 @@ from twisted.python.failure import Failure from scrapy import signals from scrapy.exceptions import ScrapyDeprecationWarning from scrapy.http import Request, Response -from scrapy.pipelines.files import FileException from scrapy.pipelines.media import ( + FileException, FileInfo, FileInfoOrError, MediaPipeline,