From 5f87e63687041f906552bbee928314488a579b46 Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Fri, 20 Feb 2026 07:02:16 +0300 Subject: [PATCH 1/3] Only close file handle in ImagePalette.save() when we opened it --- src/PIL/ImagePalette.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index eae7aea8fc3..602ca97fbbf 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -191,19 +191,24 @@ def save(self, fp: str | IO[str]) -> None: if self.rawmode: msg = "palette contains raw palette data" raise ValueError(msg) + close_fp = False if isinstance(fp, str): fp = open(fp, "w") - fp.write("# Palette\n") - fp.write(f"# Mode: {self.mode}\n") - for i in range(256): - fp.write(f"{i}") - for j in range(i * len(self.mode), (i + 1) * len(self.mode)): - try: - fp.write(f" {self.palette[j]}") - except IndexError: - fp.write(" 0") - fp.write("\n") - fp.close() + close_fp = True + try: + fp.write("# Palette\n") + fp.write(f"# Mode: {self.mode}\n") + for i in range(256): + fp.write(f"{i}") + for j in range(i * len(self.mode), (i + 1) * len(self.mode)): + try: + fp.write(f" {self.palette[j]}") + except IndexError: + fp.write(" 0") + fp.write("\n") + finally: + if close_fp: + fp.close() # -------------------------------------------------------------------- From 519b15af0b70a3857ae016691de6483ec99a8bd6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 20 Feb 2026 18:21:02 +1100 Subject: [PATCH 2/3] Rename variable to open_fp --- src/PIL/ImagePalette.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 602ca97fbbf..99ad2771b4b 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -191,10 +191,10 @@ def save(self, fp: str | IO[str]) -> None: if self.rawmode: msg = "palette contains raw palette data" raise ValueError(msg) - close_fp = False + open_fp = False if isinstance(fp, str): fp = open(fp, "w") - close_fp = True + open_fp = True try: fp.write("# Palette\n") fp.write(f"# Mode: {self.mode}\n") @@ -207,7 +207,7 @@ def save(self, fp: str | IO[str]) -> None: fp.write(" 0") fp.write("\n") finally: - if close_fp: + if open_fp: fp.close() From 585e6df0c016f697a0d039f8cf550a3698e08a33 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 20 Feb 2026 18:26:03 +1100 Subject: [PATCH 3/3] Added test --- Tests/test_imagepalette.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Tests/test_imagepalette.py b/Tests/test_imagepalette.py index 10b89a2c0c2..526beb656e8 100644 --- a/Tests/test_imagepalette.py +++ b/Tests/test_imagepalette.py @@ -1,6 +1,6 @@ from __future__ import annotations -from io import BytesIO +import io from pathlib import Path import pytest @@ -23,6 +23,13 @@ def test_reload() -> None: assert_image_equal(im.convert("RGB"), original.convert("RGB")) +def test_save_fp() -> None: + palette = ImagePalette.ImagePalette() + with io.StringIO() as fp: + palette.save(fp) + assert not fp.closed + + def test_getcolor() -> None: palette = ImagePalette.ImagePalette() assert len(palette.palette) == 0 @@ -204,7 +211,7 @@ def test_2bit_palette(tmp_path: Path) -> None: def test_getpalette() -> None: - b = BytesIO(b"0 1\n1 2 3 4") + b = io.BytesIO(b"0 1\n1 2 3 4") p = PaletteFile.PaletteFile(b) palette, rawmode = p.getpalette() @@ -216,6 +223,6 @@ def test_invalid_palette() -> None: with pytest.raises(OSError): ImagePalette.load("Tests/images/hopper.jpg") - b = BytesIO(b"1" * 101) + b = io.BytesIO(b"1" * 101) with pytest.raises(SyntaxError, match="bad palette file"): PaletteFile.PaletteFile(b)