From 3f6422b512ff39cffaa5a37915c970d7683b6d62 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 12 Feb 2024 09:28:53 +1100 Subject: [PATCH] Added type hints --- Tests/helper.py | 2 +- Tests/test_image_access.py | 47 ++++++++++++++++++-------------- Tests/test_image_array.py | 10 ++++--- Tests/test_image_draft.py | 7 ++++- Tests/test_image_entropy.py | 2 +- Tests/test_image_filter.py | 27 ++++++++++++------ Tests/test_image_getextrema.py | 2 +- Tests/test_image_getpalette.py | 2 +- Tests/test_image_paste.py | 14 +++++----- Tests/test_image_putdata.py | 6 ++-- Tests/test_image_putpalette.py | 4 +-- Tests/test_image_resample.py | 8 ++++-- Tests/test_image_rotate.py | 14 +++++++--- Tests/test_image_thumbnail.py | 2 +- Tests/test_image_transform.py | 50 ++++++++++++++++++++++++---------- 15 files changed, 124 insertions(+), 73 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 3e2a40e02..b98883946 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -244,7 +244,7 @@ def fromstring(data: bytes) -> Image.Image: return Image.open(BytesIO(data)) -def tostring(im: Image.Image, string_format: str, **options: dict[str, Any]) -> bytes: +def tostring(im: Image.Image, string_format: str, **options: Any) -> bytes: out = BytesIO() im.save(out, string_format, **options) return out.getvalue() diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index e4cb2dad1..3bdaea750 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -4,6 +4,7 @@ import os import subprocess import sys import sysconfig +from types import ModuleType import pytest @@ -23,6 +24,7 @@ else: except ImportError: cffi = None +numpy: ModuleType | None try: import numpy except ImportError: @@ -71,9 +73,10 @@ class TestImagePutPixel(AccessTest): pix1 = im1.load() pix2 = im2.load() - for x, y in ((0, "0"), ("0", 0)): - with pytest.raises(TypeError): - pix1[x, y] + with pytest.raises(TypeError): + pix1[0, "0"] + with pytest.raises(TypeError): + pix1["0", 0] for y in range(im1.size[1]): for x in range(im1.size[0]): @@ -123,12 +126,13 @@ class TestImagePutPixel(AccessTest): im = hopper() pix = im.load() + assert numpy is not None assert pix[numpy.int32(1), numpy.int32(2)] == (18, 20, 59) class TestImageGetPixel(AccessTest): @staticmethod - def color(mode): + def color(mode: str) -> int | tuple[int, ...]: bands = Image.getmodebands(mode) if bands == 1: return 1 @@ -138,12 +142,13 @@ class TestImageGetPixel(AccessTest): return (16, 32, 49) return tuple(range(1, bands + 1)) - def check(self, mode, expected_color=None) -> None: + def check(self, mode: str, expected_color_int: int | None = None) -> None: if self._need_cffi_access and mode.startswith("BGR;"): pytest.skip("Support not added to deprecated module for BGR;* modes") - if not expected_color: - expected_color = self.color(mode) + expected_color = ( + expected_color_int if expected_color_int is not None else self.color(mode) + ) # check putpixel im = Image.new(mode, (1, 1), None) @@ -222,7 +227,7 @@ class TestImageGetPixel(AccessTest): "YCbCr", ), ) - def test_basic(self, mode) -> None: + def test_basic(self, mode: str) -> None: self.check(mode) def test_list(self) -> None: @@ -231,14 +236,14 @@ class TestImageGetPixel(AccessTest): @pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)) - def test_signedness(self, mode, expected_color) -> None: + def test_signedness(self, mode: str, expected_color: int) -> None: # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* self.check(mode, expected_color) @pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("color", ((255, 0, 0), (255, 0, 0, 255))) - def test_p_putpixel_rgb_rgba(self, mode, color) -> None: + def test_p_putpixel_rgb_rgba(self, mode: str, color: tuple[int, ...]) -> None: im = Image.new(mode, (1, 1)) im.putpixel((0, 0), color) @@ -262,7 +267,7 @@ class TestCffiGetPixel(TestImageGetPixel): class TestCffi(AccessTest): _need_cffi_access = True - def _test_get_access(self, im) -> None: + def _test_get_access(self, im: Image.Image) -> None: """Do we get the same thing as the old pixel access Using private interfaces, forcing a capi access and @@ -299,7 +304,7 @@ class TestCffi(AccessTest): # im = Image.new('I;32B', (10, 10), 2**10) # self._test_get_access(im) - def _test_set_access(self, im, color) -> None: + def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None: """Are we writing the correct bits into the image? Using private interfaces, forcing a capi access and @@ -359,7 +364,7 @@ class TestCffi(AccessTest): assert px[i, 0] == 0 @pytest.mark.parametrize("mode", ("P", "PA")) - def test_p_putpixel_rgb_rgba(self, mode) -> None: + def test_p_putpixel_rgb_rgba(self, mode: str) -> None: for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)): im = Image.new(mode, (1, 1)) with pytest.warns(DeprecationWarning): @@ -377,7 +382,7 @@ class TestImagePutPixelError(AccessTest): INVALID_TYPES = ["foo", 1.0, None] @pytest.mark.parametrize("mode", IMAGE_MODES1) - def test_putpixel_type_error1(self, mode) -> None: + def test_putpixel_type_error1(self, mode: str) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises(TypeError, match="color must be int or tuple"): @@ -400,14 +405,16 @@ class TestImagePutPixelError(AccessTest): ), ), ) - def test_putpixel_invalid_number_of_bands(self, mode, band_numbers, match) -> None: + def test_putpixel_invalid_number_of_bands( + self, mode: str, band_numbers: tuple[int, ...], match: str + ) -> None: im = hopper(mode) for band_number in band_numbers: with pytest.raises(TypeError, match=match): im.putpixel((0, 0), (0,) * band_number) @pytest.mark.parametrize("mode", IMAGE_MODES2) - def test_putpixel_type_error2(self, mode) -> None: + def test_putpixel_type_error2(self, mode: str) -> None: im = hopper(mode) for v in self.INVALID_TYPES: with pytest.raises( @@ -416,7 +423,7 @@ class TestImagePutPixelError(AccessTest): im.putpixel((0, 0), v) @pytest.mark.parametrize("mode", IMAGE_MODES1 + IMAGE_MODES2) - def test_putpixel_overflow_error(self, mode) -> None: + def test_putpixel_overflow_error(self, mode: str) -> None: im = hopper(mode) with pytest.raises(OverflowError): im.putpixel((0, 0), 2**80) @@ -428,7 +435,7 @@ class TestEmbeddable: def test_embeddable(self) -> None: import ctypes - from setuptools.command.build_ext import new_compiler + from setuptools.command import build_ext with open("embed_pil.c", "w", encoding="utf-8") as fh: fh.write( @@ -457,7 +464,7 @@ int main(int argc, char* argv[]) % sys.prefix.replace("\\", "\\\\") ) - compiler = new_compiler() + compiler = getattr(build_ext, "new_compiler")() compiler.add_include_dir(sysconfig.get_config_var("INCLUDEPY")) libdir = sysconfig.get_config_var("LIBDIR") or sysconfig.get_config_var( @@ -471,7 +478,7 @@ int main(int argc, char* argv[]) env["PATH"] = sys.prefix + ";" + env["PATH"] # do not display the Windows Error Reporting dialog - ctypes.windll.kernel32.SetErrorMode(0x0002) + getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) process = subprocess.Popen(["embed_pil.exe"], env=env) process.communicate() diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 0125ab56a..cf85ee4fa 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from packaging.version import parse as parse_version @@ -13,7 +15,7 @@ im = hopper().resize((128, 100)) def test_toarray() -> None: - def test(mode): + def test(mode: str) -> tuple[tuple[int, ...], str, int]: ai = numpy.array(im.convert(mode)) return ai.shape, ai.dtype.str, ai.nbytes @@ -50,14 +52,14 @@ def test_fromarray() -> None: class Wrapper: """Class with API matching Image.fromarray""" - def __init__(self, img, arr_params) -> None: + def __init__(self, img: Image.Image, arr_params: dict[str, Any]) -> None: self.img = img self.__array_interface__ = arr_params - def tobytes(self): + def tobytes(self) -> bytes: return self.img.tobytes() - def test(mode): + def test(mode: str) -> tuple[str, tuple[int, int], bool]: i = im.convert(mode) a = numpy.array(i) # Make wrapper instance for image, new array interface diff --git a/Tests/test_image_draft.py b/Tests/test_image_draft.py index 54474311a..1ce1a7cd8 100644 --- a/Tests/test_image_draft.py +++ b/Tests/test_image_draft.py @@ -7,7 +7,12 @@ from .helper import fromstring, skip_unless_feature, tostring pytestmark = skip_unless_feature("jpg") -def draft_roundtrip(in_mode, in_size, req_mode, req_size): +def draft_roundtrip( + in_mode: str, + in_size: tuple[int, int], + req_mode: str | None, + req_size: tuple[int, int] | None, +) -> Image.Image: im = Image.new(in_mode, in_size) data = tostring(im, "JPEG") im = fromstring(data) diff --git a/Tests/test_image_entropy.py b/Tests/test_image_entropy.py index 01107ae6b..c1dbb879b 100644 --- a/Tests/test_image_entropy.py +++ b/Tests/test_image_entropy.py @@ -4,7 +4,7 @@ from .helper import hopper def test_entropy() -> None: - def entropy(mode): + def entropy(mode: str) -> float: return hopper(mode).entropy() assert round(abs(entropy("1") - 0.9138803254693582), 7) == 0 diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 2b6787933..6a10ae453 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -36,7 +36,7 @@ from .helper import assert_image_equal, hopper ), ) @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity(filter_to_apply, mode) -> None: +def test_sanity(filter_to_apply: ImageFilter.Filter, mode: str) -> None: im = hopper(mode) if mode != "I" or isinstance(filter_to_apply, ImageFilter.BuiltinFilter): out = im.filter(filter_to_apply) @@ -45,7 +45,7 @@ def test_sanity(filter_to_apply, mode) -> None: @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) -def test_sanity_error(mode) -> None: +def test_sanity_error(mode: str) -> None: with pytest.raises(TypeError): im = hopper(mode) im.filter("hello") @@ -53,7 +53,7 @@ def test_sanity_error(mode) -> None: # crashes on small images @pytest.mark.parametrize("size", ((1, 1), (2, 2), (3, 3))) -def test_crash(size) -> None: +def test_crash(size: tuple[int, int]) -> None: im = Image.new("RGB", size) im.filter(ImageFilter.SMOOTH) @@ -67,7 +67,10 @@ def test_crash(size) -> None: ("RGB", ((4, 0, 0), (0, 0, 0))), ), ) -def test_modefilter(mode, expected) -> None: +def test_modefilter( + mode: str, + expected: tuple[int, int] | tuple[tuple[int, int, int], tuple[int, int, int]], +) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -90,7 +93,13 @@ def test_modefilter(mode, expected) -> None: ("F", (0.0, 4.0, 8.0)), ), ) -def test_rankfilter(mode, expected) -> None: +def test_rankfilter( + mode: str, + expected: ( + tuple[float, float, float] + | tuple[tuple[int, int, int], tuple[int, int, int], tuple[int, int, int]] + ), +) -> None: im = Image.new(mode, (3, 3), None) im.putdata(list(range(9))) # image is: @@ -106,7 +115,7 @@ def test_rankfilter(mode, expected) -> None: @pytest.mark.parametrize( "filter", (ImageFilter.MinFilter, ImageFilter.MedianFilter, ImageFilter.MaxFilter) ) -def test_rankfilter_error(filter) -> None: +def test_rankfilter_error(filter: ImageFilter.RankFilter) -> None: with pytest.raises(ValueError): im = Image.new("P", (3, 3), None) im.putdata(list(range(9))) @@ -137,7 +146,7 @@ def test_kernel_not_enough_coefficients() -> None: @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_3x3(mode) -> None: +def test_consistency_3x3(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -163,7 +172,7 @@ def test_consistency_3x3(mode) -> None: @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK")) -def test_consistency_5x5(mode) -> None: +def test_consistency_5x5(mode: str) -> None: with Image.open("Tests/images/hopper.bmp") as source: reference_name = "hopper_emboss_more" reference_name += "_I.png" if mode == "I" else ".bmp" @@ -199,7 +208,7 @@ def test_consistency_5x5(mode) -> None: (2, -2), ), ) -def test_invalid_box_blur_filter(radius) -> None: +def test_invalid_box_blur_filter(radius: int | tuple[int, int]) -> None: with pytest.raises(ValueError): ImageFilter.BoxBlur(radius) diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index 0107fdcc4..a5b974459 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -6,7 +6,7 @@ from .helper import hopper def test_extrema() -> None: - def extrema(mode): + def extrema(mode: str) -> tuple[int, int] | tuple[tuple[int, int], ...]: return hopper(mode).getextrema() assert extrema("1") == (0, 255) diff --git a/Tests/test_image_getpalette.py b/Tests/test_image_getpalette.py index e7304c98f..6a8f157fc 100644 --- a/Tests/test_image_getpalette.py +++ b/Tests/test_image_getpalette.py @@ -6,7 +6,7 @@ from .helper import hopper def test_palette() -> None: - def palette(mode): + def palette(mode: str) -> list[int] | None: p = hopper(mode).getpalette() if p: return p[:10] diff --git a/Tests/test_image_paste.py b/Tests/test_image_paste.py index c4d7a5dd2..ce7345572 100644 --- a/Tests/test_image_paste.py +++ b/Tests/test_image_paste.py @@ -46,7 +46,7 @@ class TestImagingPaste: self.assert_9points_image(im, expected) @CachedProperty - def mask_1(self): + def mask_1(self) -> Image.Image: mask = Image.new("1", (self.size, self.size)) px = mask.load() for y in range(mask.height): @@ -55,11 +55,11 @@ class TestImagingPaste: return mask @CachedProperty - def mask_L(self): + def mask_L(self) -> Image.Image: return self.gradient_L.transpose(Image.Transpose.ROTATE_270) @CachedProperty - def gradient_L(self): + def gradient_L(self) -> Image.Image: gradient = Image.new("L", (self.size, self.size)) px = gradient.load() for y in range(gradient.height): @@ -68,7 +68,7 @@ class TestImagingPaste: return gradient @CachedProperty - def gradient_RGB(self): + def gradient_RGB(self) -> Image.Image: return Image.merge( "RGB", [ @@ -79,7 +79,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_LA(self): + def gradient_LA(self) -> Image.Image: return Image.merge( "LA", [ @@ -89,7 +89,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_RGBA(self): + def gradient_RGBA(self) -> Image.Image: return Image.merge( "RGBA", [ @@ -101,7 +101,7 @@ class TestImagingPaste: ) @CachedProperty - def gradient_RGBa(self): + def gradient_RGBa(self) -> Image.Image: return Image.merge( "RGBa", [ diff --git a/Tests/test_image_putdata.py b/Tests/test_image_putdata.py index 103019916..73145faac 100644 --- a/Tests/test_image_putdata.py +++ b/Tests/test_image_putdata.py @@ -31,7 +31,7 @@ def test_sanity() -> None: def test_long_integers() -> None: # see bug-200802-systemerror - def put(value): + def put(value: int) -> tuple[int, int, int, int]: im = Image.new("RGBA", (1, 1)) im.putdata([value]) return im.getpixel((0, 0)) @@ -58,7 +58,7 @@ def test_mode_with_L_with_float() -> None: @pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B")) -def test_mode_i(mode) -> None: +def test_mode_i(mode: str) -> None: src = hopper("L") data = list(src.getdata()) im = Image.new(mode, src.size, 0) @@ -79,7 +79,7 @@ def test_mode_F() -> None: @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) -def test_mode_BGR(mode) -> None: +def test_mode_BGR(mode: str) -> None: data = [(16, 32, 49), (32, 32, 98)] im = Image.new(mode, (1, 2)) im.putdata(data) diff --git a/Tests/test_image_putpalette.py b/Tests/test_image_putpalette.py index ffe2551d2..cc7cf58f0 100644 --- a/Tests/test_image_putpalette.py +++ b/Tests/test_image_putpalette.py @@ -8,7 +8,7 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper def test_putpalette() -> None: - def palette(mode): + def palette(mode: str) -> str | tuple[str, list[int]]: im = hopper(mode).copy() im.putpalette(list(range(256)) * 3) p = im.getpalette() @@ -81,7 +81,7 @@ def test_putpalette_with_alpha_values() -> None: ("RGBAX", (1, 2, 3, 4, 0)), ), ) -def test_rgba_palette(mode, palette) -> None: +def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None: im = Image.new("P", (1, 1)) im.putpalette(palette, mode) assert im.getpalette() == [1, 2, 3] diff --git a/Tests/test_image_resample.py b/Tests/test_image_resample.py index f3ec12c05..7090ff9cd 100644 --- a/Tests/test_image_resample.py +++ b/Tests/test_image_resample.py @@ -231,11 +231,13 @@ class TestImagingCoreResampleAccuracy: class TestCoreResampleConsistency: - def make_case(self, mode: str, fill: tuple[int, int, int] | float): + def make_case( + self, mode: str, fill: tuple[int, int, int] | float + ) -> tuple[Image.Image, tuple[int, ...]]: im = Image.new(mode, (512, 9), fill) return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0] - def run_case(self, case) -> None: + def run_case(self, case: tuple[Image.Image, Image.Image]) -> None: channel, color = case px = channel.load() for x in range(channel.size[0]): @@ -353,7 +355,7 @@ class TestCoreResampleAlphaCorrect: class TestCoreResamplePasses: @contextmanager - def count(self, diff): + def count(self, diff: int) -> Generator[None, None, None]: count = Image.core.get_stats()["new_count"] yield assert Image.core.get_stats()["new_count"] - count == diff diff --git a/Tests/test_image_rotate.py b/Tests/test_image_rotate.py index 51e0f5854..c10c96da6 100644 --- a/Tests/test_image_rotate.py +++ b/Tests/test_image_rotate.py @@ -12,7 +12,13 @@ from .helper import ( ) -def rotate(im, mode, angle, center=None, translate=None) -> None: +def rotate( + im: Image.Image, + mode: str, + angle: int, + center: tuple[int, int] | None = None, + translate: tuple[int, int] | None = None, +) -> None: out = im.rotate(angle, center=center, translate=translate) assert out.mode == mode assert out.size == im.size # default rotate clips output @@ -27,13 +33,13 @@ def rotate(im, mode, angle, center=None, translate=None) -> None: @pytest.mark.parametrize("mode", ("1", "P", "L", "RGB", "I", "F")) -def test_mode(mode) -> None: +def test_mode(mode: str) -> None: im = hopper(mode) rotate(im, mode, 45) @pytest.mark.parametrize("angle", (0, 90, 180, 270)) -def test_angle(angle) -> None: +def test_angle(angle: int) -> None: with Image.open("Tests/images/test-card.png") as im: rotate(im, im.mode, angle) @@ -42,7 +48,7 @@ def test_angle(angle) -> None: @pytest.mark.parametrize("angle", (0, 45, 90, 180, 270)) -def test_zero(angle) -> None: +def test_zero(angle: int) -> None: im = Image.new("RGB", (0, 0)) rotate(im, im.mode, angle) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 6aeeea2ed..2ca1d2cfc 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -111,7 +111,7 @@ def test_load_first_unless_jpeg() -> None: with Image.open("Tests/images/hopper.jpg") as im: draft = im.draft - def im_draft(mode, size): + def im_draft(mode: str, size: tuple[int, int]): result = draft(mode, size) assert result is not None diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index 1067dd563..638d12247 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +from typing import Callable import pytest @@ -91,7 +92,7 @@ class TestImageTransform: ("LA", (76, 0)), ), ) - def test_fill(self, mode, expected_pixel) -> None: + def test_fill(self, mode: str, expected_pixel: tuple[int, ...]) -> None: im = hopper(mode) (w, h) = im.size transformed = im.transform( @@ -142,7 +143,9 @@ class TestImageTransform: assert_image_equal(blank, transformed.crop((w // 2, 0, w, h // 2))) assert_image_equal(blank, transformed.crop((0, h // 2, w // 2, h))) - def _test_alpha_premult(self, op) -> None: + def _test_alpha_premult( + self, op: Callable[[Image.Image, tuple[int, int]], Image.Image] + ) -> None: # create image with half white, half black, # with the black half transparent. # do op, @@ -159,13 +162,13 @@ class TestImageTransform: assert 40 * 10 == hist[-1] def test_alpha_premult_resize(self) -> None: - def op(im, sz): + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: return im.resize(sz, Image.Resampling.BILINEAR) self._test_alpha_premult(op) def test_alpha_premult_transform(self) -> None: - def op(im, sz): + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: (w, h) = im.size return im.transform( sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.BILINEAR @@ -173,7 +176,9 @@ class TestImageTransform: self._test_alpha_premult(op) - def _test_nearest(self, op, mode) -> None: + def _test_nearest( + self, op: Callable[[Image.Image, tuple[int, int]], Image.Image], mode: str + ) -> None: # create white image with half transparent, # do op, # the image should remain white with half transparent @@ -196,15 +201,15 @@ class TestImageTransform: ) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_resize(self, mode) -> None: - def op(im, sz): + def test_nearest_resize(self, mode: str) -> None: + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: return im.resize(sz, Image.Resampling.NEAREST) self._test_nearest(op, mode) @pytest.mark.parametrize("mode", ("RGBA", "LA")) - def test_nearest_transform(self, mode) -> None: - def op(im, sz): + def test_nearest_transform(self, mode: str) -> None: + def op(im: Image.Image, sz: tuple[int, int]) -> Image.Image: (w, h) = im.size return im.transform( sz, Image.Transform.EXTENT, (0, 0, w, h), Image.Resampling.NEAREST @@ -227,7 +232,9 @@ class TestImageTransform: # Running by default, but I'd totally understand not doing it in # the future - pattern = [Image.new("RGBA", (1024, 1024), (a, a, a, a)) for a in range(1, 65)] + pattern: list[Image.Image] | None = [ + Image.new("RGBA", (1024, 1024), (a, a, a, a)) for a in range(1, 65) + ] # Yeah. Watch some JIT optimize this out. pattern = None # noqa: F841 @@ -240,7 +247,7 @@ class TestImageTransform: im.transform((100, 100), None) @pytest.mark.parametrize("resample", (Image.Resampling.BOX, "unknown")) - def test_unknown_resampling_filter(self, resample) -> None: + def test_unknown_resampling_filter(self, resample: Image.Resampling | str) -> None: with hopper() as im: (w, h) = im.size with pytest.raises(ValueError): @@ -250,7 +257,7 @@ class TestImageTransform: class TestImageTransformAffine: transform = Image.Transform.AFFINE - def _test_image(self): + def _test_image(self) -> Image.Image: im = hopper("RGB") return im.crop((10, 20, im.width - 10, im.height - 20)) @@ -263,7 +270,7 @@ class TestImageTransformAffine: (270, Image.Transpose.ROTATE_270), ), ) - def test_rotate(self, deg, transpose) -> None: + def test_rotate(self, deg: int, transpose: Image.Transpose | None) -> None: im = self._test_image() angle = -math.radians(deg) @@ -313,7 +320,13 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_resize(self, scale, epsilon_scale, resample, epsilon) -> None: + def test_resize( + self, + scale: float, + epsilon_scale: float, + resample: Image.Resampling, + epsilon: int, + ) -> None: im = self._test_image() size_up = int(round(im.width * scale)), int(round(im.height * scale)) @@ -342,7 +355,14 @@ class TestImageTransformAffine: (Image.Resampling.BICUBIC, 1), ), ) - def test_translate(self, x, y, epsilon_scale, resample, epsilon) -> None: + def test_translate( + self, + x: float, + y: float, + epsilon_scale: float, + resample: Image.Resampling, + epsilon: float, + ) -> None: im = self._test_image() size_up = int(round(im.width + x)), int(round(im.height + y))