Repository: pypa/wheel

Issue:
onerror now producing warnings in Python 3.12 latest alpha
Using `onerror` is producing warnings in the latest Python 3.12 alpha.

I believe it's used here: https://github.com/pypa/wheel/blob/c87e6ed82b58b41b258a3e8c852af8bc1817bb00/src/wheel/bdist_wheel.py#L419

And deprecated here: https://github.com/python/cpython/pull/102829 & changelog: https://docs.python.org/zh-cn/3.12/whatsnew/3.12.html - to be removed in 3.14.

(It's breaking my tests where I have warnings as errors on)

Comments:

Patch:
diff --git a/docs/news.rst b/docs/news.rst
index 97d7b65..543b3bc 100644
--- a/docs/news.rst
+++ b/docs/news.rst
@@ -5,6 +5,8 @@ Release Notes
 
 - Added full support of the build tag syntax to ``wheel tags`` (you can now set a build
   tag like ``123mytag``)
+- Fixed warning on Python 3.12 about ``onerror`` deprecation. (PR by Henry Schreiner)
+- Support testing on Python 3.12 betas (PR by Ewout ter Hoeven)
 
 **0.40.0 (2023-03-14)**
 
diff --git a/src/wheel/bdist_wheel.py b/src/wheel/bdist_wheel.py
index cb9c19f..604d5d6 100644
--- a/src/wheel/bdist_wheel.py
+++ b/src/wheel/bdist_wheel.py
@@ -131,7 +131,10 @@ def safer_version(version):
 
 
 def remove_readonly(func, path, excinfo):
-    print(str(excinfo[1]))
+    remove_readonly_exc(func, path, excinfo[1])
+
+
+def remove_readonly_exc(func, path, exc):
     os.chmod(path, stat.S_IWRITE)
     func(path)
 
@@ -416,7 +419,10 @@ class bdist_wheel(Command):
         if not self.keep_temp:
             log.info(f"removing {self.bdist_dir}")
             if not self.dry_run:
-                rmtree(self.bdist_dir, onerror=remove_readonly)
+                if sys.version_info < (3, 12):
+                    rmtree(self.bdist_dir, onerror=remove_readonly)
+                else:
+                    rmtree(self.bdist_dir, onexc=remove_readonly_exc)
 
     def write_wheelfile(
         self, wheelfile_base, generator="bdist_wheel (" + wheel_version + ")"


Test patch:
diff --git a/tests/test_bdist_wheel.py b/tests/test_bdist_wheel.py
index d32a1fb..d202469 100644
--- a/tests/test_bdist_wheel.py
+++ b/tests/test_bdist_wheel.py
@@ -6,11 +6,17 @@ import stat
 import subprocess
 import sys
 import sysconfig
+from unittest.mock import Mock
 from zipfile import ZipFile
 
 import pytest
 
-from wheel.bdist_wheel import bdist_wheel, get_abi_tag
+from wheel.bdist_wheel import (
+    bdist_wheel,
+    get_abi_tag,
+    remove_readonly,
+    remove_readonly_exc,
+)
 from wheel.vendored.packaging import tags
 from wheel.wheelfile import WheelFile
 
@@ -296,3 +302,30 @@ def test_platform_with_space(dummy_dist, monkeypatch):
     subprocess.check_call(
         [sys.executable, "setup.py", "bdist_wheel", "--plat-name", "isilon onefs"]
     )
+
+
+def test_rmtree_readonly(monkeypatch, tmp_path, capsys):
+    """Verify onerr works as expected"""
+
+    bdist_dir = tmp_path / "with_readonly"
+    bdist_dir.mkdir()
+    some_file = bdist_dir.joinpath("file.txt")
+    some_file.touch()
+    some_file.chmod(stat.S_IREAD)
+
+    expected_count = 1 if sys.platform.startswith("win") else 0
+
+    if sys.version_info < (3, 12):
+        count_remove_readonly = Mock(side_effect=remove_readonly)
+        shutil.rmtree(bdist_dir, onerror=count_remove_readonly)
+        assert count_remove_readonly.call_count == expected_count
+    else:
+        count_remove_readonly_exc = Mock(side_effect=remove_readonly_exc)
+        shutil.rmtree(bdist_dir, onexc=count_remove_readonly_exc)
+        assert count_remove_readonly_exc.call_count == expected_count
+
+    assert not bdist_dir.is_dir()
+
+    if expected_count:
+        captured = capsys.readouterr()
+        assert "file.txt" in captured.stdout