This commit is contained in:
Kuri Schlarb 2022-04-29 08:23:30 +00:00 committed by GitHub
commit c45b029717
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 87 additions and 25 deletions

View File

@ -1,5 +1,6 @@
import io
import os
import pathlib
import shutil
import sys
import tempfile
@ -21,6 +22,22 @@ from .helper import (
)
class MyStrPathLike:
def __init__(self, path_str):
self.path_str = path_str
def __fspath__(self):
return self.path_str
class MyBytesPathLike:
def __init__(self, path_str):
self.path_bytes = os.fsencode(path_str)
def __fspath__(self):
return self.path_bytes
class TestImage:
def test_image_modes_success(self):
for mode in [
@ -149,22 +166,30 @@ class TestImage:
with Image.open(io.StringIO()):
pass
def test_pathlib(self, tmp_path):
from PIL.Image import Path
with Image.open(Path("Tests/images/multipage-mmap.tiff")) as im:
@pytest.mark.parametrize(
"PathCls",
(
pathlib.Path,
MyStrPathLike, # Returns `str` on `os.fspath`
MyBytesPathLike, # Returns `bytes` on `os.fspath`
str,
os.fsencode, # Converts path to `bytes`
),
)
def test_path_like(self, tmp_path, PathCls):
with Image.open(PathCls("Tests/images/multipage-mmap.tiff")) as im:
assert im.mode == "P"
assert im.size == (10, 10)
with Image.open(Path("Tests/images/hopper.jpg")) as im:
with Image.open(PathCls("Tests/images/hopper.jpg")) as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
for ext in (".jpg", ".jp2"):
temp_file = str(tmp_path / ("temp." + ext))
temp_file = str((tmp_path / "temp").with_suffix(ext))
if os.path.exists(temp_file):
os.remove(temp_file)
im.save(Path(temp_file))
im.save(PathCls(temp_file))
def test_fp_name(self, tmp_path):
temp_file = str(tmp_path / "temp.jpg")

View File

@ -4,6 +4,7 @@ import re
import shutil
import sys
from io import BytesIO
from pathlib import Path
import pytest
from packaging.version import parse as parse_version
@ -87,6 +88,7 @@ class TestImageFont:
pytest.skip("Non-ASCII path could not be created")
ImageFont.truetype(tempfile, FONT_SIZE)
ImageFont.truetype(Path(tempfile), FONT_SIZE)
def _render(self, font):
txt = "Hello World!"

View File

@ -27,6 +27,21 @@ def test_path_obj_is_path():
assert it_is
def test_path_like_is_path():
# Arrange
class PathThingy:
def __fspath__(self):
return "UNIMPORTANT" # pragma: no cover
test_path = PathThingy()
# Act
it_is = _util.is_path(test_path)
# Assert
assert it_is
def test_is_not_path(tmp_path):
# Arrange
with (tmp_path / "temp.ext").open("w") as fp:
@ -39,6 +54,22 @@ def test_is_not_path(tmp_path):
assert not it_is_not
def test_path_like_file_is_not_path(tmp_path):
# Arrange
import io
class PathAndFileThingy(io.TextIOWrapper):
def __fspath__(self):
return "UNIMPORTANT" # pragma: no cover
with PathAndFileThingy(open(tmp_path / "temp.ext", "wb")) as test_obj:
# Act
it_is_not = _util.is_path(test_obj)
# Assert
assert not it_is_not
def test_is_directory():
# Arrange
directory = "Tests"

View File

@ -2182,9 +2182,9 @@ class Image:
def save(self, fp, format=None, **params):
"""
Saves this image under the given filename. If no format is
specified, the format to use is determined from the filename
extension, if possible.
Saves this image under the given file path. If no format is
specified, the format to use is determined from the path's
file extension, if possible.
Keyword options can be used to provide additional instructions
to the writer. If a writer doesn't recognise an option, it is
@ -2192,12 +2192,12 @@ class Image:
:doc:`image format documentation
<../handbook/image-file-formats>` for each writer.
You can use a file object instead of a filename. In this case,
You can use a file object instead of a file path. In this case,
you must always specify the format. The file object must
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 file path (str or bytes), path-like 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
@ -2216,7 +2216,7 @@ class Image:
filename = str(fp)
open_fp = True
elif is_path(fp):
filename = fp
filename = os.fsdecode(fp)
open_fp = True
elif fp == sys.stdout:
try:
@ -2992,7 +2992,7 @@ def open(fp, mode="r", formats=None):
: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 file path (str or bytes), path-like object or a file object.
The file object must implement ``file.read``,
``file.seek``, and ``file.tell`` methods,
and be opened in binary mode.

View File

@ -202,6 +202,7 @@ class FreeTypeFont:
)
if is_path(font):
font = os.fspath(font)
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
@ -818,10 +819,10 @@ def load(filename):
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
"""
Load a TrueType or OpenType font from a file or file-like object,
r"""
Load a TrueType or OpenType font from a file path or file-like object,
and create a font object.
This function loads a font object from the given file or file-like
This function loads a font object from the given file path or file-like
object, and creates a font object for a font of the given size.
Pillow uses FreeType to open font files. If you are opening many fonts
@ -831,12 +832,12 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
This function requires the _imagingft service.
:param font: A filename or file-like object containing a TrueType font.
If the file is not found in this filename, the loader may also
search in other directories, such as the :file:`fonts/`
:param font: A file path or file-like object containing a TrueType font.
If the file is not found at this path, the loader may also
search in other directories, such as the :file:`%WINDIR%\fonts\`
directory on Windows or :file:`/Library/Fonts/`,
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
macOS.
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/`
on macOS.
:param size: The requested size, in pixels.
:param index: Which font face to load (default is first available face).
@ -878,7 +879,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
except OSError:
if not is_path(font):
raise
ttf_filename = os.path.basename(font)
ttf_filename = os.fsdecode(os.path.basename(font))
dirs = []
if sys.platform == "win32":

View File

@ -1,9 +1,12 @@
import os
from pathlib import Path
def is_path(f):
return isinstance(f, (bytes, str, Path))
"""
Checks whether the given object is a string or path-like object that
isn't also a file-like object
"""
return isinstance(f, (bytes, str, os.PathLike)) and not hasattr(f, "read")
def is_directory(f):