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

Merged
merged 1 commit into from
Jul 30, 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 Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_cmyk(self) -> None:
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()
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def text(self, text: str) -> None:
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:
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------------

Expand Down
20 changes: 16 additions & 4 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -227,7 +235,11 @@ def _read_comment(s: str) -> bool:
# 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
Expand Down
48 changes: 26 additions & 22 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class Quantize(IntEnum):
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[
Expand Down Expand Up @@ -676,7 +676,7 @@ def __repr__(self) -> str:
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),
Expand Down Expand Up @@ -1551,6 +1551,7 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
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
Expand All @@ -1560,12 +1561,13 @@ def get_child_images(self) -> list[ImageFile.ImageFile]:
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
Expand Down Expand Up @@ -3869,39 +3871,41 @@ 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]
except Exception:
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"
Expand Down Expand Up @@ -3966,7 +3970,7 @@ def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
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
Expand Down Expand Up @@ -4124,7 +4128,7 @@ def __len__(self) -> int:
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]
Expand All @@ -4133,7 +4137,7 @@ def __getitem__(self, tag: int):
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
Expand Down
19 changes: 14 additions & 5 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -161,7 +161,7 @@ def get_format_mimetype(self) -> str | None:
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)

Expand Down Expand Up @@ -525,7 +525,7 @@ def close(self) -> Image.Image:
# --------------------------------------------------------------------


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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -664,7 +669,11 @@ def setfd(self, fd: IO[bytes]) -> None:
"""
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

Expand Down
25 changes: 15 additions & 10 deletions src/PIL/JpegImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
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
Expand All @@ -51,6 +51,9 @@
from ._binary import o16be as o16
from .JpegPresets import presets

if TYPE_CHECKING:
from .MpoImagePlugin import MpoImageFile

#
# Parser

Expand Down Expand Up @@ -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):
Expand All @@ -342,13 +345,13 @@ def _open(self):
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]
Expand Down Expand Up @@ -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()
Expand Down
16 changes: 11 additions & 5 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading