Skip to content

Commit

Permalink
Merge pull request #7750 from hugovk/type-hints-replace-io.BytesIO
Browse files Browse the repository at this point in the history
Replace `io.BytesIO` in type hints
  • Loading branch information
radarhere authored Feb 13, 2024
2 parents cdecb3d + 29dd025 commit 3374e91
Show file tree
Hide file tree
Showing 12 changed files with 48 additions and 47 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ exclude_also =
except ImportError
if TYPE_CHECKING:
@abc.abstractmethod
# Empty bodies in protocols or abstract methods
^\s*def [a-zA-Z0-9_]+\(.*\)(\s*->.*)?:\s*\.\.\.(\s*#.*)?$
^\s*\.\.\.(\s*#.*)?$

[run]
omit =
Expand Down
2 changes: 0 additions & 2 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ def test_stringio(self) -> None:
pass

def test_pathlib(self, tmp_path: Path) -> None:
from PIL.Image import Path

with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
assert im.mode == "P"
assert im.size == (10, 10)
Expand Down
23 changes: 5 additions & 18 deletions Tests/test_util.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,16 @@
from __future__ import annotations

from pathlib import Path
from pathlib import Path, PurePath

import pytest

from PIL import _util


def test_is_path() -> None:
# Arrange
fp = "filename.ext"

# Act
it_is = _util.is_path(fp)

# Assert
assert it_is


def test_path_obj_is_path() -> None:
# Arrange
from pathlib import Path

test_path = Path("filename.ext")

@pytest.mark.parametrize(
"test_path", ["filename.ext", Path("filename.ext"), PurePath("filename.ext")]
)
def test_is_path(test_path) -> None:
# Act
it_is = _util.is_path(test_path)

Expand Down
4 changes: 2 additions & 2 deletions docs/reference/internal_design.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Internal Reference Docs
=======================
Internal Reference
==================

.. toctree::
:maxdepth: 2
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/internal_modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ Internal Modules
Provides a convenient way to import type hints that are not available
on some Python versions.

.. py:class:: StrOrBytesPath
Typing alias.

.. py:class:: SupportsRead
An object that supports the read method.

.. py:data:: TypeGuard
:value: typing.TypeGuard

Expand Down
2 changes: 1 addition & 1 deletion docs/reference/open_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
File Handling in Pillow
=======================

When opening a file as an image, Pillow requires a filename, ``pathlib.Path``
When opening a file as an image, Pillow requires a filename, ``os.PathLike``
object, or a file-like object. Pillow uses the filename or ``Path`` to open a
file, so for the rest of this article, they will all be treated as a file-like
object.
Expand Down
5 changes: 3 additions & 2 deletions src/PIL/GdImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ class is not registered for use with :py:func:`PIL.Image.open()`. To open a
"""
from __future__ import annotations

from io import BytesIO
from typing import IO

from . import ImageFile, ImagePalette, UnidentifiedImageError
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._typing import StrOrBytesPath


class GdImageFile(ImageFile.ImageFile):
Expand Down Expand Up @@ -80,7 +81,7 @@ def _open(self) -> None:
]


def open(fp: BytesIO, mode: str = "r") -> GdImageFile:
def open(fp: StrOrBytesPath | IO[bytes], mode: str = "r") -> GdImageFile:
"""
Load texture from a GD image file.
Expand Down
12 changes: 4 additions & 8 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import warnings
from collections.abc import Callable, MutableMapping
from enum import IntEnum
from pathlib import Path
from types import ModuleType
from typing import IO, TYPE_CHECKING, Any

Expand Down Expand Up @@ -2383,7 +2382,7 @@ def save(self, fp, format=None, **params) -> None:
implement the ``seek``, ``tell``, and ``write``
methods, and be opened in binary mode.
:param fp: A filename (string), pathlib.Path object or file object.
:param fp: A filename (string), os.PathLike object or file object.
:param format: Optional format override. If omitted, the
format to use is determined from the filename extension.
If a file object was used instead of a filename, this
Expand All @@ -2398,11 +2397,8 @@ def save(self, fp, format=None, **params) -> None:

filename: str | bytes = ""
open_fp = False
if isinstance(fp, Path):
filename = str(fp)
open_fp = True
elif isinstance(fp, (str, bytes)):
filename = fp
if is_path(fp):
filename = os.path.realpath(os.fspath(fp))
open_fp = True
elif fp == sys.stdout:
try:
Expand Down Expand Up @@ -3225,7 +3221,7 @@ def open(fp, mode="r", formats=None) -> Image:
:py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`. See :ref:`file-handling`.
:param fp: A filename (string), pathlib.Path object or a file object.
:param fp: A filename (string), os.PathLike object or a file object.
The file object must implement ``file.read``,
``file.seek``, and ``file.tell`` methods,
and be opened in binary mode. The file object will also seek to zero
Expand Down
7 changes: 3 additions & 4 deletions src/PIL/ImageFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
import warnings
from enum import IntEnum
from io import BytesIO
from pathlib import Path
from typing import BinaryIO

from . import Image
from ._typing import StrOrBytesPath
from ._util import is_directory, is_path


Expand Down Expand Up @@ -193,7 +193,7 @@ class FreeTypeFont:

def __init__(
self,
font: bytes | str | Path | BinaryIO | None = None,
font: StrOrBytesPath | BinaryIO | None = None,
size: float = 10,
index: int = 0,
encoding: str = "",
Expand Down Expand Up @@ -230,8 +230,7 @@ def load_from_bytes(f):
)

if is_path(font):
if isinstance(font, Path):
font = str(font)
font = os.path.realpath(os.fspath(font))
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
Expand Down
5 changes: 2 additions & 3 deletions src/PIL/MpegImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@
#
from __future__ import annotations

from io import BytesIO

from . import Image, ImageFile
from ._binary import i8
from ._typing import SupportsRead

#
# Bitstream parser


class BitStream:
def __init__(self, fp: BytesIO) -> None:
def __init__(self, fp: SupportsRead[bytes]) -> None:
self.fp = fp
self.bits = 0
self.bitbuffer = 0
Expand Down
15 changes: 13 additions & 2 deletions src/PIL/_typing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import os
import sys
from typing import Sequence, Union
from typing import Protocol, Sequence, TypeVar, Union

if sys.version_info >= (3, 10):
from typing import TypeGuard
Expand All @@ -19,4 +20,14 @@ def __class_getitem__(cls, item: Any) -> type[bool]:
Coords = Union[Sequence[float], Sequence[Sequence[float]]]


__all__ = ["TypeGuard"]
_T_co = TypeVar("_T_co", covariant=True)


class SupportsRead(Protocol[_T_co]):
def read(self, __length: int = ...) -> _T_co: ...


StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"]


__all__ = ["TypeGuard", "StrOrBytesPath", "SupportsRead"]
9 changes: 4 additions & 5 deletions src/PIL/_util.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
from __future__ import annotations

import os
from pathlib import Path
from typing import Any, NoReturn

from ._typing import TypeGuard
from ._typing import StrOrBytesPath, TypeGuard


def is_path(f: Any) -> TypeGuard[bytes | str | Path]:
return isinstance(f, (bytes, str, Path))
def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
return isinstance(f, (bytes, str, os.PathLike))


def is_directory(f: Any) -> TypeGuard[bytes | str | Path]:
def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]:
"""Checks if an object is a string, and that it points to a directory."""
return is_path(f) and os.path.isdir(f)

Expand Down

0 comments on commit 3374e91

Please sign in to comment.