From ec17dc11ba4496ae49841bd092feb70d30176baf Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Mon, 4 Dec 2023 07:49:08 -0600 Subject: [PATCH 1/2] Translate encoder error codes to strings When decoding, we use raise_oserror() to convert codec error codes to strings. Adapt that code to be used when encoding as well. Add a new helper function that returns the exception so we can still raise `from exc`. --- docs/releasenotes/10.2.0.rst | 6 ++++++ src/PIL/ImageFile.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst index bd06e09fc4e..bdcd93f69cb 100644 --- a/docs/releasenotes/10.2.0.rst +++ b/docs/releasenotes/10.2.0.rst @@ -77,3 +77,9 @@ Calculating the :py:attr:`~PIL.ImageStat.Stat.count` and :py:attr:`~PIL.ImageStat.Stat.extrema` statistics is now faster. After the histogram is created in ``st = ImageStat.Stat(im)``, ``st.count`` is 3x as fast on average and ``st.extrema`` is 12x as fast on average. + +Encoder errors now report error detail as string +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:exc:`OSError` exceptions from image encoders now include a textual description of +the error instead of a numeric error code. diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8bca19a4b8d..b16fb3c4921 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -63,15 +63,19 @@ # Helpers -def raise_oserror(error): +def _get_oserror(error, *, encoder): try: msg = Image.core.getcodecstatus(error) except AttributeError: msg = ERRORS.get(error) if not msg: - msg = f"decoder error {error}" - msg += " when reading image file" - raise OSError(msg) + msg = f"{'encoder' if encoder else 'decoder'} error {error}" + msg += f" when {'writing' if encoder else 'reading'} image file" + return OSError(msg) + + +def raise_oserror(error): + raise _get_oserror(error, encoder=False) def _tilesort(t): @@ -551,8 +555,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): # slight speedup: compress to real file object errcode = encoder.encode_to_file(fh, bufsize) if errcode < 0: - msg = f"encoder error {errcode} when writing image file" - raise OSError(msg) from exc + raise _get_oserror(errcode, encoder=True) from exc finally: encoder.cleanup() From e1fb1ab5c4a52be178bd389adcf925428a72cebb Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Sun, 10 Dec 2023 15:32:27 -0600 Subject: [PATCH 2/2] Deprecate raise_oserror() for removal in Pillow 12 It's only useful if the caller has an IMAGING_CODEC_* error code, which are only produced by codec decode() methods and are automatically translated by ImageFile. Co-authored-by: Andrew Murray --- Tests/test_imagefile.py | 5 +++-- docs/deprecations.rst | 10 ++++++++++ docs/releasenotes/10.2.0.rst | 8 ++++++++ src/PIL/ImageFile.py | 11 +++++++++-- src/PIL/_deprecate.py | 2 ++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index ff75b8c2a69..2389c471748 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -115,8 +115,9 @@ def test_safeblock(self): assert_image_equal(im1, im2) def test_raise_oserror(self): - with pytest.raises(OSError): - ImageFile.raise_oserror(1) + with pytest.warns(DeprecationWarning): + with pytest.raises(OSError): + ImageFile.raise_oserror(1) def test_raise_typeerror(self): with pytest.raises(TypeError): diff --git a/docs/deprecations.rst b/docs/deprecations.rst index b4fbb8d5053..75c0b73ebed 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -34,6 +34,16 @@ Since Pillow's C API is now faster than PyAccess on PyPy, ``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is similarly deprecated. +ImageFile.raise_oserror +~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 10.2.0 + +``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow +12.0.0 (2025-10-15). The function is undocumented and is only useful for translating +error codes returned by a codec's ``decode()`` method, which ImageFile already does +automatically. + Removed features ---------------- diff --git a/docs/releasenotes/10.2.0.rst b/docs/releasenotes/10.2.0.rst index bdcd93f69cb..9883f10baf3 100644 --- a/docs/releasenotes/10.2.0.rst +++ b/docs/releasenotes/10.2.0.rst @@ -12,6 +12,14 @@ TODO Deprecations ============ +ImageFile.raise_oserror +^^^^^^^^^^^^^^^^^^^^^^^ + +``ImageFile.raise_oserror()`` has been deprecated and will be removed in Pillow +12.0.0 (2025-10-15). The function is undocumented and is only useful for translating +error codes returned by a codec's ``decode()`` method, which ImageFile already does +automatically. + TODO ^^^^ diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index b16fb3c4921..1bc11af0f26 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -35,6 +35,7 @@ from typing import NamedTuple from . import Image +from ._deprecate import deprecate from ._util import is_path MAXBLOCK = 65536 @@ -75,6 +76,12 @@ def _get_oserror(error, *, encoder): def raise_oserror(error): + deprecate( + "raise_oserror", + 12, + action="It is only useful for translating error codes returned by a codec's " + "decode() method, which ImageFile already does automatically.", + ) raise _get_oserror(error, encoder=False) @@ -298,7 +305,7 @@ def load(self): if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0: # still raised if decoder fails to return anything - raise_oserror(err_code) + raise _get_oserror(err_code, encoder=False) return Image.Image.load(self) @@ -425,7 +432,7 @@ def feed(self, data): if e < 0: # decoding error self.image = None - raise_oserror(e) + raise _get_oserror(e, encoder=False) else: # end of image return diff --git a/src/PIL/_deprecate.py b/src/PIL/_deprecate.py index 2f2a3df13e3..33a0e07b3f4 100644 --- a/src/PIL/_deprecate.py +++ b/src/PIL/_deprecate.py @@ -47,6 +47,8 @@ def deprecate( raise RuntimeError(msg) elif when == 11: removed = "Pillow 11 (2024-10-15)" + elif when == 12: + removed = "Pillow 12 (2025-10-15)" else: msg = f"Unknown removal version: {when}. Update {__name__}?" raise ValueError(msg)