diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 7bc91a9fc19..007e3228f9a 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -2216,3 +2216,10 @@ because of the behavior of the socket option ``SO_REUSEADDR`` in UDP. For more details, see the documentation for ``loop.create_datagram_endpoint()``. (Contributed by Kyle Stanley, Antoine Pitrou, and Yury Selivanov in :issue:`37228`.) + +Notable changes in Python 3.8.2 +=============================== + +Fixed a regression with the ``ignore`` callback of :func:`shutil.copytree`. +The argument types are now str and List[str] again. +(Contributed by Manuel Barkhau and Giampaolo Rodola in :issue:`39390`.) diff --git a/Lib/shutil.py b/Lib/shutil.py index 755ce392e6d..cde7b860050 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -442,7 +442,7 @@ def ignore_patterns(*patterns): def _copytree(entries, src, dst, symlinks, ignore, copy_function, ignore_dangling_symlinks, dirs_exist_ok=False): if ignore is not None: - ignored_names = ignore(src, {x.name for x in entries}) + ignored_names = ignore(os.fspath(src), [x.name for x in entries]) else: ignored_names = set() diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 286e333a8aa..bcb7e498e19 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -880,6 +880,48 @@ class TestShutil(unittest.TestCase): shutil.rmtree(src_dir) shutil.rmtree(os.path.dirname(dst_dir)) + def test_copytree_arg_types_of_ignore(self): + join = os.path.join + exists = os.path.exists + + tmp_dir = self.mkdtemp() + src_dir = join(tmp_dir, "source") + + os.mkdir(join(src_dir)) + os.mkdir(join(src_dir, 'test_dir')) + os.mkdir(os.path.join(src_dir, 'test_dir', 'subdir')) + write_file((src_dir, 'test_dir', 'subdir', 'test.txt'), '456') + + invokations = [] + + def _ignore(src, names): + invokations.append(src) + self.assertIsInstance(src, str) + self.assertIsInstance(names, list) + self.assertEqual(len(names), len(set(names))) + for name in names: + self.assertIsInstance(name, str) + return [] + + dst_dir = join(self.mkdtemp(), 'destination') + shutil.copytree(src_dir, dst_dir, ignore=_ignore) + self.assertTrue(exists(join(dst_dir, 'test_dir', 'subdir', + 'test.txt'))) + + dst_dir = join(self.mkdtemp(), 'destination') + shutil.copytree(pathlib.Path(src_dir), dst_dir, ignore=_ignore) + self.assertTrue(exists(join(dst_dir, 'test_dir', 'subdir', + 'test.txt'))) + + dst_dir = join(self.mkdtemp(), 'destination') + src_dir_entry = list(os.scandir(tmp_dir))[0] + self.assertIsInstance(src_dir_entry, os.DirEntry) + shutil.copytree(src_dir_entry, dst_dir, ignore=_ignore) + self.assertTrue(exists(join(dst_dir, 'test_dir', 'subdir', + 'test.txt'))) + + self.assertEqual(len(invokations), 9) + def test_copytree_retains_permissions(self): tmp_dir = tempfile.mkdtemp() src_dir = os.path.join(tmp_dir, 'source') diff --git a/Misc/NEWS.d/next/Library/2020-01-23-21-34-29.bpo-39390.D2tSXk.rst b/Misc/NEWS.d/next/Library/2020-01-23-21-34-29.bpo-39390.D2tSXk.rst new file mode 100644 index 00000000000..ffa961ea4cd --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-01-23-21-34-29.bpo-39390.D2tSXk.rst @@ -0,0 +1,2 @@ +Fixed a regression with the `ignore` callback of :func:`shutil.copytree`. +The argument types are now str and List[str] again.