From 25e616fa04bf4199b9017553b3e57699c40b0683 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Fri, 15 Mar 2019 18:09:47 +0530 Subject: [PATCH 01/19] do not degrade JPEG files. --- scrapy/pipelines/images.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index a1457c7e9..add606a2e 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -127,7 +127,7 @@ class ImagesPipeline(FilesPipeline): raise ImageException("Image too small (%dx%d < %dx%d)" % (width, height, self.min_width, self.min_height)) - image, buf = self.convert_image(orig_image) + image, buf = self.convert_image(orig_image, BytesIO(response.body)) yield path, image, buf for thumb_id, size in six.iteritems(self.thumbs): @@ -135,7 +135,7 @@ class ImagesPipeline(FilesPipeline): thumb_image, thumb_buf = self.convert_image(image, size) yield thumb_path, thumb_image, thumb_buf - def convert_image(self, image, size=None): + def convert_image(self, image, response_body, size=None): if image.format == 'PNG' and image.mode == 'RGBA': background = Image.new('RGBA', image.size, (255, 255, 255)) background.paste(image, image) @@ -152,6 +152,9 @@ class ImagesPipeline(FilesPipeline): image = image.copy() image.thumbnail(size, Image.ANTIALIAS) + if not size and image.format == 'JPEG': + return image, response_body + buf = BytesIO() image.save(buf, 'JPEG') return image, buf From 07487dd487f7a10fbf0693e378b7b66909a67d6b Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Fri, 15 Mar 2019 23:29:53 +0530 Subject: [PATCH 02/19] make tests work with new convert_image --- tests/test_pipeline_images.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index a7c652959..eb3347442 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -79,28 +79,28 @@ class ImagesPipelineTestCase(unittest.TestCase): SIZE = (100, 100) # straigh forward case: RGB and JPEG COLOUR = (0, 127, 255) - im = _create_image('JPEG', 'RGB', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im) + im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) + converted, buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) # check that thumbnail keep image ratio - thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25)) + thumbnail, buf = self.pipeline.convert_image(converted, buf, size=(10, 25)) self.assertEqual(thumbnail.mode, 'RGB') self.assertEqual(thumbnail.size, (10, 10)) # transparency case: RGBA and PNG COLOUR = (0, 127, 255, 50) - im = _create_image('PNG', 'RGBA', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + converted, buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) # transparency case with palette: P and PNG COLOUR = (0, 127, 255, 50) - im = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) im = im.convert('P') - converted, _ = self.pipeline.convert_image(im) + converted, buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) @@ -406,7 +406,7 @@ def _create_image(format, *a, **kw): buf = io.BytesIO() Image.new(*a, **kw).save(buf, format) buf.seek(0) - return Image.open(buf) + return Image.open(buf), buf if __name__ == "__main__": From ca882d8d9f094244d9f1d3476fb72fab9c230765 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Wed, 27 Mar 2019 19:10:44 +0530 Subject: [PATCH 03/19] include test --- tests/test_pipeline_images.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index eb3347442..efa96e146 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -80,9 +80,10 @@ class ImagesPipelineTestCase(unittest.TestCase): # straigh forward case: RGB and JPEG COLOUR = (0, 127, 255) im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) - converted, buf = self.pipeline.convert_image(im, buf) + converted, converted_buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) + self.assertEqual(converted_buf.read(), buf.read()) # check that thumbnail keep image ratio thumbnail, buf = self.pipeline.convert_image(converted, buf, size=(10, 25)) From 398639a0bfe749eee07e51249f04b2c6c93eab73 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Mon, 8 Apr 2019 12:27:36 +0530 Subject: [PATCH 04/19] fix test --- scrapy/pipelines/images.py | 5 +++-- tests/test_pipeline_images.py | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index add606a2e..746244dab 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -132,7 +132,7 @@ class ImagesPipeline(FilesPipeline): for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) - thumb_image, thumb_buf = self.convert_image(image, size) + thumb_image, thumb_buf = self.convert_image(image, buf, size) yield thumb_path, thumb_image, thumb_buf def convert_image(self, image, response_body, size=None): @@ -153,7 +153,8 @@ class ImagesPipeline(FilesPipeline): image.thumbnail(size, Image.ANTIALIAS) if not size and image.format == 'JPEG': - return image, response_body + buf = BytesIO(response_body.read()) + return image, buf buf = BytesIO() image.save(buf, 'JPEG') diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index efa96e146..dde0fa030 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -83,6 +83,47 @@ class ImagesPipelineTestCase(unittest.TestCase): converted, converted_buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) + + # check that we don't convert JPEGs again + buf = io.BytesIO((b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00' + b'\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c' + b'\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c ' + b'$.\' ",#\x1c\x1c(7),01444\x1f\'9=82<.342\xff\xdb\x00C\x01\t\t\t\x0c\x0b\x0c' + b'\x18\r\r\x182!\x1c!222222222222222222222222222222222222222222222222' + b'22\xff\xc0\x00\x11\x08\x00\x14\x00\x14\x03\x01"\x00\x02\x11\x01\x03\x11' + b'\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4' + b'\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00' + b'\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81' + b"\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&'()*456" + b'789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a' + b'\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8' + b'\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6' + b'\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3' + b'\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9' + b'\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01' + b'\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4' + b'\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00' + b'\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08' + b"\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&'" + b'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88' + b'\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6' + b'\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4' + b'\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2' + b'\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9' + b'\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xbb' + b'\xe2\x1b\xcb\x88\xe4\x90\t\x0e1\xd2\xb9\xab\x1dF\xe6\xda\xe0\xb4l\xd9' + b'5>\xaf\xac\xc7y}$k\x92\x03u\xaaL\xeb\x18VL\x9fZ+\xb9\xb9EBV]Ow\x0f\x8a\xc0' + b'\xac\x14\xbd\xa2NG_\xa5x\x8esg\xfb\xd9\x8e\xed\xc79\xa2\xa1\xd0\xf4\x8bk' + b'\x9d5e\xdeN\xe6=\xa8\xae\xa9V\xc2\xb6|\xdf2\xeep:w\xfc\x84d\x04g\x9e\xf5\xd3' + b'L\xa9\x0c\x1f*/>\xa2\x8a+\xca\x93z\x1c\x15]\xa9\xc6\xc4Q\xea\x97V\xca' + b'c\x85\xc2\xaes\x80(\xa2\x8a\xe3\x92W<\xdb\x9f\xff\xd9')) + im = Image.open(buf) + buf.seek(0) + converted, converted_buf = self.pipeline.convert_image(im, buf) + converted_buf.seek(0) + buf.seek(0) + self.assertEqual(im.format, "JPEG") self.assertEqual(converted_buf.read(), buf.read()) # check that thumbnail keep image ratio From c6769d6887b5c311c9083a5ac0349e81d1a2aea7 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Sat, 13 Apr 2019 10:10:08 +0530 Subject: [PATCH 05/19] make suggested changes --- scrapy/pipelines/images.py | 6 ++--- tests/test_pipeline_images.py | 42 +---------------------------------- 2 files changed, 3 insertions(+), 45 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index 746244dab..3450ee721 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -151,10 +151,8 @@ class ImagesPipeline(FilesPipeline): if size: image = image.copy() image.thumbnail(size, Image.ANTIALIAS) - - if not size and image.format == 'JPEG': - buf = BytesIO(response_body.read()) - return image, buf + elif image.format == 'JPEG': + return image, response_body buf = BytesIO() image.save(buf, 'JPEG') diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index dde0fa030..b419039b3 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -83,48 +83,8 @@ class ImagesPipelineTestCase(unittest.TestCase): converted, converted_buf = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) - # check that we don't convert JPEGs again - buf = io.BytesIO((b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00' - b'\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c' - b'\x14\r\x0c\x0b\x0b\x0c\x19\x12\x13\x0f\x14\x1d\x1a\x1f\x1e\x1d\x1a\x1c\x1c ' - b'$.\' ",#\x1c\x1c(7),01444\x1f\'9=82<.342\xff\xdb\x00C\x01\t\t\t\x0c\x0b\x0c' - b'\x18\r\r\x182!\x1c!222222222222222222222222222222222222222222222222' - b'22\xff\xc0\x00\x11\x08\x00\x14\x00\x14\x03\x01"\x00\x02\x11\x01\x03\x11' - b'\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4' - b'\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00' - b'\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81' - b"\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&'()*456" - b'789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a' - b'\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8' - b'\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6' - b'\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3' - b'\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9' - b'\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01' - b'\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4' - b'\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00' - b'\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08' - b"\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&'" - b'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88' - b'\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6' - b'\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4' - b'\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2' - b'\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9' - b'\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xbb' - b'\xe2\x1b\xcb\x88\xe4\x90\t\x0e1\xd2\xb9\xab\x1dF\xe6\xda\xe0\xb4l\xd9' - b'5>\xaf\xac\xc7y}$k\x92\x03u\xaaL\xeb\x18VL\x9fZ+\xb9\xb9EBV]Ow\x0f\x8a\xc0' - b'\xac\x14\xbd\xa2NG_\xa5x\x8esg\xfb\xd9\x8e\xed\xc79\xa2\xa1\xd0\xf4\x8bk' - b'\x9d5e\xdeN\xe6=\xa8\xae\xa9V\xc2\xb6|\xdf2\xeep:w\xfc\x84d\x04g\x9e\xf5\xd3' - b'L\xa9\x0c\x1f*/>\xa2\x8a+\xca\x93z\x1c\x15]\xa9\xc6\xc4Q\xea\x97V\xca' - b'c\x85\xc2\xaes\x80(\xa2\x8a\xe3\x92W<\xdb\x9f\xff\xd9')) - im = Image.open(buf) - buf.seek(0) - converted, converted_buf = self.pipeline.convert_image(im, buf) - converted_buf.seek(0) - buf.seek(0) - self.assertEqual(im.format, "JPEG") - self.assertEqual(converted_buf.read(), buf.read()) + self.assertEqual(converted_buf, buf) # check that thumbnail keep image ratio thumbnail, buf = self.pipeline.convert_image(converted, buf, size=(10, 25)) From 6039b66f42fe2c6e7708febb70939952cedaedd0 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Sat, 13 Apr 2019 10:17:26 +0530 Subject: [PATCH 06/19] aesthetic changes --- tests/test_pipeline_images.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index b419039b3..0a2153ddb 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -87,14 +87,14 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(converted_buf, buf) # check that thumbnail keep image ratio - thumbnail, buf = self.pipeline.convert_image(converted, buf, size=(10, 25)) + thumbnail, _ = self.pipeline.convert_image(converted, converted_buf, size=(10, 25)) self.assertEqual(thumbnail.mode, 'RGB') self.assertEqual(thumbnail.size, (10, 10)) # transparency case: RGBA and PNG COLOUR = (0, 127, 255, 50) im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) - converted, buf = self.pipeline.convert_image(im, buf) + converted, _ = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) @@ -102,7 +102,7 @@ class ImagesPipelineTestCase(unittest.TestCase): COLOUR = (0, 127, 255, 50) im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) im = im.convert('P') - converted, buf = self.pipeline.convert_image(im, buf) + converted, _ = self.pipeline.convert_image(im, buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) From 2a6bcdb413da0a4202fe905279d46d581368769d Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Mon, 29 Apr 2019 21:21:02 +0530 Subject: [PATCH 07/19] makes fix backward compatible --- scrapy/pipelines/images.py | 45 ++++++++++++++++++++++++++++++----- tests/test_pipeline_images.py | 8 +++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index 3450ee721..ca8ac7b83 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -126,16 +126,42 @@ class ImagesPipeline(FilesPipeline): if width < self.min_width or height < self.min_height: raise ImageException("Image too small (%dx%d < %dx%d)" % (width, height, self.min_width, self.min_height)) + + def _is_convert_image_overriden(): + import inspect + if six.PY2: + convert_image_signature = inspect.getargspec(self.convert_image) + elif six.PY3: + convert_image_signature = inspect.getfullargspec(self.convert_image) + if 'response_body' not in convert_image_signature.args: + return True + return False - image, buf = self.convert_image(orig_image, BytesIO(response.body)) + def _warn(): + from scrapy.exceptions import ScrapyDeprecationWarning + import warnings + warnings.warn('ImagesPipeline.convert_image() method overriden in a incompatible way, ' + 'overriden method does not accept response_body attribute.', + category=ScrapyDeprecationWarning, stacklevel=1) + + convert_image_overriden = _is_convert_image_overriden() + if convert_image_overriden: + _warn() + image, buf = self.convert_image(orig_image) + else: + image, buf = self.convert_image(orig_image, response_body=BytesIO(response.body)) yield path, image, buf for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) - thumb_image, thumb_buf = self.convert_image(image, buf, size) + if convert_image_overriden: + _warn() + thumb_image, thumb_buf = self.convert_image(image, size) + else: + thumb_image, thumb_buf = self.convert_image(image, size, buf) yield thumb_path, thumb_image, thumb_buf - def convert_image(self, image, response_body, size=None): + def convert_image(self, image, size=None, response_body=None): if image.format == 'PNG' and image.mode == 'RGBA': background = Image.new('RGBA', image.size, (255, 255, 255)) background.paste(image, image) @@ -151,9 +177,16 @@ class ImagesPipeline(FilesPipeline): if size: image = image.copy() image.thumbnail(size, Image.ANTIALIAS) - elif image.format == 'JPEG': - return image, response_body - + else: + if not response_body: + from scrapy.exceptions import ScrapyDeprecationWarning + import warnings + warnings.warn('ImagesPipeline.convert_image() method called in a incompatible way, ' + 'method called without response_body attribute.', + category=ScrapyDeprecationWarning, stacklevel=1) + elif image.format == 'JPEG': + return image, response_body + buf = BytesIO() image.save(buf, 'JPEG') return image, buf diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 0a2153ddb..3fe71589d 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -80,21 +80,21 @@ class ImagesPipelineTestCase(unittest.TestCase): # straigh forward case: RGB and JPEG COLOUR = (0, 127, 255) im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) - converted, converted_buf = self.pipeline.convert_image(im, buf) + converted, converted_buf = self.pipeline.convert_image(im, response_body=buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) # check that we don't convert JPEGs again self.assertEqual(converted_buf, buf) # check that thumbnail keep image ratio - thumbnail, _ = self.pipeline.convert_image(converted, converted_buf, size=(10, 25)) + thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25), response_body=converted_buf) self.assertEqual(thumbnail.mode, 'RGB') self.assertEqual(thumbnail.size, (10, 10)) # transparency case: RGBA and PNG COLOUR = (0, 127, 255, 50) im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im, buf) + converted, _ = self.pipeline.convert_image(im, response_body=buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) @@ -102,7 +102,7 @@ class ImagesPipelineTestCase(unittest.TestCase): COLOUR = (0, 127, 255, 50) im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) im = im.convert('P') - converted, _ = self.pipeline.convert_image(im, buf) + converted, _ = self.pipeline.convert_image(im, response_body=buf) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) From 33925a77610293c44a169efd1c274239a75ba968 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Tue, 7 May 2019 15:44:21 +0530 Subject: [PATCH 08/19] test for deprecation warning --- scrapy/pipelines/images.py | 20 +++++------ tests/test_pipeline_images.py | 63 ++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index ca8ac7b83..9776817bc 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -141,7 +141,7 @@ class ImagesPipeline(FilesPipeline): from scrapy.exceptions import ScrapyDeprecationWarning import warnings warnings.warn('ImagesPipeline.convert_image() method overriden in a incompatible way, ' - 'overriden method does not accept response_body attribute.', + 'overriden method does not accept response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) convert_image_overriden = _is_convert_image_overriden() @@ -162,6 +162,13 @@ class ImagesPipeline(FilesPipeline): yield thumb_path, thumb_image, thumb_buf def convert_image(self, image, size=None, response_body=None): + if not response_body: + from scrapy.exceptions import ScrapyDeprecationWarning + import warnings + warnings.warn('ImagesPipeline.convert_image() method called in a incompatible way, ' + 'method called without response_body argument.', + category=ScrapyDeprecationWarning, stacklevel=1) + if image.format == 'PNG' and image.mode == 'RGBA': background = Image.new('RGBA', image.size, (255, 255, 255)) background.paste(image, image) @@ -177,15 +184,8 @@ class ImagesPipeline(FilesPipeline): if size: image = image.copy() image.thumbnail(size, Image.ANTIALIAS) - else: - if not response_body: - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings - warnings.warn('ImagesPipeline.convert_image() method called in a incompatible way, ' - 'method called without response_body attribute.', - category=ScrapyDeprecationWarning, stacklevel=1) - elif image.format == 'JPEG': - return image, response_body + elif response_body and image.format == 'JPEG': + return image, response_body buf = BytesIO() image.save(buf, 'JPEG') diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 3fe71589d..651691862 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -76,37 +76,48 @@ class ImagesPipelineTestCase(unittest.TestCase): 'thumbs/50/850233df65a5b83361798f532f1fc549cd13cbe9.jpg') def test_convert_image(self): - SIZE = (100, 100) - # straigh forward case: RGB and JPEG + # tests for old API + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + + SIZE = (100, 100) + # straigh forward case: RGB and JPEG + COLOUR = (0, 127, 255) + im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) + converted, converted_buf = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) + + # check that thumbnail keep image ratio + thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25)) + self.assertEqual(thumbnail.mode, 'RGB') + self.assertEqual(thumbnail.size, (10, 10)) + + # transparency case: RGBA and PNG + COLOUR = (0, 127, 255, 50) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + converted, _ = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + + # transparency case with palette: P and PNG + COLOUR = (0, 127, 255, 50) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im = im.convert('P') + converted, _ = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + + # ensure that we recieved deprecation warnings + self.assertTrue(len(w) >= 4) + + # tests for new API + # check that we don't convert JPEGs again COLOUR = (0, 127, 255) im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) converted, converted_buf = self.pipeline.convert_image(im, response_body=buf) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) - # check that we don't convert JPEGs again self.assertEqual(converted_buf, buf) - # check that thumbnail keep image ratio - thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25), response_body=converted_buf) - self.assertEqual(thumbnail.mode, 'RGB') - self.assertEqual(thumbnail.size, (10, 10)) - - # transparency case: RGBA and PNG - COLOUR = (0, 127, 255, 50) - im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im, response_body=buf) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) - - # transparency case with palette: P and PNG - COLOUR = (0, 127, 255, 50) - im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) - im = im.convert('P') - converted, _ = self.pipeline.convert_image(im, response_body=buf) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) - - class DeprecatedImagesPipeline(ImagesPipeline): def file_key(self, url): return self.image_key(url) From 881bade2c1b2d5842f7d0bdf2648455996f814b7 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Tue, 7 May 2019 16:12:26 +0530 Subject: [PATCH 09/19] tests for new API --- tests/test_pipeline_images.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 651691862..1dfca5c11 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -79,12 +79,11 @@ class ImagesPipelineTestCase(unittest.TestCase): # tests for old API with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') - SIZE = (100, 100) # straigh forward case: RGB and JPEG COLOUR = (0, 127, 255) - im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) - converted, converted_buf = self.pipeline.convert_image(im) + im, _ = _create_image('JPEG', 'RGB', SIZE, COLOUR) + converted, _ = self.pipeline.convert_image(im) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) @@ -95,14 +94,14 @@ class ImagesPipelineTestCase(unittest.TestCase): # transparency case: RGBA and PNG COLOUR = (0, 127, 255, 50) - im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) converted, _ = self.pipeline.convert_image(im) self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) # transparency case with palette: P and PNG COLOUR = (0, 127, 255, 50) - im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) im = im.convert('P') converted, _ = self.pipeline.convert_image(im) self.assertEqual(converted.mode, 'RGB') @@ -112,12 +111,36 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertTrue(len(w) >= 4) # tests for new API - # check that we don't convert JPEGs again + SIZE = (100, 100) + # straigh forward case: RGB and JPEG COLOUR = (0, 127, 255) im, buf = _create_image('JPEG', 'RGB', SIZE, COLOUR) converted, converted_buf = self.pipeline.convert_image(im, response_body=buf) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) + # check that we don't convert JPEGs again self.assertEqual(converted_buf, buf) + # check that thumbnail keep image ratio + thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25), response_body=converted_buf) + self.assertEqual(thumbnail.mode, 'RGB') + self.assertEqual(thumbnail.size, (10, 10)) + + # transparency case: RGBA and PNG + COLOUR = (0, 127, 255, 50) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + converted, _ = self.pipeline.convert_image(im, response_body=buf) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + + # transparency case with palette: P and PNG + COLOUR = (0, 127, 255, 50) + im, buf = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im = im.convert('P') + converted, _ = self.pipeline.convert_image(im, response_body=buf) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + class DeprecatedImagesPipeline(ImagesPipeline): def file_key(self, url): return self.image_key(url) From 653ac3eebe96bdfff8128cdb1e500728215486a2 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Thu, 16 May 2019 06:20:39 +0000 Subject: [PATCH 10/19] makes suggested changes --- scrapy/pipelines/images.py | 1 - tests/test_pipeline_images.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index 9776817bc..b95383b3b 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -155,7 +155,6 @@ class ImagesPipeline(FilesPipeline): for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) if convert_image_overriden: - _warn() thumb_image, thumb_buf = self.convert_image(image, size) else: thumb_image, thumb_buf = self.convert_image(image, size, buf) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 1dfca5c11..ba79dd6bd 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -108,7 +108,7 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) # ensure that we recieved deprecation warnings - self.assertTrue(len(w) >= 4) + self.assertTrue(len([warning for warning in w if 'ImagesPipeline.convert_image() method called in a incompatible way' in str(warning.message)]) == 4) # tests for new API SIZE = (100, 100) From 2994b624e03ec9a2cba2c024eacf549329c056de Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Fri, 17 May 2019 12:14:43 +0000 Subject: [PATCH 11/19] makes suggested changes --- scrapy/pipelines/images.py | 21 +++++---------------- tests/test_pipeline_images.py | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index b95383b3b..fd34c9df6 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -15,7 +15,7 @@ except ImportError: from PIL import Image from scrapy.utils.misc import md5sum -from scrapy.utils.python import to_bytes +from scrapy.utils.python import to_bytes, get_func_args from scrapy.http import Request from scrapy.settings import Settings from scrapy.exceptions import DropItem @@ -127,25 +127,14 @@ class ImagesPipeline(FilesPipeline): raise ImageException("Image too small (%dx%d < %dx%d)" % (width, height, self.min_width, self.min_height)) - def _is_convert_image_overriden(): - import inspect - if six.PY2: - convert_image_signature = inspect.getargspec(self.convert_image) - elif six.PY3: - convert_image_signature = inspect.getfullargspec(self.convert_image) - if 'response_body' not in convert_image_signature.args: - return True - return False - def _warn(): from scrapy.exceptions import ScrapyDeprecationWarning import warnings - warnings.warn('ImagesPipeline.convert_image() method overriden in a incompatible way, ' + warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' 'overriden method does not accept response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) - convert_image_overriden = _is_convert_image_overriden() - if convert_image_overriden: + if 'response_body' not in get_func_args(self.convert_image): _warn() image, buf = self.convert_image(orig_image) else: @@ -154,7 +143,7 @@ class ImagesPipeline(FilesPipeline): for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) - if convert_image_overriden: + if 'response_body' not in get_func_args(self.convert_image): thumb_image, thumb_buf = self.convert_image(image, size) else: thumb_image, thumb_buf = self.convert_image(image, size, buf) @@ -164,7 +153,7 @@ class ImagesPipeline(FilesPipeline): if not response_body: from scrapy.exceptions import ScrapyDeprecationWarning import warnings - warnings.warn('ImagesPipeline.convert_image() method called in a incompatible way, ' + warnings.warn('ImagesPipeline.convert_image() method called in a deprecated way, ' 'method called without response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index ba79dd6bd..ec0c87264 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -108,7 +108,7 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) # ensure that we recieved deprecation warnings - self.assertTrue(len([warning for warning in w if 'ImagesPipeline.convert_image() method called in a incompatible way' in str(warning.message)]) == 4) + self.assertTrue(len([warning for warning in w if 'ImagesPipeline.convert_image() method called in a deprecated way' in str(warning.message)]) == 4) # tests for new API SIZE = (100, 100) From c8e28ec194b3730e7954a18526ac77cb44138feb Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Thu, 23 May 2019 15:04:21 +0530 Subject: [PATCH 12/19] makes suggested changes --- scrapy/pipelines/images.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index fd34c9df6..a3f5f9292 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -134,7 +134,8 @@ class ImagesPipeline(FilesPipeline): 'overriden method does not accept response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) - if 'response_body' not in get_func_args(self.convert_image): + convert_image_overriden = 'response_body' not in get_func_args(self.convert_image) + if convert_image_overriden: _warn() image, buf = self.convert_image(orig_image) else: @@ -143,7 +144,7 @@ class ImagesPipeline(FilesPipeline): for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) - if 'response_body' not in get_func_args(self.convert_image): + if convert_image_overriden: thumb_image, thumb_buf = self.convert_image(image, size) else: thumb_image, thumb_buf = self.convert_image(image, size, buf) From 90fdefcbca89d0ef2cc81955b9fd8b8af1dff392 Mon Sep 17 00:00:00 2001 From: Anubhav Patel Date: Sat, 25 May 2019 19:11:48 +0530 Subject: [PATCH 13/19] cache if convert_image has deprecated signature --- scrapy/pipelines/images.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index a3f5f9292..f709c5057 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -84,6 +84,8 @@ class ImagesPipeline(FilesPipeline): resolve('IMAGES_THUMBS'), self.THUMBS ) + self._deprecated_convert_image = None + @classmethod def from_settings(cls, settings): s3store = cls.STORE_SCHEMES['s3'] @@ -126,17 +128,17 @@ class ImagesPipeline(FilesPipeline): if width < self.min_width or height < self.min_height: raise ImageException("Image too small (%dx%d < %dx%d)" % (width, height, self.min_width, self.min_height)) - - def _warn(): - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings - warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' - 'overriden method does not accept response_body argument.', - category=ScrapyDeprecationWarning, stacklevel=1) - convert_image_overriden = 'response_body' not in get_func_args(self.convert_image) - if convert_image_overriden: - _warn() + if self._deprecated_convert_image is None: + self._deprecated_convert_image = 'response_body' not in get_func_args(self.convert_image) + if self._deprecated_convert_image: + from scrapy.exceptions import ScrapyDeprecationWarning + import warnings + warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' + 'overriden method does not accept response_body argument.', + category=ScrapyDeprecationWarning, stacklevel=1) + + if self._deprecated_convert_image: image, buf = self.convert_image(orig_image) else: image, buf = self.convert_image(orig_image, response_body=BytesIO(response.body)) @@ -144,7 +146,7 @@ class ImagesPipeline(FilesPipeline): for thumb_id, size in six.iteritems(self.thumbs): thumb_path = self.thumb_path(request, thumb_id, response=response, info=info) - if convert_image_overriden: + if self._deprecated_convert_image: thumb_image, thumb_buf = self.convert_image(image, size) else: thumb_image, thumb_buf = self.convert_image(image, size, buf) From 8b84a65a6b2d391fdd9c49426d748751a03351f0 Mon Sep 17 00:00:00 2001 From: drs-11 Date: Tue, 25 Aug 2020 00:30:17 +0530 Subject: [PATCH 14/19] cleaned up code relating to issue #3689 --- scrapy/pipelines/images.py | 14 ++----- tests/test_pipeline_images.py | 72 ++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index f709c5057..e265685fb 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -6,6 +6,7 @@ See documentation in topics/media-pipeline.rst import functools import hashlib import six +import warnings try: from cStringIO import StringIO as BytesIO @@ -19,6 +20,7 @@ from scrapy.utils.python import to_bytes, get_func_args from scrapy.http import Request from scrapy.settings import Settings from scrapy.exceptions import DropItem +from scrapy.exceptions import ScrapyDeprecationWarning #TODO: from scrapy.pipelines.media import MediaPipeline from scrapy.pipelines.files import FileException, FilesPipeline @@ -132,8 +134,6 @@ class ImagesPipeline(FilesPipeline): if self._deprecated_convert_image is None: self._deprecated_convert_image = 'response_body' not in get_func_args(self.convert_image) if self._deprecated_convert_image: - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' 'overriden method does not accept response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) @@ -153,9 +153,7 @@ class ImagesPipeline(FilesPipeline): yield thumb_path, thumb_image, thumb_buf def convert_image(self, image, size=None, response_body=None): - if not response_body: - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings + if response_body is None: warnings.warn('ImagesPipeline.convert_image() method called in a deprecated way, ' 'method called without response_body argument.', category=ScrapyDeprecationWarning, stacklevel=1) @@ -175,7 +173,7 @@ class ImagesPipeline(FilesPipeline): if size: image = image.copy() image.thumbnail(size, Image.ANTIALIAS) - elif response_body and image.format == 'JPEG': + elif response_body is not None and image.format == 'JPEG': return image, response_body buf = BytesIO() @@ -193,8 +191,6 @@ class ImagesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None): ## start of deprecation warning block (can be removed in the future) def _warn(): - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings warnings.warn('ImagesPipeline.image_key(url) and file_key(url) methods are deprecated, ' 'please use file_path(request, response=None, info=None) instead', category=ScrapyDeprecationWarning, stacklevel=1) @@ -221,8 +217,6 @@ class ImagesPipeline(FilesPipeline): def thumb_path(self, request, thumb_id, response=None, info=None): ## start of deprecation warning block (can be removed in the future) def _warn(): - from scrapy.exceptions import ScrapyDeprecationWarning - import warnings warnings.warn('ImagesPipeline.thumb_key(url) method is deprecated, please use ' 'thumb_path(request, thumb_id, response=None, info=None) instead', category=ScrapyDeprecationWarning, stacklevel=1) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index ec0c87264..915b6a579 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -75,41 +75,7 @@ class ImagesPipelineTestCase(unittest.TestCase): info=object()), 'thumbs/50/850233df65a5b83361798f532f1fc549cd13cbe9.jpg') - def test_convert_image(self): - # tests for old API - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - SIZE = (100, 100) - # straigh forward case: RGB and JPEG - COLOUR = (0, 127, 255) - im, _ = _create_image('JPEG', 'RGB', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) - - # check that thumbnail keep image ratio - thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25)) - self.assertEqual(thumbnail.mode, 'RGB') - self.assertEqual(thumbnail.size, (10, 10)) - - # transparency case: RGBA and PNG - COLOUR = (0, 127, 255, 50) - im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) - converted, _ = self.pipeline.convert_image(im) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) - - # transparency case with palette: P and PNG - COLOUR = (0, 127, 255, 50) - im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) - im = im.convert('P') - converted, _ = self.pipeline.convert_image(im) - self.assertEqual(converted.mode, 'RGB') - self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) - - # ensure that we recieved deprecation warnings - self.assertTrue(len([warning for warning in w if 'ImagesPipeline.convert_image() method called in a deprecated way' in str(warning.message)]) == 4) - + def test_convert_image_new(self): # tests for new API SIZE = (100, 100) # straigh forward case: RGB and JPEG @@ -207,6 +173,42 @@ class DeprecatedImagesPipelineTestCase(unittest.TestCase): self.assertEqual(len(w), 1) self.assertTrue('thumb_key(url) method is deprecated' in str(w[-1].message)) + def test_overriden_convert_image_method(self): + self.init_pipeline(ImagesPipeline) + # tests for old API + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + SIZE = (100, 100) + # straigh forward case: RGB and JPEG + COLOUR = (0, 127, 255) + im, _ = _create_image('JPEG', 'RGB', SIZE, COLOUR) + converted, _ = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, COLOUR)]) + + # check that thumbnail keep image ratio + thumbnail, _ = self.pipeline.convert_image(converted, size=(10, 25)) + self.assertEqual(thumbnail.mode, 'RGB') + self.assertEqual(thumbnail.size, (10, 10)) + + # transparency case: RGBA and PNG + COLOUR = (0, 127, 255, 50) + im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) + converted, _ = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + + # transparency case with palette: P and PNG + COLOUR = (0, 127, 255, 50) + im, _ = _create_image('PNG', 'RGBA', SIZE, COLOUR) + im = im.convert('P') + converted, _ = self.pipeline.convert_image(im) + self.assertEqual(converted.mode, 'RGB') + self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) + + # ensure that we recieved deprecation warnings + self.assertTrue(len([warning for warning in w if 'ImagesPipeline.convert_image() method called in a deprecated way' in str(warning.message)]) == 4) + def tearDown(self): rmtree(self.tempdir) From ecec5f9e5171568c4ead64336245a1bb3be2ecfe Mon Sep 17 00:00:00 2001 From: drs-11 Date: Tue, 25 Aug 2020 02:46:44 +0530 Subject: [PATCH 15/19] Cleaned up code --- scrapy/pipelines/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index d7f437adc..47d688c62 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -12,7 +12,7 @@ from io import BytesIO from itemadapter import ItemAdapter from PIL import Image -from scrapy.exceptions import DropItem,ScrapyDeprecationWarning +from scrapy.exceptions import DropItem, ScrapyDeprecationWarning from scrapy.http import Request from scrapy.pipelines.files import FileException, FilesPipeline # TODO: from scrapy.pipelines.media import MediaPipeline @@ -175,7 +175,7 @@ class ImagesPipeline(FilesPipeline): image.thumbnail(size, Image.ANTIALIAS) elif response_body is not None and image.format == 'JPEG': return image, response_body - + buf = BytesIO() image.save(buf, 'JPEG') return image, buf From 6565adc471360c2f542392befb7bb9448e8f171f Mon Sep 17 00:00:00 2001 From: drs-11 Date: Wed, 2 Sep 2020 20:44:26 +0530 Subject: [PATCH 16/19] added test case for get_images --- scrapy/pipelines/images.py | 6 +-- tests/test_pipeline_images.py | 75 ++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index 47d688c62..09194a0fe 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -18,7 +18,7 @@ from scrapy.pipelines.files import FileException, FilesPipeline # TODO: from scrapy.pipelines.media import MediaPipeline from scrapy.settings import Settings from scrapy.utils.misc import md5sum -from scrapy.utils.python import to_bytes, get_func_args +from scrapy.utils.python import get_func_args, to_bytes class NoimagesDrop(DropItem): @@ -136,7 +136,7 @@ class ImagesPipeline(FilesPipeline): if self._deprecated_convert_image: warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' 'overriden method does not accept response_body argument.', - category=ScrapyDeprecationWarning, stacklevel=1) + category=ScrapyDeprecationWarning) if self._deprecated_convert_image: image, buf = self.convert_image(orig_image) @@ -156,7 +156,7 @@ class ImagesPipeline(FilesPipeline): if response_body is None: warnings.warn('ImagesPipeline.convert_image() method called in a deprecated way, ' 'method called without response_body argument.', - category=ScrapyDeprecationWarning, stacklevel=1) + category=ScrapyDeprecationWarning, stacklevel=2) if image.format == 'PNG' and image.mode == 'RGBA': background = Image.new('RGBA', image.size, (255, 255, 255)) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 6b71b64bc..8e98b8734 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -5,6 +5,7 @@ import warnings from shutil import rmtree from tempfile import mkdtemp from unittest import skipIf +from unittest.mock import patch import attr from itemadapter import ItemAdapter @@ -12,7 +13,7 @@ from twisted.trial import unittest from scrapy.http import Request, Response from scrapy.item import Field, Item -from scrapy.pipelines.images import ImagesPipeline +from scrapy.pipelines.images import ImageException, ImagesPipeline from scrapy.settings import Settings from scrapy.utils.python import to_bytes @@ -93,6 +94,78 @@ class ImagesPipelineTestCase(unittest.TestCase): info=object()), 'thumbs/50/850233df65a5b83361798f532f1fc549cd13cbe9.jpg') + def test_get_images_exception(self): + self.pipeline.min_width = 100 + self.pipeline.min_height = 100 + + _, buf1 = _create_image('JPEG', 'RGB', (50, 50), (0, 0, 0)) + _, buf2 = _create_image('JPEG', 'RGB', (150, 50), (0, 0, 0)) + _, buf3 = _create_image('JPEG', 'RGB', (50, 150), (0, 0, 0)) + + resp1 = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf1.getvalue()) + resp2 = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf2.getvalue()) + resp3 = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf3.getvalue()) + req = Request(url="https://dev.mydeco.com/mydeco.gif") + + with self.assertRaises(ImageException): + next(self.pipeline.get_images(response=resp1, request=req, info=object())) + with self.assertRaises(ImageException): + next(self.pipeline.get_images(response=resp2, request=req, info=object())) + with self.assertRaises(ImageException): + next(self.pipeline.get_images(response=resp3, request=req, info=object())) + + def test_get_images_new(self): + self.pipeline.min_width = 0 + self.pipeline.min_height = 0 + self.pipeline.thumbs = {'small': (20, 20)} + + orig_im, buf = _create_image('JPEG', 'RGB', (50, 50), (0, 0, 0)) + orig_thumb, orig_thumb_buf = _create_image('JPEG', 'RGB', (20, 20), (0, 0, 0)) + resp = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf.getvalue()) + req = Request(url="https://dev.mydeco.com/mydeco.gif") + + get_images_gen = self.pipeline.get_images(response=resp, request=req, info=object()) + + path, new_im, new_buf = next(get_images_gen) + self.assertEqual(path, 'full/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg') + self.assertEqual(orig_im, new_im) + self.assertEqual(buf.getvalue(), new_buf.getvalue()) + + thumb_path, thumb_img, thumb_buf = next(get_images_gen) + self.assertEqual(thumb_path, 'thumbs/small/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg') + self.assertEqual(thumb_img, thumb_img) + self.assertEqual(orig_thumb_buf.getvalue(), thumb_buf.getvalue()) + + def test_get_images_old(self): + self.pipeline.thumbs = {'small': (20, 20)} + orig_im, buf = _create_image('JPEG', 'RGB', (50, 50), (0, 0, 0)) + resp = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf.getvalue()) + req = Request(url="https://dev.mydeco.com/mydeco.gif") + + def overridden_convert_image(image, size=None): + im, buf = _create_image('JPEG', 'RGB', (50, 50), (0, 0, 0)) + return im, buf + + with patch.object(self.pipeline, 'convert_image', overridden_convert_image): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + get_images_gen = self.pipeline.get_images(response=resp, request=req, info=object()) + path, new_im, new_buf = next(get_images_gen) + self.assertEqual(path, 'full/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg') + self.assertEqual(orig_im.mode, new_im.mode) + self.assertEqual(orig_im.getcolors(), new_im.getcolors()) + self.assertEqual(buf.getvalue(), new_buf.getvalue()) + + thumb_path, thumb_img, thumb_buf = next(get_images_gen) + self.assertEqual(thumb_path, 'thumbs/small/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg') + self.assertEqual(orig_im.mode, thumb_img.mode) + self.assertEqual(orig_im.getcolors(), thumb_img.getcolors()) + self.assertEqual(buf.getvalue(), thumb_buf.getvalue()) + + expected_warning_msg = ('ImagesPipeline.convert_image() method overriden in a deprecated way, ' + 'overriden method does not accept response_body argument.') + self.assertEqual(len([warning for warning in w if expected_warning_msg in str(warning.message)]), 1) + def test_convert_image_old(self): # tests for old API with warnings.catch_warnings(record=True) as w: From 0c24cdb2573958cc0fb9127c6f195d3174640ff4 Mon Sep 17 00:00:00 2001 From: D R Siddhartha Date: Sun, 4 Oct 2020 02:09:21 +0530 Subject: [PATCH 17/19] Improved warning messages a little --- scrapy/pipelines/images.py | 4 ++-- tests/test_pipeline_images.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scrapy/pipelines/images.py b/scrapy/pipelines/images.py index d3254bc20..48e8a7b83 100644 --- a/scrapy/pipelines/images.py +++ b/scrapy/pipelines/images.py @@ -135,7 +135,7 @@ class ImagesPipeline(FilesPipeline): if self._deprecated_convert_image is None: self._deprecated_convert_image = 'response_body' not in get_func_args(self.convert_image) if self._deprecated_convert_image: - warnings.warn('ImagesPipeline.convert_image() method overriden in a deprecated way, ' + warnings.warn(f'{self.__class__.__name__}.convert_image() method overriden in a deprecated way, ' 'overriden method does not accept response_body argument.', category=ScrapyDeprecationWarning) @@ -155,7 +155,7 @@ class ImagesPipeline(FilesPipeline): def convert_image(self, image, size=None, response_body=None): if response_body is None: - warnings.warn('ImagesPipeline.convert_image() method called in a deprecated way, ' + warnings.warn(f'{self.__class__.__name__}.convert_image() method called in a deprecated way, ' 'method called without response_body argument.', category=ScrapyDeprecationWarning, stacklevel=2) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 380c775c4..0a294db80 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -162,7 +162,7 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(orig_im.getcolors(), thumb_img.getcolors()) self.assertEqual(buf.getvalue(), thumb_buf.getvalue()) - expected_warning_msg = ('ImagesPipeline.convert_image() method overriden in a deprecated way, ' + expected_warning_msg = ('.convert_image() method overriden in a deprecated way, ' 'overriden method does not accept response_body argument.') self.assertEqual(len([warning for warning in w if expected_warning_msg in str(warning.message)]), 1) @@ -199,7 +199,7 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) # ensure that we recieved deprecation warnings - expected_warning_msg = 'ImagesPipeline.convert_image() method called in a deprecated way' + expected_warning_msg = '.convert_image() method called in a deprecated way' self.assertTrue(len([warning for warning in w if expected_warning_msg in str(warning.message)]) == 4) def test_convert_image_new(self): From 6c0890ff54a8d49237415e5b7d7dfbf216e88577 Mon Sep 17 00:00:00 2001 From: Andrey Rahmatullin Date: Mon, 7 Nov 2022 16:36:54 +0500 Subject: [PATCH 18/19] Simplify the changes after the merge --- tests/test_pipeline_images.py | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 81c9a027a..0c9a5733f 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -7,7 +7,6 @@ from shutil import rmtree from tempfile import mkdtemp from unittest import skipIf from unittest.mock import patch -from warnings import catch_warnings import attr from itemadapter import ItemAdapter @@ -91,6 +90,22 @@ class ImagesPipelineTestCase(unittest.TestCase): info=object()), 'thumbs/50/850233df65a5b83361798f532f1fc549cd13cbe9.jpg') + def test_thumbnail_name_from_item(self): + """ + Custom thumbnail name based on item data, overriding default implementation + """ + + class CustomImagesPipeline(ImagesPipeline): + def thumb_path(self, request, thumb_id, response=None, info=None, item=None): + return f"thumb/{thumb_id}/{item.get('path')}" + + thumb_path = CustomImagesPipeline.from_settings(Settings( + {'IMAGES_STORE': self.tempdir} + )).thumb_path + item = dict(path='path-to-store-file') + request = Request("http://example.com") + self.assertEqual(thumb_path(request, 'small', item=item), 'thumb/small/path-to-store-file') + def test_get_images_exception(self): self.pipeline.min_width = 100 self.pipeline.min_height = 100 @@ -231,22 +246,6 @@ class ImagesPipelineTestCase(unittest.TestCase): self.assertEqual(converted.mode, 'RGB') self.assertEqual(converted.getcolors(), [(10000, (205, 230, 255))]) - def test_thumbnail_name_from_item(self): - """ - Custom thumbnail name based on item data, overriding default implementation - """ - - class CustomImagesPipeline(ImagesPipeline): - def thumb_path(self, request, thumb_id, response=None, info=None, item=None): - return f"thumb/{thumb_id}/{item.get('path')}" - - thumb_path = CustomImagesPipeline.from_settings(Settings( - {'IMAGES_STORE': self.tempdir} - )).thumb_path - item = dict(path='path-to-store-file') - request = Request("http://example.com") - self.assertEqual(thumb_path(request, 'small', item=item), 'thumb/small/path-to-store-file') - class DeprecatedImagesPipeline(ImagesPipeline): def file_key(self, url): @@ -536,11 +535,11 @@ class NoimagesDropTestCase(unittest.TestCase): def test_deprecation_warning(self): arg = str() - with catch_warnings(record=True) as warnings: + with warnings.catch_warnings(record=True) as warnings: NoimagesDrop(arg) self.assertEqual(len(warnings), 1) self.assertEqual(warnings[0].category, ScrapyDeprecationWarning) - with catch_warnings(record=True) as warnings: + with warnings.catch_warnings(record=True) as warnings: class SubclassedNoimagesDrop(NoimagesDrop): pass SubclassedNoimagesDrop(arg) From bbe24d79a5ee6a2afc8cd50bff4ac0e6df26886c Mon Sep 17 00:00:00 2001 From: Andrey Rahmatullin Date: Mon, 7 Nov 2022 17:08:54 +0500 Subject: [PATCH 19/19] Fix test issues --- tests/test_pipeline_images.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/test_pipeline_images.py b/tests/test_pipeline_images.py index 0c9a5733f..f98d40fda 100644 --- a/tests/test_pipeline_images.py +++ b/tests/test_pipeline_images.py @@ -5,7 +5,6 @@ import random import warnings from shutil import rmtree from tempfile import mkdtemp -from unittest import skipIf from unittest.mock import patch import attr @@ -535,16 +534,16 @@ class NoimagesDropTestCase(unittest.TestCase): def test_deprecation_warning(self): arg = str() - with warnings.catch_warnings(record=True) as warnings: + with warnings.catch_warnings(record=True) as w: NoimagesDrop(arg) - self.assertEqual(len(warnings), 1) - self.assertEqual(warnings[0].category, ScrapyDeprecationWarning) - with warnings.catch_warnings(record=True) as warnings: + self.assertEqual(len(w), 1) + self.assertEqual(w[0].category, ScrapyDeprecationWarning) + with warnings.catch_warnings(record=True) as w: class SubclassedNoimagesDrop(NoimagesDrop): pass SubclassedNoimagesDrop(arg) - self.assertEqual(len(warnings), 1) - self.assertEqual(warnings[0].category, ScrapyDeprecationWarning) + self.assertEqual(len(w), 1) + self.assertEqual(w[0].category, ScrapyDeprecationWarning) def _create_image(format, *a, **kw):