diff --git a/Tests/helper.py b/Tests/helper.py index 909fff879..8a54ca242 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -101,6 +101,7 @@ def assert_image_equal_tofile( msg: str | None = None, mode: str | None = None, ) -> None: + img: Image.Image with Image.open(filename) as img: if mode: img = img.convert(mode) @@ -144,6 +145,7 @@ def assert_image_similar_tofile( epsilon: float, msg: str | None = None, ) -> None: + img: Image.Image with Image.open(filename) as img: assert_image_similar(a, img, epsilon, msg) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 82cab39c6..b1f571c23 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -94,8 +94,11 @@ def test_good() -> None: for f in get_files("g"): try: + im: Image.Image with Image.open(f) as im: im.load() + + compare: Image.Image with Image.open(get_compare(f)) as compare: compare.load() if im.mode == "P": diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index a5734c202..f80e4822b 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -277,25 +277,25 @@ def test_apng_mode() -> None: assert isinstance(im, PngImagePlugin.PngImageFile) assert im.mode == "P" im.seek(im.n_frames - 1) - im = im.convert("RGB") - assert im.getpixel((0, 0)) == (0, 255, 0) - assert im.getpixel((64, 32)) == (0, 255, 0) + rgb_im = im.convert("RGB") + assert rgb_im.getpixel((0, 0)) == (0, 255, 0) + assert rgb_im.getpixel((64, 32)) == (0, 255, 0) with Image.open("Tests/images/apng/mode_palette_alpha.png") as im: assert isinstance(im, PngImagePlugin.PngImageFile) assert im.mode == "P" im.seek(im.n_frames - 1) - im = im.convert("RGBA") - assert im.getpixel((0, 0)) == (0, 255, 0, 255) - assert im.getpixel((64, 32)) == (0, 255, 0, 255) + rgb_im = im.convert("RGBA") + assert rgb_im.getpixel((0, 0)) == (0, 255, 0, 255) + assert rgb_im.getpixel((64, 32)) == (0, 255, 0, 255) with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im: assert isinstance(im, PngImagePlugin.PngImageFile) assert im.mode == "P" im.seek(im.n_frames - 1) - im = im.convert("RGBA") - assert im.getpixel((0, 0)) == (0, 0, 255, 128) - assert im.getpixel((64, 32)) == (0, 0, 255, 128) + rgb_im = im.convert("RGBA") + assert rgb_im.getpixel((0, 0)) == (0, 0, 255, 128) + assert rgb_im.getpixel((64, 32)) == (0, 0, 255, 128) def test_apng_chunk_errors() -> None: diff --git a/Tests/test_file_avif.py b/Tests/test_file_avif.py index bd87947c0..dc4f561a6 100644 --- a/Tests/test_file_avif.py +++ b/Tests/test_file_avif.py @@ -14,6 +14,7 @@ import pytest from PIL import ( AvifImagePlugin, + GifImagePlugin, Image, ImageDraw, ImageFile, @@ -220,6 +221,7 @@ class TestFileAvif: def test_background_from_gif(self, tmp_path: Path) -> None: with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) + assert isinstance(original_value, tuple) # Save as AVIF out_avif = tmp_path / "temp.avif" @@ -232,6 +234,7 @@ class TestFileAvif: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) + assert isinstance(reread_value, tuple) difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)]) assert difference <= 3 @@ -240,6 +243,7 @@ class TestFileAvif: with Image.open("Tests/images/chi.gif") as im: im.save(temp_file) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 def test_invalid_file(self) -> None: @@ -254,7 +258,9 @@ class TestFileAvif: assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0] == (876, 0) + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0] == (876, 0) def test_save_transparent(self, tmp_path: Path) -> None: im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) @@ -596,10 +602,12 @@ class TestAvifAnimation: """ with Image.open(TEST_AVIF_FILE) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 1 assert not im.is_animated with Image.open("Tests/images/avif/star.avifs") as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -610,11 +618,13 @@ class TestAvifAnimation: """ with Image.open("Tests/images/avif/star.gif") as original: + assert isinstance(original, GifImagePlugin.GifImageFile) assert original.n_frames > 1 temp_file = tmp_path / "temp.avif" original.save(temp_file, save_all=True) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == original.n_frames # Compare first frame in P mode to frame from original GIF @@ -634,6 +644,7 @@ class TestAvifAnimation: def check(temp_file: Path) -> None: with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 4 # Compare first frame to original @@ -706,6 +717,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated @@ -735,6 +747,7 @@ class TestAvifAnimation: ) with Image.open(temp_file) as im: + assert isinstance(im, AvifImagePlugin.AvifImageFile) assert im.n_frames == 5 assert im.is_animated diff --git a/Tests/test_file_bmp.py b/Tests/test_file_bmp.py index 757650711..decc3665b 100644 --- a/Tests/test_file_bmp.py +++ b/Tests/test_file_bmp.py @@ -160,9 +160,9 @@ def test_save_dib(tmp_path: Path) -> None: def test_rgba_bitfields() -> None: # This test image has been manually hexedited # to change the bitfield compression in the header from XBGR to RGBA - with Image.open("Tests/images/rgb32bf-rgba.bmp") as im: + with Image.open("Tests/images/rgb32bf-rgba.bmp") as bmp_im: # So before the comparing the image, swap the channels - b, g, r = im.split()[1:] + b, g, r = bmp_im.split()[1:] im = Image.merge("RGB", (r, g, b)) assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp") diff --git a/Tests/test_file_bufrstub.py b/Tests/test_file_bufrstub.py index 362578c56..8c6bb1a69 100644 --- a/Tests/test_file_bufrstub.py +++ b/Tests/test_file_bufrstub.py @@ -61,6 +61,7 @@ def test_handler(tmp_path: Path) -> None: def load(self, im: ImageFile.StubImageFile) -> Image.Image: self.loaded = True + assert im.fp is not None im.fp.close() return Image.new("RGB", (1, 1)) diff --git a/Tests/test_file_cur.py b/Tests/test_file_cur.py index dbf1b866d..ff82e2983 100644 --- a/Tests/test_file_cur.py +++ b/Tests/test_file_cur.py @@ -26,6 +26,7 @@ def test_invalid_file() -> None: no_cursors_file = "Tests/images/no_cursors.cur" cur = CurImagePlugin.CurImageFile(TEST_FILE) + assert cur.fp is not None cur.fp.close() with open(no_cursors_file, "rb") as cur.fp: with pytest.raises(TypeError): diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 3388fce16..1c33ca8f4 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -56,6 +56,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds" ) def test_sanity_dxt1_bc1(image_path: str) -> None: """Check DXT1 and BC1 images can be opened""" + target: Image.Image with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: target = target.convert("RGBA") with Image.open(image_path) as im: @@ -504,9 +505,9 @@ def test_save_dxt5(tmp_path: Path) -> None: def test_save_dx10_bc5(tmp_path: Path) -> None: out = tmp_path / "temp.dds" - with Image.open(TEST_FILE_DX10_BC5_TYPELESS) as im: - im.save(out, pixel_format="BC5") - assert_image_similar_tofile(im, out, 9.56) + with Image.open(TEST_FILE_DX10_BC5_TYPELESS) as img: + img.save(out, pixel_format="BC5") + assert_image_similar_tofile(img, out, 9.56) im = hopper("L") with pytest.raises(OSError, match="only RGB mode can be written as BC5"): diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index b484a8cfa..e406f9472 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -255,8 +255,8 @@ def test_bytesio_object() -> None: with Image.open(img_bytes) as img: img.load() - with Image.open(FILE1_COMPARE) as image1_scale1_compare: - image1_scale1_compare = image1_scale1_compare.convert("RGB") + with Image.open(FILE1_COMPARE) as im: + image1_scale1_compare = im.convert("RGB") image1_scale1_compare.load() assert_image_similar(img, image1_scale1_compare, 5) @@ -291,16 +291,16 @@ def test_render_scale1() -> None: # Zero bounding box with Image.open(FILE1) as image1_scale1: image1_scale1.load() - with Image.open(FILE1_COMPARE) as image1_scale1_compare: - image1_scale1_compare = image1_scale1_compare.convert("RGB") + with Image.open(FILE1_COMPARE) as im: + image1_scale1_compare = im.convert("RGB") image1_scale1_compare.load() assert_image_similar(image1_scale1, image1_scale1_compare, 5) # Non-zero bounding box with Image.open(FILE2) as image2_scale1: image2_scale1.load() - with Image.open(FILE2_COMPARE) as image2_scale1_compare: - image2_scale1_compare = image2_scale1_compare.convert("RGB") + with Image.open(FILE2_COMPARE) as im: + image2_scale1_compare = im.convert("RGB") image2_scale1_compare.load() assert_image_similar(image2_scale1, image2_scale1_compare, 10) @@ -314,8 +314,8 @@ def test_render_scale2() -> None: with Image.open(FILE1) as image1_scale2: assert isinstance(image1_scale2, EpsImagePlugin.EpsImageFile) image1_scale2.load(scale=2) - with Image.open(FILE1_COMPARE_SCALE2) as image1_scale2_compare: - image1_scale2_compare = image1_scale2_compare.convert("RGB") + with Image.open(FILE1_COMPARE_SCALE2) as im: + image1_scale2_compare = im.convert("RGB") image1_scale2_compare.load() assert_image_similar(image1_scale2, image1_scale2_compare, 5) @@ -323,8 +323,8 @@ def test_render_scale2() -> None: with Image.open(FILE2) as image2_scale2: assert isinstance(image2_scale2, EpsImagePlugin.EpsImageFile) image2_scale2.load(scale=2) - with Image.open(FILE2_COMPARE_SCALE2) as image2_scale2_compare: - image2_scale2_compare = image2_scale2_compare.convert("RGB") + with Image.open(FILE2_COMPARE_SCALE2) as im: + image2_scale2_compare = im.convert("RGB") image2_scale2_compare.load() assert_image_similar(image2_scale2, image2_scale2_compare, 10) @@ -334,9 +334,9 @@ def test_render_scale2() -> None: "filename", (FILE1, FILE2, "Tests/images/eps/illu10_preview.eps") ) def test_resize(filename: str) -> None: - with Image.open(filename) as im: + with Image.open(filename) as img: new_size = (100, 100) - im = im.resize(new_size) + im = img.resize(new_size) assert im.size == new_size diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index 81df1ab0b..49c528e11 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -43,6 +43,8 @@ def test_sanity() -> None: def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True) with Image.open(animated_test_file_with_prefix_chunk) as im: + assert isinstance(im, FliImagePlugin.FliImageFile) + assert im.mode == "P" assert im.size == (320, 200) assert im.format == "FLI" @@ -50,6 +52,7 @@ def test_prefix_chunk(monkeypatch: pytest.MonkeyPatch) -> None: assert im.is_animated palette = im.getpalette() + assert palette is not None assert palette[3:6] == [255, 255, 255] assert palette[381:384] == [204, 204, 12] assert palette[765:] == [252, 0, 0] diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 20d58a9dd..3b60c0860 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -224,6 +224,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None: out = BytesIO() im.save(out, "GIF", optimize=optimize) with Image.open(out) as reloaded: + assert reloaded.palette is not None assert len(reloaded.palette.palette) // 3 == colors @@ -280,6 +281,7 @@ def test_roundtrip_save_all(tmp_path: Path) -> None: im.save(out, save_all=True) with Image.open(out) as reread: + assert isinstance(reread, GifImagePlugin.GifImageFile) assert reread.n_frames == 5 @@ -305,6 +307,7 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None: ), ) def test_loading_multiple_palettes(path: str, mode: str) -> None: + im: Image.Image with Image.open(path) as im: assert im.mode == "P" assert im.palette is not None @@ -338,7 +341,7 @@ def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None: def test_palette_handling(tmp_path: Path) -> None: # see https://github.com/python-pillow/Pillow/issues/513 - + im: Image.Image with Image.open(TEST_GIF) as im: im = im.convert("RGB") @@ -362,8 +365,8 @@ def test_palette_434(tmp_path: Path) -> None: return reloaded - orig = "Tests/images/test.colors.gif" - with Image.open(orig) as im: + im: Image.Image + with Image.open("Tests/images/test.colors.gif") as im: with roundtrip(im) as reloaded: assert_image_similar(im, reloaded, 1) with roundtrip(im, optimize=True) as reloaded: @@ -378,6 +381,7 @@ def test_palette_434(tmp_path: Path) -> None: @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_bmp_mode(tmp_path: Path) -> None: + img: Image.Image with Image.open(TEST_GIF) as img: img = img.convert("RGB") @@ -390,6 +394,7 @@ def test_save_netpbm_bmp_mode(tmp_path: Path) -> None: @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_l_mode(tmp_path: Path) -> None: + img: Image.Image with Image.open(TEST_GIF) as img: img = img.convert("L") @@ -540,7 +545,9 @@ def test_dispose_background_transparency() -> None: img.seek(2) px = img.load() assert px is not None - assert px[35, 30][3] == 0 + value = px[35, 30] + assert isinstance(value, tuple) + assert value[3] == 0 @pytest.mark.parametrize( @@ -1017,9 +1024,9 @@ def test_webp_background(tmp_path: Path) -> None: # Test opaque WebP background if features.check("webp"): - with Image.open("Tests/images/hopper.webp") as im: - assert im.info["background"] == (255, 255, 255, 255) - im.save(out) + with Image.open("Tests/images/hopper.webp") as img: + assert img.info["background"] == (255, 255, 255, 255) + img.save(out) # Test non-opaque WebP background im = Image.new("L", (100, 100), "#000") @@ -1028,6 +1035,7 @@ def test_webp_background(tmp_path: Path) -> None: def test_comment(tmp_path: Path) -> None: + im: Image.Image with Image.open(TEST_GIF) as im: assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0" @@ -1358,7 +1366,9 @@ def test_palette_save_all_P(tmp_path: Path) -> None: with Image.open(out) as im: # Assert that the frames are correct, and each frame has the same palette assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) + assert isinstance(im, GifImagePlugin.GifImageFile) assert im.palette is not None + assert im.global_palette is not None assert im.palette.palette == im.global_palette.palette im.seek(1) @@ -1422,7 +1432,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None: def test_lzw_bits() -> None: # see https://github.com/python-pillow/Pillow/issues/2811 with Image.open("Tests/images/issue_2811.gif") as im: - assert im.tile[0][3][0] == 11 # LZW bits + args = im.tile[0][3] + assert isinstance(args, tuple) + assert args[0] == 11 # LZW bits # codec error prepatch im.load() @@ -1477,7 +1489,11 @@ def test_saving_rgba(tmp_path: Path) -> None: with Image.open(out) as reloaded: reloaded_rgba = reloaded.convert("RGBA") - assert reloaded_rgba.load()[0, 0][3] == 0 + px = reloaded_rgba.load() + assert px is not None + value = px[0, 0] + assert isinstance(value, tuple) + assert value[3] == 0 @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) diff --git a/Tests/test_file_gribstub.py b/Tests/test_file_gribstub.py index 960e5f4be..05925d502 100644 --- a/Tests/test_file_gribstub.py +++ b/Tests/test_file_gribstub.py @@ -59,8 +59,9 @@ def test_handler(tmp_path: Path) -> None: def open(self, im: Image.Image) -> None: self.opened = True - def load(self, im: Image.Image) -> Image.Image: + def load(self, im: ImageFile.ImageFile) -> Image.Image: self.loaded = True + assert im.fp is not None im.fp.close() return Image.new("RGB", (1, 1)) diff --git a/Tests/test_file_hdf5stub.py b/Tests/test_file_hdf5stub.py index e4f09a09c..e1a56309b 100644 --- a/Tests/test_file_hdf5stub.py +++ b/Tests/test_file_hdf5stub.py @@ -61,8 +61,9 @@ def test_handler(tmp_path: Path) -> None: def open(self, im: Image.Image) -> None: self.opened = True - def load(self, im: Image.Image) -> Image.Image: + def load(self, im: ImageFile.ImageFile) -> Image.Image: self.loaded = True + assert im.fp is not None im.fp.close() return Image.new("RGB", (1, 1)) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index c6c0c1aab..e0b3e3533 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -28,7 +28,7 @@ def test_getiptcinfo_jpg_none() -> None: # Arrange with hopper() as im: # Act - iptc = IptcImagePlugin.getiptcinfo(im) + iptc = IptcImagePlugin.getiptcinfo(im) # type: ignore[arg-type] # Assert assert iptc is None diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 79f0ec1a8..3787d3b7c 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -132,26 +132,30 @@ class TestFileJpeg: f = "Tests/images/pil_sample_cmyk.jpg" with Image.open(f) as im: # the source image has red pixels in the upper left corner. - c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) + value = im.getpixel((0, 0)) + assert isinstance(value, tuple) + c, m, y, k = (x / 255.0 for x in value) assert c == 0.0 assert m > 0.8 assert y > 0.8 assert k == 0.0 # the opposite corner is black - c, m, y, k = ( - x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ) + value = im.getpixel((im.size[0] - 1, im.size[1] - 1)) + assert isinstance(value, tuple) + c, m, y, k = (x / 255.0 for x in value) assert k > 0.9 # roundtrip, and check again im = self.roundtrip(im) - c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) + value = im.getpixel((0, 0)) + assert isinstance(value, tuple) + c, m, y, k = (x / 255.0 for x in value) assert c == 0.0 assert m > 0.8 assert y > 0.8 assert k == 0.0 - c, m, y, k = ( - x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) - ) + value = im.getpixel((im.size[0] - 1, im.size[1] - 1)) + assert isinstance(value, tuple) + c, m, y, k = (x / 255.0 for x in value) assert k > 0.9 def test_rgb(self) -> None: @@ -334,8 +338,10 @@ class TestFileJpeg: # Reading with Image.open("Tests/images/exif_gps.jpg") as im: - exif = im._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps # Writing f = tmp_path / "temp.jpg" @@ -344,8 +350,10 @@ class TestFileJpeg: hopper().save(f, exif=exif) with Image.open(f) as reloaded: - exif = reloaded._getexif() - assert exif[gps_index] == expected_exif_gps + assert isinstance(reloaded, JpegImagePlugin.JpegImageFile) + exif_data = reloaded._getexif() + assert exif_data is not None + assert exif_data[gps_index] == expected_exif_gps def test_empty_exif_gps(self) -> None: with Image.open("Tests/images/empty_gps_ifd.jpg") as im: @@ -372,6 +380,7 @@ class TestFileJpeg: exifs = [] for i in range(2): with Image.open("Tests/images/exif-200dpcm.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) exifs.append(im._getexif()) assert exifs[0] == exifs[1] @@ -405,13 +414,18 @@ class TestFileJpeg: } with Image.open("Tests/images/exif_gps.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif = im._getexif() + assert exif is not None for tag, value in expected_exif.items(): assert value == exif[tag] def test_exif_gps_typeerror(self) -> None: with Image.open("Tests/images/exif_gps_typeerror.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) + # Should not raise a TypeError im._getexif() @@ -491,7 +505,9 @@ class TestFileJpeg: def test_exif(self) -> None: with Image.open("Tests/images/pil_sample_rgb.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) info = im._getexif() + assert info is not None assert info[305] == "Adobe Photoshop CS Macintosh" def test_get_child_images(self) -> None: @@ -677,10 +693,12 @@ class TestFileJpeg: def test_save_multiple_16bit_qtables(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: im2 = self.roundtrip(im, qtables="keep") + assert isinstance(im, JpegImagePlugin.JpegImageFile) assert im.quantization == im2.quantization def test_save_single_16bit_qtable(self) -> None: with Image.open("Tests/images/hopper_16bit_qtables.jpg") as im: + assert isinstance(im, JpegImagePlugin.JpegImageFile) im2 = self.roundtrip(im, qtables={0: im.quantization[0]}) assert len(im2.quantization) == 1 assert im2.quantization[0] == im.quantization[0] @@ -889,7 +907,10 @@ class TestFileJpeg: # in contrast to normal 8 with Image.open("Tests/images/exif-ifd-offset.jpg") as im: # Act / Assert - assert im._getexif()[306] == "2017:03:13 23:03:09" + assert isinstance(im, JpegImagePlugin.JpegImageFile) + exif = im._getexif() + assert exif is not None + assert exif[306] == "2017:03:13 23:03:09" def test_multiple_exif(self) -> None: with Image.open("Tests/images/multiple_exif.jpg") as im: @@ -1110,8 +1131,9 @@ class TestFileCloseW32: im.save(tmpfile) im = Image.open(tmpfile) + assert im.fp is not None + assert not im.fp.closed fp = im.fp - assert not fp.closed with pytest.raises(OSError): os.remove(tmpfile) im.load() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 4095bfaf2..5b85829c0 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -164,7 +164,7 @@ def test_reduce() -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: assert callable(im.reduce) - im.reduce = 2 + im.reduce = 2 # type: ignore[method-assign, assignment] assert im.reduce == 2 im.load() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 9916215fb..6255f6536 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -11,7 +11,15 @@ from typing import Any, NamedTuple import pytest -from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features +from PIL import ( + Image, + ImageFile, + ImageFilter, + ImageOps, + TiffImagePlugin, + TiffTags, + features, +) from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD from .helper import ( @@ -27,7 +35,7 @@ from .helper import ( @skip_unless_feature("libtiff") class LibTiffTestCase: - def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None: + def _assert_noerr(self, tmp_path: Path, im: ImageFile.ImageFile) -> None: """Helper tests that assert basic sanity about the g4 tiff reading""" # 1 bit assert im.mode == "1" @@ -441,7 +449,6 @@ class TestFileLibTiff(LibTiffTestCase): assert isinstance(orig, TiffImagePlugin.TiffImageFile) out = tmp_path / "temp.tif" - orig.tag[269] = "temp.tif" orig.save(out) @@ -469,8 +476,8 @@ class TestFileLibTiff(LibTiffTestCase): # test case from irc, how to do blur on b/w image # and save to compressed tif. out = tmp_path / "temp.tif" - with Image.open("Tests/images/pport_g4.tif") as im: - im = im.convert("L") + with Image.open("Tests/images/pport_g4.tif") as img: + im = img.convert("L") im = im.filter(ImageFilter.GaussianBlur(4)) im.save(out, compression="tiff_adobe_deflate") @@ -564,8 +571,9 @@ class TestFileLibTiff(LibTiffTestCase): im.save(out, compression=compression) def test_fp_leak(self) -> None: - im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif") + im: ImageFile.ImageFile | None = Image.open("Tests/images/hopper_g4_500.tif") assert im is not None + assert im.fp is not None fn = im.fp.fileno() os.fstat(fn) @@ -1053,8 +1061,8 @@ class TestFileLibTiff(LibTiffTestCase): # Set EXIF Orientation to 2 data = data[:102] + b"\x02" + data[103:] - with Image.open(io.BytesIO(data)) as im: - im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + with Image.open(io.BytesIO(data)) as img: + im = img.transpose(Image.Transpose.FLIP_LEFT_RIGHT) assert_image_equal_tofile(im, "Tests/images/old-style-jpeg-compression.png") def test_open_missing_samplesperpixel(self) -> None: @@ -1122,9 +1130,8 @@ class TestFileLibTiff(LibTiffTestCase): with Image.open("Tests/images/g4_orientation_1.tif") as base_im: for i in range(2, 9): with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im: - im = ImageOps.exif_transpose(im) - - assert_image_similar(base_im, im, 0.7) + transposed_im = ImageOps.exif_transpose(im) + assert_image_similar(base_im, transposed_im, 0.7) @pytest.mark.parametrize( "test_file", diff --git a/Tests/test_file_mic.py b/Tests/test_file_mic.py index 9aeb306e4..0706af4c0 100644 --- a/Tests/test_file_mic.py +++ b/Tests/test_file_mic.py @@ -22,10 +22,10 @@ def test_sanity() -> None: # Adjust for the gamma of 2.2 encoded into the file lut = ImagePalette.make_gamma_lut(1 / 2.2) - im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) + im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()]) im2 = hopper("RGBA") - assert_image_similar(im, im2, 10) + assert_image_similar(im1, im2, 10) def test_n_frames() -> None: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 73838ef44..6f88d5a27 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -120,9 +120,11 @@ def test_ignore_frame_size() -> None: # Ignore the different size of the second frame # since this is not a "Large Thumbnail" image with Image.open("Tests/images/ignore_frame_size.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) assert im.size == (64, 64) im.seek(1) + assert im.mpinfo is not None assert ( im.mpinfo[0xB002][1]["Attribute"]["MPType"] == "Multi-Frame Image: (Disparity)" @@ -155,7 +157,9 @@ def test_reload_exif_after_seek() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None assert mpinfo[45056] == b"0100" assert mpinfo[45057] == 2 @@ -164,7 +168,9 @@ def test_mp_offset() -> None: # This image has been manually hexedited to have an IFD offset of 10 # in APP2 data, in contrast to normal 8 with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None assert mpinfo[45056] == b"0100" assert mpinfo[45057] == 2 @@ -180,7 +186,9 @@ def test_mp_no_data() -> None: @pytest.mark.parametrize("test_file", test_files) def test_mp_attribute(test_file: str) -> None: with Image.open(test_file) as im: + assert isinstance(im, MpoImagePlugin.MpoImageFile) mpinfo = im._getmp() + assert mpinfo is not None for frame_number, mpentry in enumerate(mpinfo[0xB002]): mpattr = mpentry["Attribute"] if frame_number: @@ -281,6 +289,7 @@ def test_save(test_file: str) -> None: def test_save_all() -> None: + im: Image.Image for test_file in test_files: with Image.open(test_file) as im: im_reloaded = roundtrip(im, save_all=True) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0f0886ab8..4714699b9 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -93,6 +93,7 @@ class TestFilePng: hopper("RGB").save(test_file) + im: Image.Image with Image.open(test_file) as im: im.load() assert im.mode == "RGB" @@ -103,6 +104,8 @@ class TestFilePng: for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]: im = hopper(mode) im.save(test_file) + + reloaded: Image.Image with Image.open(test_file) as reloaded: if mode in ("I", "I;16B"): reloaded = reloaded.convert(mode) @@ -225,11 +228,13 @@ class TestFilePng: test_file = "Tests/images/pil123p.png" with Image.open(test_file) as im: assert_image(im, "P", (162, 150)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (162, 150)) + rgba_im = im.convert("RGBA") + assert_image(rgba_im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = rgba_im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_load_transparent_rgb(self) -> None: test_file = "Tests/images/rgb_trns.png" @@ -237,11 +242,13 @@ class TestFilePng: assert im.info["transparency"] == (0, 255, 52) assert_image(im, "RGB", (64, 64)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (64, 64)) + rgba_im = im.convert("RGBA") + assert_image(rgba_im, "RGBA", (64, 64)) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = rgba_im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_palette(self, tmp_path: Path) -> None: in_file = "Tests/images/pil123p.png" @@ -258,11 +265,13 @@ class TestFilePng: assert len(im.info["transparency"]) == 256 assert_image(im, "P", (162, 150)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (162, 150)) + rgba_im = im.convert("RGBA") + assert_image(rgba_im, "RGBA", (162, 150)) # image has 124 unique alpha values - assert len(im.getchannel("A").getcolors()) == 124 + colors = rgba_im.getchannel("A").getcolors() + assert colors is not None + assert len(colors) == 124 def test_save_p_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/p_trns_single.png" @@ -279,13 +288,15 @@ class TestFilePng: assert im.info["transparency"] == 164 assert im.getpixel((31, 31)) == 164 assert_image(im, "P", (64, 64)) - im = im.convert("RGBA") - assert_image(im, "RGBA", (64, 64)) + rgba_im = im.convert("RGBA") + assert_image(rgba_im, "RGBA", (64, 64)) - assert im.getpixel((31, 31)) == (0, 255, 52, 0) + assert rgba_im.getpixel((31, 31)) == (0, 255, 52, 0) # image has 876 transparent pixels - assert im.getchannel("A").getcolors()[0][0] == 876 + colors = rgba_im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == 876 def test_save_p_transparent_black(self, tmp_path: Path) -> None: # check if solid black image with full transparency @@ -313,7 +324,9 @@ class TestFilePng: assert im.info["transparency"] == 255 im_rgba = im.convert("RGBA") - assert im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent test_file = tmp_path / "temp.png" im.save(test_file) @@ -324,7 +337,9 @@ class TestFilePng: assert_image_equal(im, test_im) test_im_rgba = test_im.convert("RGBA") - assert test_im_rgba.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im_rgba.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent def test_save_rgb_single_transparency(self, tmp_path: Path) -> None: in_file = "Tests/images/caption_6_33_22.png" @@ -671,6 +686,9 @@ class TestFilePng: im.save(out, bits=4, save_all=save_all) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 48 def test_plte_length(self, tmp_path: Path) -> None: @@ -681,6 +699,9 @@ class TestFilePng: im.save(out) with Image.open(out) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.png is not None + assert reloaded.png.im_palette is not None assert len(reloaded.png.im_palette[1]) == 3 def test_getxmp(self) -> None: @@ -702,13 +723,17 @@ class TestFilePng: def test_exif(self) -> None: # With an EXIF chunk with Image.open("Tests/images/exif.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With an ImageMagick zTXt chunk with Image.open("Tests/images/exif_imagemagick.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # Assert that info still can be extracted # when the image is no longer a PngImageFile instance @@ -717,8 +742,10 @@ class TestFilePng: # With a tEXt chunk with Image.open("Tests/images/exif_text.png") as im: - exif = im._getexif() - assert exif[274] == 1 + assert isinstance(im, PngImagePlugin.PngImageFile) + exif_data = im._getexif() + assert exif_data is not None + assert exif_data[274] == 1 # With XMP tags with Image.open("Tests/images/xmp_tags_orientation.png") as im: @@ -740,7 +767,9 @@ class TestFilePng: im.save(test_file, exif=im.getexif()) with Image.open(test_file) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) exif = reloaded._getexif() + assert exif is not None assert exif[274] == 1 @mark_if_feature_version( @@ -752,7 +781,9 @@ class TestFilePng: im.save(test_file, exif=im.getexif()) with Image.open(test_file) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) exif = reloaded._getexif() + assert exif is not None assert exif[305] == "Adobe Photoshop CS Macintosh" def test_exif_argument(self, tmp_path: Path) -> None: @@ -776,7 +807,6 @@ class TestFilePng: @pytest.mark.parametrize("buffer", (True, False)) def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None: - class MyStdOut: buffer = BytesIO() @@ -785,7 +815,7 @@ class TestFilePng: monkeypatch.setattr(sys, "stdout", mystdout) with Image.open(TEST_PNG_FILE) as im: - im.save(sys.stdout, "PNG") + im.save(sys.stdout, "PNG") # type: ignore[arg-type] if isinstance(mystdout, MyStdOut): mystdout = mystdout.buffer diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index 41e2b5416..d399c0e95 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -365,7 +365,6 @@ def test_mimetypes(tmp_path: Path) -> None: @pytest.mark.parametrize("buffer", (True, False)) def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None: - class MyStdOut: buffer = BytesIO() @@ -374,7 +373,7 @@ def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(sys, "stdout", mystdout) with Image.open(TEST_FILE) as im: - im.save(sys.stdout, "PPM") + im.save(sys.stdout, "PPM") # type: ignore[arg-type] if isinstance(mystdout, MyStdOut): mystdout = mystdout.buffer diff --git a/Tests/test_file_sun.py b/Tests/test_file_sun.py index c2f162cf9..782b5aac7 100644 --- a/Tests/test_file_sun.py +++ b/Tests/test_file_sun.py @@ -84,8 +84,8 @@ def test_rgbx() -> None: with Image.open(io.BytesIO(data)) as im: r, g, b = im.split() - im = Image.merge("RGB", (b, g, r)) - assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png")) + im_bgr = Image.merge("RGB", (b, g, r)) + assert_image_equal_tofile(im_bgr, os.path.join(EXTRA_DIR, "32bpp.png")) @pytest.mark.skipif( diff --git a/Tests/test_file_tga.py b/Tests/test_file_tga.py index 8b6ed3ed2..82532e12c 100644 --- a/Tests/test_file_tga.py +++ b/Tests/test_file_tga.py @@ -220,12 +220,16 @@ def test_horizontal_orientations() -> None: with Image.open("Tests/images/rgb32rle_top_right.tga") as im: px = im.load() assert px is not None - assert px[90, 90][:3] == (0, 0, 0) + value = px[90, 90] + assert isinstance(value, tuple) + assert value[:3] == (0, 0, 0) with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: px = im.load() assert px is not None - assert px[90, 90][:3] == (0, 255, 0) + value = px[90, 90] + assert isinstance(value, tuple) + assert value[:3] == (0, 255, 0) def test_save_rle(tmp_path: Path) -> None: @@ -268,13 +272,17 @@ def test_save_l_transparency(tmp_path: Path) -> None: in_file = "Tests/images/la.tga" with Image.open(in_file) as im: assert im.mode == "LA" - assert im.getchannel("A").getcolors()[0][0] == num_transparent + colors = im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent out = tmp_path / "temp.tga" im.save(out) with Image.open(out) as test_im: assert test_im.mode == "LA" - assert test_im.getchannel("A").getcolors()[0][0] == num_transparent + colors = test_im.getchannel("A").getcolors() + assert colors is not None + assert colors[0][0] == num_transparent assert_image_equal(im, test_im) diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 502d9df9a..11ecb757d 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -395,6 +395,7 @@ class TestFileTiff: assert isinstance(im, TiffImagePlugin.TiffImageFile) # Act + assert isinstance(im, TiffImagePlugin.TiffImageFile) ret = str(im.ifd) # Assert @@ -762,13 +763,13 @@ class TestFileTiff: def test_tiff_save_all(self) -> None: mp = BytesIO() - with Image.open("Tests/images/multipage.tiff") as im: - im.save(mp, format="tiff", save_all=True) + with Image.open("Tests/images/multipage.tiff") as img: + img.save(mp, format="tiff", save_all=True) mp.seek(0, os.SEEK_SET) - with Image.open(mp) as im: - assert isinstance(im, TiffImagePlugin.TiffImageFile) - assert im.n_frames == 3 + with Image.open(mp) as img: + assert isinstance(img, TiffImagePlugin.TiffImageFile) + assert img.n_frames == 3 # Test appending images mp = BytesIO() @@ -956,6 +957,7 @@ class TestFileTiff: im = Image.open(tmpfile) fp = im.fp + assert fp is not None assert not fp.closed im.load() assert fp.closed @@ -969,6 +971,7 @@ class TestFileTiff: with open(tmpfile, "rb") as f: im = Image.open(f) fp = im.fp + assert fp is not None assert not fp.closed im.load() assert not fp.closed @@ -1019,8 +1022,9 @@ class TestFileTiffW32: im.save(tmpfile) im = Image.open(tmpfile) + assert im.fp is not None + assert not im.fp.closed fp = im.fp - assert not fp.closed with pytest.raises(OSError): os.remove(tmpfile) im.load() diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 884868345..155ffee7f 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -175,13 +175,13 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None: del info[278] # Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT - im = im.resize((500, 500)) - info[TiffImagePlugin.IMAGEWIDTH] = im.width + resized_im = im.resize((500, 500)) + info[TiffImagePlugin.IMAGEWIDTH] = resized_im.width # STRIPBYTECOUNTS can be a SHORT or a LONG info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT - im.save(out, tiffinfo=info) + resized_im.save(out, tiffinfo=info) with Image.open(out) as reloaded: assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index f61e2c82e..4ea7629d1 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -219,6 +219,7 @@ class TestFileWebp: # Save P mode GIF with background with Image.open("Tests/images/chi.gif") as im: original_value = im.convert("RGB").getpixel((1, 1)) + assert isinstance(original_value, tuple) # Save as WEBP im.save(out_webp, save_all=True) @@ -230,6 +231,7 @@ class TestFileWebp: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) + assert isinstance(reread_value, tuple) difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3)) assert difference < 5 diff --git a/Tests/test_file_webp_metadata.py b/Tests/test_file_webp_metadata.py index 7543d22da..3de412b83 100644 --- a/Tests/test_file_webp_metadata.py +++ b/Tests/test_file_webp_metadata.py @@ -22,11 +22,13 @@ except ImportError: def test_read_exif_metadata() -> None: file_path = "Tests/images/flower.webp" with Image.open(file_path) as image: + assert isinstance(image, WebPImagePlugin.WebPImageFile) assert image.format == "WEBP" exif_data = image.info.get("exif", None) assert exif_data exif = image._getexif() + assert exif is not None # Camera make assert exif[271] == "Canon" diff --git a/Tests/test_image.py b/Tests/test_image.py index 7e6118d52..1156805d6 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -615,8 +615,8 @@ class TestImage: assert im.mode == mode assert im.getpixel((0, 0)) == 0 assert im.getpixel((255, 255)) == 255 - with Image.open(target_file) as target: - target = target.convert(mode) + with Image.open(target_file) as img: + target = img.convert(mode) assert_image_equal(im, target) def test_radial_gradient_wrong_mode(self) -> None: @@ -640,8 +640,8 @@ class TestImage: assert im.mode == mode assert im.getpixel((0, 0)) == 255 assert im.getpixel((128, 128)) == 0 - with Image.open(target_file) as target: - target = target.convert(mode) + with Image.open(target_file) as img: + target = img.convert(mode) assert_image_equal(im, target) def test_register_extensions(self) -> None: @@ -662,8 +662,8 @@ class TestImage: def test_remap_palette(self) -> None: # Test identity transform - with Image.open("Tests/images/hopper.gif") as im: - assert_image_equal(im, im.remap_palette(list(range(256)))) + with Image.open("Tests/images/hopper.gif") as img: + assert_image_equal(img, img.remap_palette(list(range(256)))) # Test identity transform with an RGBA palette im = Image.new("P", (256, 1)) @@ -673,6 +673,7 @@ class TestImage: im_remapped = im.remap_palette(list(range(256))) assert_image_equal(im, im_remapped) assert im.palette is not None + assert im_remapped.palette is not None assert im.palette.palette == im_remapped.palette.palette # Test illegal image mode diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 7d4f78c23..1f9ac41fc 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -76,8 +76,8 @@ def test_8bit() -> None: def test_16bit() -> None: - with Image.open("Tests/images/16bit.cropped.tif") as im: - _test_float_conversion(im) + with Image.open("Tests/images/16bit.cropped.tif") as img: + _test_float_conversion(img) for color in (65535, 65536): im = Image.new("I", (1, 1), color) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index 07fec2e64..b90ce84bc 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -78,13 +78,13 @@ def test_crop_crash() -> None: extents = (1, 1, 10, 10) # works prepatch with Image.open(test_img) as img: - img2 = img.crop(extents) - img2.load() + img1 = img.crop(extents) + img1.load() # fail prepatch with Image.open(test_img) as img: - img = img.crop(extents) - img.load() + img2 = img.crop(extents) + img2.load() def test_crop_zero() -> None: diff --git a/Tests/test_image_load.py b/Tests/test_image_load.py index 4f1d63b8f..1d5f0d17c 100644 --- a/Tests/test_image_load.py +++ b/Tests/test_image_load.py @@ -38,6 +38,7 @@ def test_close_after_load(caplog: pytest.LogCaptureFixture) -> None: def test_contextmanager() -> None: fn = None with Image.open("Tests/images/hopper.gif") as im: + assert im.fp is not None fn = im.fp.fileno() os.fstat(fn) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index f2c447f71..661764b60 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -62,6 +62,7 @@ def test_putpalette_with_alpha_values() -> None: expected = im.convert("RGBA") palette = im.getpalette() + assert palette is not None transparency = im.info.pop("transparency") palette_with_alpha_values = [] diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 0ca7ad86e..b9b2cbb0d 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -56,20 +56,21 @@ def test_rgba_quantize() -> None: def test_quantize() -> None: with Image.open("Tests/images/caption_6_33_22.png") as image: - image = image.convert("RGB") - converted = image.quantize() + converted = image.convert("RGB") + converted = converted.quantize() assert converted.mode == "P" assert_image_similar(converted.convert("RGB"), image, 1) def test_quantize_no_dither() -> None: - image = hopper() + palette: Image.Image with Image.open("Tests/images/caption_6_33_22.png") as palette: palette = palette.convert("P") - converted = image.quantize(dither=Image.Dither.NONE, palette=palette) + converted = hopper().quantize(dither=Image.Dither.NONE, palette=palette) assert converted.mode == "P" assert converted.palette is not None + assert palette.palette is not None assert converted.palette.palette == palette.palette.palette @@ -93,8 +94,8 @@ def test_quantize_no_dither2() -> None: def test_quantize_dither_diff() -> None: image = hopper() - with Image.open("Tests/images/caption_6_33_22.png") as palette: - palette = palette.convert("P") + with Image.open("Tests/images/caption_6_33_22.png") as im: + palette = im.convert("P") dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette) nodither = image.quantize(dither=Image.Dither.NONE, palette=palette) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index f700d20c0..63a372ff4 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -313,8 +313,8 @@ class TestImageResize: @skip_unless_feature("libtiff") def test_transposed(self) -> None: - with Image.open("Tests/images/g4_orientation_5.tif") as im: - im = im.resize((64, 64)) + with Image.open("Tests/images/g4_orientation_5.tif") as img: + im = img.resize((64, 64)) assert im.size == (64, 64) @pytest.mark.parametrize( diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 252a15db7..317328469 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -40,8 +40,8 @@ def test_mode(mode: str) -> None: @pytest.mark.parametrize("angle", (0, 90, 180, 270)) def test_angle(angle: int) -> None: - with Image.open("Tests/images/test-card.png") as im: - rotate(im, im.mode, angle) + with Image.open("Tests/images/test-card.png") as img: + rotate(img, img.mode, angle) im = hopper() assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1)) @@ -74,6 +74,7 @@ def test_center_0() -> None: im = hopper() im = im.rotate(45, center=(0, 0), resample=Image.Resampling.BICUBIC) + target: Image.Image with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 target = target.crop((0, target_origin, 128, target_origin + 128)) @@ -85,6 +86,7 @@ def test_center_14() -> None: im = hopper() im = im.rotate(45, center=(14, 14), resample=Image.Resampling.BICUBIC) + target: Image.Image with Image.open("Tests/images/hopper_45.png") as target: target_origin = target.size[1] / 2 - 14 target = target.crop((6, target_origin, 128 + 6, target_origin + 128)) @@ -94,6 +96,8 @@ def test_center_14() -> None: def test_translate() -> None: im = hopper() + + target: Image.Image with Image.open("Tests/images/hopper_45.png") as target: target_origin = (target.size[1] / 2 - 64) - 5 target = target.crop( diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 1181f6fca..bc1edeb26 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -156,6 +156,7 @@ def test_reducing_gap_values() -> None: def test_reducing_gap_for_DCT_scaling() -> None: + ref: Image.Image with Image.open("Tests/images/hopper.jpg") as ref: # thumbnail should call draft with reducing_gap scale ref.draft(None, (18 * 3, 18 * 3)) diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 77916929b..818f784b1 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -48,6 +48,7 @@ class TestImageTransform: im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] ) assert im.palette is not None + assert transformed.palette is not None assert im.palette.palette == transformed.palette.palette def test_extent(self) -> None: @@ -246,14 +247,14 @@ class TestImageTransform: def test_missing_method_data(self) -> None: with hopper() as im: with pytest.raises(ValueError): - im.transform((100, 100), None) + im.transform((100, 100), None) # type: ignore[arg-type] @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None: with hopper() as im: (w, h) = im.size with pytest.raises(ValueError): - im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) + im.transform((100, 100), Image.Transform.EXTENT, (0, 0, w, h), resample) # type: ignore[arg-type] class TestImageTransformAffine: diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ffe9c0979..994da24fb 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -194,6 +194,7 @@ def test_bitmap() -> None: # Arrange im = Image.new("RGB", (W, H)) draw = ImageDraw.Draw(im) + small: Image.Image with Image.open("Tests/images/pil123rgba.png") as small: small = small.resize((50, 50), Image.Resampling.NEAREST) diff --git a/Tests/test_imageops.py b/Tests/test_imageops.py index 9f2fd5ba2..d085ecc42 100644 --- a/Tests/test_imageops.py +++ b/Tests/test_imageops.py @@ -245,8 +245,8 @@ def test_colorize_2color() -> None: # Test the colorizing function with 2-color functionality # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as img: + im = img.convert("L") # Create image with original 2-color functionality im_test = ImageOps.colorize(im, "red", "green") @@ -285,8 +285,8 @@ def test_colorize_2color_offset() -> None: # Test the colorizing function with 2-color functionality and offset # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as img: + im = img.convert("L") # Create image with original 2-color functionality with offsets im_test = ImageOps.colorize( @@ -327,8 +327,8 @@ def test_colorize_3color_offset() -> None: # Test the colorizing function with 3-color functionality and offset # Open test image (256px by 10px, black to white) - with Image.open("Tests/images/bw_gradient.png") as im: - im = im.convert("L") + with Image.open("Tests/images/bw_gradient.png") as img: + im = img.convert("L") # Create image with new three color functionality with offsets im_test = ImageOps.colorize( @@ -422,6 +422,7 @@ def test_exif_transpose() -> None: check(orientation_im) # Orientation from "XML:com.adobe.xmp" info key + im: Image.Image for suffix in ("", "_exiftool"): with Image.open("Tests/images/xmp_tags_orientation" + suffix + ".png") as im: assert im.getexif()[0x0112] == 3 diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 7b9ac80bc..32da22e04 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -76,9 +76,14 @@ def test_consecutive() -> None: def test_palette_mmap() -> None: # Using mmap in ImageFile can require to reload the palette. with Image.open("Tests/images/multipage-mmap.tiff") as im: - color1 = im.getpalette()[:3] + palette = im.getpalette() + assert palette is not None + color1 = palette[:3] im.seek(0) - color2 = im.getpalette()[:3] + + palette = im.getpalette() + assert palette is not None + color2 = palette[:3] assert color1 == color2 diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index 1c48cb743..8e1fcec8b 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -17,6 +17,7 @@ def helper_pickle_file( tmp_path: Path, protocol: int, test_file: str, mode: str | None ) -> None: # Arrange + im: Image.Image with Image.open(test_file) as im: filename = tmp_path / "temp.pkl" if mode: @@ -33,6 +34,7 @@ def helper_pickle_file( def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None: + im: Image.Image with Image.open(test_file) as im: if mode: im = im.convert(mode) @@ -89,8 +91,8 @@ def test_pickle_jpeg() -> None: def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: # Arrange filename = tmp_path / "temp.pkl" - with Image.open("Tests/images/hopper.jpg") as im: - im = im.convert("PA") + with Image.open("Tests/images/hopper.jpg") as img: + im = img.convert("PA") # Act / Assert for protocol in range(pickle.HIGHEST_PROTOCOL + 1): diff --git a/Tests/test_shell_injection.py b/Tests/test_shell_injection.py index 03e92b5b9..f49153f3e 100644 --- a/Tests/test_shell_injection.py +++ b/Tests/test_shell_injection.py @@ -50,11 +50,13 @@ class TestShellInjection: @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: - im = im.convert("RGB") - self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) + im_rgb = im.convert("RGB") + self.assert_save_filename_check( + tmp_path, im_rgb, GifImagePlugin._save_netpbm + ) @pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available") def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None: with Image.open(TEST_GIF) as im: - im = im.convert("L") - self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm) + im_l = im.convert("L") + self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm) diff --git a/docs/example/DdsImagePlugin.py b/docs/example/DdsImagePlugin.py index e4b6b9c01..e0557976c 100644 --- a/docs/example/DdsImagePlugin.py +++ b/docs/example/DdsImagePlugin.py @@ -213,6 +213,7 @@ class DdsImageFile(ImageFile.ImageFile): format_description = "DirectDraw Surface" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "not a DDS file" raise SyntaxError(msg) diff --git a/pyproject.toml b/pyproject.toml index e8e76796a..416f78f7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,6 +175,7 @@ testpaths = [ python_version = "3.9" pretty = true disallow_any_generics = true +disallow_untyped_defs = true enable_error_code = "ignore-without-code" extra_checks = true follow_imports = "silent" diff --git a/src/PIL/AvifImagePlugin.py b/src/PIL/AvifImagePlugin.py index b2c5ab15d..1f6a4e908 100644 --- a/src/PIL/AvifImagePlugin.py +++ b/src/PIL/AvifImagePlugin.py @@ -78,6 +78,8 @@ class AvifImageFile(ImageFile.ImageFile): ): msg = "Invalid opening codec" raise ValueError(msg) + + assert self.fp is not None self._decoder = _avif.AvifDecoder( self.fp.read(), DECODE_CODEC_CHOICE, diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index f7be7746d..6bb92edf8 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -258,6 +258,7 @@ class BlpImageFile(ImageFile.ImageFile): format_description = "Blizzard Mipmap Format" def _open(self) -> None: + assert self.fp is not None self.magic = self.fp.read(4) if not _accept(self.magic): msg = f"Bad BLP magic {repr(self.magic)}" diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 43131cfe2..e052fbb50 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -76,6 +76,7 @@ class BmpImageFile(ImageFile.ImageFile): def _bitmap(self, header: int = 0, offset: int = 0) -> None: """Read relevant info about the BMP""" + assert self.fp is not None read, seek = self.fp.read, self.fp.seek if header: seek(header) @@ -311,6 +312,7 @@ class BmpImageFile(ImageFile.ImageFile): def _open(self) -> None: """Open file, check magic number and read header""" # read 14 bytes: magic number, filesize, reserved, header final offset + assert self.fp is not None head_data = self.fp.read(14) # choke if the file does not have the required magic bytes if not _accept(head_data): diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 8c5da14f5..264564d2b 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -41,6 +41,7 @@ class BufrStubImageFile(ImageFile.StubImageFile): format_description = "BUFR" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "Not a BUFR file" raise SyntaxError(msg) diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b817dbc87..ebdea561a 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -38,6 +38,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): format_description = "Windows Cursor" def _open(self) -> None: + assert self.fp is not None offset = self.fp.tell() # check magic diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index aea661b9c..d3f456ddc 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -45,6 +45,7 @@ class DcxImageFile(PcxImageFile): def _open(self) -> None: # Header + assert self.fp is not None s = self.fp.read(4) if not _accept(s): msg = "not a DCX file" diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 26307817c..a2ae9b148 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -333,6 +333,7 @@ class DdsImageFile(ImageFile.ImageFile): format_description = "DirectDraw Surface" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "not a DDS file" raise SyntaxError(msg) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 5e2ddad99..d8d1efdb2 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -189,6 +189,7 @@ class EpsImageFile(ImageFile.ImageFile): mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} def _open(self) -> None: + assert self.fp is not None (length, offset) = self._find_offset(self.fp) # go to offset - start of "%!PS" @@ -400,6 +401,7 @@ class EpsImageFile(ImageFile.ImageFile): ) -> Image.core.PixelAccess | None: # Load EPS via Ghostscript if self.tile: + assert self.fp is not None self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency) self._mode = self.im.mode self._size = self.im.size diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 7c5bfeefa..94ffbb1c7 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -48,6 +48,7 @@ class FliImageFile(ImageFile.ImageFile): def _open(self) -> None: # HEAD + assert self.fp is not None s = self.fp.read(128) if not (_accept(s) and s[20:22] == b"\x00\x00"): msg = "not an FLI/FLC file" @@ -111,6 +112,7 @@ class FliImageFile(ImageFile.ImageFile): # load palette i = 0 + assert self.fp is not None for e in range(i16(self.fp.read(2))): s = self.fp.read(2) i = i + s[0] diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index fd992cd9e..297971234 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -58,6 +58,7 @@ class FpxImageFile(ImageFile.ImageFile): # read the OLE directory and see if this is a likely # to be a FlashPix file + assert self.fp is not None try: self.ole = olefile.OleFileIO(self.fp) except OSError as e: @@ -229,6 +230,7 @@ class FpxImageFile(ImageFile.ImageFile): if y >= ysize: break # isn't really required + assert self.fp is not None self.stream = stream self._fp = self.fp self.fp = None diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index d60e75bb6..e4d836cbd 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -72,6 +72,7 @@ class FtexImageFile(ImageFile.ImageFile): format_description = "Texture File Format (IW2:EOC)" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "not an FTEX file" raise SyntaxError(msg) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index f319d7e84..a61bb883f 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -42,6 +42,7 @@ class GbrImageFile(ImageFile.ImageFile): format_description = "GIMP brush file" def _open(self) -> None: + assert self.fp is not None header_size = i32(self.fp.read(4)) if header_size < 20: msg = "not a GIMP brush" @@ -91,6 +92,8 @@ class GbrImageFile(ImageFile.ImageFile): def load(self) -> Image.core.PixelAccess | None: if self._im is None: self.im = Image.core.new(self.mode, self.size) + + assert self.fp is not None self.frombytes(self.fp.read(self._data_size)) return Image.Image.load(self) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 4392c4cb9..a83f0fad7 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -31,7 +31,7 @@ import os import subprocess from enum import IntEnum from functools import cached_property -from typing import IO, Any, Literal, NamedTuple, Union +from typing import IO, Any, Literal, NamedTuple, Union, cast from . import ( Image, @@ -85,6 +85,7 @@ class GifImageFile(ImageFile.ImageFile): global_palette = None def data(self) -> bytes | None: + assert self.fp is not None s = self.fp.read(1) if s and s[0]: return self.fp.read(s[0]) @@ -98,6 +99,7 @@ class GifImageFile(ImageFile.ImageFile): def _open(self) -> None: # Screen + assert self.fp is not None s = self.fp.read(13) if not _accept(s): msg = "not a GIF file" @@ -114,8 +116,8 @@ class GifImageFile(ImageFile.ImageFile): # check if palette contains colour indices p = self.fp.read(3 << bits) if self._is_palette_needed(p): - p = ImagePalette.raw("RGB", p) - self.global_palette = self.palette = p + palette = ImagePalette.raw("RGB", p) + self.global_palette = self.palette = palette self._fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() @@ -254,7 +256,7 @@ class GifImageFile(ImageFile.ImageFile): info["comment"] += b"\n" + comment else: info["comment"] = comment - s = None + s = b"" continue elif s[0] == 255 and frame == 0 and block is not None: # @@ -297,7 +299,7 @@ class GifImageFile(ImageFile.ImageFile): bits = self.fp.read(1)[0] self.__offset = self.fp.tell() break - s = None + s = b"" if interlace is None: msg = "image not found in GIF frame" @@ -350,7 +352,10 @@ class GifImageFile(ImageFile.ImageFile): if self._frame_palette: if color * 3 + 3 > len(self._frame_palette.palette): color = 0 - return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) + return cast( + tuple[int, int, int], + tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]), + ) else: return (color, color, color) diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 439fc5a3e..59ba572c7 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -41,6 +41,7 @@ class GribStubImageFile(ImageFile.StubImageFile): format_description = "GRIB" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(8)): msg = "Not a GRIB file" raise SyntaxError(msg) diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index 76e640f15..1523e95d5 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -41,6 +41,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile): format_description = "HDF5" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(8)): msg = "Not an HDF file" raise SyntaxError(msg) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 5a88429e5..4353987dd 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -266,6 +266,7 @@ class IcnsImageFile(ImageFile.ImageFile): format_description = "Mac OS icns resource" def _open(self) -> None: + assert self.fp is not None self.icns = IcnsFile(self.fp) self._mode = "RGBA" self.info["sizes"] = self.icns.itersizes() diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 55c57f203..7d7e9a0fb 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -326,6 +326,7 @@ class IcoImageFile(ImageFile.ImageFile): format_description = "Windows Icon" def _open(self) -> None: + assert self.fp is not None self.ico = IcoFile(self.fp) self.info["sizes"] = self.ico.sizes() self.size = self.ico.entry[0].dim diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 71b999678..f90e080be 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -125,6 +125,7 @@ class ImImageFile(ImageFile.ImageFile): # Quick rejection: if there's not an LF among the first # 100 bytes, this is (probably) not a text header. + assert self.fp is not None if b"\n" not in self.fp.read(100): msg = "not an IM file" raise SyntaxError(msg) @@ -304,6 +305,8 @@ class ImImageFile(ImageFile.ImageFile): size = ((self.size[0] * bits + 7) // 8) * self.size[1] offs = self.__offset + frame * size + if isinstance(self._fp, DeferredError): + raise self._fp.ex self.fp = self._fp self.tile = [ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 88ea6f3b5..bef7dd8f5 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -601,16 +601,11 @@ class Image: return new # Context manager support - def __enter__(self): + def __enter__(self) -> Image: return self - def __exit__(self, *args): - from . import ImageFile - - if isinstance(self, ImageFile.ImageFile): - if getattr(self, "_exclusive_fp", False): - self._close_fp() - self.fp = None + def __exit__(self, *args: object) -> None: + pass def close(self) -> None: """ @@ -1528,10 +1523,14 @@ class Image: exif_info = bytes.fromhex( "".join(self.info["Raw profile type exif"].split("\n")[3:]) ) - elif hasattr(self, "tag_v2"): - self._exif.bigtiff = self.tag_v2._bigtiff - self._exif.endian = self.tag_v2._endian - self._exif.load_from_fp(self.fp, self.tag_v2._offset) + else: + from . import TiffImagePlugin + + if isinstance(self, TiffImagePlugin.TiffImageFile): + self._exif.bigtiff = self.tag_v2._bigtiff + self._exif.endian = self.tag_v2._endian + assert self.fp is not None + self._exif.load_from_fp(self.fp, self.tag_v2._offset) if exif_info is not None: self._exif.load(exif_info) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index bcb7d462e..bf88c8458 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -130,6 +130,8 @@ class ImageFile(Image.Image): self.decoderconfig: tuple[Any, ...] = () self.decodermaxblock = MAXBLOCK + self.fp: IO[bytes] | None + self._fp: IO[bytes] | DeferredError if is_path(fp): # filename self.fp = open(fp, "rb") @@ -166,14 +168,23 @@ class ImageFile(Image.Image): def _open(self) -> None: pass - def _close_fp(self): - if getattr(self, "_fp", False) and not isinstance(self._fp, DeferredError): + # Context manager support + def __enter__(self) -> ImageFile: + return self + + def _close_fp(self) -> None: + if hasattr(self, "_fp") and not isinstance(self._fp, DeferredError): if self._fp != self.fp: self._fp.close() self._fp = DeferredError(ValueError("Operation on closed image")) if self.fp: self.fp.close() + def __exit__(self, *args: object) -> None: + if getattr(self, "_exclusive_fp", False): + self._close_fp() + self.fp = None + def close(self) -> None: """ Closes the file pointer, if possible. @@ -265,7 +276,7 @@ class ImageFile(Image.Image): # raise exception if something's wrong. must be called # directly after open, and closes file when finished. - if self._exclusive_fp: + if self._exclusive_fp and self.fp: self.fp.close() self.fp = None @@ -283,6 +294,7 @@ class ImageFile(Image.Image): self.map: mmap.mmap | None = None use_mmap = self.filename and len(self.tile) == 1 + assert self.fp is not None readonly = 0 # look for read/seek overrides diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 60ab7c83f..a867cba77 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -77,6 +77,7 @@ class IptcImageFile(ImageFile.ImageFile): def field(self) -> tuple[tuple[int, int] | None, int]: # # get a IPTC field header + assert self.fp is not None s = self.fp.read(5) if not s.strip(b"\x00"): return None, 0 @@ -104,6 +105,7 @@ class IptcImageFile(ImageFile.ImageFile): def _open(self) -> None: # load descriptive fields + assert self.fp is not None while True: offset = self.fp.tell() tag, size = self.field() @@ -157,6 +159,7 @@ class IptcImageFile(ImageFile.ImageFile): offset, compression = self.tile[0][2:] + assert self.fp is not None self.fp.seek(offset) # Copy image data to temporary file @@ -165,6 +168,7 @@ class IptcImageFile(ImageFile.ImageFile): # To simplify access to the extracted file, # prepend a PPM header o.write(b"P5\n%d %d\n255\n" % self.size) + assert self.fp is not None while True: type, size = self.field() if type != (8, 10): diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index e0f4ecae5..63f6061f5 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -248,6 +248,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): format_description = "JPEG 2000 (ISO 15444)" def _open(self) -> None: + assert self.fp is not None sig = self.fp.read(4) if sig == b"\xff\x4f\xff\x51": self.codec = "j2k" @@ -300,6 +301,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): ] def _parse_comment(self) -> None: + assert self.fp is not None while True: marker = self.fp.read(2) if not marker: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 969528841..b9d493373 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -61,6 +61,7 @@ if TYPE_CHECKING: def Skip(self: JpegImageFile, marker: int) -> None: + assert self.fp is not None n = i16(self.fp.read(2)) - 2 ImageFile._safe_read(self.fp, n) @@ -70,6 +71,7 @@ def APP(self: JpegImageFile, marker: int) -> None: # Application marker. Store these in the APP dictionary. # Also look for well-known application markers. + assert self.fp is not None n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) @@ -174,6 +176,7 @@ def APP(self: JpegImageFile, marker: int) -> None: def COM(self: JpegImageFile, marker: int) -> None: # # Comment marker. Store these in the APP dictionary. + assert self.fp is not None n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) @@ -190,6 +193,7 @@ def SOF(self: JpegImageFile, marker: int) -> None: # mode. Note that this could be made a bit brighter, by # looking for JFIF and Adobe APP markers. + assert self.fp is not None n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) self._size = i16(s, 3), i16(s, 1) @@ -238,6 +242,7 @@ def DQT(self: JpegImageFile, marker: int) -> None: # FIXME: The quantization tables can be used to estimate the # compression quality. + assert self.fp is not None n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) while len(s): @@ -338,6 +343,7 @@ class JpegImageFile(ImageFile.ImageFile): format_description = "JPEG (ISO 10918)" def _open(self) -> None: + assert self.fp is not None s = self.fp.read(3) if not _accept(s): @@ -412,6 +418,7 @@ class JpegImageFile(ImageFile.ImageFile): For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker so libjpeg can finish decoding """ + assert self.fp is not None s = self.fp.read(read_bytes) if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"): diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 9ce38c427..99a07bae0 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -67,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self._n_frames = len(self.images) self.is_animated = self._n_frames > 1 + assert self.fp is not None self.__fp = self.fp self.seek(0) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f7393eac0..3d04f9b95 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -99,6 +99,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): _close_exclusive_fp_after_loading = False def _open(self) -> None: + assert self.fp is not None self.fp.seek(0) # prep the fp in order to pass the JPEG test JpegImagePlugin.JpegImageFile._open(self) self._after_jpeg_open() @@ -118,6 +119,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): assert self.n_frames == len(self.__mpoffsets) del self.info["mpoffset"] # no longer needed self.is_animated = self.n_frames > 1 + assert self.fp is not None self._fp = self.fp # FIXME: hack self._fp.seek(self.__mpoffsets[0]) # get ready to read first frame self.__frame = 0 diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index f3815a122..ecc93627b 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -754,6 +754,7 @@ class PngImageFile(ImageFile.ImageFile): format_description = "Portable network graphics" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(8)): msg = "not a PNG file" raise SyntaxError(msg) @@ -985,6 +986,7 @@ class PngImageFile(ImageFile.ImageFile): """internal: read more image data""" assert self.png is not None + assert self.fp is not None while self.__idat == 0: # end of chunk, skip forward to next one @@ -1018,6 +1020,7 @@ class PngImageFile(ImageFile.ImageFile): def load_end(self) -> None: """internal: finished reading image data""" assert self.png is not None + assert self.fp is not None if self.__idat != 0: self.fp.read(self.__idat) while True: diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index f49aaeeb1..a00576a81 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -61,6 +61,7 @@ class PsdImageFile(ImageFile.ImageFile): _close_exclusive_fp_after_loading = False def _open(self) -> None: + assert self.fp is not None read = self.fp.read # @@ -178,6 +179,8 @@ class PsdImageFile(ImageFile.ImageFile): self._mode = mode self.tile = tile self.frame = layer + if isinstance(self._fp, DeferredError): + raise self._fp.ex self.fp = self._fp def tell(self) -> int: diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index df552243e..9b06a973e 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -22,6 +22,7 @@ class QoiImageFile(ImageFile.ImageFile): format_description = "Quite OK Image" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(4)): msg = "not a QOI file" raise SyntaxError(msg) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 868019e80..62c834745 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -103,6 +103,7 @@ class SpiderImageFile(ImageFile.ImageFile): def _open(self) -> None: # check header + assert self.fp is not None n = 27 * 4 # read 27 float values f = self.fp.read(n) @@ -184,6 +185,9 @@ class SpiderImageFile(ImageFile.ImageFile): if isinstance(self._fp, DeferredError): raise self._fp.ex self.stkoffset = self.hdrlen + frame * (self.hdrlen + self.imgbytes) + + if isinstance(self._fp, DeferredError): + raise self._fp.ex self.fp = self._fp self.fp.seek(self.stkoffset) self._open() @@ -323,9 +327,9 @@ if __name__ == "__main__": outfile = sys.argv[2] # perform some image operation - im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + transposed_im = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) print( f"saving a flipped version of {os.path.basename(filename)} " f"as {outfile} " ) - im.save(outfile, SpiderImageFile.format) + transposed_im.save(outfile, SpiderImageFile.format) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 88af9162e..20b77c563 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1179,6 +1179,7 @@ class TiffImageFile(ImageFile.ImageFile): """Open the first image in a TIFF file""" # Header + assert self.fp is not None ifh = self.fp.read(8) if ifh[2] == 43: ifh += self.fp.read(8) @@ -1341,6 +1342,7 @@ class TiffImageFile(ImageFile.ImageFile): # To be nice on memory footprint, if there's a # file descriptor, use that instead of reading # into a string in python. + assert self.fp is not None try: fp = hasattr(self.fp, "fileno") and self.fp.fileno() # flush the file descriptor, prevents error on pypy 2.4+ diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 87e32878b..7e967ff14 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -39,6 +39,7 @@ class WalImageFile(ImageFile.ImageFile): self._mode = "P" # read header fields + assert self.fp is not None header = self.fp.read(32 + 24 + 32 + 12) self._size = i32(header, 32), i32(header, 36) Image._decompression_bomb_check(self.size) @@ -55,6 +56,7 @@ class WalImageFile(ImageFile.ImageFile): def load(self) -> Image.core.PixelAccess | None: if self._im is None: + assert self.fp is not None self.im = Image.core.new(self.mode, self.size) self.frombytes(self.fp.read(self.size[0] * self.size[1])) self.putpalette(quake2palette) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 1716a18cc..f09ed9191 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -43,6 +43,7 @@ class WebPImageFile(ImageFile.ImageFile): def _open(self) -> None: # Use the newer AnimDecoder API to parse the (possibly) animated file, # and access muxed chunks like ICC/EXIF/XMP. + assert self.fp is not None self._decoder = _webp.WebPAnimDecoder(self.fp.read()) # Get info from decoder diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index f709d026b..f4945a2d8 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -49,6 +49,7 @@ if hasattr(Image.core, "drawwmf"): self.bbox = im.info["wmf_bbox"] def load(self, im: ImageFile.StubImageFile) -> Image.Image: + assert im.fp is not None im.fp.seek(0) # rewind return Image.frombytes( "RGB", @@ -81,6 +82,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): def _open(self) -> None: # check placable header + assert self.fp is not None s = self.fp.read(80) if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): diff --git a/src/PIL/XVThumbImagePlugin.py b/src/PIL/XVThumbImagePlugin.py index cde28388f..9bb93bad1 100644 --- a/src/PIL/XVThumbImagePlugin.py +++ b/src/PIL/XVThumbImagePlugin.py @@ -58,15 +58,15 @@ class XVThumbImageFile(ImageFile.ImageFile): # skip info comments while True: - s = self.fp.readline() - if not s: + line = self.fp.readline() + if not line: msg = "Unexpected EOF reading XV thumbnail file" raise SyntaxError(msg) - if s[0] != 35: # ie. when not a comment: '#' + if line[0] != 35: # ie. when not a comment: '#' break # parse header line (already read) - s = s.strip().split() + s = line.strip().split() self._mode = "P" self._size = int(s[0]), int(s[1]) diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index 3c932c41b..17576b86c 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -37,17 +37,18 @@ class XpmImageFile(ImageFile.ImageFile): format_description = "X11 Pixel Map" def _open(self) -> None: + assert self.fp is not None if not _accept(self.fp.read(9)): msg = "not an XPM file" raise SyntaxError(msg) # skip forward to next string while True: - s = self.fp.readline() - if not s: + line = self.fp.readline() + if not line: msg = "broken XPM file" raise SyntaxError(msg) - m = xpm_head.match(s) + m = xpm_head.match(line) if m: break @@ -66,14 +67,14 @@ class XpmImageFile(ImageFile.ImageFile): palette = [b"\0\0\0"] * 256 for _ in range(pal): - s = self.fp.readline() - if s.endswith(b"\r\n"): - s = s[:-2] - elif s.endswith((b"\r", b"\n")): - s = s[:-1] + line = self.fp.readline() + if line.endswith(b"\r\n"): + line = line[:-2] + elif line.endswith((b"\r", b"\n")): + line = line[:-1] - c = s[1] - s = s[2:-2].split() + c = line[1] + s = line[2:-2].split() for i in range(0, len(s), 2): if s[i] == b"c": @@ -83,9 +84,11 @@ class XpmImageFile(ImageFile.ImageFile): self.info["transparency"] = c elif rgb.startswith(b"#"): # FIXME: handle colour names (see ImagePalette.py) - rgb = int(rgb[1:], 16) + rgb_int = int(rgb[1:], 16) palette[c] = ( - o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255) + o8((rgb_int >> 16) & 255) + + o8((rgb_int >> 8) & 255) + + o8(rgb_int & 255) ) else: # unknown colour @@ -109,6 +112,7 @@ class XpmImageFile(ImageFile.ImageFile): xsize, ysize = self.size + assert self.fp is not None s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] return b"".join(s)