Skip to content

Commit a86df29

Browse files
authored
gh-99203: shutil.make_archive(): restore select CPython <= 3.10.5 behavior (GH-99802)
Restore following CPython <= 3.10.5 behavior of shutil.make_archive() that went away as part of gh-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 a794ebe commit a86df29

File tree

3 files changed

+52
-0
lines changed

3 files changed

+52
-0
lines changed

Lib/shutil.py

+4
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,10 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
11561156
supports_root_dir = getattr(func, 'supports_root_dir', False)
11571157
save_cwd = None
11581158
if root_dir is not None:
1159+
stmd = os.stat(root_dir).st_mode
1160+
if not stat.S_ISDIR(stmd):
1161+
raise NotADirectoryError(errno.ENOTDIR, 'Not a directory', root_dir)
1162+
11591163
if supports_root_dir:
11601164
# Support path-like base_name here for backwards-compatibility.
11611165
base_name = os.fspath(base_name)

Lib/test/test_shutil.py

+43
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,49 @@ def test_register_archive_format(self):
18391839
formats = [name for name, params in get_archive_formats()]
18401840
self.assertNotIn('xxx', formats)
18411841

1842+
def test_make_tarfile_rootdir_nodir(self):
1843+
# GH-99203
1844+
self.addCleanup(os_helper.unlink, f'{TESTFN}.tar')
1845+
for dry_run in (False, True):
1846+
with self.subTest(dry_run=dry_run):
1847+
tmp_dir = self.mkdtemp()
1848+
nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
1849+
with self.assertRaises(FileNotFoundError) as cm:
1850+
make_archive(TESTFN, 'tar', nonexisting_file, dry_run=dry_run)
1851+
self.assertEqual(cm.exception.errno, errno.ENOENT)
1852+
self.assertEqual(cm.exception.filename, nonexisting_file)
1853+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1854+
1855+
tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
1856+
os.close(tmp_fd)
1857+
with self.assertRaises(NotADirectoryError) as cm:
1858+
make_archive(TESTFN, 'tar', tmp_file, dry_run=dry_run)
1859+
self.assertEqual(cm.exception.errno, errno.ENOTDIR)
1860+
self.assertEqual(cm.exception.filename, tmp_file)
1861+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1862+
1863+
@support.requires_zlib()
1864+
def test_make_zipfile_rootdir_nodir(self):
1865+
# GH-99203
1866+
self.addCleanup(os_helper.unlink, f'{TESTFN}.zip')
1867+
for dry_run in (False, True):
1868+
with self.subTest(dry_run=dry_run):
1869+
tmp_dir = self.mkdtemp()
1870+
nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
1871+
with self.assertRaises(FileNotFoundError) as cm:
1872+
make_archive(TESTFN, 'zip', nonexisting_file, dry_run=dry_run)
1873+
self.assertEqual(cm.exception.errno, errno.ENOENT)
1874+
self.assertEqual(cm.exception.filename, nonexisting_file)
1875+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1876+
1877+
tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
1878+
os.close(tmp_fd)
1879+
with self.assertRaises(NotADirectoryError) as cm:
1880+
make_archive(TESTFN, 'zip', tmp_file, dry_run=dry_run)
1881+
self.assertEqual(cm.exception.errno, errno.ENOTDIR)
1882+
self.assertEqual(cm.exception.filename, tmp_file)
1883+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1884+
18421885
### shutil.unpack_archive
18431886

18441887
def check_unpack_archive(self, format, **kwargs):
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)