Added type hints

This commit is contained in:
Andrew Murray 2024-07-30 20:20:09 +10:00
parent 5833a8b18e
commit accfaf1c09
9 changed files with 101 additions and 53 deletions

View File

@ -154,7 +154,7 @@ class TestFileJpeg:
assert k > 0.9
def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, int, int]:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
return tuple(v[0] for v in im.layer)
im = hopper()

View File

@ -99,7 +99,7 @@ class TestImage:
im = Image.new("L", (100, 100))
p = Pretty()
im._repr_pretty_(p, None)
im._repr_pretty_(p, False)
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"
def test_open_formats(self) -> None:

View File

@ -185,6 +185,14 @@ Plugin reference
:undoc-members:
:show-inheritance:
:mod:`~PIL.MpoImagePlugin` Module
----------------------------------
.. automodule:: PIL.MpoImagePlugin
:members:
:undoc-members:
:show-inheritance:
:mod:`~PIL.MspImagePlugin` Module
---------------------------------

View File

@ -65,16 +65,24 @@ def has_ghostscript() -> bool:
return gs_binary is not False
def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
def Ghostscript(
tile: list[ImageFile._Tile],
size: tuple[int, int],
fp: IO[bytes],
scale: int = 1,
transparency: bool = False,
) -> Image.Image:
"""Render an image using Ghostscript"""
global gs_binary
if not has_ghostscript():
msg = "Unable to locate Ghostscript on paths"
raise OSError(msg)
assert isinstance(gs_binary, str)
# Unpack decoder tile
decoder, tile, offset, data = tile[0]
length, bbox = data
args = tile[0].args
assert isinstance(args, tuple)
length, bbox = args
# Hack to support hi-res rendering
scale = int(scale) or 1
@ -227,7 +235,11 @@ class EpsImageFile(ImageFile.ImageFile):
# put floating point values there anyway.
box = [int(float(i)) for i in v.split()]
self._size = box[2] - box[0], box[3] - box[1]
self.tile = [("eps", (0, 0) + self.size, offset, (length, box))]
self.tile = [
ImageFile._Tile(
"eps", (0, 0) + self.size, offset, (length, box)
)
]
except Exception:
pass
return True

View File

@ -220,7 +220,7 @@ if hasattr(core, "DEFAULT_STRATEGY"):
if TYPE_CHECKING:
from xml.etree.ElementTree import Element
from . import ImageFile, ImagePalette
from . import ImageFile, ImagePalette, TiffImagePlugin
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
ID: list[str] = []
OPEN: dict[
@ -676,7 +676,7 @@ class Image:
id(self),
)
def _repr_pretty_(self, p, cycle) -> None:
def _repr_pretty_(self, p, cycle: bool) -> None:
"""IPython plain text display support"""
# Same as __repr__ but without unpredictable id(self),
@ -1551,6 +1551,7 @@ class Image:
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
if ifd1 and ifd1.get(513):
assert exif._info is not None
ifds.append((ifd1, exif._info.next))
offset = None
@ -1560,12 +1561,13 @@ class Image:
offset = current_offset
fp = self.fp
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
fp = io.BytesIO(data)
if ifd is not None:
thumbnail_offset = ifd.get(513)
if thumbnail_offset is not None:
thumbnail_offset += getattr(self, "_exif_offset", 0)
self.fp.seek(thumbnail_offset)
data = self.fp.read(ifd.get(514))
fp = io.BytesIO(data)
with open(fp) as im:
from . import TiffImagePlugin
@ -3869,14 +3871,14 @@ class Exif(_ExifBase):
bigtiff = False
_loaded = False
def __init__(self):
self._data = {}
self._hidden_data = {}
self._ifds = {}
self._info = None
self._loaded_exif = None
def __init__(self) -> None:
self._data: dict[int, Any] = {}
self._hidden_data: dict[int, Any] = {}
self._ifds: dict[int, dict[int, Any]] = {}
self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None
self._loaded_exif: bytes | None = None
def _fixup(self, value):
def _fixup(self, value: Any) -> Any:
try:
if len(value) == 1 and isinstance(value, tuple):
return value[0]
@ -3884,24 +3886,26 @@ class Exif(_ExifBase):
pass
return value
def _fixup_dict(self, src_dict):
def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]:
# Helper function
# returns a dict with any single item tuples/lists as individual values
return {k: self._fixup(v) for k, v in src_dict.items()}
def _get_ifd_dict(self, offset: int, group: int | None = None):
def _get_ifd_dict(
self, offset: int, group: int | None = None
) -> dict[int, Any] | None:
try:
# an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted.
self.fp.seek(offset)
except (KeyError, TypeError):
pass
return None
else:
from . import TiffImagePlugin
info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group)
info.load(self.fp)
return self._fixup_dict(info)
return self._fixup_dict(dict(info))
def _get_head(self) -> bytes:
version = b"\x2B" if self.bigtiff else b"\x2A"
@ -3966,7 +3970,7 @@ class Exif(_ExifBase):
self.fp.seek(offset)
self._info.load(self.fp)
def _get_merged_dict(self):
def _get_merged_dict(self) -> dict[int, Any]:
merged_dict = dict(self)
# get EXIF extension
@ -4124,7 +4128,7 @@ class Exif(_ExifBase):
keys.update(self._info)
return len(keys)
def __getitem__(self, tag: int):
def __getitem__(self, tag: int) -> Any:
if self._info is not None and tag not in self._data and tag in self._info:
self._data[tag] = self._fixup(self._info[tag])
del self._info[tag]
@ -4133,7 +4137,7 @@ class Exif(_ExifBase):
def __contains__(self, tag: object) -> bool:
return tag in self._data or (self._info is not None and tag in self._info)
def __setitem__(self, tag: int, value) -> None:
def __setitem__(self, tag: int, value: Any) -> None:
if self._info is not None and tag in self._info:
del self._info[tag]
self._data[tag] = value

View File

@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
raise _get_oserror(error, encoder=False)
def _tilesort(t) -> int:
def _tilesort(t: _Tile) -> int:
# sort on offset
return t[2]
@ -161,7 +161,7 @@ class ImageFile(Image.Image):
return Image.MIME.get(self.format.upper())
return None
def __setstate__(self, state) -> None:
def __setstate__(self, state: list[Any]) -> None:
self.tile = []
super().__setstate__(state)
@ -525,7 +525,7 @@ class Parser:
# --------------------------------------------------------------------
def _save(im, fp, tile, bufsize: int = 0) -> None:
def _save(im: Image.Image, fp: IO[bytes], tile, bufsize: int = 0) -> None:
"""Helper to save image based on tile list
:param im: Image object.
@ -554,7 +554,12 @@ def _save(im, fp, tile, bufsize: int = 0) -> None:
def _encode_tile(
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
im: Image.Image,
fp: IO[bytes],
tile: list[_Tile],
bufsize: int,
fh,
exc: BaseException | None = None,
) -> None:
for encoder_name, extents, offset, args in tile:
if offset > 0:
@ -664,7 +669,11 @@ class PyCodec:
"""
self.fd = fd
def setimage(self, im, extents=None):
def setimage(
self,
im: Image.core.ImagingCore,
extents: tuple[int, int, int, int] | None = None,
) -> None:
"""
Called from ImageFile to set the core output image for the codec

View File

@ -42,7 +42,7 @@ import subprocess
import sys
import tempfile
import warnings
from typing import IO, Any
from typing import IO, TYPE_CHECKING, Any
from . import Image, ImageFile
from ._binary import i16be as i16
@ -51,6 +51,9 @@ from ._binary import o8
from ._binary import o16be as o16
from .JpegPresets import presets
if TYPE_CHECKING:
from .MpoImagePlugin import MpoImageFile
#
# Parser
@ -329,7 +332,7 @@ class JpegImageFile(ImageFile.ImageFile):
format = "JPEG"
format_description = "JPEG (ISO 10918)"
def _open(self):
def _open(self) -> None:
s = self.fp.read(3)
if not _accept(s):
@ -342,13 +345,13 @@ class JpegImageFile(ImageFile.ImageFile):
self._exif_offset = 0
# JPEG specifics (internal)
self.layer = []
self.huffman_dc = {}
self.huffman_ac = {}
self.quantization = {}
self.app = {} # compatibility
self.applist = []
self.icclist = []
self.layer: list[tuple[int, int, int, int]] = []
self.huffman_dc: dict[Any, Any] = {}
self.huffman_ac: dict[Any, Any] = {}
self.quantization: dict[int, list[int]] = {}
self.app: dict[str, bytes] = {} # compatibility
self.applist: list[tuple[str, bytes]] = []
self.icclist: list[bytes] = []
while True:
i = s[0]
@ -831,7 +834,9 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
def jpeg_factory(
fp: IO[bytes] | None = None, filename: str | bytes | None = None
) -> JpegImageFile | MpoImageFile:
im = JpegImageFile(fp, filename)
try:
mpheader = im._getmp()

View File

@ -40,7 +40,7 @@ import warnings
import zlib
from collections.abc import Callable
from enum import IntEnum
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn, cast
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
@ -1223,7 +1223,11 @@ def _write_multiple_frames(
if default_image:
if im.mode != mode:
im = im.convert(mode)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
ImageFile._save(
im,
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + im.size, 0, rawmode)],
)
seq_num = 0
for frame, frame_data in enumerate(im_frames):
@ -1258,14 +1262,14 @@ def _write_multiple_frames(
# first frame must be in IDAT chunks for backwards compatibility
ImageFile._save(
im_frame,
_idat(fp, chunk),
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
else:
fdat_chunks = _fdat(fp, chunk, seq_num)
ImageFile._save(
im_frame,
fdat_chunks,
cast(IO[bytes], fdat_chunks),
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
)
seq_num = fdat_chunks.seq_num
@ -1465,7 +1469,9 @@ def _save(
)
if single_im:
ImageFile._save(
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
single_im,
cast(IO[bytes], _idat(fp, chunk)),
[("zip", (0, 0) + single_im.size, 0, rawmode)],
)
if info:

View File

@ -288,8 +288,10 @@ def _accept(prefix: bytes) -> bool:
return prefix[:4] in PREFIXES
def _limit_rational(val, max_val):
inv = abs(val) > 1
def _limit_rational(
val: float | Fraction | IFDRational, max_val: int
) -> tuple[float, float]:
inv = abs(float(val)) > 1
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
return n_d[::-1] if inv else n_d
@ -792,7 +794,9 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value + b"\0"
@_register_loader(5, 8)
def load_rational(self, data, legacy_api: bool = True):
def load_rational(
self, data: bytes, legacy_api: bool = True
) -> tuple[tuple[int, int] | IFDRational, ...]:
vals = self._unpack(f"{len(data) // 4}L", data)
def combine(a: int, b: int) -> tuple[int, int] | IFDRational:
@ -801,7 +805,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5)
def write_rational(self, *values) -> bytes:
def write_rational(self, *values: IFDRational) -> bytes:
return b"".join(
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
)
@ -828,7 +832,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10)
def write_signed_rational(self, *values) -> bytes:
def write_signed_rational(self, *values: IFDRational) -> bytes:
return b"".join(
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
for frac in values