Skip to content

Commit

Permalink
Added type hints
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Mar 1, 2024
1 parent 334c26d commit ef14e97
Show file tree
Hide file tree
Showing 24 changed files with 89 additions and 66 deletions.
5 changes: 4 additions & 1 deletion Tests/check_imaging_leaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ def _get_mem_usage() -> float:


def _test_leak(
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
min_iterations: int,
max_iterations: int,
fn: Callable[..., Image.Image | None],
*args: Any,
) -> None:
mem_limit = None
for i in range(max_iterations):
Expand Down
3 changes: 3 additions & 0 deletions Tests/check_png_dos.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def test_ignore_dos_text() -> None:
finally:
ImageFile.LOAD_TRUNCATED_IMAGES = False

assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"

Expand All @@ -32,6 +33,7 @@ def test_dos_text() -> None:
assert msg, "Decompressed Data Too Large"
return

assert isinstance(im, PngImagePlugin.PngImageFile)
for s in im.text.values():
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"

Expand All @@ -57,6 +59,7 @@ def test_dos_total_memory() -> None:
return

total_len = 0
assert isinstance(im2, PngImagePlugin.PngImageFile)
for txt in im2.text.values():
total_len += len(txt)
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
2 changes: 1 addition & 1 deletion Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ def is_mingw() -> bool:


class CachedProperty:
def __init__(self, func: Callable[[Any], None]) -> None:
def __init__(self, func: Callable[[Any], Any]) -> None:
self.func = func

def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
Expand Down
15 changes: 6 additions & 9 deletions Tests/test_file_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,41 +23,38 @@ def test_isatty() -> None:

def test_seek_mode_0() -> None:
# Arrange
mode = 0
with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)

# Act
container.seek(33, mode)
container.seek(33, mode)
container.seek(33, 0)
container.seek(33, 0)

# Assert
assert container.tell() == 33


def test_seek_mode_1() -> None:
# Arrange
mode = 1
with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)

# Act
container.seek(33, mode)
container.seek(33, mode)
container.seek(33, 1)
container.seek(33, 1)

# Assert
assert container.tell() == 66


def test_seek_mode_2() -> None:
# Arrange
mode = 2
with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)

# Act
container.seek(33, mode)
container.seek(33, mode)
container.seek(33, 2)
container.seek(33, 2)

# Assert
assert container.tell() == 100
Expand Down
43 changes: 25 additions & 18 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
from typing import Any, cast

import pytest

Expand Down Expand Up @@ -45,14 +45,20 @@

@skip_unless_feature("jpg")
class TestFileJpeg:
def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image:
def roundtrip_with_bytes(
self, im: Image.Image, **options: Any
) -> tuple[JpegImagePlugin.JpegImageFile, int]:
out = BytesIO()
im.save(out, "JPEG", **options)
test_bytes = out.tell()
out.seek(0)
im = Image.open(out)
im.bytes = test_bytes # for testing only
return im
reloaded = cast(JpegImagePlugin.JpegImageFile, Image.open(out))
return reloaded, test_bytes

def roundtrip(
self, im: Image.Image, **options: Any
) -> JpegImagePlugin.JpegImageFile:
return self.roundtrip_with_bytes(im, **options)[0]

def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image:
"""Generates a very hard to compress file
Expand Down Expand Up @@ -246,13 +252,13 @@ def test_large_icc_meta(self, tmp_path: Path) -> None:
im.save(f, progressive=True, quality=94, exif=b" " * 43668)

def test_optimize(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), optimize=0)
im3 = self.roundtrip(hopper(), optimize=1)
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), optimize=0)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), optimize=1)
assert_image_equal(im1, im2)
assert_image_equal(im1, im3)
assert im1.bytes >= im2.bytes
assert im1.bytes >= im3.bytes
assert im1_bytes >= im2_bytes
assert im1_bytes >= im3_bytes

def test_optimize_large_buffer(self, tmp_path: Path) -> None:
# https://github.com/python-pillow/Pillow/issues/148
Expand All @@ -262,15 +268,15 @@ def test_optimize_large_buffer(self, tmp_path: Path) -> None:
im.save(f, format="JPEG", optimize=True)

def test_progressive(self) -> None:
im1 = self.roundtrip(hopper())
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2 = self.roundtrip(hopper(), progressive=False)
im3 = self.roundtrip(hopper(), progressive=True)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), progressive=True)
assert not im1.info.get("progressive")
assert not im2.info.get("progressive")
assert im3.info.get("progressive")

assert_image_equal(im1, im3)
assert im1.bytes >= im3.bytes
assert im1_bytes >= im3_bytes

def test_progressive_large_buffer(self, tmp_path: Path) -> None:
f = str(tmp_path / "temp.jpg")
Expand Down Expand Up @@ -341,6 +347,7 @@ def test_empty_exif_gps(self) -> None:
assert exif.get_ifd(0x8825) == {}

transposed = ImageOps.exif_transpose(im)
assert transposed is not None
exif = transposed.getexif()
assert exif.get_ifd(0x8825) == {}

Expand Down Expand Up @@ -419,14 +426,14 @@ def test_progressive_compat(self) -> None:
assert im3.info.get("progression")

def test_quality(self) -> None:
im1 = self.roundtrip(hopper())
im2 = self.roundtrip(hopper(), quality=50)
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), quality=50)
assert_image(im1, im2.mode, im2.size)
assert im1.bytes >= im2.bytes
assert im1_bytes >= im2_bytes

im3 = self.roundtrip(hopper(), quality=0)
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), quality=0)
assert_image(im1, im3.mode, im3.size)
assert im2.bytes > im3.bytes
assert im2_bytes > im3_bytes

def test_smooth(self) -> None:
im1 = self.roundtrip(hopper())
Expand Down
3 changes: 1 addition & 2 deletions Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
out = BytesIO()
im.save(out, "JPEG2000", **options)
test_bytes = out.tell()
out.seek(0)
with Image.open(out) as im:
im.bytes = test_bytes # for testing only
im.load()
return im

Expand Down Expand Up @@ -340,6 +338,7 @@ def test_parser_feed() -> None:
p.feed(data)

# Assert
assert p.image is not None
assert p.image.size == (640, 480)


Expand Down
8 changes: 6 additions & 2 deletions Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

@skip_unless_feature("libtiff")
class LibTiffTestCase:
def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None:
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit
assert im.mode == "1"
Expand Down Expand Up @@ -524,7 +524,8 @@ def test_bw_compression_w_rgb(self, compression: str, tmp_path: Path) -> None:
im.save(out, compression=compression)

def test_fp_leak(self) -> None:
im = Image.open("Tests/images/hopper_g4_500.tif")
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
assert im is not None
fn = im.fp.fileno()

os.fstat(fn)
Expand Down Expand Up @@ -716,6 +717,7 @@ def test_fd_duplication(self, tmp_path: Path) -> None:
f.write(src.read())

im = Image.open(tmpfile)
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.n_frames
im.close()
# Should not raise PermissionError.
Expand Down Expand Up @@ -1097,6 +1099,7 @@ def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:

with Image.open(out) as im:
# Assert that there are multiple strips
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert len(im.tag_v2[STRIPOFFSETS]) > 1

@pytest.mark.parametrize("argument", (True, False))
Expand All @@ -1113,6 +1116,7 @@ def test_save_single_strip(self, argument: bool, tmp_path: Path) -> None:
im.save(out, **arguments)

with Image.open(out) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert len(im.tag_v2[STRIPOFFSETS]) == 1
finally:
TiffImagePlugin.STRIP_SIZE = 65536
Expand Down
11 changes: 4 additions & 7 deletions Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import warnings
from io import BytesIO
from typing import Any
from typing import Any, cast

import pytest

from PIL import Image
from PIL import Image, MpoImagePlugin

from .helper import (
assert_image_equal,
Expand All @@ -20,14 +20,11 @@
pytestmark = skip_unless_feature("jpg")


def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile:
out = BytesIO()
im.save(out, "MPO", **options)
test_bytes = out.tell()
out.seek(0)
im = Image.open(out)
im.bytes = test_bytes # for testing only
return im
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))


@pytest.mark.parametrize("test_file", test_files)
Expand Down
6 changes: 3 additions & 3 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from io import BytesIO
from pathlib import Path
from types import ModuleType
from typing import Any
from typing import Any, cast

import pytest

Expand Down Expand Up @@ -59,11 +59,11 @@ def load(data: bytes) -> Image.Image:
return Image.open(BytesIO(data))


def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile:
out = BytesIO()
im.save(out, "PNG", **options)
out.seek(0)
return Image.open(out)
return cast(PngImagePlugin.PngImageFile, Image.open(out))


@skip_unless_feature("zlib")
Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ def test_rowsperstrip(self, tmp_path: Path) -> None:
im.save(outfile, tiffinfo={278: 256})

with Image.open(outfile) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[278] == 256

def test_strip_raw(self) -> None:
Expand Down
4 changes: 2 additions & 2 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,13 @@ def test_width_height(self) -> None:
assert im.height == 2

with pytest.raises(AttributeError):
im.size = (3, 4)
im.size = (3, 4) # type: ignore[misc]

def test_set_mode(self) -> None:
im = Image.new("RGB", (1, 1))

with pytest.raises(AttributeError):
im.mode = "P"
im.mode = "P" # type: ignore[misc]

def test_invalid_image(self) -> None:
im = io.BytesIO(b"")
Expand Down
1 change: 1 addition & 0 deletions Tests/test_image_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
cffi: ModuleType | None
if os.environ.get("PYTHONOPTIMIZE") == "2":
cffi = None
else:
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image_fromqimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_images() -> Generator[Image.Image, None, None]:
Image.open("Tests/images/7x13.png"),
]
try:
yield ims
yield from ims

Check warning on line 29 in Tests/test_image_fromqimage.py

View check run for this annotation

Codecov / codecov/patch

Tests/test_image_fromqimage.py#L29

Added line #L29 was not covered by tests
finally:
for im in ims:
im.close()
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image_paste.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def assert_9points_image(
def assert_9points_paste(
self,
im: Image.Image,
im2: Image.Image,
im2: Image.Image | str | tuple[int, ...],
mask: Image.Image,
expected: list[tuple[int, int, int, int]],
) -> None:
Expand Down
7 changes: 4 additions & 3 deletions Tests/test_imagemorph.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,16 @@ 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:
mop.apply(None)
mop.apply(im)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.match(None)
mop.match(im)
assert str(e.value) == "No operator loaded"
with pytest.raises(Exception) as e:
mop.save_lut(None)
mop.save_lut("")
assert str(e.value) == "No operator loaded"


Expand Down
Loading

0 comments on commit ef14e97

Please sign in to comment.