Skip to content
Open
Show file tree
Hide file tree
Changes from all 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"
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