feat(ml): grupo comfyui-styles — catálogo curado + merge/dedup + generador LLM de estilos WAS
Tres funciones para gestionar y ampliar el repositorio de estilos del selector
WAS de ComfyUI (Prompt Styles Selector / Prompt Multiple Styles Selector):
- comfyui_curated_styles_catalog (pure): catálogo curado de 190 estilos en 13
categorías (photography, render3d, painting, anime, pixel, illustration,
comic, lighting, camera, material, scifi, fantasy, mood), formato WAS exacto.
- comfyui_append_styles (impure): merge+dedup no destructivo sobre el styles.json
real, con backup atómico, validación de entradas y preservación de existentes.
- comfyui_generate_styles_llm (impure): genera estilos de una categoría vía
ask_llm (grupo claude-direct); robusta (devuelve {} ante 429/JSON corrupto).
Aplicado en vivo: styles.json 269 -> 503 estilos (+190 curados +44 LLM),
backup hecho, selector verifica 503 en /object_info. Tests offline verdes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
"""Tests offline de comfyui_append_styles — no toca la instalación real ni la red.
|
||||
|
||||
Usa un styles.json temporal en /tmp para validar merge, dedup, backup, validación y dry-run.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
from comfyui_append_styles import comfyui_append_styles, DEFAULT_NEGATIVE
|
||||
|
||||
|
||||
def _write_styles(tmpdir: str, data: dict) -> str:
|
||||
path = os.path.join(tmpdir, "styles.json")
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
json.dump(data, fh, ensure_ascii=False, indent=4)
|
||||
return path
|
||||
|
||||
|
||||
def test_merge_preserva_existentes_y_anade_nuevos():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {
|
||||
"A": {"prompt": "a-style", "negative_prompt": "neg-a"},
|
||||
"B": {"prompt": "b-style", "negative_prompt": "neg-b"},
|
||||
})
|
||||
res = comfyui_append_styles(
|
||||
{"C": {"prompt": "c-style", "negative_prompt": "neg-c"}},
|
||||
styles_path=path,
|
||||
)
|
||||
assert res["total_before"] == 2
|
||||
assert res["total_after"] == 3
|
||||
assert res["added"] == ["C"]
|
||||
loaded = json.load(open(path, encoding="utf-8"))
|
||||
# Los existentes intactos.
|
||||
assert loaded["A"] == {"prompt": "a-style", "negative_prompt": "neg-a"}
|
||||
assert loaded["B"] == {"prompt": "b-style", "negative_prompt": "neg-b"}
|
||||
assert loaded["C"] == {"prompt": "c-style", "negative_prompt": "neg-c"}
|
||||
|
||||
|
||||
def test_dedup_no_pisa_por_defecto():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {"A": {"prompt": "orig", "negative_prompt": "n"}})
|
||||
res = comfyui_append_styles(
|
||||
{"A": {"prompt": "NUEVO", "negative_prompt": "n2"}},
|
||||
styles_path=path,
|
||||
)
|
||||
assert res["skipped_existing"] == ["A"]
|
||||
assert res["added"] == []
|
||||
assert res["total_after"] == 1
|
||||
loaded = json.load(open(path, encoding="utf-8"))
|
||||
assert loaded["A"]["prompt"] == "orig" # preservado
|
||||
|
||||
|
||||
def test_overwrite_si_se_pide():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {"A": {"prompt": "orig", "negative_prompt": "n"}})
|
||||
res = comfyui_append_styles(
|
||||
{"A": {"prompt": "NUEVO", "negative_prompt": "n2"}},
|
||||
styles_path=path,
|
||||
overwrite=True,
|
||||
)
|
||||
assert res["overwritten"] == ["A"]
|
||||
loaded = json.load(open(path, encoding="utf-8"))
|
||||
assert loaded["A"]["prompt"] == "NUEVO"
|
||||
|
||||
|
||||
def test_negative_por_defecto_cuando_falta():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {})
|
||||
res = comfyui_append_styles(
|
||||
{"X": {"prompt": "solo-prompt"}}, # sin negative_prompt
|
||||
styles_path=path,
|
||||
)
|
||||
assert res["added"] == ["X"]
|
||||
loaded = json.load(open(path, encoding="utf-8"))
|
||||
assert loaded["X"]["negative_prompt"] == DEFAULT_NEGATIVE
|
||||
|
||||
|
||||
def test_entradas_invalidas_se_descartan():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {})
|
||||
res = comfyui_append_styles(
|
||||
{
|
||||
"ok": {"prompt": "valido"},
|
||||
"vacio": {"prompt": " "}, # prompt vacío
|
||||
"no_dict": "string", # no es dict
|
||||
"sin_prompt": {"negative_prompt": "n"},
|
||||
},
|
||||
styles_path=path,
|
||||
)
|
||||
assert res["added"] == ["ok"]
|
||||
assert set(res["invalid"]) == {"vacio", "no_dict", "sin_prompt"}
|
||||
assert res["total_after"] == 1
|
||||
|
||||
|
||||
def test_backup_creado():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {"A": {"prompt": "a", "negative_prompt": "n"}})
|
||||
res = comfyui_append_styles(
|
||||
{"B": {"prompt": "b"}},
|
||||
styles_path=path,
|
||||
)
|
||||
assert res["backup_path"]
|
||||
assert os.path.isfile(res["backup_path"])
|
||||
# El backup contiene el estado ANTERIOR (sólo A).
|
||||
bk = json.load(open(res["backup_path"], encoding="utf-8"))
|
||||
assert list(bk) == ["A"]
|
||||
|
||||
|
||||
def test_dry_run_no_escribe():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
path = _write_styles(d, {"A": {"prompt": "a", "negative_prompt": "n"}})
|
||||
before = open(path, encoding="utf-8").read()
|
||||
res = comfyui_append_styles(
|
||||
{"B": {"prompt": "b"}},
|
||||
styles_path=path,
|
||||
dry_run=True,
|
||||
)
|
||||
assert res["dry_run"] is True
|
||||
assert res["added"] == ["B"]
|
||||
assert res["total_after"] == 2 # calculado
|
||||
assert res["backup_path"] == ""
|
||||
after = open(path, encoding="utf-8").read()
|
||||
assert before == after # archivo intacto
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for name, fn in sorted(globals().items()):
|
||||
if name.startswith("test_") and callable(fn):
|
||||
fn()
|
||||
print("PASS", name)
|
||||
print("OK")
|
||||
Reference in New Issue
Block a user