diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2254178d5..d2592da97 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -601,7 +601,7 @@ def test_save_dispose(tmp_path: Path) -> None: Image.new("L", (100, 100), "#111"), Image.new("L", (100, 100), "#222"), ] - for method in range(0, 4): + for method in range(4): im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=method) with Image.open(out) as img: for _ in range(2): diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 369c2db1b..f284c3f2f 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1140,11 +1140,9 @@ class TestFileLibTiff(LibTiffTestCase): def test_realloc_overflow(self, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(TiffImagePlugin, "READ_LIBTIFF", True) with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im: - with pytest.raises(OSError) as e: - im.load() - # Assert that the error code is IMAGING_CODEC_MEMORY - assert str(e.value) == "decoder error -9" + with pytest.raises(OSError, match="decoder error -9"): + im.load() @pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg")) def test_save_multistrip(self, compression: str, tmp_path: Path) -> None: diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index d87192ca5..c93a8c73a 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -293,12 +293,10 @@ def test_header_token_too_long(tmp_path: Path) -> None: with open(path, "wb") as f: f.write(b"P6\n 01234567890") - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="Token too long in file header: 01234567890"): with Image.open(path): pass - assert str(e.value) == "Token too long in file header: 01234567890" - def test_truncated_file(tmp_path: Path) -> None: # Test EOF in header @@ -306,12 +304,10 @@ def test_truncated_file(tmp_path: Path) -> None: with open(path, "wb") as f: f.write(b"P6") - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="Reached EOF while reading header"): with Image.open(path): pass - assert str(e.value) == "Reached EOF while reading header" - # Test EOF for PyDecoder fp = BytesIO(b"P5 3 1 4") with Image.open(fp) as im: @@ -335,12 +331,12 @@ def test_invalid_maxval(maxval: bytes, tmp_path: Path) -> None: with open(path, "wb") as f: f.write(b"P6\n3 1 " + maxval) - with pytest.raises(ValueError) as e: + with pytest.raises( + ValueError, match="maxval must be greater than 0 and less than 65536" + ): with Image.open(path): pass - assert str(e.value) == "maxval must be greater than 0 and less than 65536" - def test_neg_ppm() -> None: # Storage.c accepted negative values for xsize, ysize. the diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index a8a407963..c1ccf3fe2 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -134,9 +134,8 @@ class TestFileTiff: def test_set_legacy_api(self) -> None: ifd = TiffImagePlugin.ImageFileDirectory_v2() - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match="Not allowing setting of legacy api"): ifd.legacy_api = False - assert str(e.value) == "Not allowing setting of legacy api" def test_xyres_tiff(self) -> None: filename = "Tests/images/pil168.tif" diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index abe888241..f61e2c82e 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -154,9 +154,8 @@ class TestFileWebp: @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_message(self, tmp_path: Path) -> None: im = Image.new("RGB", (15000, 15000)) - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="encoding error 6"): im.save(tmp_path / "temp.webp", method=0) - assert str(e.value) == "encoding error 6" @pytest.mark.skipif(sys.maxsize <= 2**32, reason="Requires 64-bit system") def test_write_encoding_error_bad_dimension(self, tmp_path: Path) -> None: @@ -231,7 +230,7 @@ class TestFileWebp: with Image.open(out_gif) as reread: reread_value = reread.convert("RGB").getpixel((1, 1)) - difference = sum(abs(original_value[i] - reread_value[i]) for i in range(0, 3)) + difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3)) assert difference < 5 def test_duration(self, tmp_path: Path) -> None: diff --git a/Tests/test_image.py b/Tests/test_image.py index 5474f951c..d64816b1e 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -65,9 +65,8 @@ class TestImage: @pytest.mark.parametrize("mode", ("", "bad", "very very long")) def test_image_modes_fail(self, mode: str) -> None: - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="unrecognized image mode"): Image.new(mode, (1, 1)) - assert str(e.value) == "unrecognized image mode" def test_exception_inheritance(self) -> None: assert issubclass(UnidentifiedImageError, OSError) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index ae0968764..2767418ea 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1048,8 +1048,8 @@ def create_base_image_draw( background2: tuple[int, int, int] = GRAY, ) -> tuple[Image.Image, ImageDraw.ImageDraw]: img = Image.new(mode, size, background1) - for x in range(0, size[0]): - for y in range(0, size[1]): + for x in range(size[0]): + for y in range(size[1]): if (x + y) % 2 == 0: img.putpixel((x, y), background2) return img, ImageDraw.Draw(img) @@ -1630,7 +1630,7 @@ def test_compute_regular_polygon_vertices( 0, ValueError, "bounding_circle should contain 2D coordinates " - "and a radius (e.g. (x, y, r) or ((x, y), r) )", + r"and a radius \(e.g. \(x, y, r\) or \(\(x, y\), r\) \)", ), ( 3, @@ -1644,7 +1644,7 @@ def test_compute_regular_polygon_vertices( ((50, 50, 50), 25), 0, ValueError, - "bounding_circle centre should contain 2D coordinates (e.g. (x, y))", + r"bounding_circle centre should contain 2D coordinates \(e.g. \(x, y\)\)", ), ( 3, @@ -1669,9 +1669,8 @@ def test_compute_regular_polygon_vertices_input_error_handling( expected_error: type[Exception], error_message: str, ) -> None: - with pytest.raises(expected_error) as e: + with pytest.raises(expected_error, match=error_message): ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation) # type: ignore[arg-type] - assert str(e.value) == error_message def test_continuous_horizontal_edges_polygon() -> None: diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index b05d29dae..c60a475a3 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -176,9 +176,8 @@ class TestImageFile: b"0" * ImageFile.SAFEBLOCK ) # only SAFEBLOCK bytes, so that the header is truncated ) - with pytest.raises(OSError) as e: + with pytest.raises(OSError, match="Truncated File Read"): BmpImagePlugin.BmpImageFile(b) - assert str(e.value) == "Truncated File Read" @skip_unless_feature("zlib") def test_truncated_with_errors(self) -> None: diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 6180a7b5d..515e29cea 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -80,15 +80,12 @@ def test_lut(op: str) -> None: def test_no_operator_loaded() -> None: im = Image.new("L", (1, 1)) mop = ImageMorph.MorphOp() - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match="No operator loaded"): mop.apply(im) - assert str(e.value) == "No operator loaded" - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match="No operator loaded"): mop.match(im) - assert str(e.value) == "No operator loaded" - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match="No operator loaded"): mop.save_lut("") - assert str(e.value) == "No operator loaded" # Test the named patterns @@ -238,15 +235,12 @@ def test_incorrect_mode() -> None: im = hopper("RGB") mop = ImageMorph.MorphOp(op_name="erosion8") - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="Image mode must be L"): mop.apply(im) - assert str(e.value) == "Image mode must be L" - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="Image mode must be L"): mop.match(im) - assert str(e.value) == "Image mode must be L" - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="Image mode must be L"): mop.get_on_pixels(im) - assert str(e.value) == "Image mode must be L" def test_add_patterns() -> None: @@ -279,9 +273,10 @@ def test_pattern_syntax_error() -> None: lb.add_patterns(new_patterns) # Act / Assert - with pytest.raises(Exception) as e: + with pytest.raises( + Exception, match='Syntax error in pattern "a pattern with a syntax error"' + ): lb.build_lut() - assert str(e.value) == 'Syntax error in pattern "a pattern with a syntax error"' def test_load_invalid_mrl() -> None: @@ -290,9 +285,8 @@ def test_load_invalid_mrl() -> None: mop = ImageMorph.MorphOp() # Act / Assert - with pytest.raises(Exception) as e: + with pytest.raises(Exception, match="Wrong size operator file!"): mop.load_lut(invalid_mrl) - assert str(e.value) == "Wrong size operator file!" def test_roundtrip_mrl(tmp_path: Path) -> None: diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index e0b6359b0..782022f51 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -112,7 +112,7 @@ def test_make_linear_lut() -> None: assert isinstance(lut, list) assert len(lut) == 256 # Check values - for i in range(0, len(lut)): + for i in range(len(lut)): assert lut[i] == i diff --git a/Tests/test_imagepath.py b/Tests/test_imagepath.py index 8b85ec523..ad8acde49 100644 --- a/Tests/test_imagepath.py +++ b/Tests/test_imagepath.py @@ -70,12 +70,9 @@ def test_path_constructors( def test_invalid_path_constructors() -> None: # Arrange / Act - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="incorrect coordinate type"): ImagePath.Path(("a", "b")) - # Assert - assert str(e.value) == "incorrect coordinate type" - @pytest.mark.parametrize( "coords", @@ -87,13 +84,9 @@ def test_invalid_path_constructors() -> None: ), ) def test_path_odd_number_of_coordinates(coords: Sequence[int]) -> None: - # Act - with pytest.raises(ValueError) as e: + with pytest.raises(ValueError, match="wrong number of coordinates"): ImagePath.Path(coords) - # Assert - assert str(e.value) == "wrong number of coordinates" - @pytest.mark.parametrize( "coords, expected", diff --git a/Tests/test_imagesequence.py b/Tests/test_imagesequence.py index 9b37435eb..26b287bb4 100644 --- a/Tests/test_imagesequence.py +++ b/Tests/test_imagesequence.py @@ -32,7 +32,7 @@ def test_sanity(tmp_path: Path) -> None: def test_iterator() -> None: with Image.open("Tests/images/multipage.tiff") as im: i = ImageSequence.Iterator(im) - for index in range(0, im.n_frames): + for index in range(im.n_frames): assert i[index] == next(i) with pytest.raises(IndexError): i[index + 1] diff --git a/Tests/test_pickle.py b/Tests/test_pickle.py index c4f8de013..05c41a802 100644 --- a/Tests/test_pickle.py +++ b/Tests/test_pickle.py @@ -65,7 +65,7 @@ def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> Non ("Tests/images/itxt_chunks.png", None), ], ) -@pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1)) +@pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle_image( tmp_path: Path, test_file: str, test_mode: str | None, protocol: int ) -> None: @@ -92,7 +92,7 @@ def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: im = im.convert("PA") # Act / Assert - for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1): + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): im._mode = "LA" with open(filename, "wb") as f: pickle.dump(im, f, protocol) @@ -133,7 +133,7 @@ def helper_assert_pickled_font_images( @skip_unless_feature("freetype2") -@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +@pytest.mark.parametrize("protocol", list(range(pickle.HIGHEST_PROTOCOL + 1))) def test_pickle_font_string(protocol: int) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) @@ -147,7 +147,7 @@ def test_pickle_font_string(protocol: int) -> None: @skip_unless_feature("freetype2") -@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) +@pytest.mark.parametrize("protocol", list(range(pickle.HIGHEST_PROTOCOL + 1))) def test_pickle_font_file(tmp_path: Path, protocol: int) -> None: # Arrange font = ImageFont.truetype(FONT_PATH, FONT_SIZE) diff --git a/pyproject.toml b/pyproject.toml index 2ffd9faca..780a938a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -121,6 +121,7 @@ lint.select = [ "ISC", # flake8-implicit-str-concat "LOG", # flake8-logging "PGH", # pygrep-hooks + "PIE", # flake8-pie "PT", # flake8-pytest-style "PYI", # flake8-pyi "RUF100", # unused noqa (yesqa) @@ -133,6 +134,7 @@ lint.ignore = [ "E221", # Multiple spaces before operator "E226", # Missing whitespace around arithmetic operator "E241", # Multiple spaces after ',' + "PIE790", # flake8-pie: unnecessary-placeholder "PT001", # pytest-fixture-incorrect-parentheses-style "PT007", # pytest-parametrize-values-wrong-type "PT011", # pytest-raises-too-broad diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index a5d5b93ae..5a88429e5 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -123,8 +123,7 @@ def read_png_or_jpeg2000( Image._decompression_bomb_check(im.size) return {"RGBA": im} elif ( - sig.startswith(b"\xff\x4f\xff\x51") - or sig.startswith(b"\x0d\x0a\x87\x0a") + sig.startswith((b"\xff\x4f\xff\x51", b"\x0d\x0a\x87\x0a")) or sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a" ): if not enable_jpeg2k: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6a2aa3e4c..684c87c4d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1001,7 +1001,7 @@ class Image: elif len(mode) == 3: transparency = tuple( convert_transparency(matrix[i * 4 : i * 4 + 4], transparency) - for i in range(0, len(transparency)) + for i in range(len(transparency)) ) new_im.info["transparency"] = transparency return new_im @@ -4003,7 +4003,7 @@ class Exif(_ExifBase): ifd_data = tag_data[ifd_offset:] makernote = {} - for i in range(0, struct.unpack("H", tag_data[:2])[0]): + for i in range(struct.unpack(">H", tag_data[:2])[0]): ifd_tag, typ, count, data = struct.unpack( ">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2] ) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index c4ebc5931..c2ed9034d 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -1204,7 +1204,7 @@ def _compute_regular_polygon_vertices( degrees = 360 / n_sides # Start with the bottom left polygon vertex current_angle = (270 - 0.5 * degrees) + rotation - for _ in range(0, n_sides): + for _ in range(n_sides): angles.append(current_angle) current_angle += degrees if current_angle > 360: @@ -1227,4 +1227,4 @@ def _color_diff( first = color1 if isinstance(color1, tuple) else (color1,) second = color2 if isinstance(color2, tuple) else (color2,) - return sum(abs(first[i] - second[i]) for i in range(0, len(second))) + return sum(abs(first[i] - second[i]) for i in range(len(second))) diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py index 75dfbee22..da28854b5 100644 --- a/src/PIL/ImageOps.py +++ b/src/PIL/ImageOps.py @@ -213,14 +213,14 @@ def colorize( blue = [] # Create the low-end values - for i in range(0, blackpoint): + for i in range(blackpoint): red.append(rgb_black[0]) green.append(rgb_black[1]) blue.append(rgb_black[2]) # Create the mapping (2-color) if rgb_mid is None: - range_map = range(0, whitepoint - blackpoint) + range_map = range(whitepoint - blackpoint) for i in range_map: red.append( @@ -235,8 +235,8 @@ def colorize( # Create the mapping (3-color) else: - range_map1 = range(0, midpoint - blackpoint) - range_map2 = range(0, whitepoint - midpoint) + range_map1 = range(midpoint - blackpoint) + range_map2 = range(whitepoint - midpoint) for i in range_map1: red.append( @@ -256,7 +256,7 @@ def colorize( blue.append(rgb_mid[2] + i * (rgb_white[2] - rgb_mid[2]) // len(range_map2)) # Create the high-end values - for i in range(0, 256 - whitepoint): + for i in range(256 - whitepoint): red.append(rgb_white[0]) green.append(rgb_white[1]) blue.append(rgb_white[2]) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 3e882403b..9465d8e2d 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -569,7 +569,7 @@ def _getmp(self: JpegImageFile) -> dict[int, Any] | None: mpentries = [] try: rawmpentries = mp[0xB002] - for entrynum in range(0, quant): + for entrynum in range(quant): unpackedentry = struct.unpack_from( f"{endianness}LLLHH", rawmpentries, entrynum * 16 ) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 3d36d1abc..b8ff47a12 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1584,7 +1584,7 @@ class TiffImageFile(ImageFile.ImageFile): # byte order. elif rawmode == "I;16": rawmode = "I;16N" - elif rawmode.endswith(";16B") or rawmode.endswith(";16L"): + elif rawmode.endswith((";16B", ";16L")): rawmode = rawmode[:-1] + "N" # Offset in the tile tuple is 0, we go from 0,0 to