Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added type hints #8250

Merged
merged 2 commits into from
Jul 20, 2024
Merged
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
3 changes: 2 additions & 1 deletion Tests/test_file_libtiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,10 @@ def test_additional_metadata(

new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
for tag, info in core_items.items():
assert info.type is not None
if info.length == 1:
new_ifd[tag] = values[info.type]
if info.length == 0:
elif not info.length:
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
else:
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/ImageFile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Example: Parse an image
Classes
-------

.. autoclass:: PIL.ImageFile._Tile()
:member-order: bysource
:members:
:show-inheritance:

.. autoclass:: PIL.ImageFile.Parser()
:members:

Expand Down
38 changes: 18 additions & 20 deletions src/PIL/ImageWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
assert not isinstance(image, str)
self.paste(image)

def expose(self, handle):
def expose(self, handle: int | HDC | HWND) -> None:
"""
Copy the bitmap contents to a device context.

Expand All @@ -101,19 +101,18 @@
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
try:
result = self.image.expose(dc)
self.image.expose(dc)

Check warning on line 104 in src/PIL/ImageWin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageWin.py#L104

Added line #L104 was not covered by tests
finally:
self.image.releasedc(handle, dc)
else:
result = self.image.expose(handle)
return result
self.image.expose(handle)

def draw(
self,
handle,
handle: int | HDC | HWND,
dst: tuple[int, int, int, int],
src: tuple[int, int, int, int] | None = None,
):
) -> None:
"""
Same as expose, but allows you to specify where to draw the image, and
what part of it to draw.
Expand All @@ -128,14 +127,13 @@
if isinstance(handle, HWND):
dc = self.image.getdc(handle)
try:
result = self.image.draw(dc, dst, src)
self.image.draw(dc, dst, src)

Check warning on line 130 in src/PIL/ImageWin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageWin.py#L130

Added line #L130 was not covered by tests
finally:
self.image.releasedc(handle, dc)
else:
result = self.image.draw(handle, dst, src)
return result
self.image.draw(handle, dst, src)

Check warning on line 134 in src/PIL/ImageWin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageWin.py#L134

Added line #L134 was not covered by tests

def query_palette(self, handle):
def query_palette(self, handle: int | HDC | HWND) -> int:
"""
Installs the palette associated with the image in the given device
context.
Expand All @@ -147,8 +145,8 @@

:param handle: Device context (HDC), cast to a Python integer, or an
HDC or HWND instance.
:return: A true value if one or more entries were changed (this
indicates that the image should be redrawn).
:return: The number of entries that were changed (if one or more entries,
this indicates that the image should be redrawn).
"""
if isinstance(handle, HWND):
handle = self.image.getdc(handle)
Expand Down Expand Up @@ -210,22 +208,22 @@
title, self.__dispatcher, width or 0, height or 0
)

def __dispatcher(self, action: str, *args):
return getattr(self, f"ui_handle_{action}")(*args)
def __dispatcher(self, action: str, *args: int) -> None:
getattr(self, f"ui_handle_{action}")(*args)

Check warning on line 212 in src/PIL/ImageWin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageWin.py#L212

Added line #L212 was not covered by tests

def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_damage(self, x0, y0, x1, y1) -> None:
def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_destroy(self) -> None:
pass

def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass

def ui_handle_resize(self, width, height) -> None:
def ui_handle_resize(self, width: int, height: int) -> None:
pass

def mainloop(self) -> None:
Expand All @@ -235,12 +233,12 @@
class ImageWindow(Window):
"""Create an image window which displays the given image."""

def __init__(self, image, title: str = "PIL") -> None:
def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
if not isinstance(image, Dib):
image = Dib(image)
self.image = image
width, height = image.size
super().__init__(title, width=width, height=height)

def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None:
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
self.image.draw(dc, (x0, y0, x1, y1))
48 changes: 28 additions & 20 deletions src/PIL/PsdImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import io
from functools import cached_property
from typing import IO

from . import Image, ImageFile, ImagePalette
from ._binary import i8
Expand Down Expand Up @@ -142,7 +143,9 @@
self._min_frame = 1

@cached_property
def layers(self):
def layers(
self,
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
layers = []
if self._layers_position is not None:
self._fp.seek(self._layers_position)
Expand Down Expand Up @@ -181,7 +184,9 @@
return self.frame


def _layerinfo(fp, ct_bytes):
def _layerinfo(
fp: IO[bytes], ct_bytes: int
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
# read layerinfo block
layers = []

Expand All @@ -203,7 +208,7 @@
x1 = si32(read(4))

# image info
mode = []
bands = []
ct_types = i16(read(2))
if ct_types > 4:
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
Expand All @@ -215,23 +220,23 @@
type = i16(read(2))

if type == 65535:
m = "A"
b = "A"
else:
m = "RGBA"[type]
b = "RGBA"[type]

mode.append(m)
bands.append(b)
read(4) # size

# figure out the image mode
mode.sort()
if mode == ["R"]:
bands.sort()
if bands == ["R"]:
mode = "L"
elif mode == ["B", "G", "R"]:
elif bands == ["B", "G", "R"]:
mode = "RGB"
elif mode == ["A", "B", "G", "R"]:
elif bands == ["A", "B", "G", "R"]:
mode = "RGBA"
else:
mode = None # unknown
mode = "" # unknown

Check warning on line 239 in src/PIL/PsdImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/PsdImagePlugin.py#L239

Added line #L239 was not covered by tests

# skip over blend flags and extra information
read(12) # filler
Expand All @@ -258,19 +263,22 @@
layers.append((name, mode, (x0, y0, x1, y1)))

# get tiles
layerinfo = []
for i, (name, mode, bbox) in enumerate(layers):
tile = []
for m in mode:
t = _maketile(fp, m, bbox, 1)
if t:
tile.extend(t)
layers[i] = name, mode, bbox, tile
layerinfo.append((name, mode, bbox, tile))

return layers
return layerinfo


def _maketile(file, mode, bbox, channels):
tile = None
def _maketile(
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
) -> list[ImageFile._Tile] | None:
tiles = None
read = file.read

compression = i16(read(2))
Expand All @@ -283,26 +291,26 @@
if compression == 0:
#
# raw compression
tile = []
tiles = []
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
tile.append(("raw", bbox, offset, layer))
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
offset = offset + xsize * ysize

elif compression == 1:
#
# packbits compression
i = 0
tile = []
tiles = []
bytecount = read(channels * ysize * 2)
offset = file.tell()
for channel in range(channels):
layer = mode[channel]
if mode == "CMYK":
layer += ";I"
tile.append(("packbits", bbox, offset, layer))
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
for y in range(ysize):
offset = offset + i16(bytecount, i)
i += 2
Expand All @@ -312,7 +320,7 @@
if offset & 1:
read(1) # padding

return tile
return tiles


# --------------------------------------------------------------------
Expand Down
28 changes: 14 additions & 14 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ def __setstate__(self, state):
__int__ = _delegate("__int__")


def _register_loader(idx, size):
def _register_loader(idx: int, size: int):
def decorator(func):
from .TiffTags import TYPES

Expand All @@ -457,15 +457,15 @@ def decorator(func):
return decorator


def _register_writer(idx):
def _register_writer(idx: int):
def decorator(func):
_write_dispatch[idx] = func # noqa: F821
return func

return decorator


def _register_basic(idx_fmt_name):
def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
from .TiffTags import TYPES

idx, fmt, name = idx_fmt_name
Expand Down Expand Up @@ -640,7 +640,7 @@ def __getitem__(self, tag):
def __contains__(self, tag: object) -> bool:
return tag in self._tags_v2 or tag in self._tagdata

def __setitem__(self, tag, value) -> None:
def __setitem__(self, tag: int, value) -> None:
self._setitem(tag, value, self.legacy_api)

def _setitem(self, tag, value, legacy_api) -> None:
Expand Down Expand Up @@ -731,10 +731,10 @@ def __delitem__(self, tag: int) -> None:
def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v2))

def _unpack(self, fmt, data):
def _unpack(self, fmt: str, data):
return struct.unpack(self._endian + fmt, data)

def _pack(self, fmt, *values):
def _pack(self, fmt: str, *values):
return struct.pack(self._endian + fmt, *values)

list(
Expand All @@ -755,7 +755,7 @@ def _pack(self, fmt, *values):
)

@_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True):
def load_byte(self, data, legacy_api: bool = True):
return data

@_register_writer(1) # Basic type, except for the legacy API.
Expand All @@ -767,7 +767,7 @@ def write_byte(self, data) -> bytes:
return data

@_register_loader(2, 1)
def load_string(self, data, legacy_api=True):
def load_string(self, data: bytes, legacy_api: bool = True) -> str:
if data.endswith(b"\0"):
data = data[:-1]
return data.decode("latin-1", "replace")
Expand Down Expand Up @@ -797,7 +797,7 @@ def write_rational(self, *values) -> bytes:
)

@_register_loader(7, 1)
def load_undefined(self, data, legacy_api=True):
def load_undefined(self, data, legacy_api: bool = True):
return data

@_register_writer(7)
Expand All @@ -809,7 +809,7 @@ def write_undefined(self, value) -> bytes:
return value

@_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api=True):
def load_signed_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data)

def combine(a, b):
Expand Down Expand Up @@ -1030,7 +1030,7 @@ def __init__(self, *args, **kwargs) -> None:
"""Dictionary of tag types"""

@classmethod
def from_v2(cls, original) -> ImageFileDirectory_v1:
def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
"""Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
instance with the same data as is contained in the original
Expand Down Expand Up @@ -1073,7 +1073,7 @@ def __len__(self) -> int:
def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v1))

def __setitem__(self, tag, value) -> None:
def __setitem__(self, tag: int, value) -> None:
for legacy_api in (False, True):
self._setitem(tag, value, legacy_api)

Expand Down Expand Up @@ -1212,7 +1212,7 @@ def tell(self) -> int:
"""Return the current frame number"""
return self.__frame

def get_photoshop_blocks(self):
def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
"""
Returns a dictionary of Photoshop "Image Resource Blocks".
The keys are the image resource ID. For more information, see
Expand Down Expand Up @@ -1259,7 +1259,7 @@ def load_end(self) -> None:
if ExifTags.Base.Orientation in self.tag_v2:
del self.tag_v2[ExifTags.Base.Orientation]

def _load_libtiff(self):
def _load_libtiff(self) -> Image.core.PixelAccess | None:
"""Overload method triggered when we detect a compressed tiff
Calls out to libtiff"""

Expand Down
Loading
Loading