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 2, 2024
1 parent 2bd5426 commit 6d78d42
Show file tree
Hide file tree
Showing 27 changed files with 115 additions and 88 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
8 changes: 5 additions & 3 deletions Tests/test_file_container.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Literal

import pytest

from PIL import ContainerIO, Image
Expand All @@ -22,14 +24,14 @@ def test_isatty() -> None:


@pytest.mark.parametrize(
"mode, expected_value",
"mode, expected_position",
(
(0, 33),
(1, 66),
(2, 100),
),
)
def test_seek_mode(mode: int, expected_value: int) -> None:
def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
# Arrange
with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100)
Expand All @@ -39,7 +41,7 @@ def test_seek_mode(mode: int, expected_value: int) -> None:
container.seek(33, mode)

# Assert
assert container.tell() == expected_value
assert container.tell() == expected_position


@pytest.mark.parametrize("bytesmode", (True, False))
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
7 changes: 4 additions & 3 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 @@ -77,7 +75,9 @@ def test_invalid_file() -> None:
def test_bytesio() -> None:
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
data = BytesIO(f.read())
assert_image_similar_tofile(test_card, data, 1.0e-3)
with Image.open(data) as im:
im.load()
assert_image_similar(im, test_card, 1.0e-3)


# These two test pre-written JPEG 2000 files that were not written with
Expand Down Expand Up @@ -340,6 +340,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
7 changes: 4 additions & 3 deletions Tests/test_file_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from PIL import Image, ImageSequence, SpiderImagePlugin

from .helper import assert_image_equal_tofile, hopper, is_pypy
from .helper import assert_image_equal, hopper, is_pypy

TEST_FILE = "Tests/images/hopper.spider"

Expand Down Expand Up @@ -152,12 +152,13 @@ def test_nonstack_dos() -> None:
assert i <= 1, "Non-stack DOS file test failed"


# for issue #4093
# for issue #4093s
def test_odd_size() -> None:
data = BytesIO()
width = 100
im = Image.new("F", (width, 64))
im.save(data, format="SPIDER")

data.seek(0)
assert_image_equal_tofile(im, data)
with Image.open(data) as im2:
assert_image_equal(im, im2)
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
Loading

0 comments on commit 6d78d42

Please sign in to comment.