diff --git a/__init__.py b/__init__.py index 551aef4..f6d596c 100644 --- a/__init__.py +++ b/__init__.py @@ -936,6 +936,6 @@ try: extension = ModelLinkerExtension() extension.initialize() except Exception as e: - logging.error( + log_error( f"ComfyUI Model Linker extension initialization failed: {e}", exc_info=True ) diff --git a/core/downloader.py b/core/downloader.py index 43e9685..89bae27 100644 --- a/core/downloader.py +++ b/core/downloader.py @@ -5,7 +5,6 @@ Handles downloading models from various sources with progress tracking. """ import os -import logging import threading import time import requests @@ -13,7 +12,13 @@ from typing import Dict, Any, Optional, Callable, List from pathlib import Path from collections import deque -from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception +from .log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) try: import folder_paths @@ -30,8 +35,6 @@ SPEED_HISTORY_SIZE = 5 # Number of samples for smoothing CHUNK_SIZE = 1024 * 1024 # 1MB chunks for faster downloads CLI_LOG_INTERVAL = 5 # Log progress to CLI every N seconds -logger = logging.getLogger(__name__) - def format_bytes(bytes_value: int) -> str: """Format bytes to human readable string (e.g., 1.5 GB).""" @@ -107,7 +110,7 @@ def get_download_directory(category: str) -> Optional[str]: if fallback_paths: return fallback_paths[0] except Exception as e: - logger.debug(f"Could not get folder path for {folder_key}: {e}") + log_debug(f"Could not get folder path for {folder_key}: {e}") return None @@ -373,7 +376,7 @@ def download_file( # CLI error log print(f"[Model Linker] ✗ Download failed: {os.path.basename(dest_path)}") print(f"[Model Linker] Error: {error_msg}") - logger.error(f"Download error: {e}", exc_info=True) + log_error(f"Download error: {e}", exc_info=True) return result diff --git a/core/scanner.py b/core/scanner.py index db90d65..a4b22eb 100644 --- a/core/scanner.py +++ b/core/scanner.py @@ -5,17 +5,22 @@ Scans configured model directories and finds available model files. """ import os -import logging from typing import List, Dict, Tuple -from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception +from .log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) # Import folder_paths lazily - it may not be available until ComfyUI is initialized try: import folder_paths except ImportError: folder_paths = None - logging.warning("Model Linker: folder_paths not available yet - will retry later") + log_warn("Model Linker: folder_paths not available yet - will retry later") # Model file extensions to look for # This matches folder_paths.supported_pt_extensions @@ -51,7 +56,7 @@ def get_model_directories() -> Dict[str, Tuple[List[str], set]]: folder_paths = fp except ImportError: - logging.error("Model Linker: folder_paths still not available") + log_error("Model Linker: folder_paths still not available") return {} return folder_paths.folder_names_and_paths.copy() @@ -81,7 +86,7 @@ def scan_directory( models = [] if not os.path.exists(directory) or not os.path.isdir(directory): - logging.debug(f"Directory does not exist or is not accessible: {directory}") + log_debug(f"Directory does not exist or is not accessible: {directory}") return models try: @@ -132,7 +137,7 @@ def scan_directory( } ) except (OSError, PermissionError) as e: - logging.warning(f"Error scanning directory {directory}: {e}") + log_warn(f"Error scanning directory {directory}: {e}") return models @@ -161,7 +166,7 @@ def scan_all_directories() -> List[Dict[str, str]]: paths = value[0] or [] raw_exts = value[1] else: - # Unexpected format: treat value as paths + # Unexpected format; treat value as paths paths = list(value) raw_exts = [] elif isinstance(value, dict): @@ -169,7 +174,7 @@ def scan_all_directories() -> List[Dict[str, str]]: raw_exts = value.get("extensions") or [] else: # Unknown format; skip category - logging.debug( + log_debug( f"Unexpected folder_paths format for category {category}: {type(value)}" ) continue @@ -180,22 +185,16 @@ def scan_all_directories() -> List[Dict[str, str]]: elif raw_exts: extensions = {str(raw_exts).lower()} except Exception as e: - logging.warning( - f"Error interpreting folder_paths entry for {category}: {e}" - ) + log_warn(f"Error interpreting folder_paths entry for {category}: {e}") continue for directory_path in paths: try: models = scan_directory(directory_path, extensions, category) all_models.extend(models) - logging.debug( - f"Found {len(models)} models in {category}/{directory_path}" - ) + log_debug(f"Found {len(models)} models in {category}/{directory_path}") except Exception as e: - logging.warning( - f"Error scanning {category} directory {directory_path}: {e}" - ) + log_warn(f"Error scanning {category} directory {directory_path}: {e}") return all_models diff --git a/core/sources/civitai.py b/core/sources/civitai.py index 593b1af..803be5a 100644 --- a/core/sources/civitai.py +++ b/core/sources/civitai.py @@ -7,15 +7,18 @@ Search and download models from CivitAI. import os import re import json -import logging import hashlib import requests from typing import Dict, Any, Optional, List from urllib.parse import urlparse, parse_qs, quote -from ..log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception - -logger = logging.getLogger(__name__) +from ..log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) CIVITAI_API_URL = "https://civitai.com/api/v1" @@ -109,7 +112,7 @@ def search_civitai_for_file( response = requests.get(search_url, headers=headers, timeout=15) if response.status_code != 200: - logger.debug(f"CivitAI search returned {response.status_code}") + log_debug(f"CivitAI search returned {response.status_code}") return None data = response.json() @@ -148,7 +151,7 @@ def search_civitai_for_file( "match_type": "exact", } _search_cache[cache_key] = result - logger.info(f"Found {filename} on CivitAI: {model_name}") + log_info(f"Found {filename} on CivitAI: {model_name}") return result # Check for partial match (filename_base in file_base or vice versa) @@ -171,7 +174,7 @@ def search_civitai_for_file( "match_type": "partial", } _search_cache[cache_key] = result - logger.info( + log_info( f"Found similar file for {filename} on CivitAI: {model_name}" ) return result @@ -181,7 +184,7 @@ def search_civitai_for_file( return None except Exception as e: - logger.error(f"CivitAI search error: {e}") + log_error(f"CivitAI search error: {e}") return None @@ -267,7 +270,7 @@ def search_civitai( results.append(result) except Exception as e: - logger.error(f"CivitAI search error: {e}") + log_error(f"CivitAI search error: {e}") return results @@ -307,7 +310,7 @@ def search_civitai_by_hash( } except Exception as e: - logger.error(f"CivitAI hash lookup error: {e}") + log_error(f"CivitAI hash lookup error: {e}") return None @@ -341,7 +344,7 @@ def resolve_urn( response = requests.get(url, headers=headers, params=params, timeout=15) if response.status_code != 200: - logger.warning(f"CivitAI URN resolve failed: {response.status_code}") + log_warn(f"CivitAI URN resolve failed: {response.status_code}") _urn_cache[cache_key] = None return None @@ -349,9 +352,7 @@ def resolve_urn( versions = data.get("modelVersions", []) if not versions: - logger.warning( - f"No versions found for model {model_id}/version {version_id}" - ) + log_warn(f"No versions found for model {model_id}/version {version_id}") _urn_cache[cache_key] = None return None @@ -363,7 +364,7 @@ def resolve_urn( break if not target_version: - logger.warning(f"Version {version_id} not found in model {model_id}") + log_warn(f"Version {version_id} not found in model {model_id}") _urn_cache[cache_key] = None return None @@ -380,7 +381,7 @@ def resolve_urn( primary_file = files[0] # Fallback to first if not primary_file: - logger.warning(f"No files found for version {version_id}") + log_warn(f"No files found for version {version_id}") _urn_cache[cache_key] = None return None @@ -395,13 +396,13 @@ def resolve_urn( } _urn_cache[cache_key] = result - logger.info( + log_info( f"Resolved URN model {model_id}@{version_id} → {result['expected_filename']}" ) return result except Exception as e: - logger.error(f"CivitAI URN resolve error for {model_id}@{version_id}: {e}") + log_error(f"CivitAI URN resolve error for {model_id}@{version_id}: {e}") _urn_cache[cache_key] = None return None @@ -428,7 +429,7 @@ def _get_sha256_hash(file_path: str) -> Optional[str]: sha256_hash.update(byte_block) return sha256_hash.hexdigest() except Exception as e: - logger.error(f"Error computing hash for {file_path}: {e}") + log_error(f"Error computing hash for {file_path}: {e}") return None @@ -502,21 +503,21 @@ def get_model_info_by_hash( } _hash_cache[cache_key] = result - logger.info(f"Found model by hash {file_hash}: {result.get('model_name')}") + log_info(f"Found model by hash {file_hash}: {result.get('model_name')}") return result elif response.status_code == 404: - logger.info(f"Model not found on CivitAI for hash {file_hash}") + log_info(f"Model not found on CivitAI for hash {file_hash}") _hash_cache[cache_key] = None return None else: - logger.warning( + log_warn( f"CivitAI hash lookup returned {response.status_code} for {file_hash}" ) return None except Exception as e: - logger.error(f"Error looking up model by hash {file_hash}: {e}") + log_error(f"Error looking up model by hash {file_hash}: {e}") return None cache_key = f"hash_{file_hash}" @@ -569,21 +570,21 @@ def get_model_info_by_hash( } _hash_cache[cache_key] = result - logger.info(f"Found model by hash {file_hash}: {result.get('model_name')}") + log_info(f"Found model by hash {file_hash}: {result.get('model_name')}") return result elif response.status_code == 404: - logger.info(f"Model not found on CivitAI for hash {file_hash}") + log_info(f"Model not found on CivitAI for hash {file_hash}") _hash_cache[cache_key] = None return None else: - logger.warning( + log_warn( f"CivitAI hash lookup returned {response.status_code} for {file_hash}" ) return None except Exception as e: - logger.error(f"Error looking up model by hash {file_hash}: {e}") + log_error(f"Error looking up model by hash {file_hash}: {e}") return None @@ -687,7 +688,7 @@ def _get_metadata_file_path(model_path: str) -> str: if name: path = os.path.join(directory, name) if os.path.exists(path): - logger.info(f"Found metadata file: {path}") + log_info(f"Found metadata file: {path}") return path return "" @@ -713,10 +714,10 @@ def _read_model_metadata(metadata_path: str) -> Optional[Dict[str, Any]]: if not data.get("sha256") and not data.get("civitai"): return None - logger.info(f"Successfully read metadata from: {metadata_path}") + log_info(f"Successfully read metadata from: {metadata_path}") return data except Exception as e: - logger.debug(f"Error reading metadata file {metadata_path}: {e}") + log_debug(f"Error reading metadata file {metadata_path}: {e}") return None @@ -797,16 +798,16 @@ def get_model_info_for_file( Returns: Dict with model info from CivitAI, or None if not found """ - logger.info(f"get_model_info_for_file called with: {file_path}") + log_info(f"get_model_info_for_file called with: {file_path}") # First check for metadata file metadata_path = _get_metadata_file_path(file_path) - logger.info(f"Looking for metadata file, checked: {metadata_path}") + log_info(f"Looking for metadata file, checked: {metadata_path}") if metadata_path: metadata = _read_model_metadata(metadata_path) if metadata: - logger.info(f"Using metadata file for {file_path}") + log_info(f"Using metadata file for {file_path}") return _metadata_to_model_info(metadata) else: # Debug: list all files in the same directory @@ -816,7 +817,7 @@ def get_model_info_for_file( metadata_files = [ f for f in files if "metadata" in f.lower() or f.endswith(".json") ] - logger.info(f"Files in {directory}: {metadata_files}") + log_info(f"Files in {directory}: {metadata_files}") # If no metadata file, compute hash and look up on CivitAI file_hash = _get_sha256_hash(file_path) diff --git a/core/sources/huggingface.py b/core/sources/huggingface.py index 85fbb2e..82d20ba 100644 --- a/core/sources/huggingface.py +++ b/core/sources/huggingface.py @@ -6,14 +6,17 @@ Search and download models from HuggingFace Hub. import os import re -import logging import requests from typing import Dict, Any, Optional, List from urllib.parse import urlparse, quote -from ..log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception - -logger = logging.getLogger(__name__) +from ..log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) HF_API_URL = "https://huggingface.co/api" @@ -110,7 +113,7 @@ def search_huggingface_for_file( response = requests.get(search_url, headers=headers, timeout=10) if response.status_code != 200: - logger.debug(f"HuggingFace search returned {response.status_code}") + log_debug(f"HuggingFace search returned {response.status_code}") return None repos = response.json() @@ -146,7 +149,7 @@ def search_huggingface_for_file( "match_type": "exact", } _search_cache[cache_key] = result - logger.info(f"Found {filename} on HuggingFace: {repo_id}") + log_info(f"Found {filename} on HuggingFace: {repo_id}") return result # Check for partial match (filename_base in file_base or vice versa) @@ -169,13 +172,13 @@ def search_huggingface_for_file( "match_type": "partial", } _search_cache[cache_key] = result - logger.info( + log_info( f"Found similar file for {filename} on HuggingFace: {repo_id}/{file_path}" ) return result except Exception as e: - logger.debug(f"Error checking repo {repo_id}: {e}") + log_debug(f"Error checking repo {repo_id}: {e}") continue # Not found @@ -183,7 +186,7 @@ def search_huggingface_for_file( return None except Exception as e: - logger.error(f"HuggingFace search error: {e}") + log_error(f"HuggingFace search error: {e}") return None @@ -228,7 +231,7 @@ def search_huggingface( ) except Exception as e: - logger.error(f"HuggingFace search error: {e}") + log_error(f"HuggingFace search error: {e}") return results @@ -264,6 +267,6 @@ def get_repo_files(repo: str, token: Optional[str] = None) -> List[Dict[str, Any ) except Exception as e: - logger.error(f"Error getting repo files: {e}") + log_error(f"Error getting repo files: {e}") return files diff --git a/core/sources/model_list.py b/core/sources/model_list.py index 2b06621..0c734b2 100644 --- a/core/sources/model_list.py +++ b/core/sources/model_list.py @@ -6,13 +6,16 @@ Search the ComfyUI Manager model-list.json database with fuzzy matching. import os import json -import logging from typing import Dict, Any, Optional, List from difflib import SequenceMatcher -from ..log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception - -logger = logging.getLogger(__name__) +from ..log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) # Path to metadata directory METADATA_DIR = os.path.join( @@ -36,12 +39,12 @@ def _load_model_list() -> List[Dict]: with open(MODEL_LIST_FILE, "r", encoding="utf-8") as f: data = json.load(f) _model_list_cache = data.get("models", []) - logger.info( + log_info( f"Loaded {len(_model_list_cache)} models from model-list.json" ) return _model_list_cache except Exception as e: - logger.error(f"Error loading model list: {e}") + log_error(f"Error loading model list: {e}") _model_list_cache = [] return _model_list_cache diff --git a/core/sources/popular.py b/core/sources/popular.py index cb2fe86..b34f43d 100644 --- a/core/sources/popular.py +++ b/core/sources/popular.py @@ -6,12 +6,15 @@ Curated list of common models with known download URLs. import os import json -import logging from typing import Dict, Any, Optional, List -from ..log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception - -logger = logging.getLogger(__name__) +from ..log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) # Path to metadata directory METADATA_DIR = os.path.join( @@ -39,7 +42,7 @@ def _load_popular_models() -> Dict[str, Any]: _popular_models_cache = data.get("models", {}) return _popular_models_cache except Exception as e: - logger.error(f"Error loading popular models: {e}") + log_error(f"Error loading popular models: {e}") _popular_models_cache = {} return _popular_models_cache @@ -59,7 +62,7 @@ def _load_model_aliases() -> Dict[str, List[str]]: _model_aliases_cache = data.get("aliases", {}) return _model_aliases_cache except Exception as e: - logger.error(f"Error loading model aliases: {e}") + log_error(f"Error loading model aliases: {e}") _model_aliases_cache = {} return _model_aliases_cache diff --git a/core/workflow_analyzer.py b/core/workflow_analyzer.py index 4a46c21..295d344 100644 --- a/core/workflow_analyzer.py +++ b/core/workflow_analyzer.py @@ -5,17 +5,23 @@ Extracts model references from workflow JSON and identifies missing models. """ import os -import logging +import re from typing import List, Dict, Any, Optional -from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception +from .log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) # Import folder_paths lazily - it may not be available until ComfyUI is initialized try: import folder_paths except ImportError: folder_paths = None - logging.warning("Model Linker: folder_paths not available yet - will retry later") + log_warn("Model Linker: folder_paths not available yet - will retry later") # Common model file extensions @@ -32,8 +38,6 @@ MODEL_EXTENSIONS = { ".gguf", } -import re - # URN Type to ComfyUI category mapping URN_TYPE_MAP = { "checkpoint": "checkpoints", @@ -140,7 +144,7 @@ def try_resolve_model_path( folder_paths = fp except ImportError: - logging.error("Model Linker: folder_paths not available") + log_error("Model Linker: folder_paths not available") return None # If categories not provided, try all categories @@ -272,7 +276,7 @@ def get_node_model_info(node: Dict[str, Any]) -> List[Dict[str, Any]]: if lora_exists: break - logging.debug( + log_debug( f"Lora {name}: exists={lora_exists}, path={lora_full_path}" ) @@ -445,7 +449,7 @@ def analyze_workflow_models(workflow_json: Dict[str, Any]) -> List[Dict[str, Any ref["is_top_level"] = True # Flag to indicate this is a top-level node all_model_refs.extend(model_refs) except Exception as e: - logging.warning(f"Error analyzing node {node.get('id', 'unknown')}: {e}") + log_warn(f"Error analyzing node {node.get('id', 'unknown')}: {e}") continue # Recursively analyze subgraphs (definitions already loaded above) @@ -457,7 +461,7 @@ def analyze_workflow_models(workflow_json: Dict[str, Any]) -> List[Dict[str, Any subgraph_name = subgraph.get("name", subgraph_id) subgraph_nodes = subgraph.get("nodes", []) - logging.debug( + log_debug( f"Analyzing subgraph: {subgraph_name} (ID: {subgraph_id}) with {len(subgraph_nodes)} nodes" ) @@ -477,7 +481,7 @@ def analyze_workflow_models(workflow_json: Dict[str, Any]) -> List[Dict[str, Any ref["is_top_level"] = False # This is inside a subgraph definition all_model_refs.extend(model_refs) except Exception as e: - logging.warning( + log_warn( f"Error analyzing subgraph node {node.get('id', 'unknown')}: {e}" ) continue diff --git a/core/workflow_updater.py b/core/workflow_updater.py index 474956c..81caeef 100644 --- a/core/workflow_updater.py +++ b/core/workflow_updater.py @@ -5,10 +5,15 @@ Updates workflow JSON by replacing model paths in nodes. """ import os -import logging from typing import Dict, Any, List, Optional -from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception +from .log_system.log_funcs import ( + log_debug, + log_info, + log_warn, + log_error, + log_exception, +) def convert_to_relative_path( @@ -200,13 +205,13 @@ def update_model_path( if not node: location = f"subgraph {subgraph_id}" if subgraph_id else "top-level" - logging.warning(f"Node {node_id} not found in {location}") + log_warn(f"Node {node_id} not found in {location}") return False widgets_values = node.get("widgets_values", []) if widget_index >= len(widgets_values): - logging.warning(f"Widget index {widget_index} out of range for node {node_id}") + log_warn(f"Widget index {widget_index} out of range for node {node_id}") return False # Get category from resolved_model if not provided @@ -216,7 +221,7 @@ def update_model_path( # Check if this is a LoraManager (LoraLoaderV2) node with is_lora_v2 flag is_lora_v2 = mapping.get("is_lora_v2") if mapping else False original_lora_name = mapping.get("original_lora_name") if mapping else None - logging.info( + log_info( f"@@ update_model_path: node={node_id}, idx={widget_index}, is_lora_v2={is_lora_v2}, original={original_lora_name}" ) @@ -224,7 +229,7 @@ def update_model_path( if is_lora_v2 and original_lora_name and widget_index == 2: # For LoraManager, we need to update the name in the lora list (widgets_values[2]) lora_list = widgets_values[2] - logging.info( + log_info( f">>> LoraManager: original_lora={original_lora_name}, widget_index={widget_index}, list_type={type(lora_list)}" ) @@ -241,7 +246,7 @@ def update_model_path( if "." in new_lora_name: new_lora_name = new_lora_name.rsplit(".", 1)[0] - logging.debug( + log_debug( f"LoraManager update: original={original_lora_name}, new={new_lora_name}" ) @@ -256,14 +261,14 @@ def update_model_path( if lora_name_in_list == original_stripped: lora_item["name"] = new_lora_name updated = True - logging.info( + log_info( f"Updated LoraManager lora (exact): {original_lora_name} -> {new_lora_name}" ) break elif lora_name_in_list.lower() == original_stripped.lower(): lora_item["name"] = new_lora_name updated = True - logging.info( + log_info( f"Updated LoraManager lora (case-insensitive): {original_lora_name} -> {new_lora_name}" ) break @@ -282,18 +287,18 @@ def update_model_path( ) widgets_values[1] = new_text - logging.info( + log_info( f"Updated node {node_id} LoraManager lora list - new text: {widgets_values[1]}" ) return True else: # Log the actual lora list for debugging - logging.warning( + log_warn( f"Lora '{original_lora_name}' not found in lora list. Available: {[li.get('name') for li in lora_list if isinstance(li, dict)]}" ) return False else: - logging.warning( + log_warn( f"LoraManager widget_index == 2 but lora_list is not a list: {type(lora_list)}" ) return False @@ -318,14 +323,12 @@ def update_model_path( nested_key = mapping.get("nested_key") if mapping else None if nested_key and isinstance(widgets_values[widget_index], dict): widgets_values[widget_index][nested_key] = relative_path - logging.debug( + log_debug( f"Updated node {node_id}, widget {widget_index}[{nested_key}] to: {relative_path}" ) else: widgets_values[widget_index] = relative_path - logging.debug( - f"Updated node {node_id}, widget {widget_index} to: {relative_path}" - ) + log_debug(f"Updated node {node_id}, widget {widget_index} to: {relative_path}") return True @@ -358,7 +361,7 @@ def update_workflow_nodes( resolved_path = mapping.get("resolved_path") if not all([node_id is not None, widget_index is not None, resolved_path]): - logging.warning(f"Invalid mapping: {mapping}") + log_warn(f"Invalid mapping: {mapping}") continue # Try to get base_directory from resolved_model if provided @@ -395,5 +398,5 @@ def update_workflow_nodes( if success: updated_count += 1 - logging.info(f"Updated {updated_count} model paths in workflow") + log_info(f"Updated {updated_count} model paths in workflow") return workflow