Skip to content

bpo-28230: Document the pathlib support in tarfile and add tests. #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
28 changes: 24 additions & 4 deletions Doc/library/tarfile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ Some facts and figures:
.. versionchanged:: 3.5
The ``'x'`` (exclusive creation) mode was added.

.. versionchanged:: 3.6
The *name* parameter accepts a :term:`path-like object`.


.. class:: TarFile

Class for reading and writing tar archives. Do not use this class directly:
Expand Down Expand Up @@ -266,7 +270,8 @@ be finalized; only the internally used file object will be closed. See the
All following arguments are optional and can be accessed as instance attributes
as well.

*name* is the pathname of the archive. It can be omitted if *fileobj* is given.
*name* is the pathname of the archive. *name* may be a :term:`path-like object`.
It can be omitted if *fileobj* is given.
In this case, the file object's :attr:`name` attribute is used if it exists.

*mode* is either ``'r'`` to read from an existing archive, ``'a'`` to append
Expand Down Expand Up @@ -319,6 +324,10 @@ be finalized; only the internally used file object will be closed. See the
.. versionchanged:: 3.5
The ``'x'`` (exclusive creation) mode was added.

.. versionchanged:: 3.6
The *name* parameter accepts a :term:`path-like object`.


.. classmethod:: TarFile.open(...)

Alternative constructor. The :func:`tarfile.open` function is actually a
Expand Down Expand Up @@ -390,14 +399,17 @@ be finalized; only the internally used file object will be closed. See the
.. versionchanged:: 3.5
Added the *numeric_owner* parameter.

.. versionchanged:: 3.6
The *path* parameter accepts a :term:`path-like object`.


.. method:: TarFile.extract(member, path="", set_attrs=True, *, numeric_owner=False)

Extract a member from the archive to the current working directory, using its
full name. Its file information is extracted as accurately as possible. *member*
may be a filename or a :class:`TarInfo` object. You can specify a different
directory using *path*. File attributes (owner, mtime, mode) are set unless
*set_attrs* is false.
directory using *path*. *path* may be a :term:`path-like object`.
File attributes (owner, mtime, mode) are set unless *set_attrs* is false.

If *numeric_owner* is :const:`True`, the uid and gid numbers from the tarfile
are used to set the owner/group for the extracted files. Otherwise, the named
Expand All @@ -418,6 +430,10 @@ be finalized; only the internally used file object will be closed. See the
.. versionchanged:: 3.5
Added the *numeric_owner* parameter.

.. versionchanged:: 3.6
The *path* parameter accepts a :term:`path-like object`.


.. method:: TarFile.extractfile(member)

Extract a member from the archive as a file object. *member* may be a filename
Expand Down Expand Up @@ -457,7 +473,8 @@ be finalized; only the internally used file object will be closed. See the

Create a :class:`TarInfo` object from the result of :func:`os.stat` or
equivalent on an existing file. The file is either named by *name*, or
specified as a :term:`file object` *fileobj* with a file descriptor. If
specified as a :term:`file object` *fileobj* with a file descriptor.
*name* may be a :term:`path-like object`. If
given, *arcname* specifies an alternative name for the file in the
archive, otherwise, the name is taken from *fileobj*’s
:attr:`~io.FileIO.name` attribute, or the *name* argument. The name
Expand All @@ -471,6 +488,9 @@ be finalized; only the internally used file object will be closed. See the
The :attr:`~TarInfo.name` may also be modified, in which case *arcname*
could be a dummy string.

.. versionchanged:: 3.6
The *name* parameter accepts a :term:`path-like object`.


.. method:: TarFile.close()

Expand Down
76 changes: 76 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from hashlib import md5
from contextlib import contextmanager
from random import Random
import pathlib

import unittest
import unittest.mock
Expand Down Expand Up @@ -440,6 +441,22 @@ def test_bytes_name_attribute(self):
self.assertIsInstance(tar.name, bytes)
self.assertEqual(tar.name, os.path.abspath(fobj.name))

def test_pathlike_name(self):
tarname = pathlib.Path(self.tarname)
with tarfile.open(tarname, mode=self.mode) as tar:
self.assertIsInstance(tar.name, str)
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
with self.taropen(tarname) as tar:
self.assertIsInstance(tar.name, str)
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
with tarfile.TarFile.open(tarname, mode=self.mode) as tar:
self.assertIsInstance(tar.name, str)
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
if self.suffix == '':
with tarfile.TarFile(tarname, mode='r') as tar:
self.assertIsInstance(tar.name, str)
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))

def test_illegal_mode_arg(self):
with open(tmpname, 'wb'):
pass
Expand Down Expand Up @@ -582,6 +599,26 @@ def test_extract_directory(self):
finally:
support.rmtree(DIR)

def test_extractall_pathlike_name(self):
DIR = pathlib.Path(TEMPDIR) / "extractall"
with support.temp_dir(DIR), \
tarfile.open(tarname, encoding="iso8859-1") as tar:
directories = [t for t in tar if t.isdir()]
tar.extractall(DIR, directories)
for tarinfo in directories:
path = DIR / tarinfo.name
self.assertEqual(os.path.getmtime(path), tarinfo.mtime)

def test_extract_pathlike_name(self):
dirtype = "ustar/dirtype"
DIR = pathlib.Path(TEMPDIR) / "extractall"
with support.temp_dir(DIR), \
tarfile.open(tarname, encoding="iso8859-1") as tar:
tarinfo = tar.getmember(dirtype)
tar.extract(tarinfo, path=DIR)
extracted = DIR / dirtype
self.assertEqual(os.path.getmtime(extracted), tarinfo.mtime)

def test_init_close_fobj(self):
# Issue #7341: Close the internal file object in the TarFile
# constructor in case of an error. For the test we rely on
Expand Down Expand Up @@ -1092,6 +1129,17 @@ def test_directory_size(self):
finally:
support.rmdir(path)

def test_gettarinfo_pathlike_name(self):
with tarfile.open(tmpname, self.mode) as tar:
path = pathlib.Path(TEMPDIR) / "file"
with open(path, "wb") as fobj:
fobj.write(b"aaa")
tarinfo = tar.gettarinfo(path)
tarinfo2 = tar.gettarinfo(os.fspath(path))
self.assertIsInstance(tarinfo.name, str)
self.assertEqual(tarinfo.name, tarinfo2.name)
self.assertEqual(tarinfo.size, 3)

@unittest.skipUnless(hasattr(os, "link"),
"Missing hardlink implementation")
def test_link_size(self):
Expand Down Expand Up @@ -1501,6 +1549,34 @@ def test_create_existing_taropen(self):
self.assertEqual(len(names), 1)
self.assertIn("spameggs42", names[0])

def test_create_pathlike_name(self):
with tarfile.open(pathlib.Path(tmpname), self.mode) as tobj:
self.assertIsInstance(tobj.name, str)
self.assertEqual(tobj.name, os.path.abspath(tmpname))
tobj.add(pathlib.Path(self.file_path))
names = tobj.getnames()
self.assertEqual(len(names), 1)
self.assertIn('spameggs42', names[0])

with self.taropen(tmpname) as tobj:
names = tobj.getnames()
self.assertEqual(len(names), 1)
self.assertIn('spameggs42', names[0])

def test_create_taropen_pathlike_name(self):
with self.taropen(pathlib.Path(tmpname), "x") as tobj:
self.assertIsInstance(tobj.name, str)
self.assertEqual(tobj.name, os.path.abspath(tmpname))
tobj.add(pathlib.Path(self.file_path))
names = tobj.getnames()
self.assertEqual(len(names), 1)
self.assertIn('spameggs42', names[0])

with self.taropen(tmpname) as tobj:
names = tobj.getnames()
self.assertEqual(len(names), 1)
self.assertIn('spameggs42', names[0])


class GzipCreateTest(GzipTest, CreateTest):
pass
Expand Down