Added type hints

This commit is contained in:
Andrew Murray 2024-02-12 21:06:17 +11:00
parent cdecb3da91
commit 4ce06aac3b
20 changed files with 108 additions and 75 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from array import array from array import array
from types import ModuleType
import pytest import pytest
@ -8,6 +9,7 @@ from PIL import Image, ImageFilter
from .helper import assert_image_equal from .helper import assert_image_equal
numpy: ModuleType | None
try: try:
import numpy import numpy
except ImportError: except ImportError:
@ -397,6 +399,7 @@ class TestColorLut3DFilter:
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy_sources(self) -> None: def test_numpy_sources(self) -> None:
assert numpy is not None
table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16) table = numpy.ones((5, 6, 7, 3), dtype=numpy.float16)
with pytest.raises(ValueError, match="should have either channels"): with pytest.raises(ValueError, match="should have either channels"):
lut = ImageFilter.Color3DLUT((5, 6, 7), table) lut = ImageFilter.Color3DLUT((5, 6, 7), table)
@ -430,6 +433,7 @@ class TestColorLut3DFilter:
@pytest.mark.skipif(numpy is None, reason="NumPy not installed") @pytest.mark.skipif(numpy is None, reason="NumPy not installed")
def test_numpy_formats(self) -> None: def test_numpy_formats(self) -> None:
assert numpy is not None
g = Image.linear_gradient("L") g = Image.linear_gradient("L")
im = Image.merge( im = Image.merge(
"RGB", "RGB",

View File

@ -187,6 +187,6 @@ class TestEnvVars:
{"PILLOW_BLOCKS_MAX": "wat"}, {"PILLOW_BLOCKS_MAX": "wat"},
), ),
) )
def test_warnings(self, var) -> None: def test_warnings(self, var: dict[str, str]) -> None:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
Image._apply_env_variables(var) Image._apply_env_variables(var)

View File

@ -48,7 +48,7 @@ TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
TEST_FILE_DX10_BC1_TYPELESS, TEST_FILE_DX10_BC1_TYPELESS,
), ),
) )
def test_sanity_dxt1_bc1(image_path) -> None: def test_sanity_dxt1_bc1(image_path: str) -> None:
"""Check DXT1 and BC1 images can be opened""" """Check DXT1 and BC1 images can be opened"""
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target: with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA") target = target.convert("RGBA")
@ -96,7 +96,7 @@ def test_sanity_dxt5() -> None:
TEST_FILE_BC4U, TEST_FILE_BC4U,
), ),
) )
def test_sanity_ati1_bc4u(image_path) -> None: def test_sanity_ati1_bc4u(image_path: str) -> None:
"""Check ATI1 and BC4U images can be opened""" """Check ATI1 and BC4U images can be opened"""
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -117,7 +117,7 @@ def test_sanity_ati1_bc4u(image_path) -> None:
TEST_FILE_DX10_BC4_TYPELESS, TEST_FILE_DX10_BC4_TYPELESS,
), ),
) )
def test_dx10_bc4(image_path) -> None: def test_dx10_bc4(image_path: str) -> None:
"""Check DX10 BC4 images can be opened""" """Check DX10 BC4 images can be opened"""
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -138,7 +138,7 @@ def test_dx10_bc4(image_path) -> None:
TEST_FILE_BC5U, TEST_FILE_BC5U,
), ),
) )
def test_sanity_ati2_bc5u(image_path) -> None: def test_sanity_ati2_bc5u(image_path: str) -> None:
"""Check ATI2 and BC5U images can be opened""" """Check ATI2 and BC5U images can be opened"""
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -162,7 +162,7 @@ def test_sanity_ati2_bc5u(image_path) -> None:
(TEST_FILE_BC5S, TEST_FILE_BC5S), (TEST_FILE_BC5S, TEST_FILE_BC5S),
), ),
) )
def test_dx10_bc5(image_path, expected_path) -> None: def test_dx10_bc5(image_path: str, expected_path: str) -> None:
"""Check DX10 BC5 images can be opened""" """Check DX10 BC5 images can be opened"""
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -176,7 +176,7 @@ def test_dx10_bc5(image_path, expected_path) -> None:
@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS)) @pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS))
def test_dx10_bc6h(image_path) -> None: def test_dx10_bc6h(image_path: str) -> None:
"""Check DX10 BC6H/BC6HS images can be opened""" """Check DX10 BC6H/BC6HS images can be opened"""
with Image.open(image_path) as im: with Image.open(image_path) as im:
@ -257,7 +257,7 @@ def test_dx10_r8g8b8a8_unorm_srgb() -> None:
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA), ("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
], ],
) )
def test_uncompressed(mode, size, test_file) -> None: def test_uncompressed(mode: str, size: tuple[int, int], test_file: str) -> None:
"""Check uncompressed images can be opened""" """Check uncompressed images can be opened"""
with Image.open(test_file) as im: with Image.open(test_file) as im:
@ -359,7 +359,7 @@ def test_unsupported_bitcount() -> None:
"Tests/images/unimplemented_pfflags.dds", "Tests/images/unimplemented_pfflags.dds",
), ),
) )
def test_not_implemented(test_file) -> None: def test_not_implemented(test_file: str) -> None:
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
with Image.open(test_file): with Image.open(test_file):
pass pass
@ -381,7 +381,7 @@ def test_save_unsupported_mode(tmp_path: Path) -> None:
("RGBA", "Tests/images/pil123rgba.png"), ("RGBA", "Tests/images/pil123rgba.png"),
], ],
) )
def test_save(mode, test_file, tmp_path: Path) -> None: def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
out = str(tmp_path / "temp.dds") out = str(tmp_path / "temp.dds")
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.mode == mode assert im.mode == mode

View File

@ -147,7 +147,7 @@ def test_seek() -> None:
], ],
) )
@pytest.mark.timeout(timeout=3) @pytest.mark.timeout(timeout=3)
def test_timeouts(test_file) -> None: def test_timeouts(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):
@ -160,7 +160,7 @@ def test_timeouts(test_file) -> None:
"Tests/images/crash-5762152299364352.fli", "Tests/images/crash-5762152299364352.fli",
], ],
) )
def test_crash(test_file) -> None: def test_crash(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import IO
import pytest import pytest
@ -55,15 +56,15 @@ def test_handler(tmp_path: Path) -> None:
loaded = False loaded = False
saved = False saved = False
def open(self, im) -> None: def open(self, im: Image.Image) -> None:
self.opened = True self.opened = True
def load(self, im): def load(self, im: Image.Image) -> Image.Image:
self.loaded = True self.loaded = True
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))
def save(self, im, fp, filename) -> None: def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
self.saved = True self.saved = True
handler = TestHandler() handler = TestHandler()

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import IO
import pytest import pytest
@ -56,15 +57,15 @@ def test_handler(tmp_path: Path) -> None:
loaded = False loaded = False
saved = False saved = False
def open(self, im) -> None: def open(self, im: Image.Image) -> None:
self.opened = True self.opened = True
def load(self, im): def load(self, im: Image.Image) -> Image.Image:
self.loaded = True self.loaded = True
im.fp.close() im.fp.close()
return Image.new("RGB", (1, 1)) return Image.new("RGB", (1, 1))
def save(self, im, fp, filename) -> None: def save(self, im: Image.Image, fp: IO[bytes], filename: str) -> None:
self.saved = True self.saved = True
handler = TestHandler() handler = TestHandler()

View File

@ -5,6 +5,7 @@ import re
import warnings import warnings
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from types import ModuleType
from typing import Any from typing import Any
import pytest import pytest
@ -33,6 +34,7 @@ from .helper import (
skip_unless_feature, skip_unless_feature,
) )
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
@ -440,25 +442,25 @@ class TestFileJpeg:
for subsampling in (-1, 3): # (default, invalid) for subsampling in (-1, 3): # (default, invalid)
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling)
assert getsampling(im) == (2, 2, 1, 1, 1, 1) assert getsampling(im) == (2, 2, 1, 1, 1, 1)
for subsampling in (0, "4:4:4"): for subsampling1 in (0, "4:4:4"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (1, 1, 1, 1, 1, 1) assert getsampling(im) == (1, 1, 1, 1, 1, 1)
for subsampling in (1, "4:2:2"): for subsampling1 in (1, "4:2:2"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (2, 1, 1, 1, 1, 1) assert getsampling(im) == (2, 1, 1, 1, 1, 1)
for subsampling in (2, "4:2:0", "4:1:1"): for subsampling1 in (2, "4:2:0", "4:1:1"):
im = self.roundtrip(hopper(), subsampling=subsampling) im = self.roundtrip(hopper(), subsampling=subsampling1)
assert getsampling(im) == (2, 2, 1, 1, 1, 1) assert getsampling(im) == (2, 2, 1, 1, 1, 1)
# RGB colorspace # RGB colorspace
for subsampling in (-1, 0, "4:4:4"): for subsampling1 in (-1, 0, "4:4:4"):
# "4:4:4" doesn't really make sense for RGB, but the conversion # "4:4:4" doesn't really make sense for RGB, but the conversion
# to an integer happens at a higher level # to an integer happens at a higher level
im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling) im = self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1)
assert getsampling(im) == (1, 1, 1, 1, 1, 1) assert getsampling(im) == (1, 1, 1, 1, 1, 1)
for subsampling in (1, "4:2:2", 2, "4:2:0", 3): for subsampling1 in (1, "4:2:2", 2, "4:2:0", 3):
with pytest.raises(OSError): with pytest.raises(OSError):
self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling) self.roundtrip(hopper(), keep_rgb=True, subsampling=subsampling1)
with pytest.raises(TypeError): with pytest.raises(TypeError):
self.roundtrip(hopper(), subsampling="1:1:1") self.roundtrip(hopper(), subsampling="1:1:1")

View File

@ -11,7 +11,7 @@ from PIL import Image
from .helper import assert_image_equal, hopper, magick_command from .helper import assert_image_equal, hopper, magick_command
def helper_save_as_palm(tmp_path: Path, mode) -> None: def helper_save_as_palm(tmp_path: Path, mode: str) -> None:
# Arrange # Arrange
im = hopper(mode) im = hopper(mode)
outfile = str(tmp_path / ("temp_" + mode + ".palm")) outfile = str(tmp_path / ("temp_" + mode + ".palm"))
@ -24,7 +24,7 @@ def helper_save_as_palm(tmp_path: Path, mode) -> None:
assert os.path.getsize(outfile) > 0 assert os.path.getsize(outfile) > 0
def open_with_magick(magick, tmp_path: Path, f): def open_with_magick(magick: list[str], tmp_path: Path, f: str) -> Image.Image:
outfile = str(tmp_path / "temp.png") outfile = str(tmp_path / "temp.png")
rc = subprocess.call( rc = subprocess.call(
magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT magick + [f, outfile], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT
@ -33,7 +33,7 @@ def open_with_magick(magick, tmp_path: Path, f):
return Image.open(outfile) return Image.open(outfile)
def roundtrip(tmp_path: Path, mode) -> None: def roundtrip(tmp_path: Path, mode: str) -> None:
magick = magick_command() magick = magick_command()
if not magick: if not magick:
return return
@ -66,6 +66,6 @@ def test_p_mode(tmp_path: Path) -> None:
@pytest.mark.parametrize("mode", ("L", "RGB")) @pytest.mark.parametrize("mode", ("L", "RGB"))
def test_oserror(tmp_path: Path, mode) -> None: def test_oserror(tmp_path: Path, mode: str) -> None:
with pytest.raises(OSError): with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode) helper_save_as_palm(tmp_path, mode)

View File

@ -19,7 +19,7 @@ TEST_TAR_FILE = "Tests/images/hopper.tar"
("jpg", "hopper.jpg", "JPEG"), ("jpg", "hopper.jpg", "JPEG"),
), ),
) )
def test_sanity(codec, test_path, format) -> None: def test_sanity(codec: str, test_path: str, format: str) -> None:
if features.check(codec): if features.check(codec):
with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar: with TarIO.TarIO(TEST_TAR_FILE, test_path) as tar:
with Image.open(tar) as im: with Image.open(tar) as im:

View File

@ -2,6 +2,7 @@ from __future__ import annotations
from io import BytesIO from io import BytesIO
from pathlib import Path from pathlib import Path
from types import ModuleType
import pytest import pytest
@ -14,6 +15,7 @@ pytestmark = [
skip_unless_feature("webp_mux"), skip_unless_feature("webp_mux"),
] ]
ElementTree: ModuleType | None
try: try:
from defusedxml import ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:

View File

@ -8,6 +8,7 @@ import sys
import tempfile import tempfile
import warnings import warnings
from pathlib import Path from pathlib import Path
from typing import IO
import pytest import pytest
@ -61,11 +62,11 @@ class TestImage:
"HSV", "HSV",
), ),
) )
def test_image_modes_success(self, mode) -> None: def test_image_modes_success(self, mode: str) -> None:
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
@pytest.mark.parametrize("mode", ("", "bad", "very very long")) @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
def test_image_modes_fail(self, mode) -> None: def test_image_modes_fail(self, mode: str) -> None:
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
Image.new(mode, (1, 1)) Image.new(mode, (1, 1))
assert str(e.value) == "unrecognized image mode" assert str(e.value) == "unrecognized image mode"
@ -100,7 +101,7 @@ class TestImage:
def test_repr_pretty(self) -> None: def test_repr_pretty(self) -> None:
class Pretty: class Pretty:
def text(self, text) -> None: def text(self, text: str) -> None:
self.pretty_output = text self.pretty_output = text
im = Image.new("L", (100, 100)) im = Image.new("L", (100, 100))
@ -184,7 +185,9 @@ class TestImage:
temp_file = str(tmp_path / "temp.jpg") temp_file = str(tmp_path / "temp.jpg")
class FP: class FP:
def write(self, b) -> None: name: str
def write(self, b: bytes) -> None:
pass pass
fp = FP() fp = FP()
@ -538,7 +541,7 @@ class TestImage:
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower" "PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
) )
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
def test_empty_image(self, size) -> None: def test_empty_image(self, size: tuple[int, int]) -> None:
Image.new("RGB", size) Image.new("RGB", size)
def test_storage_neg(self) -> None: def test_storage_neg(self) -> None:
@ -565,7 +568,7 @@ class TestImage:
Image.linear_gradient(wrong_mode) Image.linear_gradient(wrong_mode)
@pytest.mark.parametrize("mode", ("L", "P", "I", "F")) @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
def test_linear_gradient(self, mode) -> None: def test_linear_gradient(self, mode: str) -> None:
# Arrange # Arrange
target_file = "Tests/images/linear_gradient.png" target_file = "Tests/images/linear_gradient.png"
@ -590,7 +593,7 @@ class TestImage:
Image.radial_gradient(wrong_mode) Image.radial_gradient(wrong_mode)
@pytest.mark.parametrize("mode", ("L", "P", "I", "F")) @pytest.mark.parametrize("mode", ("L", "P", "I", "F"))
def test_radial_gradient(self, mode) -> None: def test_radial_gradient(self, mode: str) -> None:
# Arrange # Arrange
target_file = "Tests/images/radial_gradient.png" target_file = "Tests/images/radial_gradient.png"
@ -665,7 +668,11 @@ class TestImage:
blank_p.palette = None blank_p.palette = None
blank_pa.palette = None blank_pa.palette = None
def _make_new(base_image, image, palette_result=None) -> None: def _make_new(
base_image: Image.Image,
image: Image.Image,
palette_result: ImagePalette.ImagePalette | None = None,
) -> None:
new_image = base_image._new(image.im) new_image = base_image._new(image.im)
assert new_image.mode == image.mode assert new_image.mode == image.mode
assert new_image.size == image.size assert new_image.size == image.size
@ -713,7 +720,7 @@ class TestImage:
def test_load_on_nonexclusive_multiframe(self) -> None: def test_load_on_nonexclusive_multiframe(self) -> None:
with open("Tests/images/frozenpond.mpo", "rb") as fp: with open("Tests/images/frozenpond.mpo", "rb") as fp:
def act(fp) -> None: def act(fp: IO[bytes]) -> None:
im = Image.open(fp) im = Image.open(fp)
im.load() im.load()
@ -906,12 +913,12 @@ class TestImage:
assert exif.get_ifd(0xA005) assert exif.get_ifd(0xA005)
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero_tobytes(self, size) -> None: def test_zero_tobytes(self, size: tuple[int, int]) -> None:
im = Image.new("RGB", size) im = Image.new("RGB", size)
assert im.tobytes() == b"" assert im.tobytes() == b""
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
def test_zero_frombytes(self, size) -> None: def test_zero_frombytes(self, size: tuple[int, int]) -> None:
Image.frombytes("RGB", size, b"") Image.frombytes("RGB", size, b"")
im = Image.new("RGB", size) im = Image.new("RGB", size)
@ -996,7 +1003,7 @@ class TestImage:
"01r_00.pcx", "01r_00.pcx",
], ],
) )
def test_overrun(self, path) -> None: def test_overrun(self, path: str) -> None:
"""For overrun completeness, test as: """For overrun completeness, test as:
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
""" """
@ -1023,7 +1030,7 @@ class TestImage:
pass pass
assert not hasattr(im, "fp") assert not hasattr(im, "fp")
def test_close_graceful(self, caplog) -> None: def test_close_graceful(self, caplog: pytest.LogCaptureFixture) -> None:
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
copy = im.copy() copy = im.copy()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
@ -1034,10 +1041,10 @@ class TestImage:
class MockEncoder: class MockEncoder:
pass args: tuple[str, ...]
def mock_encode(*args): def mock_encode(*args: str) -> MockEncoder:
encoder = MockEncoder() encoder = MockEncoder()
encoder.args = args encoder.args = args
return encoder return encoder

View File

@ -208,7 +208,9 @@ def test_language() -> None:
), ),
ids=("None", "ltr", "rtl2", "rtl", "ttb"), ids=("None", "ltr", "rtl2", "rtl", "ttb"),
) )
def test_getlength(mode, text, direction, expected) -> None: def test_getlength(
mode: str, text: str, direction: str | None, expected: float
) -> None:
ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE) ttf = ImageFont.truetype(FONT_PATH, FONT_SIZE)
im = Image.new(mode, (1, 1), 0) im = Image.new(mode, (1, 1), 0)
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
@ -230,7 +232,7 @@ def test_getlength(mode, text, direction, expected) -> None:
("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"),
ids=("caron-above", "caron-below", "double-breve", "overline"), ids=("caron-above", "caron-below", "double-breve", "overline"),
) )
def test_getlength_combine(mode, direction, text) -> None: def test_getlength_combine(mode: str, direction: str, text: str) -> None:
if text == "i\u0305i" and direction == "ttb": if text == "i\u0305i" and direction == "ttb":
pytest.skip("fails with this font") pytest.skip("fails with this font")
@ -250,7 +252,7 @@ def test_getlength_combine(mode, direction, text) -> None:
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
def test_anchor_ttb(anchor) -> None: def test_anchor_ttb(anchor: str) -> None:
text = "f" text = "f"
path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120)
@ -306,7 +308,9 @@ combine_tests = (
@pytest.mark.parametrize( @pytest.mark.parametrize(
"name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
) )
def test_combine(name, text, dir, anchor, epsilon) -> None: def test_combine(
name: str, text: str, dir: str | None, anchor: str | None, epsilon: float
) -> None:
path = f"Tests/images/test_combine_{name}.png" path = f"Tests/images/test_combine_{name}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
@ -337,7 +341,7 @@ def test_combine(name, text, dir, anchor, epsilon) -> None:
("rm", "right"), # pass with getsize ("rm", "right"), # pass with getsize
), ),
) )
def test_combine_multiline(anchor, align) -> None: def test_combine_multiline(anchor: str, align: str) -> None:
# test that multiline text uses getlength, not getsize or getbbox # test that multiline text uses getlength, not getsize or getbbox
path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"

View File

@ -10,7 +10,7 @@ from PIL import Image, ImageMorph, _imagingmorph
from .helper import assert_image_equal_tofile, hopper from .helper import assert_image_equal_tofile, hopper
def string_to_img(image_string): def string_to_img(image_string: str) -> Image.Image:
"""Turn a string image representation into a binary image""" """Turn a string image representation into a binary image"""
rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)] rows = [s for s in image_string.replace(" ", "").split("\n") if len(s)]
height = len(rows) height = len(rows)
@ -38,7 +38,7 @@ A = string_to_img(
) )
def img_to_string(im): def img_to_string(im: Image.Image) -> str:
"""Turn a (small) binary image into a string representation""" """Turn a (small) binary image into a string representation"""
chars = ".1" chars = ".1"
width, height = im.size width, height = im.size
@ -48,11 +48,11 @@ def img_to_string(im):
) )
def img_string_normalize(im): def img_string_normalize(im: str) -> str:
return img_to_string(string_to_img(im)) return img_to_string(string_to_img(im))
def assert_img_equal_img_string(a, b_string) -> None: def assert_img_equal_img_string(a: Image.Image, b_string: str) -> None:
assert img_to_string(a) == img_string_normalize(b_string) assert img_to_string(a) == img_string_normalize(b_string)
@ -63,7 +63,7 @@ def test_str_to_img() -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
"op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge") "op", ("corner", "dilation4", "dilation8", "erosion4", "erosion8", "edge")
) )
def test_lut(op) -> None: def test_lut(op: str) -> None:
lb = ImageMorph.LutBuilder(op_name=op) lb = ImageMorph.LutBuilder(op_name=op)
assert lb.get_lut() is None assert lb.get_lut() is None

View File

@ -67,7 +67,7 @@ def test_getcolor_rgba_color_rgb_palette() -> None:
(255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)), (255, ImagePalette.ImagePalette("RGB", list(range(256)) * 3)),
], ],
) )
def test_getcolor_not_special(index, palette) -> None: def test_getcolor_not_special(index: int, palette: ImagePalette.ImagePalette) -> None:
im = Image.new("P", (1, 1)) im = Image.new("P", (1, 1))
# Do not use transparency index as a new color # Do not use transparency index as a new color

View File

@ -13,7 +13,9 @@ FONT_SIZE = 20
FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf" FONT_PATH = "Tests/fonts/DejaVuSans/DejaVuSans.ttf"
def helper_pickle_file(tmp_path: Path, pickle, protocol, test_file, mode) -> None: def helper_pickle_file(
tmp_path: Path, protocol: int, test_file: str, mode: str | None
) -> None:
# Arrange # Arrange
with Image.open(test_file) as im: with Image.open(test_file) as im:
filename = str(tmp_path / "temp.pkl") filename = str(tmp_path / "temp.pkl")
@ -30,7 +32,7 @@ def helper_pickle_file(tmp_path: Path, pickle, protocol, test_file, mode) -> Non
assert im == loaded_im assert im == loaded_im
def helper_pickle_string(pickle, protocol, test_file, mode) -> None: def helper_pickle_string(protocol: int, test_file: str, mode: str | None) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
if mode: if mode:
im = im.convert(mode) im = im.convert(mode)
@ -64,10 +66,12 @@ def helper_pickle_string(pickle, protocol, test_file, mode) -> None:
], ],
) )
@pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1)) @pytest.mark.parametrize("protocol", range(0, pickle.HIGHEST_PROTOCOL + 1))
def test_pickle_image(tmp_path: Path, test_file, test_mode, protocol) -> None: def test_pickle_image(
tmp_path: Path, test_file: str, test_mode: str | None, protocol: int
) -> None:
# Act / Assert # Act / Assert
helper_pickle_string(pickle, protocol, test_file, test_mode) helper_pickle_string(protocol, test_file, test_mode)
helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode) helper_pickle_file(tmp_path, protocol, test_file, test_mode)
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
@ -99,7 +103,9 @@ def test_pickle_tell() -> None:
assert unpickled_image.tell() == 0 assert unpickled_image.tell() == 0
def helper_assert_pickled_font_images(font1, font2) -> None: def helper_assert_pickled_font_images(
font1: ImageFont.FreeTypeFont, font2: ImageFont.FreeTypeFont
) -> None:
# Arrange # Arrange
im1 = Image.new(mode="RGBA", size=(300, 100)) im1 = Image.new(mode="RGBA", size=(300, 100))
im2 = Image.new(mode="RGBA", size=(300, 100)) im2 = Image.new(mode="RGBA", size=(300, 100))
@ -117,7 +123,7 @@ def helper_assert_pickled_font_images(font1, font2) -> None:
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
def test_pickle_font_string(protocol) -> None: def test_pickle_font_string(protocol: int) -> None:
# Arrange # Arrange
font = ImageFont.truetype(FONT_PATH, FONT_SIZE) font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
@ -131,7 +137,7 @@ def test_pickle_font_string(protocol) -> None:
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1))) @pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
def test_pickle_font_file(tmp_path: Path, protocol) -> None: def test_pickle_font_file(tmp_path: Path, protocol: int) -> None:
# Arrange # Arrange
font = ImageFont.truetype(FONT_PATH, FONT_SIZE) font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
filename = str(tmp_path / "temp.pkl") filename = str(tmp_path / "temp.pkl")

View File

@ -10,7 +10,7 @@ import pytest
from PIL import Image, PSDraw from PIL import Image, PSDraw
def _create_document(ps) -> None: def _create_document(ps: PSDraw.PSDraw) -> None:
title = "hopper" title = "hopper"
box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points
@ -50,7 +50,7 @@ def test_draw_postscript(tmp_path: Path) -> None:
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_stdout(buffer) -> None: def test_stdout(buffer: bool) -> None:
# Temporarily redirect stdout # Temporarily redirect stdout
old_stdout = sys.stdout old_stdout = sys.stdout

View File

@ -21,7 +21,7 @@ from PIL import Image
"Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi", "Tests/images/crash-db8bfa78b19721225425530c5946217720d7df4e.sgi",
], ],
) )
def test_crashes(test_file) -> None: def test_crashes(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:
with pytest.raises(OSError): with pytest.raises(OSError):

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Callable
import pytest import pytest
@ -17,7 +18,12 @@ test_filenames = ("temp_';", 'temp_";', "temp_'\"|", "temp_'\"||", "temp_'\"&&")
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
class TestShellInjection: class TestShellInjection:
def assert_save_filename_check(self, tmp_path: Path, src_img, save_func) -> None: def assert_save_filename_check(
self,
tmp_path: Path,
src_img: Image.Image,
save_func: Callable[[Image.Image, int, str], None],
) -> None:
for filename in test_filenames: for filename in test_filenames:
dest_file = str(tmp_path / filename) dest_file = str(tmp_path / filename)
save_func(src_img, 0, dest_file) save_func(src_img, 0, dest_file)

View File

@ -42,7 +42,7 @@ from .helper import on_ci
@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data") @pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data")
@pytest.mark.filterwarnings("ignore:Metadata warning") @pytest.mark.filterwarnings("ignore:Metadata warning")
@pytest.mark.filterwarnings("ignore:Truncated File Read") @pytest.mark.filterwarnings("ignore:Truncated File Read")
def test_tiff_crashes(test_file): def test_tiff_crashes(test_file: str) -> None:
try: try:
with Image.open(test_file) as im: with Image.open(test_file) as im:
im.load() im.load()

View File

@ -53,9 +53,9 @@ def test_nonetype() -> None:
def test_ifd_rational_save(tmp_path: Path) -> None: def test_ifd_rational_save(tmp_path: Path) -> None:
methods = (True, False) methods = [True]
if not features.check("libtiff"): if features.check("libtiff"):
methods = (False,) methods.append(False)
for libtiff in methods: for libtiff in methods:
TiffImagePlugin.WRITE_LIBTIFF = libtiff TiffImagePlugin.WRITE_LIBTIFF = libtiff