Skip to content

Commit 068c399

Browse files
committed
shutil.make_archive(): restore select CPython <= 3.10.5 behavior
Restore following CPython <= 3.10.5 behavior of `shutil.make_archive()`: 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 93f22d3 commit 068c399

File tree

3 files changed

+50
-0
lines changed

3 files changed

+50
-0
lines changed

Lib/shutil.py

+4
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,10 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
11071107
supports_root_dir = getattr(func, 'supports_root_dir', False)
11081108
save_cwd = None
11091109
if root_dir is not None:
1110+
stmd = os.stat(root_dir).st_mode
1111+
if not stat.S_ISDIR(stmd):
1112+
raise NotADirectoryError(root_dir)
1113+
11101114
if supports_root_dir:
11111115
# Support path-like base_name here for backwards-compatibility.
11121116
base_name = os.fspath(base_name)

Lib/test/test_shutil.py

+41
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ def _maxdataOK():
170170
else:
171171
return True
172172

173+
def _unlink_existing_file(path):
174+
try:
175+
os.unlink(path)
176+
except FileNotFoundError:
177+
pass
173178

174179
class BaseTest:
175180

@@ -182,6 +187,15 @@ def mkdtemp(self, prefix=None):
182187
self.addCleanup(os_helper.rmtree, d)
183188
return d
184189

190+
def mkstemp(self, prefix=None):
191+
"""Create a temporary file that will be cleaned up.
192+
193+
Returns the path of the file.
194+
"""
195+
f = tempfile.mkstemp(prefix=prefix, dir=os.getcwd())
196+
self.addCleanup(_unlink_existing_file, f[1])
197+
return f
198+
185199

186200
class TestRmTree(BaseTest, unittest.TestCase):
187201

@@ -1665,6 +1679,33 @@ def test_register_archive_format(self):
16651679
formats = [name for name, params in get_archive_formats()]
16661680
self.assertNotIn('xxx', formats)
16671681

1682+
def test_make_tarfile_rootdir_nodir(self):
1683+
# GH-99203
1684+
self.addCleanup(_unlink_existing_file, f'{TESTFN}.tar')
1685+
1686+
root_dir = self.mkstemp()[1]
1687+
self.assertRaises(NotADirectoryError, make_archive, TESTFN, 'tar', root_dir)
1688+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1689+
1690+
root_dir = self.mkstemp()[1]
1691+
os.unlink(root_dir)
1692+
self.assertRaises(FileNotFoundError, make_archive, TESTFN, 'tar', root_dir)
1693+
self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
1694+
1695+
@support.requires_zlib()
1696+
def test_make_zipfile_rootdir_nodir(self):
1697+
# GH-99203
1698+
self.addCleanup(_unlink_existing_file, f'{TESTFN}.zip')
1699+
1700+
root_dir = self.mkstemp()[1]
1701+
self.assertRaises(NotADirectoryError, make_archive, TESTFN, 'zip', root_dir)
1702+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1703+
1704+
root_dir = self.mkstemp()[1]
1705+
os.unlink(root_dir)
1706+
self.assertRaises(FileNotFoundError, make_archive, TESTFN, 'zip', root_dir)
1707+
self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
1708+
16681709
### shutil.unpack_archive
16691710

16701711
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)