Use NamedTuple

This commit is contained in:
Andrew Murray 2026-02-16 23:39:44 +11:00
parent c3ab770878
commit 55afac49c1
8 changed files with 166 additions and 145 deletions

View File

@ -732,19 +732,21 @@ def test_save_all_progress() -> None:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "PNG", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -759,21 +761,21 @@ def test_save_all_progress() -> None:
expected = []
for i in range(2):
expected.append(
{
"image_index": i,
"image_filename": "single_frame.png",
"completed_frames": i + 1,
"total_frames": 7,
}
Image.Progress(
image_index=i,
image_filename="single_frame.png",
completed_frames=i + 1,
total_frames=7,
)
)
for i in range(5):
expected.append(
{
"image_index": 2,
"image_filename": "delay.png",
"completed_frames": i + 3,
"total_frames": 7,
}
Image.Progress(
image_index=2,
image_filename="delay.png",
completed_frames=i + 3,
total_frames=7,
)
)
assert progress == expected

View File

@ -220,19 +220,21 @@ class TestFileAvif:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "AVIF", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -245,20 +247,20 @@ class TestFileAvif:
expected = []
for i in range(5):
expected.append(
{
"image_index": 0,
"image_filename": "star.avifs",
"completed_frames": i + 1,
"total_frames": 6,
}
Image.Progress(
image_index=0,
image_filename="star.avifs",
completed_frames=i + 1,
total_frames=6,
)
)
expected.append(
{
"image_index": 1,
"image_filename": None,
"completed_frames": 6,
"total_frames": 6,
}
Image.Progress(
image_index=1,
image_filename=None,
completed_frames=6,
total_frames=6,
)
)
assert progress == expected

View File

@ -323,19 +323,21 @@ def test_save_all_progress() -> None:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "GIF", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -345,22 +347,22 @@ def test_save_all_progress() -> None:
im = Image.new("RGB", im2.size)
im.save(out, "GIF", save_all=True, append_images=[im2], progress=callback)
expected: list[dict[str, int | str | None]] = [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 32,
}
expected: list[Image.Progress] = [
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=32,
)
]
for i in range(31):
expected.append(
{
"image_index": 1,
"image_filename": "chi.gif",
"completed_frames": i + 2,
"total_frames": 32,
}
Image.Progress(
image_index=1,
image_filename="chi.gif",
completed_frames=i + 2,
total_frames=32,
)
)
assert progress == expected

View File

@ -323,21 +323,23 @@ def test_save_all_progress() -> None:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = (
state["image_filename"].replace("\\", "/").split("Tests/images/")[-1]
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=state.image_filename.replace("\\", "/").split(
"Tests/images/"
)[-1]
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "MPO", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -351,12 +353,12 @@ def test_save_all_progress() -> None:
for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]):
for j in range(2):
expected.append(
{
"image_index": i,
"image_filename": filename,
"completed_frames": i * 2 + j + 1,
"total_frames": 4,
}
Image.Progress(
image_index=i,
image_filename=filename,
completed_frames=i * 2 + j + 1,
total_frames=4,
)
)
assert progress == expected

View File

@ -183,19 +183,21 @@ def test_save_all_progress() -> None:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "PDF", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -209,12 +211,12 @@ def test_save_all_progress() -> None:
for i, filename in enumerate(["sugarshack.mpo", "frozenpond.mpo"]):
for j in range(2):
expected.append(
{
"image_index": i,
"image_filename": filename,
"completed_frames": i * 2 + j + 1,
"total_frames": 4,
}
Image.Progress(
image_index=i,
image_filename=filename,
completed_frames=i * 2 + j + 1,
total_frames=4,
)
)
assert progress == expected

View File

@ -789,19 +789,21 @@ class TestFileTiff:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "TIFF", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -814,21 +816,21 @@ class TestFileTiff:
)
expected = [
{
"image_index": 0,
"image_filename": "hopper.tif",
"completed_frames": 1,
"total_frames": 4,
}
Image.Progress(
image_index=0,
image_filename="hopper.tif",
completed_frames=1,
total_frames=4,
)
]
for i in range(3):
expected.append(
{
"image_index": 1,
"image_filename": "multipage.tiff",
"completed_frames": i + 2,
"total_frames": 4,
}
Image.Progress(
image_index=1,
image_filename="multipage.tiff",
completed_frames=i + 2,
total_frames=4,
)
)
assert progress == expected

View File

@ -132,19 +132,21 @@ class TestFileWebp:
out = BytesIO()
progress = []
def callback(state) -> None:
if state["image_filename"]:
state["image_filename"] = os.path.basename(state["image_filename"])
def callback(state: Image.Progress) -> None:
if state.image_filename:
state = state._replace(
image_filename=os.path.basename(state.image_filename)
)
progress.append(state)
Image.new("RGB", (1, 1)).save(out, "WEBP", save_all=True, progress=callback)
assert progress == [
{
"image_index": 0,
"image_filename": None,
"completed_frames": 1,
"total_frames": 1,
}
Image.Progress(
image_index=0,
image_filename=None,
completed_frames=1,
total_frames=1,
)
]
out = BytesIO()
@ -157,20 +159,20 @@ class TestFileWebp:
expected = []
for i in range(42):
expected.append(
{
"image_index": 0,
"image_filename": "iss634.webp",
"completed_frames": i + 1,
"total_frames": 43,
}
Image.Progress(
image_index=0,
image_filename="iss634.webp",
completed_frames=i + 1,
total_frames=43,
)
)
expected.append(
{
"image_index": 1,
"image_filename": None,
"completed_frames": 43,
"total_frames": 43,
}
Image.Progress(
image_index=1,
image_filename=None,
completed_frames=43,
total_frames=43,
)
)
assert progress == expected

View File

@ -40,7 +40,7 @@ import tempfile
import warnings
from collections.abc import MutableMapping
from enum import IntEnum
from typing import IO, Protocol, cast
from typing import IO, NamedTuple, Protocol, cast
# VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0.
@ -80,6 +80,13 @@ class DecompressionBombError(Exception):
pass
class Progress(NamedTuple):
image_index: int
image_filename: str | None
completed_frames: int
total_frames: int
WARN_POSSIBLE_FORMATS: bool = False
# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
@ -2720,7 +2727,7 @@ class Image:
def _save_all_progress(
self,
progress,
progress: Callable[[Progress], None] | None,
im: Image | None = None,
im_index: int = 0,
completed: int = 1,
@ -2730,12 +2737,12 @@ class Image:
return
progress(
{
"image_index": im_index,
"image_filename": getattr(im or self, "filename", None),
"completed_frames": completed,
"total_frames": total,
}
Progress(
image_index=im_index,
image_filename=getattr(im or self, "filename", None),
completed_frames=completed,
total_frames=total,
)
)
def seek(self, frame: int) -> None: