Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added type hints #8362

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e005bcf
Added type hints
radarhere Sep 9, 2024
957db67
Use hasattr
radarhere Sep 9, 2024
15e0f1a
Merge branch 'main' into context_manager
radarhere Sep 10, 2024
6af0425
Merge branch 'main' into context_manager
radarhere Sep 18, 2024
956dd77
Merge branch 'main' into context_manager
radarhere Sep 25, 2024
241b325
Merge branch 'main' into context_manager
radarhere Oct 6, 2024
b48427d
Merge branch 'main' into context_manager
radarhere Oct 12, 2024
988a1fe
Merge branch 'main' into context_manager
radarhere Oct 13, 2024
e3f4200
Do not allow untyped functions
radarhere Oct 13, 2024
dc3c489
Merge branch 'main' into context_manager
radarhere Nov 6, 2024
8790593
Merge branch 'main' into context_manager
radarhere Nov 29, 2024
3a8eaf5
Merge branch 'main' into context_manager
radarhere Dec 22, 2024
3222d36
Do not remove second load call
radarhere Dec 26, 2024
f878cfd
Corrected argument types
radarhere Dec 27, 2024
68e9ee5
Merge branch 'main' into context_manager
radarhere Dec 28, 2024
dae3154
Merge branch 'main' into context_manager
radarhere Dec 28, 2024
5620b9e
Ignore sys.stdout type
radarhere Jan 9, 2025
ffc6939
Keep ImageFile type hint
radarhere Dec 31, 2024
66fe289
Merge branch 'main' into context_manager
radarhere Jan 9, 2025
0572221
Updated return type
radarhere Jan 9, 2025
68cbf93
Assert image type
radarhere Jan 9, 2025
a57d0e0
Merge branch 'main' into context_manager
radarhere Jan 10, 2025
0b6418d
Merge branch 'main' into context_manager
radarhere Jan 17, 2025
469f91e
Merge branch 'main' into context_manager
radarhere Jan 20, 2025
a8c88c2
Merge branch 'main' into context_manager
radarhere Jan 27, 2025
b9d3bb6
Merge branch 'main' into context_manager
radarhere Jan 31, 2025
9ca85eb
Merge branch 'main' into context_manager
radarhere Feb 6, 2025
01d5b16
Merge branch 'main' into context_manager
radarhere Feb 17, 2025
45034b2
Merge branch 'main' into context_manager
radarhere Feb 18, 2025
0dc62ba
Merge branch 'main' into context_manager
radarhere Mar 3, 2025
cb84166
Use new variable for different type
radarhere Mar 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Tests/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -
def assert_image_equal_tofile(
a: Image.Image, filename: str, msg: str | None = None, mode: str | None = None
) -> None:
img: Image.Image
with Image.open(filename) as img:
if mode:
img = img.convert(mode)
Expand Down Expand Up @@ -140,6 +141,7 @@ def assert_image_similar_tofile(
epsilon: float,
msg: str | None = None,
) -> None:
img: Image.Image
with Image.open(filename) as img:
assert_image_similar(a, img, epsilon, msg)

Expand Down
3 changes: 3 additions & 0 deletions Tests/test_bmp_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,11 @@ def get_compare(f: str) -> str:

for f in get_files("g"):
try:
im: Image.Image
with Image.open(f) as im:
im.load()

compare: Image.Image
with Image.open(get_compare(f)) as compare:
compare.load()
if im.mode == "P":
Expand Down
64 changes: 55 additions & 9 deletions Tests/test_file_apng.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# (referenced from https://wiki.mozilla.org/APNG_Specification)
def test_apng_basic() -> None:
with Image.open("Tests/images/apng/single_frame.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
assert im.n_frames == 1
assert im.get_format_mimetype() == "image/apng"
Expand All @@ -20,6 +21,7 @@ def test_apng_basic() -> None:
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/single_frame_default.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.is_animated
assert im.n_frames == 2
assert im.get_format_mimetype() == "image/apng"
Expand Down Expand Up @@ -49,60 +51,71 @@ def test_apng_basic() -> None:
)
def test_apng_fdat(filename: str) -> None:
with Image.open(filename) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)


def test_apng_dispose() -> None:
with Image.open("Tests/images/apng/dispose_op_none.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/dispose_op_background.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0)

with Image.open("Tests/images/apng/dispose_op_background_final.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/dispose_op_previous.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/dispose_op_previous_final.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/dispose_op_previous_first.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0)


def test_apng_dispose_region() -> None:
with Image.open("Tests/images/apng/dispose_op_none_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/dispose_op_background_before_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0)

with Image.open("Tests/images/apng/dispose_op_background_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 255, 255)
assert im.getpixel((64, 32)) == (0, 0, 0, 0)

with Image.open("Tests/images/apng/dispose_op_previous_region.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
Expand All @@ -129,6 +142,7 @@ def test_apng_dispose_op_previous_frame() -> None:
# ],
# )
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (255, 0, 0, 255)

Expand All @@ -142,26 +156,31 @@ def test_apng_dispose_op_background_p_mode() -> None:

def test_apng_blend() -> None:
with Image.open("Tests/images/apng/blend_op_source_solid.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/blend_op_source_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
assert im.getpixel((64, 32)) == (0, 0, 0, 0)

with Image.open("Tests/images/apng/blend_op_source_near_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 2)
assert im.getpixel((64, 32)) == (0, 255, 0, 2)

with Image.open("Tests/images/apng/blend_op_over.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/blend_op_over_near_transparent.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 97)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
Expand All @@ -175,6 +194,7 @@ def test_apng_blend_transparency() -> None:

def test_apng_chunk_order() -> None:
with Image.open("Tests/images/apng/fctl_actl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
Expand Down Expand Up @@ -230,93 +250,110 @@ def test_apng_num_plays() -> None:

def test_apng_mode() -> None:
with Image.open("Tests/images/apng/mode_16bit.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "RGBA"
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (0, 0, 128, 191)
assert im.getpixel((64, 32)) == (0, 0, 128, 191)

with Image.open("Tests/images/apng/mode_grayscale.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "L"
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == 128
assert im.getpixel((64, 32)) == 255

with Image.open("Tests/images/apng/mode_grayscale_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "LA"
im.seek(im.n_frames - 1)
assert im.getpixel((0, 0)) == (128, 191)
assert im.getpixel((64, 32)) == (128, 191)

with Image.open("Tests/images/apng/mode_palette.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGB")
assert im.getpixel((0, 0)) == (0, 255, 0)
assert im.getpixel((64, 32)) == (0, 255, 0)
rgb_im = im.convert("RGB")
assert rgb_im.getpixel((0, 0)) == (0, 255, 0)
assert rgb_im.getpixel((64, 32)) == (0, 255, 0)

with Image.open("Tests/images/apng/mode_palette_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
rgb_im = im.convert("RGBA")
assert rgb_im.getpixel((0, 0)) == (0, 255, 0, 255)
assert rgb_im.getpixel((64, 32)) == (0, 255, 0, 255)

with Image.open("Tests/images/apng/mode_palette_1bit_alpha.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.mode == "P"
im.seek(im.n_frames - 1)
im = im.convert("RGBA")
assert im.getpixel((0, 0)) == (0, 0, 255, 128)
assert im.getpixel((64, 32)) == (0, 0, 255, 128)
rgb_im = im.convert("RGBA")
assert rgb_im.getpixel((0, 0)) == (0, 0, 255, 128)
assert rgb_im.getpixel((64, 32)) == (0, 0, 255, 128)


def test_apng_chunk_errors() -> None:
with Image.open("Tests/images/apng/chunk_no_actl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated

with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/chunk_multi_actl.png") as im:
im.load()
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated

with Image.open("Tests/images/apng/chunk_actl_after_idat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated

with Image.open("Tests/images/apng/chunk_no_fctl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1)

with Image.open("Tests/images/apng/chunk_repeat_fctl.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1)

with Image.open("Tests/images/apng/chunk_no_fdat.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
with pytest.raises(SyntaxError):
im.seek(im.n_frames - 1)


def test_apng_syntax_errors() -> None:
with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
with pytest.raises(OSError):
im.load()

with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_zero_default.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()

# we can handle this case gracefully
with Image.open("Tests/images/apng/syntax_num_frames_low.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)

with pytest.raises(OSError):
with Image.open("Tests/images/apng/syntax_num_frames_high.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
im.load()

with pytest.warns(UserWarning):
with Image.open("Tests/images/apng/syntax_num_frames_invalid.png") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert not im.is_animated
im.load()

Expand All @@ -336,6 +373,7 @@ def test_apng_syntax_errors() -> None:
def test_apng_sequence_errors(test_file: str) -> None:
with pytest.raises(SyntaxError):
with Image.open(f"Tests/images/apng/{test_file}") as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
im.load()

Expand All @@ -346,6 +384,8 @@ def test_apng_save(tmp_path: Path) -> None:
im.save(test_file, save_all=True)

with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)

im.load()
assert not im.is_animated
assert im.n_frames == 1
Expand All @@ -361,6 +401,8 @@ def test_apng_save(tmp_path: Path) -> None:
)

with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)

im.load()
assert im.is_animated
assert im.n_frames == 2
Expand Down Expand Up @@ -400,6 +442,7 @@ def test_apng_save_split_fdat(tmp_path: Path) -> None:
append_images=frames,
)
with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
im.seek(im.n_frames - 1)
im.load()

Expand Down Expand Up @@ -442,6 +485,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150]
)
with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 1
assert "duration" not in im.info

Expand All @@ -453,6 +497,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
duration=[500, 100, 150],
)
with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 2
assert im.info["duration"] == 600

Expand All @@ -463,6 +508,7 @@ def test_apng_save_duration_loop(tmp_path: Path) -> None:
frame.info["duration"] = 300
frame.save(test_file, save_all=True, append_images=[frame, different_frame])
with Image.open(test_file) as im:
assert isinstance(im, PngImagePlugin.PngImageFile)
assert im.n_frames == 2
assert im.info["duration"] == 600

Expand Down
4 changes: 2 additions & 2 deletions Tests/test_file_bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ def test_save_dib(tmp_path: Path) -> None:
def test_rgba_bitfields() -> None:
# This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA
with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
with Image.open("Tests/images/rgb32bf-rgba.bmp") as bmp_im:
# So before the comparing the image, swap the channels
b, g, r = im.split()[1:]
b, g, r = bmp_im.split()[1:]
im = Image.merge("RGB", (r, g, b))

assert_image_equal_tofile(im, "Tests/images/bmp/q/rgb32bf-xbgr.bmp")
Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_bufrstub.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def open(self, im: ImageFile.StubImageFile) -> None:

def load(self, im: ImageFile.StubImageFile) -> Image.Image:
self.loaded = True
assert im.fp is not None
im.fp.close()
return Image.new("RGB", (1, 1))

Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_cur.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def test_invalid_file() -> None:
no_cursors_file = "Tests/images/no_cursors.cur"

cur = CurImagePlugin.CurImageFile(TEST_FILE)
assert cur.fp is not None
cur.fp.close()
with open(no_cursors_file, "rb") as cur.fp:
with pytest.raises(TypeError):
Expand Down
2 changes: 2 additions & 0 deletions Tests/test_file_dcx.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ def test_tell() -> None:

def test_n_frames() -> None:
with Image.open(TEST_FILE) as im:
assert isinstance(im, DcxImagePlugin.DcxImageFile)
assert im.n_frames == 1
assert not im.is_animated


def test_eoferror() -> None:
with Image.open(TEST_FILE) as im:
assert isinstance(im, DcxImagePlugin.DcxImageFile)
n_frames = im.n_frames

# Test seeking past the last frame
Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_dds.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
)
def test_sanity_dxt1_bc1(image_path: str) -> None:
"""Check DXT1 and BC1 images can be opened"""
target: Image.Image
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA")
with Image.open(image_path) as im:
Expand Down
Loading
Loading