Files
Claude 3adebb731f Rename FunPack to FunUI (5.0.3 Classy Chopper): /fun endpoint, wink logo, load-time migration of old names
Old project/workflow names and browser settings migrate on load; /funpack redirects to /fun.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016acRss69eH1235eRdFTZHY
2026-10-25 13:11:03 +00:00

225 lines
7.4 KiB
Python

"""Traits: what is read off a model, and what a model's own module contributes.
The split under test is the one the architecture depends on -- core reads only
facts that need no knowledge of any particular model, and everything else is
announced. If core ever learned a model's name, supporting the next one would
mean editing core, which is the whole thing this avoids.
"""
import pytest
from core.contract import ModuleSpec
from core.traits import contributed, has_block, missing_for, split, traits_of, universal
class _LatentFormat:
def __init__(self, dims, tdr=1):
self.latent_dimensions = dims
self.temporal_downscale_ratio = tdr
class _Kind:
def __init__(self, name):
self.name = name
class _Model:
"""The shape traits_of() reads: patcher -> model -> model_config."""
def __init__(self, dims=3, tdr=4, kind="FLOW"):
outer = self
class Inner:
model_type = _Kind(kind)
class model_config:
latent_format = _LatentFormat(dims, tdr)
self.model = Inner()
del outer
def test_latent_rank_is_read_not_guessed():
assert "temporal_latent" in universal(_Model(dims=3))
assert "spatial_latent" in universal(_Model(dims=2))
assert "waveform_latent" in universal(_Model(dims=1))
def test_the_prediction_type_is_named():
assert "predict_flow" in universal(_Model(kind="FLOW"))
assert "predict_eps" in universal(_Model(kind="EPS"))
def test_an_unreadable_model_yields_nothing_rather_than_raising():
class Bare:
model = None
assert traits_of(Bare()) == []
def test_a_module_contributes_traits_core_could_not_read():
spec = ModuleSpec(id="m", title="M", mount="", traits=lambda model: ["audio_stream"])
assert "audio_stream" in traits_of(_Model(), [spec])
def test_a_provider_that_raises_does_not_silence_the_others():
def explodes(model):
raise RuntimeError("boom")
bad = ModuleSpec(id="bad", title="B", mount="", traits=explodes)
good = ModuleSpec(id="good", title="G", mount="", traits=lambda m: ["audio_stream"])
found = traits_of(_Model(), [bad, good])
assert "audio_stream" in found, "one bad provider disabled every other module"
assert "temporal_latent" in found
def test_a_provider_that_raises_is_not_reported_as_one_that_never_loaded():
"""The log line, not only the surviving traits.
A traits provider checks whether the model is its own FIRST, so reaching the
exception means it loaded, recognised the model and then broke. Saying "did
not load" sends a reader after an import error that never happened -- and
losing a model's traits quietly narrows which modifiers are considered
compatible, so the log is the only thing pointing at the cause.
"""
from core import log
def explodes(model):
raise RuntimeError("adaln lookup exploded")
log._reset()
traits_of(_Model(), [ModuleSpec(id="bad", title="B", mount="", traits=explodes)])
said = log.history()
assert said, "a provider broke and nothing was said"
assert not any("did not load" in r["message"] for r in said), \
[r["message"] for r in said]
assert any("failed while" in r["message"] for r in said), [r["message"] for r in said]
assert said[-1]["level"] == log.WARNING
def test_declaring_no_traits_means_compatible_with_everything():
# Narrowing, not opting in -- the property that lets a modifier reach models
# FunUI knows nothing about.
plain = ModuleSpec(id="plain", title="P", mount="")
compatible, incompatible = split([plain], [])
assert compatible == [plain] and incompatible == []
def test_declaring_a_trait_excludes_the_models_that_lack_it():
picky = ModuleSpec(id="picky", title="P", mount="", requires=["audio_stream"])
compatible, incompatible = split([picky], ["temporal_latent"])
assert incompatible == [picky]
assert missing_for(picky, ["temporal_latent"]) == ["audio_stream"]
def test_a_structural_probe_finds_a_block_by_class_name():
class Target:
pass
class Root:
def modules(self):
return [self, Target()]
class Model:
class model:
diffusion_model = Root()
# The question a modifier actually has: does this model have the thing I
# touch -- not "is this the model I was written for".
assert has_block(Model(), "Target")
assert not has_block(Model(), "SomethingElse")
def test_a_probe_on_a_model_with_no_module_tree_is_false_not_fatal():
class Model:
model = None
assert has_block(Model(), "Anything") is False
# --- against ComfyUI's real model configs ---------------------------------
@pytest.fixture(scope="module")
def real_configs(comfyui):
import comfy.supported_models as sm
return sm.models
def test_every_real_model_config_can_be_read(real_configs):
# The point is coverage, not a particular answer: whatever ships upstream,
# reading it must not raise and must not invent anything.
known = {"temporal_latent", "spatial_latent", "waveform_latent",
"temporal_compression"}
for cfg in real_configs:
model = _Model.__new__(_Model)
class Inner:
model_type = None
model_config = cfg
model.model = Inner()
found = universal(model)
assert found <= known, f"{cfg.__name__} produced an unexpected trait: {found - known}"
def test_traits_describe_the_tensor_not_an_assumed_modality(real_configs):
"""QwenImage is an IMAGE model with a 3-D latent. LTXV is a video model with
a 3-D latent. They must read the same, because the latent really is the same
shape -- claiming otherwise would be FunUI guessing at modality, which
ComfyUI itself does not classify."""
by_name = {cfg.__name__: cfg for cfg in real_configs}
qwen, ltxv = by_name.get("QwenImage"), by_name.get("LTXV")
if qwen is None or ltxv is None:
pytest.skip("upstream renamed these configs")
def read(cfg):
model = _Model.__new__(_Model)
class Inner:
model_type = None
model_config = cfg
model.model = Inner()
return universal(model)
assert "temporal_latent" in read(qwen)
assert "temporal_latent" in read(ltxv)
def test_a_bare_probe_name_collides_across_architectures():
"""`Attention`, `MLP`, `Block` are not distinctive. A bare name matches any
of them, which is why a probe that needs to be sure must qualify."""
class Root:
def modules(self):
# A class named Attention, but from an unrelated architecture.
other = type("Attention", (), {})
other.__module__ = "somewhere.else.entirely"
return [self, other()]
class Model:
class model:
diffusion_model = Root()
assert has_block(Model(), "Attention") # the collision, unqualified
assert not has_block(Model(), "comfy.ldm.minimax.model.Attention")
def test_a_qualified_probe_matches_its_own_architecture():
class Root:
def modules(self):
target = type("AdalnProj", (), {})
target.__module__ = "comfy.ldm.minimax.model"
return [self, target()]
class Model:
class model:
diffusion_model = Root()
assert has_block(Model(), "comfy.ldm.minimax.model.AdalnProj")
assert has_block(Model(), "minimax.model.AdalnProj") # tail match
assert has_block(Model(), "AdalnProj") # distinctive enough
assert not has_block(Model(), "other.pack.AdalnProj")