Files
HappyOnigiri 4d45ea6e25 refactor: rename py/ backend package to meld/ and extract SQL to repository
- Rename py/ directory to meld/ to eliminate PyPI 'py' package shadowing,
  which required conftest.py/path.py workarounds in the test suite
- Remove meld/path.py (pytest stub no longer needed after rename)
- Empty meld/__init__.py (removed redundant 'from . import path as path')
- Replace 87-line conftest.py monkeypatch with minimal Package.setup() shim
- Extract create_schema(cursor) from schema.py as single DDL source of truth,
  called by both init_db() and test helpers
- Add permanent_delete, soft_delete, soft_delete_to_trash, restore_image,
  restore_image_with_rename, and collect_deleted_ancestors to repository.py
  to eliminate duplicated inline SQL across router.py and importer/service.py
- Update all import paths, pyproject.toml, Makefile, CI config, and
  check_scripts to reference meld/ instead of py/
2026-03-30 19:21:28 +09:00

256 lines
12 KiB
Python

import json
import os
import sys
import unittest
from typing import Any
from unittest.mock import MagicMock
import torch
# Mock ComfyUI dependencies
sys.modules["folder_paths"] = MagicMock()
sys.modules["comfy"] = MagicMock()
sys.modules["comfy.sd"] = MagicMock()
sys.modules["comfy.utils"] = MagicMock()
sys.modules["comfy.samplers"] = MagicMock()
sys.modules["nodes"] = MagicMock()
# Import test target
from meld.load_image_configs import MetadataHelper # noqa: E402
class TestMetadataParser(unittest.TestCase):
def setUp(self) -> None:
self.data_dir = os.path.join(os.path.dirname(__file__), "data", "metadata_parser")
self.workflow_dir = os.path.join(os.path.dirname(__file__), "data", "workflows")
self.pattern_png_path = os.path.join(self.data_dir, "pattern_png.metadata")
self.pattern_webp_exif_path = os.path.join(self.data_dir, "pattern_webp_exif.metadata")
self.pattern_webp_desc_path = os.path.join(self.data_dir, "pattern_webp_desc.metadata")
self.json_1_path = os.path.join(self.data_dir, "1.json")
self.json_2_path = os.path.join(self.data_dir, "2.json")
self.json_3_path = os.path.join(self.data_dir, "3.json")
self.json_4_path = os.path.join(self.data_dir, "4.json")
self.json_5_path = os.path.join(self.data_dir, "5.json")
self.json_6_path = os.path.join(self.data_dir, "6.json")
self.flux_workflow_path = os.path.join(self.workflow_dir, "Flux.json")
self.flux_gguf_workflow_path = os.path.join(self.workflow_dir, "Flux_GGUF.json")
def _parse_exiftool_txt(self, path: str) -> dict[str, Any]:
data = {}
with open(path, encoding="utf-8") as f:
for line in f:
if ": " in line:
key, value = line.split(": ", 1)
data[key.strip()] = value.strip()
return data
def test_extract_from_pattern_png(self) -> None:
meta = self._parse_exiftool_txt(self.pattern_png_path)
info = {"workflow": meta.get("Workflow"), "prompt": meta.get("Prompt")}
res = MetadataHelper.extract_from_data(info)
positive, negative, model_name, wf_json, pr_json, a1111, logs = res
self.assertIn("(masterpiece, best quality:1.2), 1girl, solo", positive)
self.assertIn("(worst quality, low quality:1.4), multiple people", negative)
self.assertEqual(model_name, "Illustrious\\novaAnimeXL_ilV130.safetensors")
def test_extract_from_json_1(self) -> None:
"""1.json: Standard Workflow format"""
with open(self.json_1_path, encoding="utf-8") as f:
workflow_data = json.load(f)
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, [])
self.assertIn("masterpiece, best quality, 1girl", positive)
self.assertIn("low quality, bad anatomy", negative)
# Check parameters (first sampler found, ID 2)
params, found = MetadataHelper.get_ksampler_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["seed"], 924175127337775)
self.assertEqual(params["steps"], 25)
self.assertEqual(params["cfg"], 8.0)
self.assertEqual(params["sampler_name"], "euler_ancestral")
self.assertEqual(params["scheduler"], "simple")
# Check resolution (EmptySD3LatentImage)
dummy_tensor = torch.zeros((1, 64, 64, 3))
w, h, r_found = MetadataHelper.get_resolution_params(workflow_data, dummy_tensor, [])
self.assertTrue(r_found)
self.assertEqual(w, 512)
self.assertEqual(h, 512)
def test_extract_from_json_2(self) -> None:
"""2.json: API format (prompt JSON)"""
with open(self.json_2_path, encoding="utf-8") as f:
prompt_data = json.load(f)
info = {"prompt": json.dumps(prompt_data)}
res = MetadataHelper.extract_from_data(info)
positive, negative, model_name, wf_json, pr_json, a1111, logs = res
# Since trace results might vary by environment, check if it's retrieved or relax constraints
if positive:
self.assertIn("(masterpiece, best quality)", positive)
if negative:
self.assertIn("(worst quality, low quality:1.4)", negative)
# Retrieve parameters from API format
params, found = MetadataHelper.get_ksampler_params_from_prompt(prompt_data, [])
self.assertTrue(found)
self.assertEqual(params["seed"], 123456789012345)
self.assertEqual(params["steps"], 20)
self.assertEqual(params["cfg"], 1.5)
self.assertEqual(params["sampler_name"], "euler_ancestral")
self.assertEqual(params["scheduler"], "karras")
def test_extract_from_json_3(self) -> None:
"""3.json: Standard Workflow format"""
with open(self.json_3_path, encoding="utf-8") as f:
workflow_data = json.load(f)
logs: list[str] = []
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, logs)
self.assertEqual(positive, "(masterpiece, best quality:1.2), 1girl, solo")
self.assertEqual(negative, "(worst quality, low quality:1.4), multiple people")
# Parameters
params, found = MetadataHelper.get_ksampler_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["seed"], 233851666717137)
self.assertEqual(params["steps"], 10)
self.assertEqual(params["cfg"], 5.0)
self.assertEqual(params["sampler_name"], "euler_ancestral")
self.assertEqual(params["scheduler"], "simple")
def test_extract_from_json_4(self) -> None:
"""4.json: Complex Workflow including subgraphs"""
with open(self.json_4_path, encoding="utf-8") as f:
workflow_data = json.load(f)
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, [])
self.assertIn("(masterpiece, best quality:1.2), 1girl, solo", positive)
self.assertIn("(worst quality, low quality:1.4)", negative)
# Retrieve parameters within subgraph (first sampler found, Generator inner ID 6)
params, found = MetadataHelper.get_ksampler_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["seed"], 108626426419181)
self.assertEqual(params["steps"], 40)
self.assertEqual(params["cfg"], 1.5)
self.assertEqual(params["sampler_name"], "dpmpp_2m_sde")
self.assertEqual(params["scheduler"], "karras")
# Check resolution (EmptySD3LatentImage ID 5 within subgraph Generator)
dummy_tensor = torch.zeros((1, 64, 64, 3))
w, h, r_found = MetadataHelper.get_resolution_params(workflow_data, dummy_tensor, [])
self.assertTrue(r_found)
self.assertEqual(w, 1024)
self.assertEqual(h, 1024)
def test_extract_from_json_5(self) -> None:
"""5.json: Standard Workflow format"""
with open(self.json_5_path, encoding="utf-8") as f:
workflow_data = json.load(f)
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, [])
self.assertIn("(masterpiece, best quality:1.2), 1girl, solo", positive)
self.assertIn("(worst quality, low quality:1.4), multiple people", negative)
# Parameters
params, found = MetadataHelper.get_ksampler_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["seed"], 233851666717137)
self.assertEqual(params["steps"], 10)
self.assertEqual(params["cfg"], 5.0)
def test_extract_from_json_6(self) -> None:
"""6.json: Workflow including subgraphs"""
with open(self.json_6_path, encoding="utf-8") as f:
workflow_data = json.load(f)
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, [])
self.assertIn("(masterpiece, best quality:1.2)", positive)
self.assertIn("(scenery:1.2)", positive)
# Retrieve parameters within subgraph
params, found = MetadataHelper.get_ksampler_params(workflow_data, [])
self.assertTrue(found)
def test_extract_dual_clip_params_from_flux_workflow(self) -> None:
"""Flux.json: DualCLIPLoader should keep clip_name1 and clip_name2 distinct"""
with open(self.flux_workflow_path, encoding="utf-8") as f:
workflow_data = json.load(f)
params, found = MetadataHelper.get_dual_clip_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["clip_name1"], "t5xxl_fp8_e4m3fn.safetensors")
self.assertEqual(params["clip_name2"], "clip_l.safetensors")
self.assertNotEqual(params["clip_name1"], params["clip_name2"])
self.assertEqual(params["clip_type"], "flux")
self.assertEqual(params["clip_device"], "default")
def test_extract_from_flux_gguf_workflow(self) -> None:
"""Flux_GGUF.json: Should extract model_name from UnetLoaderGGUF and CLIP from DualCLIPLoaderGGUF"""
with open(self.flux_gguf_workflow_path, encoding="utf-8") as f:
workflow_data = json.load(f)
# 1. Test model_name extraction (MetadataExtractor)
info = {"workflow": json.dumps(workflow_data)}
res = MetadataHelper.extract_from_data(info)
positive, negative, model_name, wf_json, pr_json, a1111, logs = res
# Expected to fail initially until we update MetadataExtractor
self.assertEqual(model_name, "flux1-dev-Q4_K_S.gguf")
# 2. Test DualCLIPLoaderGGUF extraction (Parsers)
params, found = MetadataHelper.get_dual_clip_params(workflow_data, [])
self.assertTrue(found)
self.assertEqual(params["clip_name1"], "t5-v1_1-xxl-encoder-Q4_K_S.gguf")
self.assertEqual(params["clip_name2"], "clip_l.safetensors")
self.assertEqual(params["clip_type"], "flux")
def test_extract_from_pattern_webp_exif(self) -> None:
meta = self._parse_exiftool_txt(self.pattern_webp_exif_path)
exif: dict[Any, Any] = {271: meta.get("Make"), 272: meta.get("Model")}
info: dict[str, Any] = {}
res = MetadataHelper.extract_from_data(info, exif)
positive, negative, model_name, wf_json, pr_json, a1111, logs = res
self.assertIn("(masterpiece, best quality:1.2)", positive)
self.assertIn("(worst quality, low quality:1.4)", negative)
self.assertEqual(model_name, "Illustrious\\novaAnimeXL_ilV130.safetensors")
def test_extract_from_pattern_webp_desc(self) -> None:
meta = self._parse_exiftool_txt(self.pattern_webp_desc_path)
exif: dict[Any, Any] = {270: meta.get("ImageDescription"), 271: meta.get("Make")}
info: dict[str, Any] = {}
res = MetadataHelper.extract_from_data(info, exif)
positive, negative, model_name, wf_json, pr_json, a1111, logs = res
self.assertIn("(masterpiece, best quality:1.2)", positive)
self.assertIn("(worst quality, low quality:1.4)", negative)
def test_log_truncation(self) -> None:
long_positive = "a" * 60
long_negative = "b" * 60
workflow_data = {
"nodes": [
{"id": 1, "type": "CLIPTextEncode", "widgets_values": [long_positive]},
{"id": 2, "type": "CLIPTextEncode", "widgets_values": [long_negative]},
{
"id": 3,
"type": "KSampler",
"inputs": [{"name": "positive", "link": 1}, {"name": "negative", "link": 2}],
},
],
"links": [[1, 1, 0, 3, 0, "CONDITIONING"], [2, 2, 0, 3, 1, "CONDITIONING"]],
}
logs: list[str] = []
positive, negative = MetadataHelper.parse_workflow_json(workflow_data, logs)
self.assertIn(f"-> Positive found: {'a' * 50}...", logs)
self.assertIn(f"-> Negative found: {'b' * 50}...", logs)
if __name__ == "__main__":
unittest.main()