Merge pull request #8339 from radarhere/type_hint

This commit is contained in:
Hugo van Kemenade 2024-09-04 14:57:01 +03:00 committed by GitHub
commit eaeda4a6be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 70 additions and 35 deletions

View File

@ -6,6 +6,7 @@ numpy
packaging
pytest
sphinx
types-atheris
types-defusedxml
types-olefile
types-setuptools

View File

@ -16,8 +16,9 @@
import atheris
from atheris.import_hook import instrument_imports
with atheris.instrument_imports():
with instrument_imports():
import sys
import fuzzers

View File

@ -14,8 +14,9 @@
import atheris
from atheris.import_hook import instrument_imports
with atheris.instrument_imports():
with instrument_imports():
import sys
import fuzzers

View File

@ -33,6 +33,10 @@ Internal Modules
Provides a convenient way to import type hints that are not available
on some Python versions.
.. py:class:: IntegralLike
Typing alias.
.. py:class:: NumpyArray
Typing alias.

View File

@ -163,7 +163,3 @@ follow_imports = "silent"
warn_redundant_casts = true
warn_unreachable = true
warn_unused_ignores = true
exclude = [
'^Tests/oss-fuzz/fuzz_font.py$',
'^Tests/oss-fuzz/fuzz_pillow.py$',
]

View File

@ -387,7 +387,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if self.fd.tell() % 2 != 0:
self.fd.seek(1, os.SEEK_CUR)
rawmode = "L" if self.mode == "L" else "P"
self.set_as_raw(bytes(data), (rawmode, 0, self.args[-1]))
self.set_as_raw(bytes(data), rawmode, (0, self.args[-1]))
return -1, 0

View File

@ -225,6 +225,11 @@ if TYPE_CHECKING:
from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
if sys.version_info >= (3, 13):
from types import CapsuleType
else:
CapsuleType = object
ID: list[str] = []
OPEN: dict[
str,
@ -1598,7 +1603,7 @@ class Image:
self.fp.seek(offset)
return child_images
def getim(self):
def getim(self) -> CapsuleType:
"""
Returns a capsule that points to the internal image memory.

View File

@ -745,19 +745,22 @@ class PyDecoder(PyCodec):
msg = "unavailable in base decoder"
raise NotImplementedError(msg)
def set_as_raw(self, data: bytes, rawmode=None) -> None:
def set_as_raw(
self, data: bytes, rawmode: str | None = None, extra: tuple[Any, ...] = ()
) -> None:
"""
Convenience method to set the internal image from a stream of raw data
:param data: Bytes to be set
:param rawmode: The rawmode to be used for the decoder.
If not specified, it will default to the mode of the image
:param extra: Extra arguments for the decoder.
:returns: None
"""
if not rawmode:
rawmode = self.mode
d = Image._getdecoder(self.mode, "raw", rawmode)
d = Image._getdecoder(self.mode, "raw", rawmode, extra)
assert self.im is not None
d.setimage(self.im, self.state.extents())
s = d.decode(data)

View File

@ -34,7 +34,7 @@ import warnings
from enum import IntEnum
from io import BytesIO
from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict
from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict, cast
from . import Image
from ._typing import StrOrBytesPath
@ -245,7 +245,7 @@ class FreeTypeFont:
self.layout_engine = layout_engine
def load_from_bytes(f) -> None:
def load_from_bytes(f: IO[bytes]) -> None:
self.font_bytes = f.read()
self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine
@ -267,7 +267,7 @@ class FreeTypeFont:
font, size, index, encoding, layout_engine=layout_engine
)
else:
load_from_bytes(font)
load_from_bytes(cast(IO[bytes], font))
def __getstate__(self) -> list[Any]:
return [self.path, self.size, self.index, self.encoding, self.layout_engine]

View File

@ -18,6 +18,7 @@ from __future__ import annotations
import io
import os
import struct
from collections.abc import Callable
from typing import IO, cast
from . import Image, ImageFile, ImagePalette, _binary
@ -316,8 +317,13 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
else:
self.fp.seek(length - 2, os.SEEK_CUR)
@property
def reduce(self):
@property # type: ignore[override]
def reduce(
self,
) -> (
Callable[[int | tuple[int, int], tuple[int, int, int, int] | None], Image.Image]
| int
):
# https://github.com/python-pillow/Pillow/issues/4343 found that the
# new Image 'reduce' method was shadowed by this plugin's 'reduce'
# property. This attempts to allow for both scenarios

View File

@ -152,7 +152,7 @@ class MspDecoder(ImageFile.PyDecoder):
msg = f"Corrupted MSP file in row {x}"
raise OSError(msg) from e
self.set_as_raw(img.getvalue(), ("1", 0, 1))
self.set_as_raw(img.getvalue(), "1")
return -1, 0

View File

@ -258,7 +258,9 @@ class iTXt(str):
tkey: str | bytes | None
@staticmethod
def __new__(cls, text, lang=None, tkey=None):
def __new__(
cls, text: str, lang: str | None = None, tkey: str | None = None
) -> iTXt:
"""
:param cls: the class to use when creating the instance
:param text: value for this key

View File

@ -61,6 +61,9 @@ from ._typing import StrOrBytesPath
from ._util import is_path
from .TiffTags import TYPES
if TYPE_CHECKING:
from ._typing import IntegralLike
logger = logging.getLogger(__name__)
# Set these to true to force use of libtiff for reading or writing.
@ -291,22 +294,24 @@ def _accept(prefix: bytes) -> bool:
def _limit_rational(
val: float | Fraction | IFDRational, max_val: int
) -> tuple[float, float]:
) -> tuple[IntegralLike, IntegralLike]:
inv = abs(float(val)) > 1
n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
return n_d[::-1] if inv else n_d
def _limit_signed_rational(val, max_val, min_val):
def _limit_signed_rational(
val: IFDRational, max_val: int, min_val: int
) -> tuple[IntegralLike, IntegralLike]:
frac = Fraction(val)
n_d = frac.numerator, frac.denominator
n_d: tuple[IntegralLike, IntegralLike] = frac.numerator, frac.denominator
if min(n_d) < min_val:
if min(float(i) for i in n_d) < min_val:
n_d = _limit_rational(val, abs(min_val))
if max(n_d) > max_val:
val = Fraction(*n_d)
n_d = _limit_rational(val, max_val)
n_d_float = tuple(float(i) for i in n_d)
if max(n_d_float) > max_val:
n_d = _limit_rational(n_d_float[0] / n_d_float[1], max_val)
return n_d
@ -318,8 +323,10 @@ _load_dispatch = {}
_write_dispatch = {}
def _delegate(op: str):
def delegate(self, *args):
def _delegate(op: str) -> Any:
def delegate(
self: IFDRational, *args: tuple[float, ...]
) -> bool | float | Fraction:
return getattr(self._val, op)(*args)
return delegate
@ -357,6 +364,9 @@ class IFDRational(Rational):
if isinstance(value, Fraction):
self._numerator = value.numerator
self._denominator = value.denominator
else:
if TYPE_CHECKING:
self._numerator = cast(IntegralLike, value)
else:
self._numerator = value
self._denominator = denominator
@ -371,14 +381,14 @@ class IFDRational(Rational):
self._val = Fraction(value / denominator)
@property
def numerator(self):
def numerator(self) -> IntegralLike:
return self._numerator
@property
def denominator(self) -> int:
return self._denominator
def limit_rational(self, max_denominator: int) -> tuple[float, int]:
def limit_rational(self, max_denominator: int) -> tuple[IntegralLike, int]:
"""
:param max_denominator: Integer, the maximum denominator value
@ -406,13 +416,17 @@ class IFDRational(Rational):
val = float(val)
return val == other
def __getstate__(self) -> list[float | Fraction]:
def __getstate__(self) -> list[float | Fraction | IntegralLike]:
return [self._val, self._numerator, self._denominator]
def __setstate__(self, state: list[float | Fraction]) -> None:
def __setstate__(self, state: list[float | Fraction | IntegralLike]) -> None:
IFDRational.__init__(self, 0)
_val, _numerator, _denominator = state
assert isinstance(_val, (float, Fraction))
self._val = _val
if TYPE_CHECKING:
self._numerator = cast(IntegralLike, _numerator)
else:
self._numerator = _numerator
assert isinstance(_denominator, int)
self._denominator = _denominator
@ -471,8 +485,8 @@ def _register_loader(idx: int, size: int) -> Callable[[_LoaderFunc], _LoaderFunc
return decorator
def _register_writer(idx: int):
def decorator(func):
def _register_writer(idx: int) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
_write_dispatch[idx] = func # noqa: F821
return func

View File

@ -6,6 +6,8 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union
if TYPE_CHECKING:
from numbers import _IntegralLike as IntegralLike
try:
import numpy.typing as npt
@ -38,4 +40,4 @@ class SupportsRead(Protocol[_T_co]):
StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]
__all__ = ["TypeGuard", "StrOrBytesPath", "SupportsRead"]
__all__ = ["IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]