Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
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
4 changes: 3 additions & 1 deletion Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
UnidentifiedImageError,
features,
)
from PIL._typing import Buffer

from .helper import (
assert_image,
Expand All @@ -40,6 +41,7 @@
except ImportError:
ElementTree = None


TEST_FILE = "Tests/images/hopper.jpg"


Expand Down Expand Up @@ -1064,7 +1066,7 @@ def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
# the image should still end when there is no new data
class InfiniteMockPyDecoder(ImageFile.PyDecoder):
def decode(
self, buffer: bytes | Image.SupportsArrayInterface
self, buffer: Buffer | Image.SupportsArrayInterface
) -> tuple[int, int]:
return 0, 0

Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_jpeg2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_reduce() -> None:
assert callable(im.reduce)

im.reduce = 2 # type: ignore[assignment, method-assign]
assert im.reduce == 2
assert im.reduce == 2 # type: ignore[comparison-overlap]

im.load()
assert im.size == (160, 120)
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image_frombytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
def test_sanity(data_type: str) -> None:
im1 = hopper()

data = im1.tobytes()
data: bytes | memoryview[int] = im1.tobytes()
if data_type == "memoryview":
data = memoryview(data)
im2 = Image.frombytes(im1.mode, im1.size, data)
Expand Down
3 changes: 2 additions & 1 deletion Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
_binary,
features,
)
from PIL._typing import Buffer

from .helper import (
assert_image,
Expand Down Expand Up @@ -238,7 +239,7 @@ def __init__(self, mode: str, *args: Any) -> None:

super().__init__(mode, *args)

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
# eof
return -1, 0

Expand Down
2 changes: 1 addition & 1 deletion Tests/test_imagewin.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def test_dib_frombytes_tobytes_roundtrip(self) -> None:

# Act
# Make one the same as the using tobytes()/frombytes()
test_buffer = dib1.tobytes()
test_buffer: bytes | memoryview[int] = dib1.tobytes()
for datatype in ("bytes", "memoryview"):
if datatype == "memoryview":
test_buffer = memoryview(test_buffer)
Expand Down
8 changes: 5 additions & 3 deletions Tests/test_qt_image_toqimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
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
12 changes: 9 additions & 3 deletions docs/example/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,16 @@

import struct
from io import BytesIO
from typing import IO

from PIL import Image, ImageFile

TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import IO

from typing_extensions import Buffer


# Magic ("DDS ")
DDS_MAGIC = 0x20534444

Expand Down Expand Up @@ -258,7 +264,7 @@ def load_seek(self, pos: int) -> None:
class DXT1Decoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
try:
self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize))
Expand All @@ -271,7 +277,7 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int
class DXT5Decoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
try:
self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize))
Expand Down
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,10 @@ testpaths = [
[tool.mypy]
python_version = "3.10"
pretty = true
disallow_any_generics = true
disallow_untyped_defs = true
strict = true
disallow_subclassing_any = false
disallow_untyped_calls = false
enable_error_code = "ignore-without-code"
extra_checks = true
follow_imports = "silent"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see anything at https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict that mentions follow_imports? What was the thinking behind changing this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's right, follow_imports is unrelated to --strict. It's just unnecessary to suppress those import errors.

warn_redundant_casts = true
no_implicit_reexport = false
warn_return_any = false
warn_unreachable = true
warn_unused_ignores = true
3 changes: 2 additions & 1 deletion src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from typing import IO

from . import Image, ImageFile
from ._typing import Buffer


class Format(IntEnum):
Expand Down Expand Up @@ -295,7 +296,7 @@ def _open(self) -> None:
class _BLPBaseDecoder(abc.ABC, ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
try:
self._read_header()
self._load()
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ._binary import o8
from ._binary import o16le as o16
from ._binary import o32le as o32
from ._typing import Buffer

#
# --------------------------------------------------------------------
Expand Down Expand Up @@ -327,7 +328,7 @@ def _open(self) -> None:
class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
rle4 = self.args[1]
data = bytearray()
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ._binary import i32le as i32
from ._binary import o8
from ._binary import o32le as o32
from ._typing import Buffer

# Magic ("DDS ")
DDS_MAGIC = 0x20534444
Expand Down Expand Up @@ -488,7 +489,7 @@ def load_seek(self, pos: int) -> None:
class DdsRgbDecoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
bitcount, masks = self.args

Expand Down
4 changes: 3 additions & 1 deletion src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ def _open(self) -> None:
imagedata_size: tuple[int, int] | None = None

byte_arr = bytearray(255)
bytes_mv = memoryview(byte_arr)
# the extra `bytes` annotation here works around several false positive
# `comparison-overlap` mypy errors
bytes_mv: bytes | memoryview = memoryview(byte_arr)
bytes_read = 0
reading_header_comments = True
reading_trailer_comments = False
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/FitsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import math

from . import Image, ImageFile
from ._typing import Buffer


def _accept(prefix: bytes) -> bool:
Expand Down Expand Up @@ -126,7 +127,7 @@ def _parse_headers(
class FitsGzipDecoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
value = gzip.decompress(self.fd.read())

Expand Down
3 changes: 1 addition & 2 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
from typing import IO, Literal

from . import _imaging
from ._typing import Buffer


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

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

Expand Down
74 changes: 42 additions & 32 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
from types import ModuleType
from typing import Any, Literal

from ._typing import Buffer

logger = logging.getLogger(__name__)


Expand All @@ -86,37 +88,45 @@ class DecompressionBombError(Exception):
MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3)


try:
# If the _imaging C module is not present, Pillow will not load.
# Note that other modules should not refer to _imaging directly;
# import Image and use the Image.core variable instead.
# Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change.
from . import _imaging as core

if __version__ != getattr(core, "PILLOW_VERSION", None):
msg = (
"The _imaging extension was built for another version of Pillow or PIL:\n"
f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n"
f"Pillow version: {__version__}"
)
raise ImportError(msg)
if TYPE_CHECKING:
from . import _imaging

except ImportError as v:
# Explanations for ways that we know we might have an import error
if str(v).startswith("Module use of python"):
# The _imaging C module is present, but not compiled for
# the right version (windows only). Print a warning, if
# possible.
warnings.warn(
"The _imaging extension was built for another version of Python.",
RuntimeWarning,
)
elif str(v).startswith("The _imaging extension"):
warnings.warn(str(v), RuntimeWarning)
# Fail here anyway. Don't let people run with a mostly broken Pillow.
# see docs/porting.rst
raise
# mypy will not recognize `core` as a public symbol when imported as
# `from . import _imaging as core`
core = _imaging
else:
try:
# If the _imaging C module is not present, Pillow will not load.
# Note that other modules should not refer to _imaging directly;
# import Image and use the Image.core variable instead.
# Also note that Image.core is not a publicly documented interface,
# and should be considered private and subject to change.
from . import _imaging as core

if __version__ != getattr(core, "PILLOW_VERSION", None):
msg = (
f"The _imaging extension was built for another version of Pillow or "
f"PIL:\n "
f"Core version: {getattr(core, 'PILLOW_VERSION', None)}\n"
f"Pillow version: {__version__}"
)
raise ImportError(msg)

except ImportError as v:
# Explanations for ways that we know we might have an import error
if str(v).startswith("Module use of python"):
# The _imaging C module is present, but not compiled for
# the right version (windows only). Print a warning, if
# possible.
warnings.warn(
"The _imaging extension was built for another version of Python.",
RuntimeWarning,
)
elif str(v).startswith("The _imaging extension"):
warnings.warn(str(v), RuntimeWarning)
# Fail here anyway. Don't let people run with a mostly broken Pillow.
# see docs/porting.rst
raise


#
Expand Down Expand Up @@ -931,7 +941,7 @@ def tobitmap(self, name: str = "image") -> bytes:

def frombytes(
self,
data: bytes | bytearray | SupportsArrayInterface,
data: Buffer | SupportsArrayInterface,
decoder_name: str = "raw",
*args: Any,
) -> None:
Expand Down Expand Up @@ -3232,7 +3242,7 @@ def new(
def frombytes(
mode: str,
size: tuple[int, int],
data: bytes | bytearray | SupportsArrayInterface,
data: Buffer | SupportsArrayInterface,
decoder_name: str = "raw",
*args: Any,
) -> Image:
Expand Down
11 changes: 7 additions & 4 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

TYPE_CHECKING = False
if TYPE_CHECKING:
from ._typing import StrOrBytesPath
from ._typing import Buffer, StrOrBytesPath

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -839,7 +839,7 @@ class PyDecoder(PyCodec):
def pulls_fd(self) -> bool:
return self._pulls_fd

def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
def decode(self, buffer: Buffer | Image.SupportsArrayInterface) -> tuple[int, int]:
"""
Override to perform the decoding process.

Expand All @@ -852,7 +852,10 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int
raise NotImplementedError(msg)

def set_as_raw(
self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = ()
self,
data: bytes | bytearray,
rawmode: str | None = None,
extra: tuple[Any, ...] = (),
) -> None:
"""
Convenience method to set the internal image from a stream of raw data
Expand Down Expand Up @@ -893,7 +896,7 @@ class PyEncoder(PyCodec):
def pushes_fd(self) -> bool:
return self._pushes_fd

def encode(self, bufsize: int) -> tuple[int, int, bytes]:
def encode(self, bufsize: int) -> tuple[int, int, bytes | bytearray]:
"""
Override to perform the encoding process.

Expand Down
3 changes: 2 additions & 1 deletion src/PIL/ImageWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from __future__ import annotations

from . import Image
from ._typing import Buffer


class HDC:
Expand Down Expand Up @@ -183,7 +184,7 @@ def paste(
else:
self.image.paste(im.im)

def frombytes(self, buffer: bytes) -> None:
def frombytes(self, buffer: Buffer) -> None:
"""
Load display memory contents from byte data.

Expand Down
Loading
Loading