From 6bcf807fe2cb82c859fb757a498edfe5692ededa Mon Sep 17 00:00:00 2001 From: Nulano Date: Wed, 27 Dec 2023 00:17:57 +0100 Subject: [PATCH 1/6] add type hints for _util --- pyproject.toml | 3 +++ src/PIL/_util.py | 23 +++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 193e8c9b247..0c25edeb308 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,9 @@ classifiers = [ dynamic = [ "version", ] +dependencies = [ + 'typing-extensions; python_version < "3.10"', +] [project.optional-dependencies] docs = [ "furo", diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 4634d335bba..d11cbfa5860 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,21 +1,36 @@ from __future__ import annotations import os +import sys from pathlib import Path +from typing import Any, NoReturn +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + from typing_extensions import TypeGuard -def is_path(f): + +def is_path(f: Any) -> TypeGuard[bytes | str | Path]: return isinstance(f, (bytes, str, Path)) -def is_directory(f): +def is_directory(f: Any) -> TypeGuard[bytes | str | Path]: """Checks if an object is a string, and that it points to a directory.""" return is_path(f) and os.path.isdir(f) class DeferredError: - def __init__(self, ex): + def __init__(self, ex: BaseException): self.ex = ex - def __getattr__(self, elt): + def __getattr__(self, elt: str) -> NoReturn: raise self.ex + + @staticmethod + def new(ex: BaseException) -> Any: + """ + Creates an object that raises the wrapped exception ``ex`` when used, + and casts it to :py:obj:`~typing.Any` type. + """ + return DeferredError(ex) From 90d5552800692d3c9c467eeef406dab984a50e68 Mon Sep 17 00:00:00 2001 From: Nulano Date: Wed, 27 Dec 2023 00:40:55 +0100 Subject: [PATCH 2/6] use _util.DeferredError.new everywhere --- Tests/test_util.py | 2 +- pyproject.toml | 2 -- src/PIL/Image.py | 2 +- src/PIL/ImageCms.py | 2 +- src/PIL/ImageFont.py | 6 +++--- src/PIL/PyAccess.py | 2 +- src/PIL/_imagingcms.pyi | 5 +++++ src/PIL/_imagingft.pyi | 5 +++++ 8 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 src/PIL/_imagingcms.pyi create mode 100644 src/PIL/_imagingft.pyi diff --git a/Tests/test_util.py b/Tests/test_util.py index 1457d85f795..4a312beb440 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -66,7 +66,7 @@ def test_deferred_error(): # Arrange # Act - thing = _util.DeferredError(ValueError("Some error text")) + thing = _util.DeferredError.new(ValueError("Some error text")) # Assert with pytest.raises(ValueError): diff --git a/pyproject.toml b/pyproject.toml index 0c25edeb308..ef0ff8a8ece 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,9 +144,7 @@ exclude = [ '^src/PIL/DdsImagePlugin.py$', '^src/PIL/FpxImagePlugin.py$', '^src/PIL/Image.py$', - '^src/PIL/ImageCms.py$', '^src/PIL/ImageFile.py$', - '^src/PIL/ImageFont.py$', '^src/PIL/ImageMath.py$', '^src/PIL/ImageMorph.py$', '^src/PIL/ImageQt.py$', diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d04801cba75..5a2e8541928 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -92,7 +92,7 @@ class DecompressionBombError(Exception): raise ImportError(msg) except ImportError as v: - core = DeferredError(ImportError("The _imaging C module is not installed.")) + core = DeferredError.new(ImportError("The _imaging C module is not installed.")) # Explanations for ways that we know we might have an import error if str(v).startswith("Module use of python"): # The _imaging C module is present, but not compiled for diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 9d27f2513a7..643fce830ba 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -28,7 +28,7 @@ # anything in core. from ._util import DeferredError - _imagingcms = DeferredError(ex) + _imagingcms = DeferredError.new(ex) DESCRIPTION = """ pyCMS diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 6db7cc4eccb..41d8fbc17de 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -34,7 +34,7 @@ from enum import IntEnum from io import BytesIO from pathlib import Path -from typing import IO +from typing import BinaryIO from . import Image from ._util import is_directory, is_path @@ -53,7 +53,7 @@ class Layout(IntEnum): except ImportError as ex: from ._util import DeferredError - core = DeferredError(ex) + core = DeferredError.new(ex) def _string_length_check(text): @@ -191,7 +191,7 @@ class FreeTypeFont: def __init__( self, - font: bytes | str | Path | IO | None = None, + font: bytes | str | Path | BinaryIO | None = None, size: float = 10, index: int = 0, encoding: str = "", diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 23ff154f6cc..07bb712d83e 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -43,7 +43,7 @@ # anything in core. from ._util import DeferredError - FFI = ffi = DeferredError(ex) + FFI = ffi = DeferredError.new(ex) logger = logging.getLogger(__name__) diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi new file mode 100644 index 00000000000..b0235555dc5 --- /dev/null +++ b/src/PIL/_imagingcms.pyi @@ -0,0 +1,5 @@ +from __future__ import annotations + +from typing import Any + +def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi new file mode 100644 index 00000000000..b0235555dc5 --- /dev/null +++ b/src/PIL/_imagingft.pyi @@ -0,0 +1,5 @@ +from __future__ import annotations + +from typing import Any + +def __getattr__(name: str) -> Any: ... From cc51dace35bd6e144c3fff05d1b14c78af41c0e0 Mon Sep 17 00:00:00 2001 From: Nulano Date: Wed, 27 Dec 2023 00:41:30 +0100 Subject: [PATCH 3/6] fix types hints for ImageFile._Tile --- pyproject.toml | 1 - src/PIL/ImageFile.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ef0ff8a8ece..de707a2838b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -144,7 +144,6 @@ exclude = [ '^src/PIL/DdsImagePlugin.py$', '^src/PIL/FpxImagePlugin.py$', '^src/PIL/Image.py$', - '^src/PIL/ImageFile.py$', '^src/PIL/ImageMath.py$', '^src/PIL/ImageMorph.py$', '^src/PIL/ImageQt.py$', diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index ae4e23db17b..0923979af8b 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -32,7 +32,7 @@ import itertools import struct import sys -from typing import NamedTuple +from typing import Any, NamedTuple from . import Image from ._deprecate import deprecate @@ -94,7 +94,7 @@ class _Tile(NamedTuple): encoder_name: str extents: tuple[int, int, int, int] offset: int - args: tuple | str | None + args: tuple[Any, ...] | str | None # From 3a4298d16c660d5dec46cfa372b6aceb79c1e056 Mon Sep 17 00:00:00 2001 From: Nulano Date: Wed, 27 Dec 2023 14:54:48 +0100 Subject: [PATCH 4/6] avoid hard dependency on typing_extensions --- docs/reference/internal_modules.rst | 13 +++++++++++++ pyproject.toml | 6 +++--- src/PIL/_typing.py | 16 ++++++++++++++++ src/PIL/_util.py | 7 +------ tox.ini | 2 ++ 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/PIL/_typing.py diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index 363a67d9b02..57dd3f35be7 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -25,6 +25,19 @@ Internal Modules :undoc-members: :show-inheritance: +:mod:`~PIL._typing` Module +-------------------------- + +.. module:: PIL._typing + +Provides a convenient way to import type hints that are not available +on some supported Python versions. + +.. py:data:: TypeGuard + :value: typing.TypeGuard + + See :py:obj:`typing.TypeGuard`. + :mod:`~PIL._util` Module ------------------------ diff --git a/pyproject.toml b/pyproject.toml index de707a2838b..abb32f14d82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,6 @@ classifiers = [ dynamic = [ "version", ] -dependencies = [ - 'typing-extensions; python_version < "3.10"', -] [project.optional-dependencies] docs = [ "furo", @@ -68,6 +65,9 @@ tests = [ "pytest-cov", "pytest-timeout", ] +typing = [ + 'typing-extensions; python_version < "3.10"', +] xmp = [ "defusedxml", ] diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py new file mode 100644 index 00000000000..583be41b775 --- /dev/null +++ b/src/PIL/_typing.py @@ -0,0 +1,16 @@ +import sys + +if sys.version_info >= (3, 10): + from typing import TypeGuard +else: + try: + from typing_extensions import TypeGuard + except ImportError: + from typing import Any, Type + + class TypeGuard: # type: ignore[no-redef] + def __class_getitem__(cls, item: Any) -> Type[bool]: + return bool + + +__all__ = ["TypeGuard"] diff --git a/src/PIL/_util.py b/src/PIL/_util.py index d11cbfa5860..37cd979f67e 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -1,14 +1,9 @@ from __future__ import annotations import os -import sys from pathlib import Path from typing import Any, NoReturn - -if sys.version_info >= (3, 10): - from typing import TypeGuard -else: - from typing_extensions import TypeGuard +from ._typing import TypeGuard def is_path(f: Any) -> TypeGuard[bytes | str | Path]: diff --git a/tox.ini b/tox.ini index 034d893721b..bfcfaf5d90a 100644 --- a/tox.ini +++ b/tox.ini @@ -37,3 +37,5 @@ deps = numpy commands = mypy src {posargs} +extras = + typing From 0d90bc818789b801170171fe902bc2921850fc11 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:57:20 +0000 Subject: [PATCH 5/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/_typing.py | 6 ++++-- src/PIL/_util.py | 1 + tox.ini | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 583be41b775..608b2b41fa8 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys if sys.version_info >= (3, 10): @@ -6,10 +8,10 @@ try: from typing_extensions import TypeGuard except ImportError: - from typing import Any, Type + from typing import Any class TypeGuard: # type: ignore[no-redef] - def __class_getitem__(cls, item: Any) -> Type[bool]: + def __class_getitem__(cls, item: Any) -> type[bool]: return bool diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 37cd979f67e..13f369cca1d 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -3,6 +3,7 @@ import os from pathlib import Path from typing import Any, NoReturn + from ._typing import TypeGuard diff --git a/tox.ini b/tox.ini index bfcfaf5d90a..d89d017e45c 100644 --- a/tox.ini +++ b/tox.ini @@ -35,7 +35,7 @@ skip_install = true deps = mypy==1.7.1 numpy -commands = - mypy src {posargs} extras = typing +commands = + mypy src {posargs} From de381d0efb96096cb9a56c69711635a8e53417b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Wed, 27 Dec 2023 16:17:51 +0100 Subject: [PATCH 6/6] Update docs/reference/internal_modules.rst Co-authored-by: Hugo van Kemenade --- docs/reference/internal_modules.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index 57dd3f35be7..f2932c32200 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -31,7 +31,7 @@ Internal Modules .. module:: PIL._typing Provides a convenient way to import type hints that are not available -on some supported Python versions. +on some Python versions. .. py:data:: TypeGuard :value: typing.TypeGuard