diff --git a/Lib/test/test_zipapp.py b/Lib/test/test_zipapp.py index 8fb0a68deba535c..466cdc2f785c75a 100644 --- a/Lib/test/test_zipapp.py +++ b/Lib/test/test_zipapp.py @@ -366,6 +366,20 @@ def test_shebang_is_executable(self): zipapp.create_archive(str(source), str(target), interpreter='python') self.assertTrue(target.stat().st_mode & stat.S_IEXEC) + @unittest.skipIf(sys.platform == 'win32', + 'Windows does not support an executable bit') + @os_helper.skip_unless_working_chmod + def test_copied_archive_with_pathlike_target_is_executable(self): + # Test that copying an archive to a PathLike target makes it executable. + source = self.tmpdir / 'source' + source.mkdir() + (source / '__main__.py').touch() + target = self.tmpdir / 'source.pyz' + zipapp.create_archive(source, target, interpreter='python') + new_target = self.tmpdir / 'changed.pyz' + zipapp.create_archive(target, new_target, interpreter='python') + self.assertTrue(new_target.stat().st_mode & stat.S_IEXEC) + @unittest.skipIf(sys.platform == 'win32', 'Windows does not support an executable bit') def test_no_shebang_is_not_executable(self): diff --git a/Lib/zipapp.py b/Lib/zipapp.py index a1cef18ada9d05d..39e765d1ffdc6d4 100644 --- a/Lib/zipapp.py +++ b/Lib/zipapp.py @@ -69,7 +69,7 @@ def _copy_archive(archive, new_archive, interpreter=None): dst.write(first_2) shutil.copyfileobj(src, dst) - if interpreter and isinstance(new_archive, str): + if interpreter and isinstance(new_archive, (str, os.PathLike)): os.chmod(new_archive, os.stat(new_archive).st_mode | stat.S_IEXEC) diff --git a/Misc/NEWS.d/next/Library/2026-08-29-14-28-46.gh-issue-156568.mkxLY8.rst b/Misc/NEWS.d/next/Library/2026-08-29-14-28-46.gh-issue-156568.mkxLY8.rst new file mode 100644 index 000000000000000..18ea6ecba9a0d2b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-29-14-28-46.gh-issue-156568.mkxLY8.rst @@ -0,0 +1,2 @@ +:func:`zipapp.create_archive` now correctly sets the executable bit on the +target archive when the target is a path-like object.