From 457d39832d4f433a59c78598849eef3d073cf7e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Aug 2015 20:54:33 +1000 Subject: [PATCH 1/3] Added support for pathlib Path objects to open and save --- PIL/Image.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 861599bf7a4..87d77ef0982 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -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): + 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() @@ -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 @@ -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) From cf4145e2c9c838336f84dcf5e67ced3b2e4cfc22 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Aug 2015 21:29:24 +1000 Subject: [PATCH 2/3] Added test for pathlib --- Tests/test_image.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Tests/test_image.py b/Tests/test_image.py index bd5fd352227..fa7f8ec064b 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -1,6 +1,7 @@ from helper import unittest, PillowTestCase, hopper from PIL import Image +import sys class TestImage(PillowTestCase): @@ -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)) From 7775ff3ac73f0d6444432bafc8ee140d14cfbb3d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Aug 2015 22:32:15 +1000 Subject: [PATCH 3/3] Updated docstrings --- PIL/Image.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 87d77ef0982..0c254c61d88 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -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 @@ -2267,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