Skip to content
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
34 changes: 20 additions & 14 deletions PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,7 @@ def save(self, fp, format=None, **params):
implement the ``seek``, ``tell``, and ``write``
methods, and be opened in binary mode.

:param fp: File name or file object.
:param fp: A filename (string), pathlib.Path 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 @@ -1653,13 +1653,15 @@ def save(self, fp, format=None, **params):
may have been created, and may contain partial data.
"""

filename = ""
if isPath(fp):
filename = fp
else:
if hasattr(fp, "name") and isPath(fp.name):
filename = fp.name
else:
filename = ""
elif sys.version_info >= (3, 4):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm literally a year late on this, but this breaks support for passing in file objects (with a name property) in Python 3.4 and up. Previously the file name would be pulled from fp.name (assuming it was a valid path), but now because this check is in here for Python 3.4, it no longer pulls the file name. As a result, you must set the format explicitly whenever you are saving files now, as the filename will always be an empty string after this point (because a file object is not a path), which means it can't introspect the extension.

This is broken in Pillow 3.0.0 up to the latest master, and isn't documented anywhere.

from pathlib import Path
if isinstance(fp, Path):
filename = str(fp.resolve())
elif hasattr(fp, "name") and isPath(fp.name):
filename = fp.name

# may mutate self!
self.load()
Expand Down Expand Up @@ -1687,8 +1689,8 @@ def save(self, fp, format=None, **params):
else:
save_handler = SAVE[format.upper()]

if isPath(fp):
fp = builtins.open(fp, "wb")
if filename:
fp = builtins.open(filename, "wb")
close = 1
else:
close = 0
Expand Down Expand Up @@ -2265,9 +2267,9 @@ def open(fp, mode="r"):
:py:meth:`~PIL.Image.Image.load` method). See
:py:func:`~PIL.Image.new`.

:param fp: A filename (string) or a file object. The file object
must implement :py:meth:`~file.read`, :py:meth:`~file.seek`, and
:py:meth:`~file.tell` methods, and be opened in binary mode.
:param fp: A filename (string), pathlib.Path object or a file object.
The file object must implement :py:meth:`~file.read`, :py:meth:`~file.seek`,
and :py:meth:`~file.tell` methods, and be opened in binary mode.
:param mode: The mode. If given, this argument must be "r".
:returns: An :py:class:`~PIL.Image.Image` object.
:exception IOError: If the file cannot be found, or the image cannot be
Expand All @@ -2277,11 +2279,15 @@ def open(fp, mode="r"):
if mode != "r":
raise ValueError("bad mode %r" % mode)

filename = ""
if isPath(fp):
filename = fp
fp = builtins.open(fp, "rb")
else:
filename = ""
elif sys.version_info >= (3, 4):
from pathlib import Path
if isinstance(fp, Path):
filename = str(fp.resolve())
if filename:
fp = builtins.open(filename, "rb")

try:
fp.seek(0)
Expand Down
9 changes: 9 additions & 0 deletions Tests/test_image.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from helper import unittest, PillowTestCase, hopper

from PIL import Image
import sys


class TestImage(PillowTestCase):
Expand Down Expand Up @@ -48,6 +49,14 @@ def test_invalid_image(self):
im = io.BytesIO(b'')
self.assertRaises(IOError, lambda: Image.open(im))

@unittest.skipIf(sys.version_info < (3, 4),
"pathlib only available in Python 3.4 or later")
def test_pathlib(self):
from pathlib import Path
im = Image.open(Path("Tests/images/hopper.jpg"))
self.assertEqual(im.mode, "RGB")
self.assertEqual(im.size, (128, 128))

def test_internals(self):

im = Image.new("L", (100, 100))
Expand Down