Skip to content

Commit 5d3338f

Browse files
authored
Merge pull request #8134 from radarhere/type_hint
2 parents cde0524 + be73b13 commit 5d3338f

File tree

8 files changed

+95
-57
lines changed

8 files changed

+95
-57
lines changed

src/PIL/BdfFontFile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def bdf_char(
103103
class BdfFontFile(FontFile.FontFile):
104104
"""Font file plugin for the X11 BDF format."""
105105

106-
def __init__(self, fp: BinaryIO):
106+
def __init__(self, fp: BinaryIO) -> None:
107107
super().__init__()
108108

109109
s = fp.readline()

src/PIL/ImageDraw.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,16 @@
3434
import math
3535
import numbers
3636
import struct
37+
from types import ModuleType
3738
from typing import TYPE_CHECKING, AnyStr, Sequence, cast
3839

3940
from . import Image, ImageColor
4041
from ._deprecate import deprecate
4142
from ._typing import Coords
4243

44+
if TYPE_CHECKING:
45+
from . import ImageDraw2, ImageFont
46+
4347
"""
4448
A simple 2D drawing interface for PIL images.
4549
<p>
@@ -93,9 +97,6 @@ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
9397
self.fontmode = "L" # aliasing is okay for other modes
9498
self.fill = False
9599

96-
if TYPE_CHECKING:
97-
from . import ImageFont
98-
99100
def getfont(
100101
self,
101102
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
@@ -879,7 +880,7 @@ def multiline_textbbox(
879880
return bbox
880881

881882

882-
def Draw(im, mode: str | None = None) -> ImageDraw:
883+
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
883884
"""
884885
A simple 2D drawing interface for PIL images.
885886
@@ -891,7 +892,7 @@ def Draw(im, mode: str | None = None) -> ImageDraw:
891892
defaults to the mode of the image.
892893
"""
893894
try:
894-
return im.getdraw(mode)
895+
return getattr(im, "getdraw")(mode)
895896
except AttributeError:
896897
return ImageDraw(im, mode)
897898

@@ -903,7 +904,9 @@ def Draw(im, mode: str | None = None) -> ImageDraw:
903904
Outline = None
904905

905906

906-
def getdraw(im=None, hints=None):
907+
def getdraw(
908+
im: Image.Image | None = None, hints: list[str] | None = None
909+
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
907910
"""
908911
:param im: The image to draw in.
909912
:param hints: An optional list of hints. Deprecated.
@@ -913,9 +916,8 @@ def getdraw(im=None, hints=None):
913916
deprecate("'hints' parameter", 12)
914917
from . import ImageDraw2
915918

916-
if im:
917-
im = ImageDraw2.Draw(im)
918-
return im, ImageDraw2
919+
draw = ImageDraw2.Draw(im) if im is not None else None
920+
return draw, ImageDraw2
919921

920922

921923
def floodfill(

src/PIL/ImagePalette.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def palette(self, palette):
5454
self._palette = palette
5555

5656
@property
57-
def colors(self):
57+
def colors(self) -> dict[tuple[int, int, int] | tuple[int, int, int, int], int]:
5858
if self._colors is None:
5959
mode_len = len(self.mode)
6060
self._colors = {}
@@ -66,7 +66,9 @@ def colors(self):
6666
return self._colors
6767

6868
@colors.setter
69-
def colors(self, colors):
69+
def colors(
70+
self, colors: dict[tuple[int, int, int] | tuple[int, int, int, int], int]
71+
) -> None:
7072
self._colors = colors
7173

7274
def copy(self) -> ImagePalette:
@@ -107,11 +109,13 @@ def tobytes(self) -> bytes:
107109
# Declare tostring as an alias for tobytes
108110
tostring = tobytes
109111

110-
def _new_color_index(self, image=None, e=None):
112+
def _new_color_index(
113+
self, image: Image.Image | None = None, e: Exception | None = None
114+
) -> int:
111115
if not isinstance(self.palette, bytearray):
112116
self._palette = bytearray(self.palette)
113117
index = len(self.palette) // 3
114-
special_colors = ()
118+
special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
115119
if image:
116120
special_colors = (
117121
image.info.get("background"),

src/PIL/MicImagePlugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def _open(self) -> None:
6363
msg = "not an MIC file; no image entries"
6464
raise SyntaxError(msg)
6565

66-
self.frame = None
66+
self.frame = -1
6767
self._n_frames = len(self.images)
6868
self.is_animated = self._n_frames > 1
6969

@@ -85,7 +85,7 @@ def seek(self, frame):
8585

8686
self.frame = frame
8787

88-
def tell(self):
88+
def tell(self) -> int:
8989
return self.frame
9090

9191
def close(self) -> None:

src/PIL/PngImagePlugin.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
import warnings
4040
import zlib
4141
from enum import IntEnum
42-
from typing import IO, Any
42+
from typing import IO, TYPE_CHECKING, Any, NoReturn
4343

4444
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
4545
from ._binary import i16be as i16
@@ -48,6 +48,9 @@
4848
from ._binary import o16be as o16
4949
from ._binary import o32be as o32
5050

51+
if TYPE_CHECKING:
52+
from . import _imaging
53+
5154
logger = logging.getLogger(__name__)
5255

5356
is_cid = re.compile(rb"\w\w\w\w").match
@@ -249,6 +252,9 @@ class iTXt(str):
249252
250253
"""
251254

255+
lang: str | bytes | None
256+
tkey: str | bytes | None
257+
252258
@staticmethod
253259
def __new__(cls, text, lang=None, tkey=None):
254260
"""
@@ -270,10 +276,10 @@ class PngInfo:
270276
271277
"""
272278

273-
def __init__(self):
274-
self.chunks = []
279+
def __init__(self) -> None:
280+
self.chunks: list[tuple[bytes, bytes, bool]] = []
275281

276-
def add(self, cid, data, after_idat=False):
282+
def add(self, cid: bytes, data: bytes, after_idat: bool = False) -> None:
277283
"""Appends an arbitrary chunk. Use with caution.
278284
279285
:param cid: a byte string, 4 bytes long.
@@ -283,12 +289,16 @@ def add(self, cid, data, after_idat=False):
283289
284290
"""
285291

286-
chunk = [cid, data]
287-
if after_idat:
288-
chunk.append(True)
289-
self.chunks.append(tuple(chunk))
292+
self.chunks.append((cid, data, after_idat))
290293

291-
def add_itxt(self, key, value, lang="", tkey="", zip=False):
294+
def add_itxt(
295+
self,
296+
key: str | bytes,
297+
value: str | bytes,
298+
lang: str | bytes = "",
299+
tkey: str | bytes = "",
300+
zip: bool = False,
301+
) -> None:
292302
"""Appends an iTXt chunk.
293303
294304
:param key: latin-1 encodable text key name
@@ -316,7 +326,9 @@ def add_itxt(self, key, value, lang="", tkey="", zip=False):
316326
else:
317327
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)
318328

319-
def add_text(self, key, value, zip=False):
329+
def add_text(
330+
self, key: str | bytes, value: str | bytes | iTXt, zip: bool = False
331+
) -> None:
320332
"""Appends a text chunk.
321333
322334
:param key: latin-1 encodable text key name
@@ -326,7 +338,13 @@ def add_text(self, key, value, zip=False):
326338
327339
"""
328340
if isinstance(value, iTXt):
329-
return self.add_itxt(key, value, value.lang, value.tkey, zip=zip)
341+
return self.add_itxt(
342+
key,
343+
value,
344+
value.lang if value.lang is not None else b"",
345+
value.tkey if value.tkey is not None else b"",
346+
zip=zip,
347+
)
330348

331349
# The tEXt chunk stores latin-1 text
332350
if not isinstance(value, bytes):
@@ -434,7 +452,7 @@ def chunk_IHDR(self, pos: int, length: int) -> bytes:
434452
raise SyntaxError(msg)
435453
return s
436454

437-
def chunk_IDAT(self, pos, length):
455+
def chunk_IDAT(self, pos: int, length: int) -> NoReturn:
438456
# image data
439457
if "bbox" in self.im_info:
440458
tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
@@ -447,7 +465,7 @@ def chunk_IDAT(self, pos, length):
447465
msg = "image data found"
448466
raise EOFError(msg)
449467

450-
def chunk_IEND(self, pos, length):
468+
def chunk_IEND(self, pos: int, length: int) -> NoReturn:
451469
msg = "end of PNG image"
452470
raise EOFError(msg)
453471

@@ -821,7 +839,10 @@ def seek(self, frame: int) -> None:
821839
msg = "no more images in APNG file"
822840
raise EOFError(msg) from e
823841

824-
def _seek(self, frame, rewind=False):
842+
def _seek(self, frame: int, rewind: bool = False) -> None:
843+
assert self.png is not None
844+
845+
self.dispose: _imaging.ImagingCore | None
825846
if frame == 0:
826847
if rewind:
827848
self._fp.seek(self.__rewind)
@@ -906,14 +927,14 @@ def _seek(self, frame, rewind=False):
906927
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
907928
self.dispose_op = Disposal.OP_BACKGROUND
908929

930+
self.dispose = None
909931
if self.dispose_op == Disposal.OP_PREVIOUS:
910-
self.dispose = self._prev_im.copy()
911-
self.dispose = self._crop(self.dispose, self.dispose_extent)
932+
if self._prev_im:
933+
self.dispose = self._prev_im.copy()
934+
self.dispose = self._crop(self.dispose, self.dispose_extent)
912935
elif self.dispose_op == Disposal.OP_BACKGROUND:
913936
self.dispose = Image.core.fill(self.mode, self.size)
914937
self.dispose = self._crop(self.dispose, self.dispose_extent)
915-
else:
916-
self.dispose = None
917938

918939
def tell(self) -> int:
919940
return self.__frame
@@ -1026,7 +1047,7 @@ def _getexif(self) -> dict[str, Any] | None:
10261047
return None
10271048
return self.getexif()._get_merged_dict()
10281049

1029-
def getexif(self):
1050+
def getexif(self) -> Image.Exif:
10301051
if "exif" not in self.info:
10311052
self.load()
10321053

@@ -1346,7 +1367,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
13461367
chunk(fp, cid, data)
13471368
elif cid[1:2].islower():
13481369
# Private chunk
1349-
after_idat = info_chunk[2:3]
1370+
after_idat = len(info_chunk) == 3 and info_chunk[2]
13501371
if not after_idat:
13511372
chunk(fp, cid, data)
13521373

@@ -1425,7 +1446,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
14251446
cid, data = info_chunk[:2]
14261447
if cid[1:2].islower():
14271448
# Private chunk
1428-
after_idat = info_chunk[2:3]
1449+
after_idat = len(info_chunk) == 3 and info_chunk[2]
14291450
if after_idat:
14301451
chunk(fp, cid, data)
14311452

src/PIL/TiffImagePlugin.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
from collections.abc import MutableMapping
5151
from fractions import Fraction
5252
from numbers import Number, Rational
53-
from typing import IO, TYPE_CHECKING, Any, Callable
53+
from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn
5454

5555
from . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
5656
from ._binary import i16be as i16
@@ -384,7 +384,7 @@ def limit_rational(self, max_denominator):
384384
def __repr__(self) -> str:
385385
return str(float(self._val))
386386

387-
def __hash__(self):
387+
def __hash__(self) -> int:
388388
return self._val.__hash__()
389389

390390
def __eq__(self, other: object) -> bool:
@@ -551,7 +551,12 @@ class ImageFileDirectory_v2(_IFDv2Base):
551551
_load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {}
552552
_write_dispatch: dict[int, Callable[..., Any]] = {}
553553

554-
def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
554+
def __init__(
555+
self,
556+
ifh: bytes = b"II\052\0\0\0\0\0",
557+
prefix: bytes | None = None,
558+
group: int | None = None,
559+
) -> None:
555560
"""Initialize an ImageFileDirectory.
556561
557562
To construct an ImageFileDirectory from a real file, pass the 8-byte
@@ -575,7 +580,7 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
575580
raise SyntaxError(msg)
576581
self._bigtiff = ifh[2] == 43
577582
self.group = group
578-
self.tagtype = {}
583+
self.tagtype: dict[int, int] = {}
579584
""" Dictionary of tag types """
580585
self.reset()
581586
(self.next,) = (
@@ -587,18 +592,18 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
587592
offset = property(lambda self: self._offset)
588593

589594
@property
590-
def legacy_api(self):
595+
def legacy_api(self) -> bool:
591596
return self._legacy_api
592597

593598
@legacy_api.setter
594-
def legacy_api(self, value):
599+
def legacy_api(self, value: bool) -> NoReturn:
595600
msg = "Not allowing setting of legacy api"
596601
raise Exception(msg)
597602

598-
def reset(self):
599-
self._tags_v1 = {} # will remain empty if legacy_api is false
600-
self._tags_v2 = {} # main tag storage
601-
self._tagdata = {}
603+
def reset(self) -> None:
604+
self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false
605+
self._tags_v2: dict[int, Any] = {} # main tag storage
606+
self._tagdata: dict[int, bytes] = {}
602607
self.tagtype = {} # added 2008-06-05 by Florian Hoech
603608
self._next = None
604609
self._offset = None
@@ -2039,7 +2044,7 @@ def skipIFDs(self) -> None:
20392044
num_tags = self.readShort()
20402045
self.f.seek(num_tags * 12, os.SEEK_CUR)
20412046

2042-
def write(self, data):
2047+
def write(self, data: bytes) -> int | None:
20432048
return self.f.write(data)
20442049

20452050
def readShort(self) -> int:
@@ -2122,7 +2127,9 @@ def fixIFD(self) -> None:
21222127
# skip the locally stored value that is not an offset
21232128
self.f.seek(4, os.SEEK_CUR)
21242129

2125-
def fixOffsets(self, count, isShort=False, isLong=False):
2130+
def fixOffsets(
2131+
self, count: int, isShort: bool = False, isLong: bool = False
2132+
) -> None:
21262133
if not isShort and not isLong:
21272134
msg = "offset is neither short nor long"
21282135
raise RuntimeError(msg)

src/PIL/_imaging.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ class ImagingDraw:
1212
class PixelAccess:
1313
def __getattr__(self, name: str) -> Any: ...
1414

15-
def font(image, glyphdata: bytes) -> ImagingFont: ...
15+
def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
1616
def __getattr__(name: str) -> Any: ...

0 commit comments

Comments
 (0)