Skip to content

Commit 4df4df2

Browse files
authored
Merge pull request #8270 from radarhere/type_hint
2 parents 5833a8b + accfaf1 commit 4df4df2

File tree

9 files changed

+101
-53
lines changed

9 files changed

+101
-53
lines changed

Tests/test_file_jpeg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def test_cmyk(self) -> None:
154154
assert k > 0.9
155155

156156
def test_rgb(self) -> None:
157-
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, int, int]:
157+
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
158158
return tuple(v[0] for v in im.layer)
159159

160160
im = hopper()

Tests/test_image.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ def text(self, text: str) -> None:
9999
im = Image.new("L", (100, 100))
100100

101101
p = Pretty()
102-
im._repr_pretty_(p, None)
102+
im._repr_pretty_(p, False)
103103
assert p.pretty_output == "<PIL.Image.Image image mode=L size=100x100>"
104104

105105
def test_open_formats(self) -> None:

docs/reference/plugins.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ Plugin reference
185185
:undoc-members:
186186
:show-inheritance:
187187

188+
:mod:`~PIL.MpoImagePlugin` Module
189+
----------------------------------
190+
191+
.. automodule:: PIL.MpoImagePlugin
192+
:members:
193+
:undoc-members:
194+
:show-inheritance:
195+
188196
:mod:`~PIL.MspImagePlugin` Module
189197
---------------------------------
190198

src/PIL/EpsImagePlugin.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,24 @@ def has_ghostscript() -> bool:
6565
return gs_binary is not False
6666

6767

68-
def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
68+
def Ghostscript(
69+
tile: list[ImageFile._Tile],
70+
size: tuple[int, int],
71+
fp: IO[bytes],
72+
scale: int = 1,
73+
transparency: bool = False,
74+
) -> Image.Image:
6975
"""Render an image using Ghostscript"""
7076
global gs_binary
7177
if not has_ghostscript():
7278
msg = "Unable to locate Ghostscript on paths"
7379
raise OSError(msg)
80+
assert isinstance(gs_binary, str)
7481

7582
# Unpack decoder tile
76-
decoder, tile, offset, data = tile[0]
77-
length, bbox = data
83+
args = tile[0].args
84+
assert isinstance(args, tuple)
85+
length, bbox = args
7886

7987
# Hack to support hi-res rendering
8088
scale = int(scale) or 1
@@ -227,7 +235,11 @@ def _read_comment(s: str) -> bool:
227235
# put floating point values there anyway.
228236
box = [int(float(i)) for i in v.split()]
229237
self._size = box[2] - box[0], box[3] - box[1]
230-
self.tile = [("eps", (0, 0) + self.size, offset, (length, box))]
238+
self.tile = [
239+
ImageFile._Tile(
240+
"eps", (0, 0) + self.size, offset, (length, box)
241+
)
242+
]
231243
except Exception:
232244
pass
233245
return True

src/PIL/Image.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ class Quantize(IntEnum):
220220
if TYPE_CHECKING:
221221
from xml.etree.ElementTree import Element
222222

223-
from . import ImageFile, ImagePalette
223+
from . import ImageFile, ImagePalette, TiffImagePlugin
224224
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
225225
ID: list[str] = []
226226
OPEN: dict[
@@ -676,7 +676,7 @@ def __repr__(self) -> str:
676676
id(self),
677677
)
678678

679-
def _repr_pretty_(self, p, cycle) -> None:
679+
def _repr_pretty_(self, p, cycle: bool) -> None:
680680
"""IPython plain text display support"""
681681

682682
# Same as __repr__ but without unpredictable id(self),
@@ -1551,6 +1551,7 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
15511551
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
15521552
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
15531553
if ifd1 and ifd1.get(513):
1554+
assert exif._info is not None
15541555
ifds.append((ifd1, exif._info.next))
15551556

15561557
offset = None
@@ -1560,12 +1561,13 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
15601561
offset = current_offset
15611562

15621563
fp = self.fp
1563-
thumbnail_offset = ifd.get(513)
1564-
if thumbnail_offset is not None:
1565-
thumbnail_offset += getattr(self, "_exif_offset", 0)
1566-
self.fp.seek(thumbnail_offset)
1567-
data = self.fp.read(ifd.get(514))
1568-
fp = io.BytesIO(data)
1564+
if ifd is not None:
1565+
thumbnail_offset = ifd.get(513)
1566+
if thumbnail_offset is not None:
1567+
thumbnail_offset += getattr(self, "_exif_offset", 0)
1568+
self.fp.seek(thumbnail_offset)
1569+
data = self.fp.read(ifd.get(514))
1570+
fp = io.BytesIO(data)
15691571

15701572
with open(fp) as im:
15711573
from . import TiffImagePlugin
@@ -3869,39 +3871,41 @@ class Exif(_ExifBase):
38693871
bigtiff = False
38703872
_loaded = False
38713873

3872-
def __init__(self):
3873-
self._data = {}
3874-
self._hidden_data = {}
3875-
self._ifds = {}
3876-
self._info = None
3877-
self._loaded_exif = None
3874+
def __init__(self) -> None:
3875+
self._data: dict[int, Any] = {}
3876+
self._hidden_data: dict[int, Any] = {}
3877+
self._ifds: dict[int, dict[int, Any]] = {}
3878+
self._info: TiffImagePlugin.ImageFileDirectory_v2 | None = None
3879+
self._loaded_exif: bytes | None = None
38783880

3879-
def _fixup(self, value):
3881+
def _fixup(self, value: Any) -> Any:
38803882
try:
38813883
if len(value) == 1 and isinstance(value, tuple):
38823884
return value[0]
38833885
except Exception:
38843886
pass
38853887
return value
38863888

3887-
def _fixup_dict(self, src_dict):
3889+
def _fixup_dict(self, src_dict: dict[int, Any]) -> dict[int, Any]:
38883890
# Helper function
38893891
# returns a dict with any single item tuples/lists as individual values
38903892
return {k: self._fixup(v) for k, v in src_dict.items()}
38913893

3892-
def _get_ifd_dict(self, offset: int, group: int | None = None):
3894+
def _get_ifd_dict(
3895+
self, offset: int, group: int | None = None
3896+
) -> dict[int, Any] | None:
38933897
try:
38943898
# an offset pointer to the location of the nested embedded IFD.
38953899
# It should be a long, but may be corrupted.
38963900
self.fp.seek(offset)
38973901
except (KeyError, TypeError):
3898-
pass
3902+
return None
38993903
else:
39003904
from . import TiffImagePlugin
39013905

39023906
info = TiffImagePlugin.ImageFileDirectory_v2(self.head, group=group)
39033907
info.load(self.fp)
3904-
return self._fixup_dict(info)
3908+
return self._fixup_dict(dict(info))
39053909

39063910
def _get_head(self) -> bytes:
39073911
version = b"\x2B" if self.bigtiff else b"\x2A"
@@ -3966,7 +3970,7 @@ def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
39663970
self.fp.seek(offset)
39673971
self._info.load(self.fp)
39683972

3969-
def _get_merged_dict(self):
3973+
def _get_merged_dict(self) -> dict[int, Any]:
39703974
merged_dict = dict(self)
39713975

39723976
# get EXIF extension
@@ -4124,7 +4128,7 @@ def __len__(self) -> int:
41244128
keys.update(self._info)
41254129
return len(keys)
41264130

4127-
def __getitem__(self, tag: int):
4131+
def __getitem__(self, tag: int) -> Any:
41284132
if self._info is not None and tag not in self._data and tag in self._info:
41294133
self._data[tag] = self._fixup(self._info[tag])
41304134
del self._info[tag]
@@ -4133,7 +4137,7 @@ def __getitem__(self, tag: int):
41334137
def __contains__(self, tag: object) -> bool:
41344138
return tag in self._data or (self._info is not None and tag in self._info)
41354139

4136-
def __setitem__(self, tag: int, value) -> None:
4140+
def __setitem__(self, tag: int, value: Any) -> None:
41374141
if self._info is not None and tag in self._info:
41384142
del self._info[tag]
41394143
self._data[tag] = value

src/PIL/ImageFile.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
8686
raise _get_oserror(error, encoder=False)
8787

8888

89-
def _tilesort(t) -> int:
89+
def _tilesort(t: _Tile) -> int:
9090
# sort on offset
9191
return t[2]
9292

@@ -161,7 +161,7 @@ def get_format_mimetype(self) -> str | None:
161161
return Image.MIME.get(self.format.upper())
162162
return None
163163

164-
def __setstate__(self, state) -> None:
164+
def __setstate__(self, state: list[Any]) -> None:
165165
self.tile = []
166166
super().__setstate__(state)
167167

@@ -525,7 +525,7 @@ def close(self) -> Image.Image:
525525
# --------------------------------------------------------------------
526526

527527

528-
def _save(im, fp, tile, bufsize: int = 0) -> None:
528+
def _save(im: Image.Image, fp: IO[bytes], tile, bufsize: int = 0) -> None:
529529
"""Helper to save image based on tile list
530530
531531
:param im: Image object.
@@ -554,7 +554,12 @@ def _save(im, fp, tile, bufsize: int = 0) -> None:
554554

555555

556556
def _encode_tile(
557-
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
557+
im: Image.Image,
558+
fp: IO[bytes],
559+
tile: list[_Tile],
560+
bufsize: int,
561+
fh,
562+
exc: BaseException | None = None,
558563
) -> None:
559564
for encoder_name, extents, offset, args in tile:
560565
if offset > 0:
@@ -664,7 +669,11 @@ def setfd(self, fd: IO[bytes]) -> None:
664669
"""
665670
self.fd = fd
666671

667-
def setimage(self, im, extents=None):
672+
def setimage(
673+
self,
674+
im: Image.core.ImagingCore,
675+
extents: tuple[int, int, int, int] | None = None,
676+
) -> None:
668677
"""
669678
Called from ImageFile to set the core output image for the codec
670679

src/PIL/JpegImagePlugin.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
import sys
4343
import tempfile
4444
import warnings
45-
from typing import IO, Any
45+
from typing import IO, TYPE_CHECKING, Any
4646

4747
from . import Image, ImageFile
4848
from ._binary import i16be as i16
@@ -51,6 +51,9 @@
5151
from ._binary import o16be as o16
5252
from .JpegPresets import presets
5353

54+
if TYPE_CHECKING:
55+
from .MpoImagePlugin import MpoImageFile
56+
5457
#
5558
# Parser
5659

@@ -329,7 +332,7 @@ class JpegImageFile(ImageFile.ImageFile):
329332
format = "JPEG"
330333
format_description = "JPEG (ISO 10918)"
331334

332-
def _open(self):
335+
def _open(self) -> None:
333336
s = self.fp.read(3)
334337

335338
if not _accept(s):
@@ -342,13 +345,13 @@ def _open(self):
342345
self._exif_offset = 0
343346

344347
# JPEG specifics (internal)
345-
self.layer = []
346-
self.huffman_dc = {}
347-
self.huffman_ac = {}
348-
self.quantization = {}
349-
self.app = {} # compatibility
350-
self.applist = []
351-
self.icclist = []
348+
self.layer: list[tuple[int, int, int, int]] = []
349+
self.huffman_dc: dict[Any, Any] = {}
350+
self.huffman_ac: dict[Any, Any] = {}
351+
self.quantization: dict[int, list[int]] = {}
352+
self.app: dict[str, bytes] = {} # compatibility
353+
self.applist: list[tuple[str, bytes]] = []
354+
self.icclist: list[bytes] = []
352355

353356
while True:
354357
i = s[0]
@@ -831,7 +834,9 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
831834

832835
##
833836
# Factory for making JPEG and MPO instances
834-
def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
837+
def jpeg_factory(
838+
fp: IO[bytes] | None = None, filename: str | bytes | None = None
839+
) -> JpegImageFile | MpoImageFile:
835840
im = JpegImageFile(fp, filename)
836841
try:
837842
mpheader = im._getmp()

src/PIL/PngImagePlugin.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import zlib
4141
from collections.abc import Callable
4242
from enum import IntEnum
43-
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
43+
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn, cast
4444

4545
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
4646
from ._binary import i16be as i16
@@ -1223,7 +1223,11 @@ def _write_multiple_frames(
12231223
if default_image:
12241224
if im.mode != mode:
12251225
im = im.convert(mode)
1226-
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
1226+
ImageFile._save(
1227+
im,
1228+
cast(IO[bytes], _idat(fp, chunk)),
1229+
[("zip", (0, 0) + im.size, 0, rawmode)],
1230+
)
12271231

12281232
seq_num = 0
12291233
for frame, frame_data in enumerate(im_frames):
@@ -1258,14 +1262,14 @@ def _write_multiple_frames(
12581262
# first frame must be in IDAT chunks for backwards compatibility
12591263
ImageFile._save(
12601264
im_frame,
1261-
_idat(fp, chunk),
1265+
cast(IO[bytes], _idat(fp, chunk)),
12621266
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
12631267
)
12641268
else:
12651269
fdat_chunks = _fdat(fp, chunk, seq_num)
12661270
ImageFile._save(
12671271
im_frame,
1268-
fdat_chunks,
1272+
cast(IO[bytes], fdat_chunks),
12691273
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
12701274
)
12711275
seq_num = fdat_chunks.seq_num
@@ -1465,7 +1469,9 @@ def _save(
14651469
)
14661470
if single_im:
14671471
ImageFile._save(
1468-
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
1472+
single_im,
1473+
cast(IO[bytes], _idat(fp, chunk)),
1474+
[("zip", (0, 0) + single_im.size, 0, rawmode)],
14691475
)
14701476

14711477
if info:

0 commit comments

Comments
 (0)