diff --git a/conftest.py b/conftest.py index f4e11ee..f4be30e 100644 --- a/conftest.py +++ b/conftest.py @@ -58,6 +58,12 @@ def _install_stubs(): fp = sys.modules["folder_paths"] if not hasattr(fp, "get_output_directory"): fp.get_output_directory = lambda: os.path.join(_NODE_ROOT, "_test_output") # type: ignore + if not hasattr(fp, "get_full_path"): + # Stub returns a synthetic path for any (folder, name) lookup so tests + # that don't care about file existence (e.g. mocked loaders) don't trip + # the "file not found -> fall back" guard in florence2_hires. Tests that + # specifically want to exercise the missing-file path can monkeypatch. + fp.get_full_path = lambda folder, name: f"/{folder}/{name}" if name else None # type: ignore # server.PromptServer if "server" not in sys.modules: diff --git a/florence2_hires.py b/florence2_hires.py index 3316454..757c189 100644 --- a/florence2_hires.py +++ b/florence2_hires.py @@ -401,7 +401,25 @@ def _get_or_load_checkpoint_lora(item, fallback, model_source, cache, session_mo cache[key] = result return result - # Different model — full load. + # Different model — full load. Validate the file exists first; without this + # guard a missing checkpoint hits comfy/utils.load_torch_file(None,...) and + # crashes with "'NoneType' has no attribute 'lower'". Common scenario: manifest + # was generated on a different machine that had the per-item checkpoint + # installed. + try: + import folder_paths as _fp_check + ckpt_path_check = _fp_check.get_full_path("checkpoints", model_name) + except Exception: + ckpt_path_check = None + if ckpt_path_check is None: + print( + f"[Florence2HiResFix] Item references model '{model_name}' but the " + f"file is not installed in ComfyUI/models/checkpoints/. Falling back to " + f"session default." + ) + cache[key] = fallback + return fallback + try: model, clip, vae = ckpt_fn( target_model_name=model_name, diff --git a/upscale_runner.py b/upscale_runner.py index 8b433d2..8b345c7 100644 --- a/upscale_runner.py +++ b/upscale_runner.py @@ -124,9 +124,30 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data, _ext = "jpg" if _fmt in ("jpg", "jpeg") else _fmt # Load model, VAE, CLIP for conditioning - model_name = meta.get("model", "") + model_name = meta.get("model", "") or "" vae_name = meta.get("vae", "") + # Validate the checkpoint exists before calling load_checkpoint. Without + # this guard, a missing file ends up at comfy/utils.py:load_torch_file(None, + # ...) and crashes with the cryptic "'NoneType' object has no attribute + # 'lower'". Common cause: manifest copied from another machine where the + # checkpoint isn't installed; or manifest entry has model=None. + try: + import folder_paths as _fp_check + ckpt_path_check = _fp_check.get_full_path("checkpoints", model_name) if model_name else None + except Exception: + ckpt_path_check = None + if not model_name or ckpt_path_check is None: + job["status"] = "error" + job["error"] = ( + f"Checkpoint '{model_name or '(empty)'}' not found in " + f"ComfyUI/models/checkpoints/. The session manifest references it but " + f"the file is not installed on this machine. Copy the .safetensors " + f"file over, or regenerate the images on this machine first." + ) + print(f"[DashboardUpscale] ❌ {job['error']}") + return + # VRAM hygiene before load_checkpoint: the previous workflow's model handles # may still be Python-referenced and won't be evicted by ComfyUI's LRU on a # new load_checkpoint call. soft_empty_cache + gc.collect releases those