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 #8125

Merged
merged 1 commit into from
Jun 10, 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
15 changes: 10 additions & 5 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

from __future__ import annotations

import abc
import os
import struct
from enum import IntEnum
Expand Down Expand Up @@ -276,7 +277,7 @@ def _open(self) -> None:
class _BLPBaseDecoder(ImageFile.PyDecoder):
_pulls_fd = True

def decode(self, buffer):
def decode(self, buffer: bytes) -> tuple[int, int]:
try:
self._read_blp_header()
self._load()
Expand All @@ -285,6 +286,10 @@ def decode(self, buffer):
raise OSError(msg) from e
return -1, 0

@abc.abstractmethod
def _load(self) -> None:
pass

def _read_blp_header(self) -> None:
assert self.fd is not None
self.fd.seek(4)
Expand Down Expand Up @@ -318,7 +323,7 @@ def _read_palette(self) -> list[tuple[int, int, int, int]]:
ret.append((b, g, r, a))
return ret

def _read_bgra(self, palette):
def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray:
data = bytearray()
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
while True:
Expand All @@ -327,7 +332,7 @@ def _read_bgra(self, palette):
except struct.error:
break
b, g, r, a = palette[offset]
d = (r, g, b)
d: tuple[int, ...] = (r, g, b)
if self._blp_alpha_depth:
d += (a,)
data.extend(d)
Expand Down Expand Up @@ -431,7 +436,7 @@ def _write_palette(self) -> bytes:
data += b"\x00" * 4
return data

def encode(self, bufsize):
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
palette_data = self._write_palette()

offset = 20 + 16 * 4 * 2 + len(palette_data)
Expand All @@ -449,7 +454,7 @@ def encode(self, bufsize):
return len(data), 0, data


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "P":
msg = "Unsupported BLP image mode"
raise ValueError(msg)
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,12 @@ def _open(self) -> None:
}


def _dib_save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, False)


def _save(
im: Image.Image, fp: IO[bytes], filename: str, bitmap_header: bool = True
im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True
) -> None:
try:
rawmode, bits, colors = SAVE[im.mode]
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/BufrStubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None:
return _handler


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "BUFR save handler not installed"
raise OSError(msg)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def decode(self, buffer):
return -1, 0


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode not in ("RGB", "RGBA", "L", "LA"):
msg = f"cannot write mode {im.mode} as DDS"
raise OSError(msg)
Expand Down
6 changes: 3 additions & 3 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,12 +715,12 @@ def _write_multiple_frames(im, fp, palette):
return True


def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True)


def _save(
im: Image.Image, fp: IO[bytes], filename: str, save_all: bool = False
im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
) -> None:
# header
if "palette" in im.encoderinfo or "palette" in im.info:
Expand Down Expand Up @@ -796,7 +796,7 @@ def _write_local_header(fp, im, offset, flags):
fp.write(o8(8)) # bits


def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save_netpbm(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Unused by default.
# To use, uncomment the register_save call at the end of the file.
#
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/GribStubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None:
return _handler


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "GRIB save handler not installed"
raise OSError(msg)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/Hdf5StubImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def _load(self) -> ImageFile.StubHandler | None:
return _handler


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if _handler is None or not hasattr(_handler, "save"):
msg = "HDF5 save handler not installed"
raise OSError(msg)
Expand Down
19 changes: 9 additions & 10 deletions src/PIL/IcnsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import os
import struct
import sys
from typing import IO

from . import Image, ImageFile, PngImagePlugin, features

Expand Down Expand Up @@ -312,7 +313,7 @@ def load(self):
return px


def _save(im, fp, filename):
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
"""
Saves the image as a series of PNG files,
that are then combined into a .icns file.
Expand Down Expand Up @@ -346,29 +347,27 @@ def _save(im, fp, filename):
entries = []
for type, size in sizes.items():
stream = size_streams[size]
entries.append(
{"type": type, "size": HEADERSIZE + len(stream), "stream": stream}
)
entries.append((type, HEADERSIZE + len(stream), stream))
Copy link
Member

Choose a reason for hiding this comment

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

What triggered this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

Once i type hinted the _save method in this plugin, mypy reported

src/PIL/IcnsImagePlugin.py:358: error: Generator has incompatible item type "object"; expected "bool"  [misc]
        file_length += sum(entry["size"] for entry in entries)
                           ^~~~~~~~~~~~~
src/PIL/IcnsImagePlugin.py:365: error: No overload variant of "write" of "IO" matches argument type "object"  [call-overload]
            fp.write(entry["type"])
            ^~~~~~~~~~~~~~~~~~~~~~~
src/PIL/IcnsImagePlugin.py:365: note: Possible overload variants:
src/PIL/IcnsImagePlugin.py:365: note:     def write(self, Buffer, /) -> int
src/PIL/IcnsImagePlugin.py:365: note:     def write(self, bytes, /) -> int
src/PIL/IcnsImagePlugin.py:370: error: No overload variant of "write" of "IO" matches argument type "object"  [call-overload]
            fp.write(entry["type"])
            ^~~~~~~~~~~~~~~~~~~~~~~
src/PIL/IcnsImagePlugin.py:370: note: Possible overload variants:
src/PIL/IcnsImagePlugin.py:370: note:     def write(self, Buffer, /) -> int
src/PIL/IcnsImagePlugin.py:370: note:     def write(self, bytes, /) -> int
src/PIL/IcnsImagePlugin.py:372: error: No overload variant of "write" of "IO" matches argument type "object"  [call-overload]
            fp.write(entry["stream"])
            ^~~~~~~~~~~~~~~~~~~~~~~~~
src/PIL/IcnsImagePlugin.py:372: note: Possible overload variants:
src/PIL/IcnsImagePlugin.py:372: note:     def write(self, Buffer, /) -> int
src/PIL/IcnsImagePlugin.py:372: note:     def write(self, bytes, /) -> int
Found 4 errors in 1 file (checked 1 source file)

This change fixes it. I can use a named tuple or something if you prefer.

Copy link
Member

@hugovk hugovk Jun 10, 2024

Choose a reason for hiding this comment

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

Thanks. Named tuples are nice, but this is very localised so I think we're fine with a basic tuple here.


# Header
fp.write(MAGIC)
file_length = HEADERSIZE # Header
file_length += HEADERSIZE + 8 * len(entries) # TOC
file_length += sum(entry["size"] for entry in entries)
file_length += sum(entry[1] for entry in entries)
fp.write(struct.pack(">i", file_length))

# TOC
fp.write(b"TOC ")
fp.write(struct.pack(">i", HEADERSIZE + len(entries) * HEADERSIZE))
for entry in entries:
fp.write(entry["type"])
fp.write(struct.pack(">i", entry["size"]))
fp.write(entry[0])
fp.write(struct.pack(">i", entry[1]))

# Data
for entry in entries:
fp.write(entry["type"])
fp.write(struct.pack(">i", entry["size"]))
fp.write(entry["stream"])
fp.write(entry[0])
fp.write(struct.pack(">i", entry[1]))
fp.write(entry[2])

if hasattr(fp, "flush"):
fp.flush()
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/IcoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
_MAGIC = b"\0\0\1\0"


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(_MAGIC) # (2+2)
bmp = im.encoderinfo.get("bitmap_format") == "bmp"
sizes = im.encoderinfo.get(
Expand Down
4 changes: 3 additions & 1 deletion src/PIL/ImImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@
}


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try:
image_type, rawmode = SAVE[im.mode]
except KeyError as e:
Expand All @@ -341,6 +341,8 @@
# or: SyntaxError("not an IM file")
# 8 characters are used for "Name: " and "\r\n"
# Keep just the filename, ditch the potentially overlong path
if isinstance(filename, bytes):
filename = filename.decode("ascii")

Check warning on line 345 in src/PIL/ImImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImImagePlugin.py#L345

Added line #L345 was not covered by tests
Comment on lines +344 to +345
Copy link
Member

Choose a reason for hiding this comment

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

Is this also fixing a bug?

Copy link
Member Author

Choose a reason for hiding this comment

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

In main at the moment, save handlers can receive strings or bytes as the filename.

filename: str | bytes = ""

Pillow/src/PIL/Image.py

Lines 231 to 232 in 53e82e4

SAVE: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}
SAVE_ALL: dict[str, Callable[[Image, IO[bytes], str | bytes], None]] = {}

Part of this PR is propagating that out to some plugins that were only using strings.

name, ext = os.path.splitext(os.path.basename(filename))
name = "".join([name[: 92 - len(ext)], ext])

Expand Down
26 changes: 17 additions & 9 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ def _ensure_mutable(self) -> None:
self.load()

def _dump(
self, file: str | None = None, format: str | None = None, **options
self, file: str | None = None, format: str | None = None, **options: Any
) -> str:
suffix = ""
if format:
Expand All @@ -649,10 +649,12 @@ def _dump(

return filename

def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if self.__class__ is not other.__class__:
return False
assert isinstance(other, Image)
return (
self.__class__ is other.__class__
and self.mode == other.mode
self.mode == other.mode
and self.size == other.size
and self.info == other.info
and self.getpalette() == other.getpalette()
Expand Down Expand Up @@ -2965,7 +2967,7 @@ def transform(
# Debugging


def _wedge():
def _wedge() -> Image:
"""Create grayscale wedge (for debugging only)"""

return Image()._new(core.wedge("L"))
Expand Down Expand Up @@ -3566,7 +3568,9 @@ def register_mime(id: str, mimetype: str) -> None:
MIME[id.upper()] = mimetype


def register_save(id: str, driver) -> None:
def register_save(
id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
) -> None:
"""
Registers an image save function. This function should not be
used in application code.
Expand All @@ -3577,7 +3581,9 @@ def register_save(id: str, driver) -> None:
SAVE[id.upper()] = driver


def register_save_all(id: str, driver) -> None:
def register_save_all(
id: str, driver: Callable[[Image, IO[bytes], str | bytes], None]
) -> None:
"""
Registers an image function to save all the frames
of a multiframe format. This function should not be
Expand Down Expand Up @@ -3651,7 +3657,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
# Simple display support.


def _show(image, **options) -> None:
def _show(image: Image, **options: Any) -> None:
from . import ImageShow

ImageShow.show(image, **options)
Expand All @@ -3661,7 +3667,9 @@ def _show(image, **options) -> None:
# Effects


def effect_mandelbrot(size, extent, quality):
def effect_mandelbrot(
size: tuple[int, int], extent: tuple[int, int, int, int], quality: int
) -> Image:
"""
Generate a Mandelbrot set covering the given extent.

Expand Down
16 changes: 10 additions & 6 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ def line(self, xy: Coords, fill=None, width=0, joint=None) -> None:
# This is a straight line, so no joint is required
continue

def coord_at_angle(coord, angle):
def coord_at_angle(
coord: Sequence[float], angle: float
) -> tuple[float, float]:
x, y = coord
angle -= 90
distance = width / 2 - 1
Expand Down Expand Up @@ -1109,11 +1111,13 @@ def _get_angles(n_sides: int, rotation: float) -> list[float]:
return [_compute_polygon_vertex(angle) for angle in angles]


def _color_diff(color1, color2: float | tuple[int, ...]) -> float:
def _color_diff(
color1: float | tuple[int, ...], color2: float | tuple[int, ...]
) -> float:
"""
Uses 1-norm distance to calculate difference between two values.
"""
if isinstance(color2, tuple):
return sum(abs(color1[i] - color2[i]) for i in range(0, len(color2)))
else:
return abs(color1 - color2)
first = color1 if isinstance(color1, tuple) else (color1,)
second = color2 if isinstance(color2, tuple) else (color2,)

return sum(abs(first[i] - second[i]) for i in range(0, len(second)))
2 changes: 1 addition & 1 deletion src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,7 +763,7 @@ class PyEncoder(PyCodec):
def pushes_fd(self):
return self._pushes_fd

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

Expand Down
6 changes: 4 additions & 2 deletions src/PIL/Jpeg2KImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,13 @@ def _accept(prefix: bytes) -> bool:
# Save support


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Get the keyword arguments
info = im.encoderinfo

if filename.endswith(".j2k") or info.get("no_jp2", False):
if isinstance(filename, str):
filename = filename.encode()
if filename.endswith(b".j2k") or info.get("no_jp2", False):
kind = "j2k"
else:
kind = "jp2"
Expand Down
6 changes: 3 additions & 3 deletions src/PIL/JpegImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import sys
import tempfile
import warnings
from typing import Any
from typing import IO, Any

from . import Image, ImageFile
from ._binary import i16be as i16
Expand Down Expand Up @@ -644,7 +644,7 @@ def get_sampling(im):
return samplings.get(sampling, -1)


def _save(im, fp, filename):
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.width == 0 or im.height == 0:
msg = "cannot write empty image as JPEG"
raise ValueError(msg)
Expand Down Expand Up @@ -827,7 +827,7 @@ def validate_qtables(qtables):
ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)


def _save_cjpeg(im, fp, filename):
def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/MpoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from ._binary import o32le


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
JpegImagePlugin._save(im, fp, filename)


Expand Down
2 changes: 1 addition & 1 deletion src/PIL/MspImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def decode(self, buffer: bytes) -> tuple[int, int]:
# write MSP files (uncompressed only)


def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None:
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode != "1":
msg = f"cannot write mode {im.mode} as MSP"
raise OSError(msg)
Expand Down
Loading
Loading