diff --git a/TODO.md b/TODO.md index 67dc72f18..aa0c134bd 100644 --- a/TODO.md +++ b/TODO.md @@ -95,6 +95,24 @@ Anything marked with **(!!!)** means a change *will* eventually be required. - [asyncio.run](https://docs.python.org/3.14/library/asyncio-runner.html#asyncio.run) - [asyncio.Runner](https://docs.python.org/3.14/library/asyncio-runner.html#asyncio.Runner) +### Shutil + +#### rmtree + +- `onerror` deprecated and replaced with `onexc` in **Python 3.12** +``` python + def excRemoveReadonly(func, path, exc: BaseException): + import stat + shared.log.debug(f'Exception during cleanup: {func} {path} {type(exc).__name__}') + if func in (os.rmdir, os.remove, os.unlink) and isinstance(exc, PermissionError): + shared.log.debug(f'Retrying cleanup: {path}') + os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + func(path) + # ... + try: + shutil.rmtree(found.path, ignore_errors=False, onexc=excRemoveReadonly) +``` + ## Code TODO > npm run todo diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index f1240a64c..640c0d622 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -191,10 +191,11 @@ def install_extension(extension_to_install, search_text, sort_column): def uninstall_extension(extension_path, search_text, sort_column): - def excRemoveReadonly(func, path, exc: Exception): + def errorRemoveReadonly(func, path, exc): import stat - shared.log.debug(f'Exception during cleanup: {func} {path} {type(exc).__name__}') - if func in (os.rmdir, os.remove, os.unlink) and isinstance(exc, PermissionError): + excvalue = exc[1] + shared.log.debug(f'Exception during cleanup: {func} {path} {excvalue.strerror}') + if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES: shared.log.debug(f'Retrying cleanup: {path}') os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) func(path) @@ -203,7 +204,7 @@ def uninstall_extension(extension_path, search_text, sort_column): if len(found) > 0 and os.path.isdir(extension_path): found = found[0] try: - shutil.rmtree(found.path, ignore_errors=False, onexc=excRemoveReadonly) + shutil.rmtree(found.path, ignore_errors=False, onerror=errorRemoveReadonly) # pylint: disable=deprecated-argument # extensions.extensions = [extension for extension in extensions.extensions if os.path.abspath(found.path) != os.path.abspath(extension_path)] except Exception as e: shared.log.warning(f'Extension uninstall failed: {found.path} {e}')