From ca74a5ea42cff8985e0350536a916e3a454c82d1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Wed, 1 Nov 2023 20:18:25 +0200 Subject: [PATCH 1/8] Docs: link exceptions to Python docs --- docs/deprecations.rst | 22 +++++++++++----------- docs/reference/ImageFont.rst | 4 ++-- docs/releasenotes/10.0.0.rst | 2 +- docs/releasenotes/10.1.0.rst | 2 +- docs/releasenotes/2.8.0.rst | 2 +- docs/releasenotes/3.4.0.rst | 2 +- docs/releasenotes/5.3.0.rst | 5 +++-- docs/releasenotes/5.4.1.rst | 4 ++-- docs/releasenotes/6.0.0.rst | 2 +- docs/releasenotes/6.1.0.rst | 2 +- docs/releasenotes/7.0.0.rst | 2 +- docs/releasenotes/7.1.2.rst | 2 +- docs/releasenotes/7.2.0.rst | 2 +- docs/releasenotes/8.0.0.rst | 2 +- docs/releasenotes/9.0.0.rst | 2 +- docs/releasenotes/9.1.0.rst | 6 +++--- 16 files changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ffde4d45c..b4fbb8d50 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -10,7 +10,7 @@ Deprecated features ------------------- Below are features which are considered deprecated. Where appropriate, -a ``DeprecationWarning`` is issued. +a :py:exc:`DeprecationWarning` is issued. PSFile ~~~~~~ @@ -267,7 +267,7 @@ ImageFile.raise_ioerror .. deprecated:: 7.2.0 .. versionremoved:: 9.0.0 -``IOError`` was merged into ``OSError`` in Python 3.3. +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. @@ -293,9 +293,9 @@ im.offset ``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. It was documented as deprecated in PIL 1.1.2, -raised a ``DeprecationWarning`` since 1.1.5, -an ``Exception`` since Pillow 3.0.0 -and ``NotImplementedError`` since 3.3.0. +raised a :py:exc:`DeprecationWarning` since 1.1.5, +an :py:exc:`Exception` since Pillow 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. Image.fromstring, im.fromstring and im.tostring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring * ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. * ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. -They issued a ``DeprecationWarning`` since 2.0.0, -an ``Exception`` since 3.0.0 -and ``NotImplementedError`` since 3.3.0. +They issued a :py:exc:`DeprecationWarning` since 2.0.0, +an :py:exc:`Exception` since 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes .. versionremoved:: 8.0.0 Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, -they issued a ``DeprecationWarning``: +they issued a :py:exc:`DeprecationWarning`: ======================== =================================================== Removed Use instead @@ -442,7 +442,7 @@ PIL.OleFileIO .. deprecated:: 4.0.0 .. versionremoved:: 6.0.0 -PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of -the upstream :pypi:`olefile` Python package, and replaced with an ``ImportError`` in 5.0.0 +``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of +the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index e15aed9fc..a944a13fa 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -20,7 +20,7 @@ the imToolkit package. .. warning:: To protect against potential DOS attacks when using arbitrary strings as - text input, Pillow will raise a ``ValueError`` if the number of characters + text input, Pillow will raise a :py:exc:`ValueError` if the number of characters is over a certain limit, :py:data:`MAX_STRING_LENGTH`. This threshold can be changed by setting @@ -89,5 +89,5 @@ Constants .. data:: MAX_STRING_LENGTH Set to 1,000,000, to protect against potential DOS attacks. Pillow will - raise a ``ValueError`` if the number of characters is over this limit. The + raise a :py:exc:`ValueError` if the number of characters is over this limit. The check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 06acfc7af..1f46e1472 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -174,7 +174,7 @@ Added ImageFont.MAX_STRING_LENGTH ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To protect against potential DOS attacks when using arbitrary strings as text -input, Pillow will now raise a ``ValueError`` if the number of characters +input, Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into ImageFont methods is over a certain limit, :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst index 8c3413c8c..fd556bdf1 100644 --- a/docs/releasenotes/10.1.0.rst +++ b/docs/releasenotes/10.1.0.rst @@ -8,7 +8,7 @@ Setting image mode ^^^^^^^^^^^^^^^^^^ If you attempt to set the mode of an image directly, e.g. -``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is +``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``convert`` method is the correct way to change an image's mode. diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst index c522fe8b0..4dbbc0bdd 100644 --- a/docs/releasenotes/2.8.0.rst +++ b/docs/releasenotes/2.8.0.rst @@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap ``cStringIO`` or ``BytesIO``. Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and -catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we +catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like objects). diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index dc5e2e295..2bbafe741 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 silently drops the alpha channel. With this release Pillow will now -issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode +issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode image as a JPEG. This will become an error in Pillow 4.2. New DDS Decoders diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index bff56566b..8f276da24 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -8,7 +8,7 @@ Image size ^^^^^^^^^^ If you attempt to set the size of an image directly, e.g. -``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is +``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``resize`` method is the correct way to change an image's size. @@ -16,7 +16,8 @@ correct way to change an image's size. The exceptions to this are: * The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage. -* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents. +* The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action, + as direct image size setting was previously necessary to work around an issue with tile extents. API Additions diff --git a/docs/releasenotes/5.4.1.rst b/docs/releasenotes/5.4.1.rst index 78f483db6..bbabd6520 100644 --- a/docs/releasenotes/5.4.1.rst +++ b/docs/releasenotes/5.4.1.rst @@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop reading image data before the IDAT chunks finish. A regression caused an -``EOFError`` exception when previously there was none. This is now fixed, and +:py:exc:`EOFError` exception when previously there was none. This is now fixed, and file reading continues in case there are subsequent text chunks. PNG: MIME type @@ -30,7 +30,7 @@ File closing ^^^^^^^^^^^^ A regression caused an unsupported image file to report a -``ValueError: seek of closed file`` exception instead of an ``OSError``. This +``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This has been fixed by ensuring that image plugins only close their internal ``__fp`` if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own file pointers. diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 70a52b58e..5e69f0b6b 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From -6.0.0, they issue a ``DeprecationWarning``: +6.0.0, they issue a :py:exc:`DeprecationWarning`: ======================== =============================== Deprecated Use instead diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst index e7c593e6e..ce3edc5fa 100644 --- a/docs/releasenotes/6.1.0.rst +++ b/docs/releasenotes/6.1.0.rst @@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods, :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and :py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes -instead. An ``IOError`` will be raised if the font is not a variation font. FreeType +instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType 2.9.1 or greater is required. Other Changes diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index f2e235289..ed6026593 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -85,7 +85,7 @@ Custom unidentified image error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be -identified. For backwards compatibility, this will inherit from ``OSError``. +identified. For backwards compatibility, this will inherit from :py:exc:`OSError`. New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst index b12d84e33..ec0063e79 100644 --- a/docs/releasenotes/7.1.2.rst +++ b/docs/releasenotes/7.1.2.rst @@ -7,7 +7,7 @@ Fix another regression seeking PNG files This fixes a regression introduced in 7.1.0 when adding support for APNG files. When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an -``EOFError`` as it should have done, resulting in: +:py:exc:`EOFError` as it should have done, resulting in: .. code-block:: pycon diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index ff1b7c9e7..91e54da19 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -53,6 +53,6 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` is now deprecated and will be removed in a future release. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst index 00c691a74..2bf299dd3 100644 --- a/docs/releasenotes/8.0.0.rst +++ b/docs/releasenotes/8.0.0.rst @@ -168,7 +168,7 @@ offset. Error for large BMP files ^^^^^^^^^^^^^^^^^^^^^^^^^ -Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, +Previously, if a BMP file was too large, an :py:exc:`OSError` would be raised. Now, ``DecompressionBombError`` is used instead, as Pillow already uses for other formats. Dark theme for docs diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 73e77ad3e..090ec8024 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -63,7 +63,7 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ^^^^^^^^^^^^^^^^^^^^^^^ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 19690ca59..02da702a7 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -8,14 +8,14 @@ Raise an error when performing a negative crop ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now -it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally +it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally provided the wrong arguments. Added specific error if path coordinate type is incorrect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Rather than returning a ``SystemError``, passing the incorrect types of coordinates into -a path will now raise a more specific ``ValueError``, with the message "incorrect +Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into +a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect coordinate type". Replace requirements.txt with extras From c29648ff53c168c856953ac9883742cf4ed79eb2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 3 Nov 2023 22:08:48 +1100 Subject: [PATCH 2/8] If save_all PNG only has one frame, do not create animated image --- Tests/test_file_apng.py | 42 +++++++++++++++++++++------------------ src/PIL/PngImagePlugin.py | 9 +++++++-- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index fffbc54ca..d0c81b5e9 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -350,7 +350,7 @@ def test_apng_save(tmp_path): im.load() assert not im.is_animated assert im.n_frames == 1 - assert im.get_format_mimetype() == "image/apng" + assert im.get_format_mimetype() == "image/png" assert im.info.get("default_image") is None assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) @@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path): test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] ) with Image.open(test_file) as im: - im.load() assert im.n_frames == 1 - assert im.info.get("duration") == 750 + assert "duration" not in im.info + + different_frame = Image.new("RGBA", (128, 64)) + frame.save( + test_file, + save_all=True, + append_images=[frame, different_frame], + duration=[500, 100, 150], + ) + with Image.open(test_file) as im: + assert im.n_frames == 2 + assert im.info["duration"] == 600 + + im.seek(1) + assert im.info["duration"] == 150 # test info duration - frame.info["duration"] = 750 - frame.save(test_file, save_all=True) + frame.info["duration"] = 300 + frame.save(test_file, save_all=True, append_images=[frame, different_frame]) with Image.open(test_file) as im: - assert im.info.get("duration") == 750 - - -def test_apng_save_duplicate_duration(tmp_path): - test_file = str(tmp_path / "temp.png") - frame = Image.new("RGB", (1, 1)) - - # Test a single duration is correctly combined across duplicate frames - frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500) - with Image.open(test_file) as im: - assert im.n_frames == 1 - assert im.info.get("duration") == 1500 + assert im.n_frames == 2 + assert im.info["duration"] == 600 def test_apng_save_disposal(tmp_path): @@ -674,7 +677,8 @@ def test_seek_after_close(): @pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) @pytest.mark.parametrize("default_image", (True, False)) -def test_different_modes_in_later_frames(mode, default_image, tmp_path): +@pytest.mark.parametrize("duplicate", (True, False)) +def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path): test_file = str(tmp_path / "temp.png") im = Image.new("L", (1, 1)) @@ -682,7 +686,7 @@ def test_different_modes_in_later_frames(mode, default_image, tmp_path): test_file, save_all=True, default_image=default_image, - append_images=[Image.new(mode, (1, 1))], + append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)], ) with Image.open(test_file) as reloaded: assert reloaded.mode == mode diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1bd0f442f..dbcdee1c2 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1156,6 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if len(im_frames) == 1 and not default_image: + return im_frames[0]["im"] + # animation control chunk( fp, @@ -1391,8 +1394,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) - else: + im = _write_multiple_frames( + im, fp, chunk, rawmode, default_image, append_images + ) + if im: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) if info: From 8cbd0b5fe09725e57f0253290f894877e408552d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 5 Nov 2023 12:18:38 +1100 Subject: [PATCH 3/8] Docs: link exceptions to Python docs --- docs/releasenotes/8.3.1.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst index e97070c11..6af2b37bf 100644 --- a/docs/releasenotes/8.3.1.rst +++ b/docs/releasenotes/8.3.1.rst @@ -22,9 +22,10 @@ Catch OSError when checking if destination is sys.stdout ======================================================== In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was -updated. This lead to an OSError being raised if the environment restricted access. +updated. This lead to an :py:exc:`OSError` being raised if the environment restricted +access. -The OSError is now silently caught. +The :py:exc:`OSError` is now silently caught. Fixed removing orientation in ImageOps.exif_transpose ===================================================== @@ -34,7 +35,7 @@ original image EXIF data was not modified, and the orientation was only removed the modified copy. However, for certain images the orientation was already missing from the modified -image, leading to a KeyError. +image, leading to a :py:exc:`KeyError`. This error has been resolved, and the copying of metadata to the modified image improved. From 80406a7c9b4956250025b7b5438a9a5d1ef44f0a Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 30 Oct 2023 19:53:20 +0200 Subject: [PATCH 4/8] editorconfig: specify 2-space indent for TOML Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d74549fe2..c3627ae4f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ indent_style = space trim_trailing_whitespace = true -[*.yml] +[*.{toml,yml}] # Two-space indentation indent_size = 2 From de6c4b09d72b321e916dee9089eec425a9c827bc Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 23 Feb 2023 14:02:58 +0200 Subject: [PATCH 5/8] Switch linting to ruff Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> Co-authored-by: Hugo van Kemenade --- .flake8 | 3 --- .pre-commit-config.yaml | 26 ++++---------------------- MANIFEST.in | 1 + Makefile | 6 +++--- pyproject.toml | 30 ++++++++++++++++++++++++++++-- 5 files changed, 36 insertions(+), 30 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e19c0a585..000000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -extend-ignore = E203 -max-line-length = 88 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8c7696df..5812e726b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,9 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.4 hooks: - - id: pyupgrade - args: [--py38-plus] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.9.1 @@ -11,11 +11,6 @@ repos: - id: black args: [--target-version=py38] - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/PyCQA/bandit rev: 1.7.5 hooks: @@ -23,28 +18,15 @@ repos: args: [--severity-level=high] files: ^src/ - - repo: https://github.com/asottile/yesqa - rev: v1.5.0 - hooks: - - id: yesqa - - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - [flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging] - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/MANIFEST.in b/MANIFEST.in index 9401ebbbf..af25dfd2d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.md include *.py include *.rst include *.sh +include *.toml include *.txt include *.yaml include .flake8 diff --git a/Makefile b/Makefile index 57d756b47..b7f07e24d 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ help: @echo " install make and install" @echo " install-coverage make and install with C coverage" @echo " lint run the lint checks" - @echo " lint-fix run Black and isort to (mostly) fix lint issues" + @echo " lint-fix run Ruff to (mostly) fix lint issues" @echo " release-test run code and package tests before release" @echo " test run tests on installed Pillow" @@ -118,6 +118,6 @@ lint: .PHONY: lint-fix lint-fix: python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black - python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort python3 -m black --target-version py38 . - python3 -m isort . + python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff + python3 -m ruff --fix . diff --git a/pyproject.toml b/pyproject.toml index 6f6ed6e93..59d8da44e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,8 +77,34 @@ package-dir = {"" = "src"} [tool.setuptools.dynamic] version = {attr = "PIL.__version__"} -[tool.isort] -profile = "black" +[tool.ruff] +target-version = "py38" +line-length = 88 +select = [ + "E", # pycodestyle errors + "EM", # flake8-errmsg + "F", # pyflakes errors + "I", # isort + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "RUF100", # unused noqa (yesqa) + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 + # "LOG", # TODO: enable flake8-logging when it's not in preview anymore +] +extend-ignore = [ + "E203", # Whitespace before ':' + "E221", # Multiple spaces before operator + "E226", # Missing whitespace around arithmetic operator + "E241", # Multiple spaces after ',' +] + +[tool.ruff.per-file-ignores] +"Tests/*.py" = ["I001"] + +[tool.ruff.isort] +known-first-party = ["PIL"] [tool.pytest.ini_options] addopts = "-ra --color=yes" From 307d00b44de2c6f7ff0565843b848b2b0dea0f62 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Thu, 23 Feb 2023 15:45:11 +0200 Subject: [PATCH 6/8] Apply ruff autofixes --- _custom_build/backend.py | 2 +- docs/conf.py | 14 +++++++------- src/PIL/ImageFilter.py | 2 +- src/PIL/IptcImagePlugin.py | 3 +-- src/PIL/PyAccess.py | 4 ++-- winbuild/build_prepare.py | 4 ++-- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/_custom_build/backend.py b/_custom_build/backend.py index 9b3265a94..23225d6b8 100644 --- a/_custom_build/backend.py +++ b/_custom_build/backend.py @@ -1,6 +1,6 @@ import sys -from setuptools.build_meta import * # noqa: F401, F403 +from setuptools.build_meta import * # noqa: F403 from setuptools.build_meta import build_wheel backend_class = build_wheel.__self__.__class__ diff --git a/docs/conf.py b/docs/conf.py index fdcda3a7c..ef2cb5b88 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -318,14 +318,14 @@ def setup(app): linkcheck_allowed_redirects = { - r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501 - r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501 - r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501 - r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501 + r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", + r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", + r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", + r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", - r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501 - r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501 - r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501 + r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", + r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", + r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", } # sphinx.ext.extlinks diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 57268b8f5..c24f86ef3 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -222,7 +222,7 @@ class UnsharpMask(MultibandFilter): .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking - """ # noqa: E501 + """ name = "UnsharpMask" diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 316cd17c7..3a40cf987 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,10 +18,9 @@ import os import tempfile from . import Image, ImageFile -from ._binary import i8 +from ._binary import i8, o8 from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 99b46a4a6..24d30d2a6 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -244,7 +244,7 @@ class _PyAccessI16_L(PyAccess): except TypeError: color = min(color[0], 65535) - pixel.l = color & 0xFF # noqa: E741 + pixel.l = color & 0xFF pixel.r = color >> 8 @@ -265,7 +265,7 @@ class _PyAccessI16_B(PyAccess): except Exception: color = min(color[0], 65535) - pixel.l = color >> 8 # noqa: E741 + pixel.l = color >> 8 pixel.r = color & 0xFF diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a1f1755be..4c47db1fb 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -239,7 +239,7 @@ DEPS = { "libs": ["*.lib"], }, "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", # noqa: E501 + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", "filename": "freetype-2.13.2.tar.gz", "dir": "freetype-2.13.2", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], @@ -321,7 +321,7 @@ DEPS = { }, "libimagequant": { # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) - "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 + "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "license": "COPYRIGHT", From 9e615b6ad38d1f20e0ad61ed9334f224a4030ce4 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 30 Oct 2023 20:04:44 +0200 Subject: [PATCH 7/8] Add noqas for UP031 --- Tests/bench_cffi_access.py | 2 +- src/PIL/EpsImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/Image.py | 2 +- src/PIL/PdfParser.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 69ebef9b4..d94b1985b 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -45,7 +45,7 @@ def test_direct(): assert caccess[(0, 0)] == access[(0, 0)] - print("Size: %sx%s" % im.size) + print("Size: %sx%s" % im.size) # noqa: UP031 timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_get, "C-api - get", im.size, caccess) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 9b2fce0ac..63369eb64 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -122,7 +122,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): gs_binary, "-q", # quiet mode "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) + "-r%fx%f" % res, # set input DPI (dots per inch) # noqa: UP031 "-dBATCH", # exit after processing "-dNOPAUSE", # don't pause between pages "-dSAFER", # safe mode diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 0aa4f7a84..5226c986d 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -392,7 +392,7 @@ if __name__ == "__main__": imf = IcnsImageFile(fp) for size in imf.info["sizes"]: imf.size = size - imf.save("out-%s-%s-%s.png" % size) + imf.save("out-%s-%s-%s.png" % size) # noqa: UP031 with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/src/PIL/Image.py b/src/PIL/Image.py index cb092f1ae..930ca060b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3100,7 +3100,7 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - msg = "Cannot handle this data type: %s, %s" % typekey + msg = "Cannot handle this data type: %s, %s" % typekey # noqa: UP031 raise TypeError(msg) from e else: rawmode = mode diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index dc1012f54..07df577ae 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -82,7 +82,7 @@ class IndirectReference( collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) ): def __str__(self): - return "%s %s R" % self + return "%s %s R" % self # noqa: UP031 def __bytes__(self): return self.__str__().encode("us-ascii") @@ -103,7 +103,7 @@ class IndirectReference( class IndirectObjectDef(IndirectReference): def __str__(self): - return "%s %s obj" % self + return "%s %s obj" % self # noqa: UP031 class XrefTable: From f8c36bfdc940bb5ae9f23dcbd9150464db5103d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 17:18:28 +0000 Subject: [PATCH 8/8] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black-pre-commit-mirror: 23.9.1 → 23.10.1](https://github.com/psf/black-pre-commit-mirror/compare/23.9.1...23.10.1) - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/sphinx-contrib/sphinx-lint: v0.6.8 → v0.8.1](https://github.com/sphinx-contrib/sphinx-lint/compare/v0.6.8...v0.8.1) - [github.com/tox-dev/pyproject-fmt: 1.2.0 → 1.4.1](https://github.com/tox-dev/pyproject-fmt/compare/1.2.0...1.4.1) - [github.com/abravalheri/validate-pyproject: v0.14 → v0.15](https://github.com/abravalheri/validate-pyproject/compare/v0.14...v0.15) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5812e726b..a6b1c6300 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black args: [--target-version=py38] @@ -30,7 +30,7 @@ repos: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-executables-have-shebangs - id: check-merge-conflict @@ -43,17 +43,17 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.6.8 + rev: v0.8.1 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.2.0 + rev: 1.4.1 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.15 hooks: - id: validate-pyproject