Skip to content

Commit ec8b8d1

Browse files
committed
pythongh-99203: shutil.make_archive(): restore select CPython <= 3.10.5 behavior
Restore following CPython <= 3.10.5 behavior of `shutil.make_archive()` that went away as part of pythongh-93160: Do not create an empty archive if `root_dir` is not a directory, and, in that case, raise `FileNotFoundError` or `NotADirectoryError` regardless of `format` choice. Beyond the brought-back behavior, the function may now also raise these exceptions in `dry_run` mode.
1 parent 024ac54 commit ec8b8d1

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

Lib/shutil.py

+4
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,10 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
11041104
supports_root_dir = getattr(func, 'supports_root_dir', False)
11051105
save_cwd = None
11061106
if root_dir is not None:
1107+
stmd = os.stat(root_dir).st_mode
1108+
if not stat.S_ISDIR(stmd):
1109+
raise NotADirectoryError(root_dir)
1110+
11071111
if supports_root_dir:
11081112
# Support path-like base_name here for backwards-compatibility.
11091113
base_name = os.fspath(base_name)

Lib/test/test_shutil.py

+42
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,48 @@ def test_register_archive_format(self):
16651665
formats = [name for name, params in get_archive_formats()]
16661666
self.assertNotIn('xxx', formats)
16671667

1668+
def _unlink_existing_file(self, path):
1669+
try:
1670+
os.unlink(path)
1671+
except FileNotFoundError:
1672+
print(f"File {path} not found")
1673+
pass
1674+
1675+
def test_make_tarfile_rootdir_nodir(self):
1676+
# GH-99203
1677+
self.addCleanup(self._unlink_existing_file, f'{TESTFN}.tar')
1678+
for dry_run in (0, True):
1679+
tmp_fd, tmp_file = tempfile.mkstemp(dir=self.mkdtemp())
1680+
os.close(tmp_fd)
1681+
with self.assertRaises(NotADirectoryError):
1682+
make_archive(TESTFN, 'tar', tmp_file, dry_run=dry_run)
1683+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1684+
1685+
tmp_fd, tmp_file = tempfile.mkstemp(dir=self.mkdtemp())
1686+
os.close(tmp_fd)
1687+
os.unlink(tmp_file)
1688+
with self.assertRaises(FileNotFoundError):
1689+
make_archive(TESTFN, 'tar', tmp_file, dry_run=dry_run)
1690+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1691+
1692+
@support.requires_zlib()
1693+
def test_make_zipfile_rootdir_nodir(self):
1694+
# GH-99203
1695+
self.addCleanup(self._unlink_existing_file, f'{TESTFN}.zip')
1696+
for dry_run in (0, True):
1697+
tmp_fd, tmp_file = tempfile.mkstemp(dir=self.mkdtemp())
1698+
os.close(tmp_fd)
1699+
with self.assertRaises(NotADirectoryError):
1700+
make_archive(TESTFN, 'zip', tmp_file, dry_run=dry_run)
1701+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1702+
1703+
tmp_fd, tmp_file = tempfile.mkstemp(dir=self.mkdtemp())
1704+
os.close(tmp_fd)
1705+
os.unlink(tmp_file)
1706+
with self.assertRaises(FileNotFoundError):
1707+
make_archive(TESTFN, 'zip', tmp_file, dry_run=dry_run)
1708+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1709+
16681710
### shutil.unpack_archive
16691711

16701712
def check_unpack_archive(self, format):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Restore following CPython <= 3.10.5 behavior of :func:`shutil.make_archive`:
2+
do not create an empty archive if ``root_dir`` is not a directory, and, in that
3+
case, raise :class:`FileNotFoundError` or :class:`NotADirectoryError`
4+
regardless of ``format`` choice. Beyond the brought-back behavior, the function
5+
may now also raise these exceptions in ``dry_run`` mode.

0 commit comments

Comments
 (0)