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

Merged
merged 1 commit into from
Jun 18, 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
2 changes: 1 addition & 1 deletion src/PIL/BdfFontFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def bdf_char(
class BdfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 BDF format."""

def __init__(self, fp: BinaryIO):
def __init__(self, fp: BinaryIO) -> None:
super().__init__()

s = fp.readline()
Expand Down
20 changes: 11 additions & 9 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@
import math
import numbers
import struct
from types import ModuleType
from typing import TYPE_CHECKING, AnyStr, Sequence, cast

from . import Image, ImageColor
from ._deprecate import deprecate
from ._typing import Coords

if TYPE_CHECKING:
from . import ImageDraw2, ImageFont

"""
A simple 2D drawing interface for PIL images.
<p>
Expand Down Expand Up @@ -93,9 +97,6 @@ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = False

if TYPE_CHECKING:
from . import ImageFont

def getfont(
self,
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
Expand Down Expand Up @@ -879,7 +880,7 @@ def multiline_textbbox(
return bbox


def Draw(im, mode: str | None = None) -> ImageDraw:
def Draw(im: Image.Image, mode: str | None = None) -> ImageDraw:
"""
A simple 2D drawing interface for PIL images.

Expand All @@ -891,7 +892,7 @@ def Draw(im, mode: str | None = None) -> ImageDraw:
defaults to the mode of the image.
"""
try:
return im.getdraw(mode)
return getattr(im, "getdraw")(mode)
except AttributeError:
return ImageDraw(im, mode)

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


def getdraw(im=None, hints=None):
def getdraw(
im: Image.Image | None = None, hints: list[str] | None = None
) -> tuple[ImageDraw2.Draw | None, ModuleType]:
"""
:param im: The image to draw in.
:param hints: An optional list of hints. Deprecated.
Expand All @@ -913,9 +916,8 @@ def getdraw(im=None, hints=None):
deprecate("'hints' parameter", 12)
from . import ImageDraw2

if im:
im = ImageDraw2.Draw(im)
return im, ImageDraw2
draw = ImageDraw2.Draw(im) if im is not None else None
return draw, ImageDraw2


def floodfill(
Expand Down
12 changes: 8 additions & 4 deletions src/PIL/ImagePalette.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def palette(self, palette):
self._palette = palette

@property
def colors(self):
def colors(self) -> dict[tuple[int, int, int] | tuple[int, int, int, int], int]:
if self._colors is None:
mode_len = len(self.mode)
self._colors = {}
Expand All @@ -66,7 +66,9 @@ def colors(self):
return self._colors

@colors.setter
def colors(self, colors):
def colors(
self, colors: dict[tuple[int, int, int] | tuple[int, int, int, int], int]
) -> None:
self._colors = colors

def copy(self) -> ImagePalette:
Expand Down Expand Up @@ -107,11 +109,13 @@ def tobytes(self) -> bytes:
# Declare tostring as an alias for tobytes
tostring = tobytes

def _new_color_index(self, image=None, e=None):
def _new_color_index(
self, image: Image.Image | None = None, e: Exception | None = None
) -> int:
if not isinstance(self.palette, bytearray):
self._palette = bytearray(self.palette)
index = len(self.palette) // 3
special_colors = ()
special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
if image:
special_colors = (
image.info.get("background"),
Expand Down
4 changes: 2 additions & 2 deletions src/PIL/MicImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _open(self) -> None:
msg = "not an MIC file; no image entries"
raise SyntaxError(msg)

self.frame = None
self.frame = -1
self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1

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

self.frame = frame

def tell(self):
def tell(self) -> int:
return self.frame

def close(self) -> None:
Expand Down
63 changes: 42 additions & 21 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import warnings
import zlib
from enum import IntEnum
from typing import IO, Any
from typing import IO, TYPE_CHECKING, Any, NoReturn

from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16
Expand All @@ -48,6 +48,9 @@
from ._binary import o16be as o16
from ._binary import o32be as o32

if TYPE_CHECKING:
from . import _imaging

logger = logging.getLogger(__name__)

is_cid = re.compile(rb"\w\w\w\w").match
Expand Down Expand Up @@ -249,6 +252,9 @@ class iTXt(str):

"""

lang: str | bytes | None
tkey: str | bytes | None

@staticmethod
def __new__(cls, text, lang=None, tkey=None):
"""
Expand All @@ -270,10 +276,10 @@ class PngInfo:

"""

def __init__(self):
self.chunks = []
def __init__(self) -> None:
self.chunks: list[tuple[bytes, bytes, bool]] = []

def add(self, cid, data, after_idat=False):
def add(self, cid: bytes, data: bytes, after_idat: bool = False) -> None:
"""Appends an arbitrary chunk. Use with caution.

:param cid: a byte string, 4 bytes long.
Expand All @@ -283,12 +289,16 @@ def add(self, cid, data, after_idat=False):

"""

chunk = [cid, data]
if after_idat:
chunk.append(True)
self.chunks.append(tuple(chunk))
self.chunks.append((cid, data, after_idat))

def add_itxt(self, key, value, lang="", tkey="", zip=False):
def add_itxt(
self,
key: str | bytes,
value: str | bytes,
lang: str | bytes = "",
tkey: str | bytes = "",
zip: bool = False,
) -> None:
"""Appends an iTXt chunk.

:param key: latin-1 encodable text key name
Expand Down Expand Up @@ -316,7 +326,9 @@ def add_itxt(self, key, value, lang="", tkey="", zip=False):
else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value)

def add_text(self, key, value, zip=False):
def add_text(
self, key: str | bytes, value: str | bytes | iTXt, zip: bool = False
) -> None:
"""Appends a text chunk.

:param key: latin-1 encodable text key name
Expand All @@ -326,7 +338,13 @@ def add_text(self, key, value, zip=False):

"""
if isinstance(value, iTXt):
return self.add_itxt(key, value, value.lang, value.tkey, zip=zip)
return self.add_itxt(
key,
value,
value.lang if value.lang is not None else b"",
value.tkey if value.tkey is not None else b"",
zip=zip,
)

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

def chunk_IDAT(self, pos, length):
def chunk_IDAT(self, pos: int, length: int) -> NoReturn:
# image data
if "bbox" in self.im_info:
tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
Expand All @@ -447,7 +465,7 @@ def chunk_IDAT(self, pos, length):
msg = "image data found"
raise EOFError(msg)

def chunk_IEND(self, pos, length):
def chunk_IEND(self, pos: int, length: int) -> NoReturn:
msg = "end of PNG image"
raise EOFError(msg)

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

def _seek(self, frame, rewind=False):
def _seek(self, frame: int, rewind: bool = False) -> None:
assert self.png is not None

self.dispose: _imaging.ImagingCore | None
if frame == 0:
if rewind:
self._fp.seek(self.__rewind)
Expand Down Expand Up @@ -906,14 +927,14 @@ def _seek(self, frame, rewind=False):
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
self.dispose_op = Disposal.OP_BACKGROUND

self.dispose = None
if self.dispose_op == Disposal.OP_PREVIOUS:
self.dispose = self._prev_im.copy()
self.dispose = self._crop(self.dispose, self.dispose_extent)
if self._prev_im:
self.dispose = self._prev_im.copy()
self.dispose = self._crop(self.dispose, self.dispose_extent)
elif self.dispose_op == Disposal.OP_BACKGROUND:
self.dispose = Image.core.fill(self.mode, self.size)
self.dispose = self._crop(self.dispose, self.dispose_extent)
else:
self.dispose = None

def tell(self) -> int:
return self.__frame
Expand Down Expand Up @@ -1026,7 +1047,7 @@ def _getexif(self) -> dict[str, Any] | None:
return None
return self.getexif()._get_merged_dict()

def getexif(self):
def getexif(self) -> Image.Exif:
if "exif" not in self.info:
self.load()

Expand Down Expand Up @@ -1346,7 +1367,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, cid, data)
elif cid[1:2].islower():
# Private chunk
after_idat = info_chunk[2:3]
after_idat = len(info_chunk) == 3 and info_chunk[2]
if not after_idat:
chunk(fp, cid, data)

Expand Down Expand Up @@ -1425,7 +1446,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
cid, data = info_chunk[:2]
if cid[1:2].islower():
# Private chunk
after_idat = info_chunk[2:3]
after_idat = len(info_chunk) == 3 and info_chunk[2]
if after_idat:
chunk(fp, cid, data)

Expand Down
31 changes: 19 additions & 12 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from collections.abc import MutableMapping
from fractions import Fraction
from numbers import Number, Rational
from typing import IO, TYPE_CHECKING, Any, Callable
from typing import IO, TYPE_CHECKING, Any, Callable, NoReturn

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

def __hash__(self):
def __hash__(self) -> int:
return self._val.__hash__()

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

def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
def __init__(
self,
ifh: bytes = b"II\052\0\0\0\0\0",
prefix: bytes | None = None,
group: int | None = None,
) -> None:
"""Initialize an ImageFileDirectory.

To construct an ImageFileDirectory from a real file, pass the 8-byte
Expand All @@ -575,7 +580,7 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
raise SyntaxError(msg)
self._bigtiff = ifh[2] == 43
self.group = group
self.tagtype = {}
self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """
self.reset()
(self.next,) = (
Expand All @@ -587,18 +592,18 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
offset = property(lambda self: self._offset)

@property
def legacy_api(self):
def legacy_api(self) -> bool:
return self._legacy_api

@legacy_api.setter
def legacy_api(self, value):
def legacy_api(self, value: bool) -> NoReturn:
msg = "Not allowing setting of legacy api"
raise Exception(msg)

def reset(self):
self._tags_v1 = {} # will remain empty if legacy_api is false
self._tags_v2 = {} # main tag storage
self._tagdata = {}
def reset(self) -> None:
self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false
self._tags_v2: dict[int, Any] = {} # main tag storage
self._tagdata: dict[int, bytes] = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech
self._next = None
self._offset = None
Expand Down Expand Up @@ -2039,7 +2044,7 @@ def skipIFDs(self) -> None:
num_tags = self.readShort()
self.f.seek(num_tags * 12, os.SEEK_CUR)

def write(self, data):
def write(self, data: bytes) -> int | None:
return self.f.write(data)

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

def fixOffsets(self, count, isShort=False, isLong=False):
def fixOffsets(
self, count: int, isShort: bool = False, isLong: bool = False
) -> None:
if not isShort and not isLong:
msg = "offset is neither short nor long"
raise RuntimeError(msg)
Expand Down
2 changes: 1 addition & 1 deletion src/PIL/_imaging.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ class ImagingDraw:
class PixelAccess:
def __getattr__(self, name: str) -> Any: ...

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