From dd754caf144009f0569dda5053465ba2accb7b4d Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 22 Jan 2020 21:24:16 -0500 Subject: [PATCH] bpo-29435: Allow is_tarfile to take a filelike obj (GH-18090) `is_tarfile()` now supports `name` being a file or file-like object. --- Doc/library/tarfile.rst | 5 ++- Lib/tarfile.py | 7 +++- Lib/test/test_tarfile.py | 32 +++++++++++++++++++ Misc/ACKS | 1 + .../2020-01-20-18-48-00.bpo-29435.qqJ2Ax.rst | 2 ++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-01-20-18-48-00.bpo-29435.qqJ2Ax.rst diff --git a/Doc/library/tarfile.rst b/Doc/library/tarfile.rst index c34f2c4a570..459e4ad991d 100644 --- a/Doc/library/tarfile.rst +++ b/Doc/library/tarfile.rst @@ -159,7 +159,10 @@ Some facts and figures: .. function:: is_tarfile(name) Return :const:`True` if *name* is a tar archive file, that the :mod:`tarfile` - module can read. + module can read. *name* may be a :class:`str`, file, or file-like object. + + .. versionchanged:: 3.9 + Support for file and file-like objects. The :mod:`tarfile` module defines the following exceptions: diff --git a/Lib/tarfile.py b/Lib/tarfile.py index 2c06f9160c6..d0b748cea17 100755 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -2461,9 +2461,14 @@ class TarFile(object): def is_tarfile(name): """Return True if name points to a tar archive that we are able to handle, else return False. + + 'name' should be a string, file, or file-like object. """ try: - t = open(name) + if hasattr(name, "read"): + t = open(fileobj=name) + else: + t = open(name) t.close() return True except TarError: diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 15324a4e488..6a901089611 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -319,6 +319,38 @@ class LzmaListTest(LzmaTest, ListTest): class CommonReadTest(ReadTest): + def test_is_tarfile_erroneous(self): + with open(tmpname, "wb"): + pass + + # is_tarfile works on filenames + self.assertFalse(tarfile.is_tarfile(tmpname)) + + # is_tarfile works on path-like objects + self.assertFalse(tarfile.is_tarfile(pathlib.Path(tmpname))) + + # is_tarfile works on file objects + with open(tmpname, "rb") as fobj: + self.assertFalse(tarfile.is_tarfile(fobj)) + + # is_tarfile works on file-like objects + self.assertFalse(tarfile.is_tarfile(io.BytesIO(b"invalid"))) + + def test_is_tarfile_valid(self): + # is_tarfile works on filenames + self.assertTrue(tarfile.is_tarfile(self.tarname)) + + # is_tarfile works on path-like objects + self.assertTrue(tarfile.is_tarfile(pathlib.Path(self.tarname))) + + # is_tarfile works on file objects + with open(self.tarname, "rb") as fobj: + self.assertTrue(tarfile.is_tarfile(fobj)) + + # is_tarfile works on file-like objects + with open(self.tarname, "rb") as fobj: + self.assertTrue(tarfile.is_tarfile(io.BytesIO(fobj.read()))) + def test_empty_tarfile(self): # Test for issue6123: Allow opening empty archives. # This test checks if tarfile.open() is able to open an empty tar diff --git a/Misc/ACKS b/Misc/ACKS index 3e45d5d0f7f..7e4b81bfdee 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1856,6 +1856,7 @@ Klaus-Juergen Wolf Dan Wolfe Richard Wolff Adam Woodbeck +William Woodruff Steven Work Gordon Worley Darren Worrall diff --git a/Misc/NEWS.d/next/Library/2020-01-20-18-48-00.bpo-29435.qqJ2Ax.rst b/Misc/NEWS.d/next/Library/2020-01-20-18-48-00.bpo-29435.qqJ2Ax.rst new file mode 100644 index 00000000000..eabc94242c8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-20-18-48-00.bpo-29435.qqJ2Ax.rst @@ -0,0 +1,2 @@ +Allow :func:`tarfile.is_tarfile` to be used with file and file-like +objects, like :func:`zipfile.is_zipfile`. Patch by William Woodruff.