mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: normalize tags to lowercase for Windows compatibility, see #637
Convert all tags to lowercase in tag processing logic to prevent case sensitivity issues on Windows filesystems. This ensures consistent tag matching and prevents duplicate tags with different cases from being created. Changes include: - TagUpdateService now converts tags to lowercase before comparison - Utils function converts model tags to lowercase before priority resolution - Test cases updated to reflect lowercase tag expectations
This commit is contained in:
@@ -240,7 +240,7 @@ def test_download_coordinator_emits_progress() -> None:
|
||||
|
||||
def test_tag_update_service_adds_unique_tags(tmp_path: Path) -> None:
|
||||
metadata_path = tmp_path / "model.metadata.json"
|
||||
metadata_path.write_text(json.dumps({"tags": ["Existing"]}))
|
||||
metadata_path.write_text(json.dumps({"tags": ["existing"]}))
|
||||
|
||||
async def loader(path: str) -> Dict[str, Any]:
|
||||
return json.loads(Path(path).read_text())
|
||||
@@ -258,12 +258,12 @@ def test_tag_update_service_adds_unique_tags(tmp_path: Path) -> None:
|
||||
tags = asyncio.run(
|
||||
service.add_tags(
|
||||
file_path=str(tmp_path / "model.safetensors"),
|
||||
new_tags=["New", "existing"],
|
||||
new_tags=["new", "existing"],
|
||||
metadata_loader=loader,
|
||||
update_cache=update_cache,
|
||||
)
|
||||
)
|
||||
|
||||
assert tags == ["Existing", "New"]
|
||||
assert tags == ["existing", "new"]
|
||||
assert manager.saved
|
||||
assert cache_updates
|
||||
|
||||
96
tests/services/test_tag_case_sensitivity.py
Normal file
96
tests/services/test_tag_case_sensitivity.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Tests for tag case sensitivity handling to prevent issues on Windows."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
|
||||
from py.services.tag_update_service import TagUpdateService
|
||||
|
||||
|
||||
class RecordingMetadataManager:
|
||||
def __init__(self) -> None:
|
||||
self.saved: list[tuple[str, Dict[str, Any]]] = []
|
||||
|
||||
async def save_metadata(self, path: str, metadata: Dict[str, Any]) -> bool:
|
||||
self.saved.append((path, json.loads(json.dumps(metadata))))
|
||||
return True
|
||||
|
||||
|
||||
class DummyProvider:
|
||||
async def __call__(self, path: str) -> Dict[str, Any]:
|
||||
return {"tags": []}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tag_update_service_handles_case_insensitive_tags(tmp_path: Path) -> None:
|
||||
"""Test that tag update service treats tags case-insensitively."""
|
||||
metadata_path = tmp_path / "model.metadata.json"
|
||||
metadata_path.write_text(json.dumps({"tags": ["test"]}))
|
||||
|
||||
async def loader(path: str) -> Dict[str, Any]:
|
||||
return json.loads(Path(path).read_text())
|
||||
|
||||
manager = RecordingMetadataManager()
|
||||
service = TagUpdateService(metadata_manager=manager)
|
||||
|
||||
cache_updates: list[Dict[str, Any]] = []
|
||||
|
||||
async def update_cache(original: str, new: str, metadata: Dict[str, Any]) -> bool:
|
||||
cache_updates.append(metadata)
|
||||
return True
|
||||
|
||||
# Try to add "Test" (different case) - should not be added since "test" already exists
|
||||
tags = await service.add_tags(
|
||||
file_path=str(tmp_path / "model.safetensors"),
|
||||
new_tags=["Test"],
|
||||
metadata_loader=loader,
|
||||
update_cache=update_cache,
|
||||
)
|
||||
|
||||
# Should still only have "test" (lowercase) in the tags
|
||||
assert tags == ["test"]
|
||||
assert len(manager.saved) == 1
|
||||
saved_metadata = manager.saved[0][1]
|
||||
assert saved_metadata["tags"] == ["test"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tag_update_service_adds_new_tags_in_lowercase(tmp_path: Path) -> None:
|
||||
"""Test that new tags are stored in lowercase."""
|
||||
metadata_path = tmp_path / "model.metadata.json"
|
||||
metadata_path.write_text(json.dumps({"tags": ["existing"]}))
|
||||
|
||||
async def loader(path: str) -> Dict[str, Any]:
|
||||
return json.loads(Path(path).read_text())
|
||||
|
||||
manager = RecordingMetadataManager()
|
||||
service = TagUpdateService(metadata_manager=manager)
|
||||
|
||||
cache_updates: list[Dict[str, Any]] = []
|
||||
|
||||
async def update_cache(original: str, new: str, metadata: Dict[str, Any]) -> bool:
|
||||
cache_updates.append(metadata)
|
||||
return True
|
||||
|
||||
# Add new tags with mixed case
|
||||
tags = await service.add_tags(
|
||||
file_path=str(tmp_path / "model.safetensors"),
|
||||
new_tags=["NewTag", "ANOTHER_TAG"],
|
||||
metadata_loader=loader,
|
||||
update_cache=update_cache,
|
||||
)
|
||||
|
||||
# New tags should be stored in lowercase
|
||||
assert "existing" in tags
|
||||
assert "newtag" in tags
|
||||
assert "another_tag" in tags
|
||||
assert len(manager.saved) == 1
|
||||
saved_metadata = manager.saved[0][1]
|
||||
assert "newtag" in saved_metadata["tags"]
|
||||
assert "another_tag" in saved_metadata["tags"]
|
||||
# Ensure all tags are lowercase
|
||||
for tag in saved_metadata["tags"]:
|
||||
assert tag == tag.lower()
|
||||
50
tests/utils/test_utils_case_sensitivity.py
Normal file
50
tests/utils/test_utils_case_sensitivity.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Tests for utils module case sensitivity handling."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from py.utils.utils import calculate_relative_path_for_model
|
||||
from py.services.settings_manager import SettingsManager
|
||||
|
||||
|
||||
def test_calculate_relative_path_handles_case_insensitive_tags():
|
||||
"""Test that calculate_relative_path_for_model handles case insensitive tags correctly."""
|
||||
# Create a mock settings manager
|
||||
mock_settings = Mock(spec=SettingsManager)
|
||||
mock_settings.get.return_value = {} # base_model_path_mappings
|
||||
mock_settings.resolve_priority_tag_for_model.return_value = "test"
|
||||
mock_settings.get_download_path_template.return_value = "{base_model}/{first_tag}"
|
||||
|
||||
# Mock the settings manager function
|
||||
import py.utils.utils as utils_module
|
||||
original_get_settings_manager = utils_module.get_settings_manager
|
||||
utils_module.get_settings_manager = Mock(return_value=mock_settings)
|
||||
|
||||
try:
|
||||
# Test model data with mixed case tags
|
||||
model_data = {
|
||||
"base_model": "SDXL",
|
||||
"tags": ["Test", "ANOTHER_TAG"], # Mixed case tags
|
||||
"model_name": "Test Model"
|
||||
}
|
||||
|
||||
model_type = "lora"
|
||||
|
||||
# Call the function
|
||||
result = calculate_relative_path_for_model(model_data, model_type)
|
||||
|
||||
# Verify that resolve_priority_tag_for_model was called with lowercase tags
|
||||
called_args = mock_settings.resolve_priority_tag_for_model.call_args[0]
|
||||
lowercase_tags = called_args[0]
|
||||
|
||||
# Check that tags are converted to lowercase
|
||||
assert all(tag == tag.lower() for tag in lowercase_tags)
|
||||
assert "test" in lowercase_tags
|
||||
assert "another_tag" in lowercase_tags
|
||||
|
||||
# Verify the result format
|
||||
assert result == "SDXL/test"
|
||||
|
||||
finally:
|
||||
# Restore original function
|
||||
utils_module.get_settings_manager = original_get_settings_manager
|
||||
Reference in New Issue
Block a user