[Python-checkins] [3.12] gh-99203: shutil.make_archive(): restore select CPython <= 3.10.5 behavior (GH-99802) (#107998)

Yhg1s webhook-mailer at python.org
Wed Aug 16 08:22:01 EDT 2023


https://github.com/python/cpython/commit/5d9f20a06c6ad3f1d7812e098717b1c6f7c673fa
commit: 5d9f20a06c6ad3f1d7812e098717b1c6f7c673fa
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: Yhg1s <thomas at python.org>
date: 2023-08-16T14:21:57+02:00
summary:

[3.12] gh-99203: shutil.make_archive(): restore select CPython <= 3.10.5 behavior (GH-99802) (#107998)

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.
(cherry picked from commit a86df298df5b02e2d69ea6879e9ed10a7adb85d0)

Co-authored-by: 6t8k <58048945+6t8k at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2022-11-26-22-05-22.gh-issue-99203.j0DUae.rst
M Lib/shutil.py
M Lib/test/test_shutil.py

diff --git a/Lib/shutil.py b/Lib/shutil.py
index 3f2864af517e7..b37bd082eee0c 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -1156,6 +1156,10 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
     supports_root_dir = getattr(func, 'supports_root_dir', False)
     save_cwd = None
     if root_dir is not None:
+        stmd = os.stat(root_dir).st_mode
+        if not stat.S_ISDIR(stmd):
+            raise NotADirectoryError(errno.ENOTDIR, 'Not a directory', root_dir)
+
         if supports_root_dir:
             # Support path-like base_name here for backwards-compatibility.
             base_name = os.fspath(base_name)
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index d74eee767c991..cd1c3d8cfbc38 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -1840,6 +1840,49 @@ def test_register_archive_format(self):
         formats = [name for name, params in get_archive_formats()]
         self.assertNotIn('xxx', formats)
 
+    def test_make_tarfile_rootdir_nodir(self):
+        # GH-99203
+        self.addCleanup(os_helper.unlink, f'{TESTFN}.tar')
+        for dry_run in (False, True):
+            with self.subTest(dry_run=dry_run):
+                tmp_dir = self.mkdtemp()
+                nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
+                with self.assertRaises(FileNotFoundError) as cm:
+                    make_archive(TESTFN, 'tar', nonexisting_file, dry_run=dry_run)
+                self.assertEqual(cm.exception.errno, errno.ENOENT)
+                self.assertEqual(cm.exception.filename, nonexisting_file)
+                self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
+
+                tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
+                os.close(tmp_fd)
+                with self.assertRaises(NotADirectoryError) as cm:
+                    make_archive(TESTFN, 'tar', tmp_file, dry_run=dry_run)
+                self.assertEqual(cm.exception.errno, errno.ENOTDIR)
+                self.assertEqual(cm.exception.filename, tmp_file)
+                self.assertFalse(os.path.exists(f'{TESTFN}.tar'))
+
+    @support.requires_zlib()
+    def test_make_zipfile_rootdir_nodir(self):
+        # GH-99203
+        self.addCleanup(os_helper.unlink, f'{TESTFN}.zip')
+        for dry_run in (False, True):
+            with self.subTest(dry_run=dry_run):
+                tmp_dir = self.mkdtemp()
+                nonexisting_file = os.path.join(tmp_dir, 'nonexisting')
+                with self.assertRaises(FileNotFoundError) as cm:
+                    make_archive(TESTFN, 'zip', nonexisting_file, dry_run=dry_run)
+                self.assertEqual(cm.exception.errno, errno.ENOENT)
+                self.assertEqual(cm.exception.filename, nonexisting_file)
+                self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
+
+                tmp_fd, tmp_file = tempfile.mkstemp(dir=tmp_dir)
+                os.close(tmp_fd)
+                with self.assertRaises(NotADirectoryError) as cm:
+                    make_archive(TESTFN, 'zip', tmp_file, dry_run=dry_run)
+                self.assertEqual(cm.exception.errno, errno.ENOTDIR)
+                self.assertEqual(cm.exception.filename, tmp_file)
+                self.assertFalse(os.path.exists(f'{TESTFN}.zip'))
+
     ### shutil.unpack_archive
 
     def check_unpack_archive(self, format, **kwargs):
diff --git a/Misc/NEWS.d/next/Library/2022-11-26-22-05-22.gh-issue-99203.j0DUae.rst b/Misc/NEWS.d/next/Library/2022-11-26-22-05-22.gh-issue-99203.j0DUae.rst
new file mode 100644
index 0000000000000..fcfb044d476ac
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-26-22-05-22.gh-issue-99203.j0DUae.rst
@@ -0,0 +1,5 @@
+Restore following CPython <= 3.10.5 behavior of :func:`shutil.make_archive`:
+do not create an empty archive if ``root_dir`` is not a directory, and, in that
+case, raise :class:`FileNotFoundError` or :class:`NotADirectoryError`
+regardless of ``format`` choice. Beyond the brought-back behavior, the function
+may now also raise these exceptions in ``dry_run`` mode.



More information about the Python-checkins mailing list