Merge pull request #8134 from radarhere/type_hint

This commit is contained in:
Hugo van Kemenade 2024-06-18 11:39:18 -06:00 committed by GitHub
commit 5d3338f13e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 95 additions and 57 deletions

View File

@ -103,7 +103,7 @@ def bdf_char(
class BdfFontFile(FontFile.FontFile): class BdfFontFile(FontFile.FontFile):
"""Font file plugin for the X11 BDF format.""" """Font file plugin for the X11 BDF format."""
def __init__(self, fp: BinaryIO): def __init__(self, fp: BinaryIO) -> None:
super().__init__() super().__init__()
s = fp.readline() s = fp.readline()

View File

@ -34,12 +34,16 @@ from __future__ import annotations
import math import math
import numbers import numbers
import struct import struct
from types import ModuleType
from typing import TYPE_CHECKING, AnyStr, Sequence, cast from typing import TYPE_CHECKING, AnyStr, Sequence, cast
from . import Image, ImageColor from . import Image, ImageColor
from ._deprecate import deprecate from ._deprecate import deprecate
from ._typing import Coords from ._typing import Coords
if TYPE_CHECKING:
from . import ImageDraw2, ImageFont
""" """
A simple 2D drawing interface for PIL images. A simple 2D drawing interface for PIL images.
<p> <p>
@ -93,9 +97,6 @@ class ImageDraw:
self.fontmode = "L" # aliasing is okay for other modes self.fontmode = "L" # aliasing is okay for other modes
self.fill = False self.fill = False
if TYPE_CHECKING:
from . import ImageFont
def getfont( def getfont(
self, self,
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont: ) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
@ -879,7 +880,7 @@ class ImageDraw:
return bbox 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. A simple 2D drawing interface for PIL images.
@ -891,7 +892,7 @@ def Draw(im, mode: str | None = None) -> ImageDraw:
defaults to the mode of the image. defaults to the mode of the image.
""" """
try: try:
return im.getdraw(mode) return getattr(im, "getdraw")(mode)
except AttributeError: except AttributeError:
return ImageDraw(im, mode) return ImageDraw(im, mode)
@ -903,7 +904,9 @@ except AttributeError:
Outline = None 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 im: The image to draw in.
:param hints: An optional list of hints. Deprecated. :param hints: An optional list of hints. Deprecated.
@ -913,9 +916,8 @@ def getdraw(im=None, hints=None):
deprecate("'hints' parameter", 12) deprecate("'hints' parameter", 12)
from . import ImageDraw2 from . import ImageDraw2
if im: draw = ImageDraw2.Draw(im) if im is not None else None
im = ImageDraw2.Draw(im) return draw, ImageDraw2
return im, ImageDraw2
def floodfill( def floodfill(

View File

@ -54,7 +54,7 @@ class ImagePalette:
self._palette = palette self._palette = palette
@property @property
def colors(self): def colors(self) -> dict[tuple[int, int, int] | tuple[int, int, int, int], int]:
if self._colors is None: if self._colors is None:
mode_len = len(self.mode) mode_len = len(self.mode)
self._colors = {} self._colors = {}
@ -66,7 +66,9 @@ class ImagePalette:
return self._colors return self._colors
@colors.setter @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 self._colors = colors
def copy(self) -> ImagePalette: def copy(self) -> ImagePalette:
@ -107,11 +109,13 @@ class ImagePalette:
# Declare tostring as an alias for tobytes # Declare tostring as an alias for tobytes
tostring = 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): if not isinstance(self.palette, bytearray):
self._palette = bytearray(self.palette) self._palette = bytearray(self.palette)
index = len(self.palette) // 3 index = len(self.palette) // 3
special_colors = () special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
if image: if image:
special_colors = ( special_colors = (
image.info.get("background"), image.info.get("background"),

View File

@ -63,7 +63,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
msg = "not an MIC file; no image entries" msg = "not an MIC file; no image entries"
raise SyntaxError(msg) raise SyntaxError(msg)
self.frame = None self.frame = -1
self._n_frames = len(self.images) self._n_frames = len(self.images)
self.is_animated = self._n_frames > 1 self.is_animated = self._n_frames > 1
@ -85,7 +85,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
self.frame = frame self.frame = frame
def tell(self): def tell(self) -> int:
return self.frame return self.frame
def close(self) -> None: def close(self) -> None:

View File

@ -39,7 +39,7 @@ import struct
import warnings import warnings
import zlib import zlib
from enum import IntEnum 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 . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16 from ._binary import i16be as i16
@ -48,6 +48,9 @@ from ._binary import o8
from ._binary import o16be as o16 from ._binary import o16be as o16
from ._binary import o32be as o32 from ._binary import o32be as o32
if TYPE_CHECKING:
from . import _imaging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
is_cid = re.compile(rb"\w\w\w\w").match is_cid = re.compile(rb"\w\w\w\w").match
@ -249,6 +252,9 @@ class iTXt(str):
""" """
lang: str | bytes | None
tkey: str | bytes | None
@staticmethod @staticmethod
def __new__(cls, text, lang=None, tkey=None): def __new__(cls, text, lang=None, tkey=None):
""" """
@ -270,10 +276,10 @@ class PngInfo:
""" """
def __init__(self): def __init__(self) -> None:
self.chunks = [] 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. """Appends an arbitrary chunk. Use with caution.
:param cid: a byte string, 4 bytes long. :param cid: a byte string, 4 bytes long.
@ -283,12 +289,16 @@ class PngInfo:
""" """
chunk = [cid, data] self.chunks.append((cid, data, after_idat))
if after_idat:
chunk.append(True)
self.chunks.append(tuple(chunk))
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. """Appends an iTXt chunk.
:param key: latin-1 encodable text key name :param key: latin-1 encodable text key name
@ -316,7 +326,9 @@ class PngInfo:
else: else:
self.add(b"iTXt", key + b"\0\0\0" + lang + b"\0" + tkey + b"\0" + value) 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. """Appends a text chunk.
:param key: latin-1 encodable text key name :param key: latin-1 encodable text key name
@ -326,7 +338,13 @@ class PngInfo:
""" """
if isinstance(value, iTXt): 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 # The tEXt chunk stores latin-1 text
if not isinstance(value, bytes): if not isinstance(value, bytes):
@ -434,7 +452,7 @@ class PngStream(ChunkStream):
raise SyntaxError(msg) raise SyntaxError(msg)
return s return s
def chunk_IDAT(self, pos, length): def chunk_IDAT(self, pos: int, length: int) -> NoReturn:
# image data # image data
if "bbox" in self.im_info: if "bbox" in self.im_info:
tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)] tile = [("zip", self.im_info["bbox"], pos, self.im_rawmode)]
@ -447,7 +465,7 @@ class PngStream(ChunkStream):
msg = "image data found" msg = "image data found"
raise EOFError(msg) raise EOFError(msg)
def chunk_IEND(self, pos, length): def chunk_IEND(self, pos: int, length: int) -> NoReturn:
msg = "end of PNG image" msg = "end of PNG image"
raise EOFError(msg) raise EOFError(msg)
@ -821,7 +839,10 @@ class PngImageFile(ImageFile.ImageFile):
msg = "no more images in APNG file" msg = "no more images in APNG file"
raise EOFError(msg) from e 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 frame == 0:
if rewind: if rewind:
self._fp.seek(self.__rewind) self._fp.seek(self.__rewind)
@ -906,14 +927,14 @@ class PngImageFile(ImageFile.ImageFile):
if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS: if self._prev_im is None and self.dispose_op == Disposal.OP_PREVIOUS:
self.dispose_op = Disposal.OP_BACKGROUND self.dispose_op = Disposal.OP_BACKGROUND
self.dispose = None
if self.dispose_op == Disposal.OP_PREVIOUS: if self.dispose_op == Disposal.OP_PREVIOUS:
if self._prev_im:
self.dispose = self._prev_im.copy() self.dispose = self._prev_im.copy()
self.dispose = self._crop(self.dispose, self.dispose_extent) self.dispose = self._crop(self.dispose, self.dispose_extent)
elif self.dispose_op == Disposal.OP_BACKGROUND: elif self.dispose_op == Disposal.OP_BACKGROUND:
self.dispose = Image.core.fill(self.mode, self.size) self.dispose = Image.core.fill(self.mode, self.size)
self.dispose = self._crop(self.dispose, self.dispose_extent) self.dispose = self._crop(self.dispose, self.dispose_extent)
else:
self.dispose = None
def tell(self) -> int: def tell(self) -> int:
return self.__frame return self.__frame
@ -1026,7 +1047,7 @@ class PngImageFile(ImageFile.ImageFile):
return None return None
return self.getexif()._get_merged_dict() return self.getexif()._get_merged_dict()
def getexif(self): def getexif(self) -> Image.Exif:
if "exif" not in self.info: if "exif" not in self.info:
self.load() self.load()
@ -1346,7 +1367,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
chunk(fp, cid, data) chunk(fp, cid, data)
elif cid[1:2].islower(): elif cid[1:2].islower():
# Private chunk # Private chunk
after_idat = info_chunk[2:3] after_idat = len(info_chunk) == 3 and info_chunk[2]
if not after_idat: if not after_idat:
chunk(fp, cid, data) chunk(fp, cid, data)
@ -1425,7 +1446,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
cid, data = info_chunk[:2] cid, data = info_chunk[:2]
if cid[1:2].islower(): if cid[1:2].islower():
# Private chunk # Private chunk
after_idat = info_chunk[2:3] after_idat = len(info_chunk) == 3 and info_chunk[2]
if after_idat: if after_idat:
chunk(fp, cid, data) chunk(fp, cid, data)

View File

@ -50,7 +50,7 @@ import warnings
from collections.abc import MutableMapping from collections.abc import MutableMapping
from fractions import Fraction from fractions import Fraction
from numbers import Number, Rational 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 . import ExifTags, Image, ImageFile, ImageOps, ImagePalette, TiffTags
from ._binary import i16be as i16 from ._binary import i16be as i16
@ -384,7 +384,7 @@ class IFDRational(Rational):
def __repr__(self) -> str: def __repr__(self) -> str:
return str(float(self._val)) return str(float(self._val))
def __hash__(self): def __hash__(self) -> int:
return self._val.__hash__() return self._val.__hash__()
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
@ -551,7 +551,12 @@ class ImageFileDirectory_v2(_IFDv2Base):
_load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {} _load_dispatch: dict[int, Callable[[ImageFileDirectory_v2, bytes, bool], Any]] = {}
_write_dispatch: dict[int, Callable[..., 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. """Initialize an ImageFileDirectory.
To construct an ImageFileDirectory from a real file, pass the 8-byte To construct an ImageFileDirectory from a real file, pass the 8-byte
@ -575,7 +580,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
raise SyntaxError(msg) raise SyntaxError(msg)
self._bigtiff = ifh[2] == 43 self._bigtiff = ifh[2] == 43
self.group = group self.group = group
self.tagtype = {} self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """ """ Dictionary of tag types """
self.reset() self.reset()
(self.next,) = ( (self.next,) = (
@ -587,18 +592,18 @@ class ImageFileDirectory_v2(_IFDv2Base):
offset = property(lambda self: self._offset) offset = property(lambda self: self._offset)
@property @property
def legacy_api(self): def legacy_api(self) -> bool:
return self._legacy_api return self._legacy_api
@legacy_api.setter @legacy_api.setter
def legacy_api(self, value): def legacy_api(self, value: bool) -> NoReturn:
msg = "Not allowing setting of legacy api" msg = "Not allowing setting of legacy api"
raise Exception(msg) raise Exception(msg)
def reset(self): def reset(self) -> None:
self._tags_v1 = {} # will remain empty if legacy_api is false self._tags_v1: dict[int, Any] = {} # will remain empty if legacy_api is false
self._tags_v2 = {} # main tag storage self._tags_v2: dict[int, Any] = {} # main tag storage
self._tagdata = {} self._tagdata: dict[int, bytes] = {}
self.tagtype = {} # added 2008-06-05 by Florian Hoech self.tagtype = {} # added 2008-06-05 by Florian Hoech
self._next = None self._next = None
self._offset = None self._offset = None
@ -2039,7 +2044,7 @@ class AppendingTiffWriter:
num_tags = self.readShort() num_tags = self.readShort()
self.f.seek(num_tags * 12, os.SEEK_CUR) 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) return self.f.write(data)
def readShort(self) -> int: def readShort(self) -> int:
@ -2122,7 +2127,9 @@ class AppendingTiffWriter:
# skip the locally stored value that is not an offset # skip the locally stored value that is not an offset
self.f.seek(4, os.SEEK_CUR) 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: if not isShort and not isLong:
msg = "offset is neither short nor long" msg = "offset is neither short nor long"
raise RuntimeError(msg) raise RuntimeError(msg)

View File

@ -12,5 +12,5 @@ class ImagingDraw:
class PixelAccess: class PixelAccess:
def __getattr__(self, name: str) -> Any: ... def __getattr__(self, name: str) -> Any: ...
def font(image, glyphdata: bytes) -> ImagingFont: ... def font(image: ImagingCore, glyphdata: bytes) -> ImagingFont: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...

View File

@ -4,6 +4,7 @@ import collections
import os import os
import sys import sys
import warnings import warnings
from typing import IO
import PIL import PIL
@ -223,7 +224,7 @@ def get_supported() -> list[str]:
return ret return ret
def pilinfo(out=None, supported_formats=True): def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
""" """
Prints information about this installation of Pillow. Prints information about this installation of Pillow.
This function can be called with ``python3 -m PIL``. This function can be called with ``python3 -m PIL``.
@ -244,9 +245,9 @@ def pilinfo(out=None, supported_formats=True):
print("-" * 68, file=out) print("-" * 68, file=out)
print(f"Pillow {PIL.__version__}", file=out) print(f"Pillow {PIL.__version__}", file=out)
py_version = sys.version.splitlines() py_version_lines = sys.version.splitlines()
print(f"Python {py_version[0].strip()}", file=out) print(f"Python {py_version_lines[0].strip()}", file=out)
for py_version in py_version[1:]: for py_version in py_version_lines[1:]:
print(f" {py_version.strip()}", file=out) print(f" {py_version.strip()}", file=out)
print("-" * 68, file=out) print("-" * 68, file=out)
print(f"Python executable is {sys.executable or 'unknown'}", file=out) print(f"Python executable is {sys.executable or 'unknown'}", file=out)
@ -282,9 +283,12 @@ def pilinfo(out=None, supported_formats=True):
("xcb", "XCB (X protocol)"), ("xcb", "XCB (X protocol)"),
]: ]:
if check(name): if check(name):
if name == "jpg" and check_feature("libjpeg_turbo"): v: str | None = None
v = "libjpeg-turbo " + version_feature("libjpeg_turbo") if name == "jpg":
else: libjpeg_turbo_version = version_feature("libjpeg_turbo")
if libjpeg_turbo_version is not None:
v = "libjpeg-turbo " + libjpeg_turbo_version
if v is None:
v = version(name) v = version(name)
if v is not None: if v is not None:
version_static = name in ("pil", "jpg") version_static = name in ("pil", "jpg")