Skip to content

Commit c086bdf

Browse files
tests(mm): another attempt to fix windows ci test issue
1 parent fc3a9cb commit c086bdf

File tree

2 files changed

+24
-6
lines changed

2 files changed

+24
-6
lines changed

invokeai/app/services/model_install/model_install_default.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from copy import deepcopy
99
from pathlib import Path
1010
from queue import Empty, Queue
11-
from shutil import move, rmtree
11+
from shutil import move
1212
from tempfile import mkdtemp
1313
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union
1414

@@ -56,7 +56,7 @@
5656
from invokeai.backend.util import InvokeAILogger
5757
from invokeai.backend.util.catch_sigint import catch_sigint
5858
from invokeai.backend.util.devices import TorchDevice
59-
from invokeai.backend.util.util import slugify
59+
from invokeai.backend.util.util import safe_rmtree, slugify
6060

6161
if TYPE_CHECKING:
6262
from invokeai.app.services.events.events_base import EventServiceBase
@@ -376,12 +376,12 @@ def unconditionally_delete(self, key: str) -> None: # noqa D102
376376
# Sanity check - file models should be in their own directory under the models dir. The parent of the
377377
# file should be the model's directory, not the Invoke models dir!
378378
assert model_path.parent != self.app_config.models_path
379-
rmtree(model_path.parent)
379+
safe_rmtree(model_path.parent)
380380
elif model_path.is_dir():
381381
# Sanity check - folder models should be in their own directory under the models dir. The path should
382382
# not be the Invoke models dir itself!
383383
assert model_path != self.app_config.models_path
384-
rmtree(model_path)
384+
safe_rmtree(model_path)
385385
self.unregister(key)
386386

387387
@classmethod
@@ -511,7 +511,7 @@ def _install_next_item(self) -> None:
511511
finally:
512512
# if this is an install of a remote file, then clean up the temporary directory
513513
if job._install_tmpdir is not None:
514-
rmtree(job._install_tmpdir)
514+
safe_rmtree(job._install_tmpdir)
515515
self._install_completed_event.set()
516516
self._install_queue.task_done()
517517
self._logger.info(f"Installer thread {threading.get_ident()} exiting")
@@ -556,7 +556,7 @@ def _remove_dangling_install_dirs(self) -> None:
556556
path = self._app_config.models_path
557557
for tmpdir in path.glob(f"{TMPDIR_PREFIX}*"):
558558
self._logger.info(f"Removing dangling temporary directory {tmpdir}")
559-
rmtree(tmpdir)
559+
safe_rmtree(tmpdir)
560560

561561
def _scan_for_missing_models(self) -> list[AnyModelConfig]:
562562
"""Scan the models directory for missing models and return a list of them."""

invokeai/backend/util/util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import io
33
import os
44
import re
5+
import shutil
6+
import stat
57
import unicodedata
68
from pathlib import Path
79

@@ -74,3 +76,19 @@ def __enter__(self):
7476

7577
def __exit__(self, *args):
7678
os.chdir(self.original)
79+
80+
81+
def remove_readonly(func, path, _):
82+
"""Clear the readonly bit and reattempt the removal. Used with shutil.rmtree."""
83+
os.chmod(path, stat.S_IWRITE)
84+
func(path)
85+
86+
87+
def safe_rmtree(path: Path):
88+
"""Recursively delete a directory tree, even if it contains read-only files.
89+
90+
Useful for cleaning up temporary directories on Windows.
91+
92+
See: https://docs.python.org/3/library/shutil.html#rmtree-example
93+
"""
94+
shutil.rmtree(path, onexc=remove_readonly)

0 commit comments

Comments
 (0)