Skip to content

Commit c45cd16

Browse files
bpo-28230: Document the pathlib support in tarfile and add tests. (#512)
1 parent 21a7431 commit c45cd16

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed

Doc/library/tarfile.rst

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ Some facts and figures:
146146
.. versionchanged:: 3.5
147147
The ``'x'`` (exclusive creation) mode was added.
148148

149+
.. versionchanged:: 3.6
150+
The *name* parameter accepts a :term:`path-like object`.
151+
152+
149153
.. class:: TarFile
150154

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

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

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

327+
.. versionchanged:: 3.6
328+
The *name* parameter accepts a :term:`path-like object`.
329+
330+
322331
.. classmethod:: TarFile.open(...)
323332

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

402+
.. versionchanged:: 3.6
403+
The *path* parameter accepts a :term:`path-like object`.
404+
393405

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

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

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

433+
.. versionchanged:: 3.6
434+
The *path* parameter accepts a :term:`path-like object`.
435+
436+
421437
.. method:: TarFile.extractfile(member)
422438

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

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

491+
.. versionchanged:: 3.6
492+
The *name* parameter accepts a :term:`path-like object`.
493+
474494

475495
.. method:: TarFile.close()
476496

Lib/test/test_tarfile.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from hashlib import md5
55
from contextlib import contextmanager
66
from random import Random
7+
import pathlib
78

89
import unittest
910
import unittest.mock
@@ -440,6 +441,22 @@ def test_bytes_name_attribute(self):
440441
self.assertIsInstance(tar.name, bytes)
441442
self.assertEqual(tar.name, os.path.abspath(fobj.name))
442443

444+
def test_pathlike_name(self):
445+
tarname = pathlib.Path(self.tarname)
446+
with tarfile.open(tarname, mode=self.mode) as tar:
447+
self.assertIsInstance(tar.name, str)
448+
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
449+
with self.taropen(tarname) as tar:
450+
self.assertIsInstance(tar.name, str)
451+
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
452+
with tarfile.TarFile.open(tarname, mode=self.mode) as tar:
453+
self.assertIsInstance(tar.name, str)
454+
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
455+
if self.suffix == '':
456+
with tarfile.TarFile(tarname, mode='r') as tar:
457+
self.assertIsInstance(tar.name, str)
458+
self.assertEqual(tar.name, os.path.abspath(os.fspath(tarname)))
459+
443460
def test_illegal_mode_arg(self):
444461
with open(tmpname, 'wb'):
445462
pass
@@ -582,6 +599,26 @@ def test_extract_directory(self):
582599
finally:
583600
support.rmtree(DIR)
584601

602+
def test_extractall_pathlike_name(self):
603+
DIR = pathlib.Path(TEMPDIR) / "extractall"
604+
with support.temp_dir(DIR), \
605+
tarfile.open(tarname, encoding="iso8859-1") as tar:
606+
directories = [t for t in tar if t.isdir()]
607+
tar.extractall(DIR, directories)
608+
for tarinfo in directories:
609+
path = DIR / tarinfo.name
610+
self.assertEqual(os.path.getmtime(path), tarinfo.mtime)
611+
612+
def test_extract_pathlike_name(self):
613+
dirtype = "ustar/dirtype"
614+
DIR = pathlib.Path(TEMPDIR) / "extractall"
615+
with support.temp_dir(DIR), \
616+
tarfile.open(tarname, encoding="iso8859-1") as tar:
617+
tarinfo = tar.getmember(dirtype)
618+
tar.extract(tarinfo, path=DIR)
619+
extracted = DIR / dirtype
620+
self.assertEqual(os.path.getmtime(extracted), tarinfo.mtime)
621+
585622
def test_init_close_fobj(self):
586623
# Issue #7341: Close the internal file object in the TarFile
587624
# constructor in case of an error. For the test we rely on
@@ -1092,6 +1129,17 @@ def test_directory_size(self):
10921129
finally:
10931130
support.rmdir(path)
10941131

1132+
def test_gettarinfo_pathlike_name(self):
1133+
with tarfile.open(tmpname, self.mode) as tar:
1134+
path = pathlib.Path(TEMPDIR) / "file"
1135+
with open(path, "wb") as fobj:
1136+
fobj.write(b"aaa")
1137+
tarinfo = tar.gettarinfo(path)
1138+
tarinfo2 = tar.gettarinfo(os.fspath(path))
1139+
self.assertIsInstance(tarinfo.name, str)
1140+
self.assertEqual(tarinfo.name, tarinfo2.name)
1141+
self.assertEqual(tarinfo.size, 3)
1142+
10951143
@unittest.skipUnless(hasattr(os, "link"),
10961144
"Missing hardlink implementation")
10971145
def test_link_size(self):
@@ -1501,6 +1549,34 @@ def test_create_existing_taropen(self):
15011549
self.assertEqual(len(names), 1)
15021550
self.assertIn("spameggs42", names[0])
15031551

1552+
def test_create_pathlike_name(self):
1553+
with tarfile.open(pathlib.Path(tmpname), self.mode) as tobj:
1554+
self.assertIsInstance(tobj.name, str)
1555+
self.assertEqual(tobj.name, os.path.abspath(tmpname))
1556+
tobj.add(pathlib.Path(self.file_path))
1557+
names = tobj.getnames()
1558+
self.assertEqual(len(names), 1)
1559+
self.assertIn('spameggs42', names[0])
1560+
1561+
with self.taropen(tmpname) as tobj:
1562+
names = tobj.getnames()
1563+
self.assertEqual(len(names), 1)
1564+
self.assertIn('spameggs42', names[0])
1565+
1566+
def test_create_taropen_pathlike_name(self):
1567+
with self.taropen(pathlib.Path(tmpname), "x") as tobj:
1568+
self.assertIsInstance(tobj.name, str)
1569+
self.assertEqual(tobj.name, os.path.abspath(tmpname))
1570+
tobj.add(pathlib.Path(self.file_path))
1571+
names = tobj.getnames()
1572+
self.assertEqual(len(names), 1)
1573+
self.assertIn('spameggs42', names[0])
1574+
1575+
with self.taropen(tmpname) as tobj:
1576+
names = tobj.getnames()
1577+
self.assertEqual(len(names), 1)
1578+
self.assertIn('spameggs42', names[0])
1579+
15041580

15051581
class GzipCreateTest(GzipTest, CreateTest):
15061582
pass

0 commit comments

Comments
 (0)