Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
except ImportError:
ElementTree = None


TEST_FILE = "Tests/images/hopper.jpg"


Expand Down
8 changes: 3 additions & 5 deletions Tests/test_qt_image_toqimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@
if TYPE_CHECKING:
from pathlib import Path

QImage = type
else:
if ImageQt.qt_is_installed:
from PIL.ImageQt import QImage

pytestmark = pytest.mark.skipif(
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
)

if ImageQt.qt_is_installed:
from PIL.ImageQt import QImage


@pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1"))
def test_sanity(mode: str, tmp_path: Path) -> None:
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from typing import IO, Literal

from . import _imaging
from ._typing import Buffer


class LoadingStrategy(IntEnum):
Expand Down Expand Up @@ -1187,7 +1188,7 @@ def getdata(
class Collector(BytesIO):
data = []

def write(self, data: bytes) -> int: # type: ignore[override]
def write(self, data: Buffer) -> int:
self.data.append(data)
return len(data)

Expand Down
3 changes: 1 addition & 2 deletions src/PIL/MspImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from . import Image, ImageFile
from ._binary import i16le as i16
from ._binary import o16le as o16
from ._typing import Buffer

#
# read MSP files
Expand Down Expand Up @@ -113,7 +112,7 @@ class MspDecoder(ImageFile.PyDecoder):

_pulls_fd = True

def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm strange, I thought this was an LSP violation 🤔. Maybe it's a mypy false negative? Either way, I'm fine with reverting this :)

assert self.fd is not None

img = io.BytesIO()
Expand Down
9 changes: 3 additions & 6 deletions src/PIL/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@
else:
CapsuleType = object

if TYPE_CHECKING:
from typing_extensions import Buffer
if sys.version_info >= (3, 12):
from collections.abc import Buffer
else:
if sys.version_info >= (3, 12):
from collections.abc import Buffer
else:
Buffer = Any
Comment on lines -24 to -30
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will ensure that Buffer annotations aren't Any on python<3.12. The reason this doesn't influence mypy's reports in CI is because the mypy job uses Python 3.12

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if you want we could just revert this. It's somewhat out of scope, after all.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I test this on Python 3.11, it still passes - https://github.com/radarhere/Pillow/actions/runs/21477253617/job/61864264358

Copy link
Copy Markdown
Owner

@jorenham jorenham Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yea of course, ignore my previous comment about Ci versions; Any is still valid. It's just (a lot) less type-safe than Buffer.

Buffer = Any
Comment on lines +24 to +27
Copy link
Copy Markdown
Owner

@jorenham jorenham Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I figured it out!

In pyproject.toml, mypy is configured to always run with python_version = "3.10", regardless of the enviroment.

So by undoing this change, mypy will interpret Buffer as Any. This will cause all the LSP violations (i.e. override errors) in the decode methods that use bytes instead of Buffer to not be picked up.

To illustrate: if you locally change the python_version in pyproject.toml to 3.12 or higher (or remove it if you're in a python>=3.12 env) and run tox -e mypy, you'll see the following:

mypy: commands[0]> mypy conftest.py selftest.py setup.py checks docs src winbuild Tests
src/PIL/PdfParser.py:329: error: Incompatible return value type (got "Buffer", expected "bytes")  [return-value]
                return self.buf
                       ^~~~~~~~
src/PIL/PpmImagePlugin.py:287: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/PpmImagePlugin.py:287: note: This violates the Liskov substitution principle
src/PIL/PpmImagePlugin.py:287: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/PpmImagePlugin.py:303: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/PpmImagePlugin.py:303: note: This violates the Liskov substitution principle
src/PIL/PpmImagePlugin.py:303: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/BmpImagePlugin.py:330: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/BmpImagePlugin.py:330: note: This violates the Liskov substitution principle
src/PIL/BmpImagePlugin.py:330: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/GifImagePlugin.py:1193: error: Argument 1 to "len" has incompatible type "Buffer"; expected "Sized"  [arg-type]
                return len(data)
                           ^~~~
src/PIL/GifImagePlugin.py:1201: error: Incompatible return value type (got "list[Buffer]", expected "list[bytes]")  [return-value]
        return fp.data
               ^~~~~~~
src/PIL/XpmImagePlugin.py:121: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/XpmImagePlugin.py:121: note: This violates the Liskov substitution principle
src/PIL/XpmImagePlugin.py:121: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/SgiImagePlugin.py:201: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/SgiImagePlugin.py:201: note: This violates the Liskov substitution principle
src/PIL/SgiImagePlugin.py:201: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/QoiImagePlugin.py:54: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/QoiImagePlugin.py:54: note: This violates the Liskov substitution principle
src/PIL/QoiImagePlugin.py:54: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/MspImagePlugin.py:115: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/MspImagePlugin.py:115: note: This violates the Liskov substitution principle
src/PIL/MspImagePlugin.py:115: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/FitsImagePlugin.py:129: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/FitsImagePlugin.py:129: note: This violates the Liskov substitution principle
src/PIL/FitsImagePlugin.py:129: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/DdsImagePlugin.py:491: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/DdsImagePlugin.py:491: note: This violates the Liskov substitution principle
src/PIL/DdsImagePlugin.py:491: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
src/PIL/BlpImagePlugin.py:298: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/BlpImagePlugin.py:298: note: This violates the Liskov substitution principle
src/PIL/BlpImagePlugin.py:298: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
docs/example/DdsImagePlugin.py:261: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
docs/example/DdsImagePlugin.py:261: note: This violates the Liskov substitution principle
docs/example/DdsImagePlugin.py:261: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
docs/example/DdsImagePlugin.py:274: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
docs/example/DdsImagePlugin.py:274: note: This violates the Liskov substitution principle
docs/example/DdsImagePlugin.py:274: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Tests/test_imagefile.py:241: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
        def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests/test_imagefile.py:241: note: This violates the Liskov substitution principle
Tests/test_imagefile.py:241: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Tests/test_file_jpeg.py:1067: error: Argument 1 of "decode" is incompatible with supertype "PIL.ImageFile.PyDecoder"; supertype defines the argument type as "Buffer | SupportsArrayInterface"  [override]
                    self, buffer: bytes | Image.SupportsArrayInterface
                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests/test_file_jpeg.py:1067: note: This violates the Liskov substitution principle
Tests/test_file_jpeg.py:1067: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Tests/test_image.py:205: error: Argument 1 to "len" has incompatible type "Buffer"; expected "Sized"  [arg-type]
                        return len(data)
                                   ^~~~
Found 18 errors in 15 files (checked 303 source files)
mypy: exit 1 (3.07 seconds) /home/joren/Workspace/Pillow> mypy conftest.py selftest.py setup.py checks docs src winbuild Tests pid=20145
  mypy: FAIL code 1 (3.07=setup[0.01]+cmd[3.07] seconds)
  evaluation failed :( (3.09 seconds)

So most of these changes are necessary. Without them, the annotations will be invalid on python>=3.12.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, to try and test different versions properly then, I've created python-pillow#9414



_Ink = float | tuple[int, ...] | str
Expand Down