From 309bc993e7ac56e017f205fd0b8a0b750a06d6b3 Mon Sep 17 00:00:00 2001 From: Francis Secada Date: Thu, 16 Jan 2025 12:33:07 -0500 Subject: [PATCH] Adding `onerror` handler to `shutil.rmtree` function To address a `PermissionError` on windows systems when invoking `shutil.rmtree` in `directory_cleaner`, the `onerror` function will change the permissions of the file to allow for write access. This may provide a brief buffer when deleting directories and files recursely due to a possible race condition; if the write permission error is actually present, then the `onerror` logic activates. Else, it serves as a passthrough. --- readmeai/preprocessor/directory_cleaner.py | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/readmeai/preprocessor/directory_cleaner.py b/readmeai/preprocessor/directory_cleaner.py index c2b7e061..6faf2d34 100644 --- a/readmeai/preprocessor/directory_cleaner.py +++ b/readmeai/preprocessor/directory_cleaner.py @@ -13,6 +13,27 @@ async def remove_directory(path: Path) -> None: await asyncio.to_thread(shutil.rmtree, path, ignore_errors=True) +def onerror(func, path, exc_info): + """ + Error handler for ``shutil.rmtree``. + + Windows may experience an access error when a file is labeled "read + only". In this instance, the function will attempt to add write + permissions and then retry the function. If the error persists or raises + for another reason, the original error gets re-raised. + + Usage: ``shutil.rmtree(path, onerror=onerror)`` + """ + import stat + + # Is the error an access error? + if not os.access(path, os.W_OK): + os.chmod(path, stat.S_IWUSR) + func(path) + else: + raise + + async def remove_hidden_contents(directory: Path) -> None: """Remove hidden files and directories from a specified directory.""" for item in directory.iterdir(): @@ -20,6 +41,6 @@ async def remove_hidden_contents(directory: Path) -> None: continue if item.name.startswith("."): if item.is_dir(): - shutil.rmtree(item) + shutil.rmtree(item, onerror=onerror) else: item.unlink()