Shot Camera: spaCy model failed to load with preshed 3.0.14 (OverflowError); pin preshed<3.0.14, repair on start, name the real cause in the log
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T81dXrsAyE9Uu8UvYzKvGt
This commit is contained in:
+36
-1
@@ -26,6 +26,10 @@ _lock = threading.Lock()
|
||||
_SPACY_MODEL = ("en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/"
|
||||
"en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl")
|
||||
|
||||
# preshed 3.0.14 breaks spaCy's model loading (OverflowError: Python int too large to convert to C
|
||||
# ssize_t, in Tokenizer.from_disk) on Python 3.13; 3.0.13 loads fine.
|
||||
PRESHED_PIN = "preshed<3.0.14"
|
||||
|
||||
|
||||
def _nvidia_gpu() -> bool:
|
||||
"""nvidia-vfx ships wheels for x86-64 Linux and Windows only, and needs a CUDA card."""
|
||||
@@ -44,7 +48,7 @@ ADDONS = (
|
||||
"requires": ("nvidia-vfx>=0.2",), "modules": ("nvvfx",), "offered": _nvidia_gpu},
|
||||
{"id": "camera", "title": "Automatic camera and focus",
|
||||
"hint": "Lets Shot camera aim camera moves, cuts, transitions and focus at the right things in your prompts, using spaCy.",
|
||||
"requires": ("spacy>=3.8,<3.9", _SPACY_MODEL), "modules": ("spacy", "en_core_web_sm"), "offered": lambda: True},
|
||||
"requires": ("spacy>=3.8,<3.9", PRESHED_PIN, _SPACY_MODEL), "modules": ("spacy", "en_core_web_sm"), "offered": lambda: True},
|
||||
{"id": "lora_training", "title": "LoRA training",
|
||||
"hint": "Train your own LoRA from your media and rated results, with a tag editor and automatic tagging (onnxruntime). Training runs on ComfyUI's own trainer.",
|
||||
"requires": ("onnxruntime>=1.17",), "modules": ("onnxruntime",), "offered": lambda: True},
|
||||
@@ -118,10 +122,41 @@ def set_wanted(addon_id: str, on: bool) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def preshed_broken() -> bool:
|
||||
"""True when the installed preshed is the release that cannot load spaCy models."""
|
||||
try:
|
||||
from importlib import metadata
|
||||
v = tuple(int(x) for x in metadata.version("preshed").split(".")[:3])
|
||||
except Exception: # noqa: BLE001
|
||||
return False
|
||||
return v >= (3, 0, 14)
|
||||
|
||||
|
||||
def repair_spacy() -> bool:
|
||||
"""Downgrade preshed when spaCy is here with the broken one. Only a restart makes the running
|
||||
process see it (the compiled module is already loaded), so the log says so."""
|
||||
if not (importlib.util.find_spec("spacy") and preshed_broken()):
|
||||
return False
|
||||
import subprocess
|
||||
try:
|
||||
proc = subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", PRESHED_PIN],
|
||||
capture_output=True, text=True, timeout=300)
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
log.alert("add-ons", f"spaCy needs `pip install \"{PRESHED_PIN}\"` and the automatic try failed: {exc}")
|
||||
return False
|
||||
if proc.returncode != 0:
|
||||
log.alert("add-ons", f"spaCy needs `pip install \"{PRESHED_PIN}\"` and pip failed: {(proc.stderr or proc.stdout)[-300:]}")
|
||||
return False
|
||||
log.info("add-ons", "spaCy could not load its model with preshed 3.0.14; installed an older preshed. Restart ComfyUI once.")
|
||||
return True
|
||||
|
||||
|
||||
def install_wanted() -> None:
|
||||
"""At ComfyUI start: install what is wanted and missing. Never raises, and never retries a failure
|
||||
on its own (a broken install would add minutes to every start); toggling the add-on retries."""
|
||||
try:
|
||||
if "camera" in _wanted(_read()):
|
||||
repair_spacy()
|
||||
with _lock:
|
||||
data = _read() or {}
|
||||
failed = dict(data.get("failed") or {})
|
||||
|
||||
@@ -127,3 +127,15 @@ def test_lora_training_starts_on_when_its_package_is_already_there(here):
|
||||
assert next(a["state"] for a in addons.listing() if a["id"] == "lora_training") == "installed"
|
||||
addons.set_wanted("lora_training", False)
|
||||
assert not addons.enabled("lora_training")
|
||||
|
||||
|
||||
def test_the_camera_add_on_pins_the_preshed_that_can_load_spacy_models():
|
||||
camera = next(a for a in addons.ADDONS if a["id"] == "camera")
|
||||
assert addons.PRESHED_PIN in camera["requires"]
|
||||
|
||||
|
||||
def test_preshed_3_0_14_is_flagged_and_older_is_not(monkeypatch):
|
||||
from importlib import metadata
|
||||
for have, bad in (("3.0.14", True), ("3.0.13", False), ("3.1.0", True)):
|
||||
monkeypatch.setattr(metadata, "version", lambda _n, have=have: have)
|
||||
assert addons.preshed_broken() is bad
|
||||
|
||||
@@ -103,8 +103,14 @@ def _spacy():
|
||||
import spacy
|
||||
_nlp = spacy.load("en_core_web_sm")
|
||||
except Exception as e: # noqa: BLE001
|
||||
fix = ("spacy not installed, run `pip install spacy`" if isinstance(e, ImportError)
|
||||
else "model missing, run `python -m spacy download en_core_web_sm`")
|
||||
if isinstance(e, ImportError):
|
||||
fix = "spacy not installed, run `pip install spacy`"
|
||||
elif isinstance(e, OverflowError) and addons.preshed_broken():
|
||||
fix = f"the installed preshed breaks spaCy's model loading, run `pip install \"{addons.PRESHED_PIN}\"` and restart ComfyUI"
|
||||
elif isinstance(e, OSError):
|
||||
fix = "model missing, run `python -m spacy download en_core_web_sm`"
|
||||
else:
|
||||
fix = "spaCy could not load its model (see the error above)"
|
||||
_say("spaCy tagger", e, f"simple word rules used; {fix}")
|
||||
return _nlp
|
||||
|
||||
|
||||
@@ -479,3 +479,22 @@ def test_a_lone_shot_can_take_a_view_but_shot_one_of_several_still_cannot():
|
||||
assert sc.view_options("[Shot 1] A cat sits.")[0]["shot"] == 1
|
||||
_o, several = sc.add_shot_views("[Shot 1] A.\n[Shot 2] B.", seed=1, chance=1.0)
|
||||
assert [a["shot"] for a in several] == [2]
|
||||
|
||||
|
||||
def test_a_model_that_overflows_on_load_is_named_as_the_preshed_problem_not_a_missing_model(monkeypatch):
|
||||
"""preshed 3.0.14 raises OverflowError inside spacy.load; the old text blamed a missing model."""
|
||||
import sys
|
||||
import types
|
||||
said = []
|
||||
monkeypatch.setattr(sc, "_nlp", None)
|
||||
monkeypatch.setattr(sc, "_tried", False)
|
||||
monkeypatch.setattr(sc.addons, "enabled", lambda _id: True)
|
||||
monkeypatch.setattr(sc.addons, "preshed_broken", lambda: True)
|
||||
monkeypatch.setattr(sc, "_say", lambda what, error, effect: said.append((type(error).__name__, effect)))
|
||||
fake = types.ModuleType("spacy")
|
||||
def _load(_name):
|
||||
raise OverflowError("Python int too large to convert to C ssize_t")
|
||||
fake.load = _load
|
||||
monkeypatch.setitem(sys.modules, "spacy", fake)
|
||||
assert sc._spacy() is None
|
||||
assert said[0][0] == "OverflowError" and "preshed" in said[0][1] and "model missing" not in said[0][1]
|
||||
|
||||
Reference in New Issue
Block a user