Added type hints

This commit is contained in:
Andrew Murray 2024-07-28 12:53:02 +10:00
parent 8f62fbdf44
commit c85eb0cae5
5 changed files with 50 additions and 42 deletions

View File

@ -424,8 +424,10 @@ class TestFilePng:
im = roundtrip(im, pnginfo=info) im = roundtrip(im, pnginfo=info)
assert im.info == {"spam": "Eggs", "eggs": "Spam"} assert im.info == {"spam": "Eggs", "eggs": "Spam"}
assert im.text == {"spam": "Eggs", "eggs": "Spam"} assert im.text == {"spam": "Eggs", "eggs": "Spam"}
assert isinstance(im.text["spam"], PngImagePlugin.iTXt)
assert im.text["spam"].lang == "en" assert im.text["spam"].lang == "en"
assert im.text["spam"].tkey == "Spam" assert im.text["spam"].tkey == "Spam"
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
assert im.text["eggs"].lang == "en" assert im.text["eggs"].lang == "en"
assert im.text["eggs"].tkey == "Eggs" assert im.text["eggs"].tkey == "Eggs"

View File

@ -38,7 +38,7 @@ import struct
import sys import sys
import tempfile import tempfile
import warnings import warnings
from collections.abc import Callable, MutableMapping, Sequence from collections.abc import Callable, Iterator, MutableMapping, Sequence
from enum import IntEnum from enum import IntEnum
from types import ModuleType from types import ModuleType
from typing import ( from typing import (
@ -744,11 +744,11 @@ class Image:
new["shape"], new["typestr"] = _conv_type_shape(self) new["shape"], new["typestr"] = _conv_type_shape(self)
return new return new
def __getstate__(self): def __getstate__(self) -> list[Any]:
im_data = self.tobytes() # load image first im_data = self.tobytes() # load image first
return [self.info, self.mode, self.size, self.getpalette(), im_data] return [self.info, self.mode, self.size, self.getpalette(), im_data]
def __setstate__(self, state) -> None: def __setstate__(self, state: list[Any]) -> None:
Image.__init__(self) Image.__init__(self)
info, mode, size, palette, data = state info, mode, size, palette, data = state
self.info = info self.info = info
@ -1683,7 +1683,9 @@ class Image:
x, y = self.im.getprojection() x, y = self.im.getprojection()
return list(x), list(y) return list(x), list(y)
def histogram(self, mask: Image | None = None, extrema=None) -> list[int]: def histogram(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> list[int]:
""" """
Returns a histogram for the image. The histogram is returned as a Returns a histogram for the image. The histogram is returned as a
list of pixel counts, one for each pixel value in the source list of pixel counts, one for each pixel value in the source
@ -1709,12 +1711,14 @@ class Image:
mask.load() mask.load()
return self.im.histogram((0, 0), mask.im) return self.im.histogram((0, 0), mask.im)
if self.mode in ("I", "F"): if self.mode in ("I", "F"):
if extrema is None: return self.im.histogram(
extrema = self.getextrema() extrema if extrema is not None else self.getextrema()
return self.im.histogram(extrema) )
return self.im.histogram() return self.im.histogram()
def entropy(self, mask: Image | None = None, extrema=None): def entropy(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> float:
""" """
Calculates and returns the entropy for the image. Calculates and returns the entropy for the image.
@ -1735,9 +1739,9 @@ class Image:
mask.load() mask.load()
return self.im.entropy((0, 0), mask.im) return self.im.entropy((0, 0), mask.im)
if self.mode in ("I", "F"): if self.mode in ("I", "F"):
if extrema is None: return self.im.entropy(
extrema = self.getextrema() extrema if extrema is not None else self.getextrema()
return self.im.entropy(extrema) )
return self.im.entropy() return self.im.entropy()
def paste( def paste(
@ -3881,7 +3885,7 @@ class Exif(_ExifBase):
# returns a dict with any single item tuples/lists as individual values # returns a dict with any single item tuples/lists as individual values
return {k: self._fixup(v) for k, v in src_dict.items()} return {k: self._fixup(v) for k, v in src_dict.items()}
def _get_ifd_dict(self, offset: int, group=None): def _get_ifd_dict(self, offset: int, group: int | None = None):
try: try:
# an offset pointer to the location of the nested embedded IFD. # an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted. # It should be a long, but may be corrupted.
@ -4136,7 +4140,7 @@ class Exif(_ExifBase):
else: else:
del self._data[tag] del self._data[tag]
def __iter__(self): def __iter__(self) -> Iterator[int]:
keys = set(self._data) keys = set(self._data)
if self._info is not None: if self._info is not None:
keys.update(self._info) keys.update(self._info)

View File

@ -24,7 +24,7 @@
""" """
from __future__ import annotations from __future__ import annotations
from typing import AnyStr, BinaryIO from typing import Any, AnyStr, BinaryIO
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import Coords, StrOrBytesPath from ._typing import Coords, StrOrBytesPath
@ -111,7 +111,7 @@ class Draw:
(xoffset, yoffset) = offset (xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset) self.transform = (1, 0, xoffset, 0, 1, yoffset)
def arc(self, xy: Coords, start, end, *options) -> None: def arc(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Draws an arc (a portion of a circle outline) between the start and end Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box. angles, inside the given bounding box.
@ -120,7 +120,7 @@ class Draw:
""" """
self.render("arc", xy, start, end, *options) self.render("arc", xy, start, end, *options)
def chord(self, xy: Coords, start, end, *options) -> None: def chord(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line. with a straight line.
@ -129,7 +129,7 @@ class Draw:
""" """
self.render("chord", xy, start, end, *options) self.render("chord", xy, start, end, *options)
def ellipse(self, xy: Coords, *options) -> None: def ellipse(self, xy: Coords, *options: Any) -> None:
""" """
Draws an ellipse inside the given bounding box. Draws an ellipse inside the given bounding box.
@ -137,7 +137,7 @@ class Draw:
""" """
self.render("ellipse", xy, *options) self.render("ellipse", xy, *options)
def line(self, xy: Coords, *options) -> None: def line(self, xy: Coords, *options: Any) -> None:
""" """
Draws a line between the coordinates in the ``xy`` list. Draws a line between the coordinates in the ``xy`` list.
@ -145,7 +145,7 @@ class Draw:
""" """
self.render("line", xy, *options) self.render("line", xy, *options)
def pieslice(self, xy: Coords, start, end, *options) -> None: def pieslice(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Same as arc, but also draws straight lines between the end points and the Same as arc, but also draws straight lines between the end points and the
center of the bounding box. center of the bounding box.
@ -154,7 +154,7 @@ class Draw:
""" """
self.render("pieslice", xy, start, end, *options) self.render("pieslice", xy, start, end, *options)
def polygon(self, xy: Coords, *options) -> None: def polygon(self, xy: Coords, *options: Any) -> None:
""" """
Draws a polygon. Draws a polygon.

View File

@ -38,6 +38,7 @@ import re
import struct import struct
import warnings import warnings
import zlib import zlib
from collections.abc import Callable
from enum import IntEnum from enum import IntEnum
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
@ -135,7 +136,7 @@ class Blend(IntEnum):
""" """
def _safe_zlib_decompress(s): def _safe_zlib_decompress(s: bytes) -> bytes:
dobj = zlib.decompressobj() dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK) plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail: if dobj.unconsumed_tail:
@ -783,7 +784,7 @@ class PngImageFile(ImageFile.ImageFile):
self._mode = self.png.im_mode self._mode = self.png.im_mode
self._size = self.png.im_size self._size = self.png.im_size
self.info = self.png.im_info self.info = self.png.im_info
self._text = None self._text: dict[str, str | iTXt] | None = None
self.tile = self.png.im_tile self.tile = self.png.im_tile
self.custom_mimetype = self.png.im_custom_mimetype self.custom_mimetype = self.png.im_custom_mimetype
self.n_frames = self.png.im_n_frames or 1 self.n_frames = self.png.im_n_frames or 1
@ -810,7 +811,7 @@ class PngImageFile(ImageFile.ImageFile):
self.is_animated = self.n_frames > 1 self.is_animated = self.n_frames > 1
@property @property
def text(self): def text(self) -> dict[str, str | iTXt]:
# experimental # experimental
if self._text is None: if self._text is None:
# iTxt, tEXt and zTXt chunks may appear at the end of the file # iTxt, tEXt and zTXt chunks may appear at the end of the file
@ -822,6 +823,7 @@ class PngImageFile(ImageFile.ImageFile):
self.load() self.load()
if self.is_animated: if self.is_animated:
self.seek(frame) self.seek(frame)
assert self._text is not None
return self._text return self._text
def verify(self) -> None: def verify(self) -> None:
@ -1105,7 +1107,7 @@ def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
class _idat: class _idat:
# wrap output from the encoder in IDAT chunks # wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk) -> None: def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None:
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
@ -1116,7 +1118,7 @@ class _idat:
class _fdat: class _fdat:
# wrap encoder output in fdAT chunks # wrap encoder output in fdAT chunks
def __init__(self, fp: IO[bytes], chunk, seq_num: int) -> None: def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None:
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
self.seq_num = seq_num self.seq_num = seq_num
@ -1135,7 +1137,7 @@ class _Frame(NamedTuple):
def _write_multiple_frames( def _write_multiple_frames(
im: Image.Image, im: Image.Image,
fp: IO[bytes], fp: IO[bytes],
chunk, chunk: Callable[..., None],
mode: str, mode: str,
rawmode: str, rawmode: str,
default_image: Image.Image | None, default_image: Image.Image | None,
@ -1275,7 +1277,11 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
def _save( def _save(
im: Image.Image, fp, filename: str | bytes, chunk=putchunk, save_all: bool = False im: Image.Image,
fp: IO[bytes],
filename: str | bytes,
chunk: Callable[..., None] = putchunk,
save_all: bool = False,
) -> None: ) -> None:
# save an image to disk (called by the save method) # save an image to disk (called by the save method)
@ -1483,22 +1489,16 @@ def _save(
def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]: def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]:
"""Return a list of PNG chunks representing this image.""" """Return a list of PNG chunks representing this image."""
from io import BytesIO
class collector: chunks = []
data = []
def write(self, data: bytes) -> None: def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
pass
def append(self, chunk: tuple[bytes, bytes, bytes]) -> None:
self.data.append(chunk)
def append(fp: collector, cid: bytes, *data: bytes) -> None:
byte_data = b"".join(data) byte_data = b"".join(data)
crc = o32(_crc32(byte_data, _crc32(cid))) crc = o32(_crc32(byte_data, _crc32(cid)))
fp.append((cid, byte_data, crc)) chunks.append((cid, byte_data, crc))
fp = collector() fp = BytesIO()
try: try:
im.encoderinfo = params im.encoderinfo = params
@ -1506,7 +1506,7 @@ def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]
finally: finally:
del im.encoderinfo del im.encoderinfo
return fp.data return chunks
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -1020,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0 .. deprecated:: 3.0.0
""" """
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._legacy_api = True self._legacy_api = True
@ -1142,13 +1142,15 @@ class TiffImageFile(ImageFile.ImageFile):
self._seek(0) self._seek(0)
@property @property
def n_frames(self): def n_frames(self) -> int:
if self._n_frames is None: current_n_frames = self._n_frames
if current_n_frames is None:
current = self.tell() current = self.tell()
self._seek(len(self._frame_pos)) self._seek(len(self._frame_pos))
while self._n_frames is None: while self._n_frames is None:
self._seek(self.tell() + 1) self._seek(self.tell() + 1)
self.seek(current) self.seek(current)
assert self._n_frames is not None
return self._n_frames return self._n_frames
def seek(self, frame: int) -> None: def seek(self, frame: int) -> None: