From ab75ef66299e7f9cd232d3b6b9335ce2bbb964f8 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Tue, 3 Jun 2025 12:00:05 +0100 Subject: [PATCH 1/5] Add typing information to ImageCmsProfile, and deprecate product_name/product_info The _set method is no longer necessary, since we no longer compute any attributes from the profile. In most cases, we only set the profile, and in only one branch do we set the filename to anything non-None. product_name/product_info were set to None at some point during what appears to be a batch of changes for Python 3 compatibility (ce041fd1995fe95de86804c83d100ed7d8ecdb3b), and never set back. Given this, let's deprecate these and schedule them for removal in Pillow 13. --- docs/deprecations.rst | 23 +++++++++++++++++++++++ src/PIL/ImageCms.py | 34 ++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 0490ba439..29a7a62c2 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -193,6 +193,29 @@ Image.Image.get_child_images() method uses an image's file pointer, and so child images could only be retrieved from an :py:class:`PIL.ImageFile.ImageFile` instance. +ImageCms.ImageCmsProfile._set +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.3.0 + +``ImageCms.ImageCmsProfile._set()`` has been deprecated, and will be removed in +Pillow 13 (2026-10-15). You should construct a new ``ImageCmsProfile`` instance +instead. + +ImageCms.ImageCmsProfile.product_name and .product_info +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.3.0 + +``ImageCms.ImageCmsProfile.product_name`` and the corresponding +``.product_info`` attributes have been deprecated, and will be removed in +Pillow 13 (2026-10-15). These attributes can be accessed on the ``.profile`` +attribute of ``ImageCmsProfile`` instead. + +Note that ``.product_name`` and ``.product_info`` have been set to ``None`` on +``ImageCmsProfile`` since Pillow 2.3.0 (2014-01-01), so any working code that +makes use of this data will already access it on ``.profile``. + Removed features ---------------- diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index fdfbee789..d9a47aa9b 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -241,6 +241,9 @@ _FLAGS = { class ImageCmsProfile: + profile: core.CmsProfile + filename: str | None + def __init__(self, profile: str | SupportsRead[bytes] | core.CmsProfile) -> None: """ :param profile: Either a string representing a filename, @@ -248,6 +251,7 @@ class ImageCmsProfile: low-level profile object """ + self.filename = None if isinstance(profile, str): if sys.platform == "win32": @@ -256,22 +260,40 @@ class ImageCmsProfile: profile_bytes_path.decode("ascii") except UnicodeDecodeError: with open(profile, "rb") as f: - self._set(core.profile_frombytes(f.read())) + self.profile = core.profile_frombytes(f.read()) return - self._set(core.profile_open(profile), profile) + self.filename = profile + self.profile = core.profile_open(profile) elif hasattr(profile, "read"): - self._set(core.profile_frombytes(profile.read())) + self.profile = core.profile_frombytes(profile.read()) elif isinstance(profile, core.CmsProfile): - self._set(profile) + self.profile = profile else: msg = "Invalid type for Profile" # type: ignore[unreachable] raise TypeError(msg) + def __getattr__(self, attr: str) -> Any: + if attr in ("product_name", "product_info"): + deprecate( + f"ImageCms.ImageCmsProfile.{attr}", + 13, + action=( + f"Use ImageCms.ImageCmsProfile.profile.{attr} instead. " + f"Note that {attr} has been set to 'None' since Pillow 2.3.0." + ), + ) + return None + msg = f"'{self.__class__.__name__}' has no attribute '{attr}'" + raise AttributeError(msg) + def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None: + deprecate( + "ImageCmsProfile._set", + 13, + action="Set the 'profile' and 'filename' attributes directly instead.", + ) self.profile = profile self.filename = filename - self.product_name = None # profile.product_name - self.product_info = None # profile.product_info def tobytes(self) -> bytes: """ From f66be01bf6ade2603190486b6fb8f1b8d2efbeb2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 3 Jun 2025 22:36:00 +1000 Subject: [PATCH 2/5] Added tests --- Tests/test_imagecms.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index f062651f0..415b09427 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -710,8 +710,16 @@ def test_deprecation() -> None: with pytest.warns(DeprecationWarning): assert isinstance(ImageCms.FLAGS, dict) - profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) + p = ImageCms.createProfile("sRGB") + profile = ImageCmsProfile(p) with pytest.warns(DeprecationWarning): ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB") with pytest.warns(DeprecationWarning): ImageCms.ImageCmsTransform(profile, profile, "RGB", "RGBA;16B") + + with pytest.warns(DeprecationWarning): + profile.product_name + with pytest.warns(DeprecationWarning): + profile.product_info + with pytest.warns(DeprecationWarning): + profile._set(p) From 54421b5f5601ba9ec4974f6b9543a3814933b530 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Tue, 3 Jun 2025 17:41:07 +0100 Subject: [PATCH 3/5] Check that ImageCmsProfile.__getattr__ raises AttributeError --- Tests/test_imagecms.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 415b09427..11c946291 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -723,3 +723,5 @@ def test_deprecation() -> None: profile.product_info with pytest.warns(DeprecationWarning): profile._set(p) + with pytest.raises(AttributeError): + profile.this_attribute_does_not_exist From f76f64a187f12f5bd99f8eafc92a9994afb83580 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 14 Jun 2025 14:32:39 +1000 Subject: [PATCH 4/5] Renamed parameter --- src/PIL/ImageCms.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index d9a47aa9b..cc1863995 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -272,18 +272,18 @@ class ImageCmsProfile: msg = "Invalid type for Profile" # type: ignore[unreachable] raise TypeError(msg) - def __getattr__(self, attr: str) -> Any: - if attr in ("product_name", "product_info"): + def __getattr__(self, name: str) -> Any: + if name in ("product_name", "product_info"): deprecate( - f"ImageCms.ImageCmsProfile.{attr}", + f"ImageCms.ImageCmsProfile.{name}", 13, action=( - f"Use ImageCms.ImageCmsProfile.profile.{attr} instead. " - f"Note that {attr} has been set to 'None' since Pillow 2.3.0." + f"Use ImageCms.ImageCmsProfile.profile.{name} instead. " + f"Note that {name} has been set to 'None' since Pillow 2.3.0." ), ) return None - msg = f"'{self.__class__.__name__}' has no attribute '{attr}'" + msg = f"'{self.__class__.__name__}' has no attribute '{name}'" raise AttributeError(msg) def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None: From 7713df961dd37a548dc976ed2bc1602d316b51c0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 16 Jun 2025 18:41:00 +1000 Subject: [PATCH 5/5] Removed ImageCms._set --- Tests/test_imagecms.py | 5 +---- docs/deprecations.rst | 9 --------- src/PIL/ImageCms.py | 9 --------- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 11c946291..9ee2c71ec 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -710,8 +710,7 @@ def test_deprecation() -> None: with pytest.warns(DeprecationWarning): assert isinstance(ImageCms.FLAGS, dict) - p = ImageCms.createProfile("sRGB") - profile = ImageCmsProfile(p) + profile = ImageCmsProfile(ImageCms.createProfile("sRGB")) with pytest.warns(DeprecationWarning): ImageCms.ImageCmsTransform(profile, profile, "RGBA;16B", "RGB") with pytest.warns(DeprecationWarning): @@ -721,7 +720,5 @@ def test_deprecation() -> None: profile.product_name with pytest.warns(DeprecationWarning): profile.product_info - with pytest.warns(DeprecationWarning): - profile._set(p) with pytest.raises(AttributeError): profile.this_attribute_does_not_exist diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 29a7a62c2..2346a3bdc 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -193,15 +193,6 @@ Image.Image.get_child_images() method uses an image's file pointer, and so child images could only be retrieved from an :py:class:`PIL.ImageFile.ImageFile` instance. -ImageCms.ImageCmsProfile._set -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. deprecated:: 11.3.0 - -``ImageCms.ImageCmsProfile._set()`` has been deprecated, and will be removed in -Pillow 13 (2026-10-15). You should construct a new ``ImageCmsProfile`` instance -instead. - ImageCms.ImageCmsProfile.product_name and .product_info ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index cc1863995..22cf6ce7a 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -286,15 +286,6 @@ class ImageCmsProfile: msg = f"'{self.__class__.__name__}' has no attribute '{name}'" raise AttributeError(msg) - def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None: - deprecate( - "ImageCmsProfile._set", - 13, - action="Set the 'profile' and 'filename' attributes directly instead.", - ) - self.profile = profile - self.filename = filename - def tobytes(self) -> bytes: """ Returns the profile in a format suitable for embedding in