From d979bbcf216e0890c9d2fb6082a2a1a0b69fa1ae Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Thu, 25 Jun 2026 17:00:27 +0200 Subject: [PATCH] refactor: extract shared utilities and deduplicate metadata path definitions - Add first_non_empty() and to_int() to core/type_utils.py - Add get_filename_from_path() and METADATA_DIR to core/path_utils.py - Replace inline implementations across resolver, downloader, and sources with calls to the shared utilities - Move strip_known_model_extension from resolver to matcher --- __init__.py | 19 ++++++------------- core/downloader.py | 12 ++---------- core/path_utils.py | 14 ++++++++++++++ core/resolver.py | 13 ++----------- core/sources/civarchive.py | 8 ++------ core/sources/civitai.py | 11 ++--------- core/sources/huggingface.py | 6 ++---- core/sources/model_list.py | 5 +---- core/sources/popular.py | 5 +---- core/type_utils.py | 29 +++++++++++++++++++++++++++++ 10 files changed, 61 insertions(+), 61 deletions(-) diff --git a/__init__.py b/__init__.py index 084afdc..9b7f3f1 100644 --- a/__init__.py +++ b/__init__.py @@ -86,6 +86,8 @@ class ModelResolverExtension: ) from .core.path_templates import infer_download_path_templates from .core.scanner import get_model_files, invalidate_model_files_cache + from .core.path_utils import get_filename_from_path + from .core.type_utils import first_non_empty, to_int from .core.settings import ( bool_setting as resolver_bool_setting, get_default_root_for_category, @@ -571,7 +573,7 @@ class ModelResolverExtension: for m in available_models: rel_path = m.get("relative_path", "") if rel_path: - filename = rel_path.split("/")[-1].split("\\")[-1] + filename = get_filename_from_path(rel_path) path_by_filename[filename] = m.get("path") # Also add without extension for matching (simple approach) if "." in filename: @@ -632,7 +634,7 @@ class ModelResolverExtension: category = ref.get("category", "unknown") # Determine model name and strength - model_name = original_path.split("/")[-1].split("\\")[-1] + model_name = get_filename_from_path(original_path) strength = None # For standard LoraLoader nodes, strength is in next widget_value @@ -2252,19 +2254,10 @@ class ModelResolverExtension: ) def _first_metadata_value(*values): - for value in values: - if value is None: - continue - if isinstance(value, str) and not value.strip(): - continue - return value - return "" + return first_non_empty(*values) def _metadata_int(value): - try: - return int(value) - except (TypeError, ValueError): - return None + return to_int(value) inferred_source = "" if "civitai.com" in url: diff --git a/core/downloader.py b/core/downloader.py index 8919818..5f39d84 100644 --- a/core/downloader.py +++ b/core/downloader.py @@ -24,7 +24,7 @@ from .log_system.log_funcs import ( ) from .resolver import normalize_sha256 from .path_utils import is_path_within, get_path_identity, write_json_atomic -from .type_utils import as_dict, as_list +from .type_utils import as_dict, as_list, first_non_empty try: import folder_paths @@ -135,15 +135,7 @@ def _as_list(value: Any) -> List[Any]: def _first_present(*values: Any) -> Any: - for value in values: - if value is None: - continue - if isinstance(value, str) and not value.strip(): - continue - if isinstance(value, (list, dict)) and not value: - continue - return value - return "" + return first_non_empty(*values) def _coerce_int_or_value(value: Any) -> Any: diff --git a/core/path_utils.py b/core/path_utils.py index cfa6ed6..e57ab54 100644 --- a/core/path_utils.py +++ b/core/path_utils.py @@ -148,3 +148,17 @@ def write_json_atomic( except OSError: pass raise + + +def get_filename_from_path(path: Any) -> str: + """Return the filename/basename of a path, handling both forward and backward slashes.""" + if not path: + return "" + return str(path).replace("\\", "/").split("/")[-1] + + +# Path to metadata directory +METADATA_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "metadata" +) + diff --git a/core/resolver.py b/core/resolver.py index 66f78b6..a7b3efa 100644 --- a/core/resolver.py +++ b/core/resolver.py @@ -26,7 +26,7 @@ from .workflow_analyzer import ( identify_missing_models, should_scan_as_model_reference, ) -from .matcher import find_matches +from .matcher import find_matches, strip_known_model_extension from .type_utils import as_dict, as_list, MODEL_EXTENSIONS as _MODEL_EXTENSIONS from .workflow_updater import update_workflow_nodes from .sources.civitai import resolve_urn @@ -49,16 +49,7 @@ from .path_utils import ( ) -def strip_known_model_extension(filename: str) -> str: - """Strip only known model extensions, preserving names like v4.0.""" - if not isinstance(filename, str): - return "" - - lowered = filename.lower() - for ext in MODEL_EXTENSIONS: - if lowered.endswith(ext): - return filename[: -len(ext)] - return filename +# Imported from .matcher def get_workflow_url_info_for_filename( diff --git a/core/sources/civarchive.py b/core/sources/civarchive.py index ba818aa..377f70e 100644 --- a/core/sources/civarchive.py +++ b/core/sources/civarchive.py @@ -22,6 +22,7 @@ from ..matcher import ( base_model_matches as _base_model_matches, base_model_score as _base_model_score, ) +from ..type_utils import to_int from ..progress import report_progress from ..log_system.log_funcs import ( log_debug, @@ -105,12 +106,7 @@ def is_civarchive_available() -> bool: def _coerce_int(value: Any) -> Optional[int]: - if value is None: - return None - try: - return int(value) - except (TypeError, ValueError): - return None + return to_int(value) def _size_kb_to_bytes(value: Any) -> Optional[int]: diff --git a/core/sources/civitai.py b/core/sources/civitai.py index e6f6719..1fbb262 100644 --- a/core/sources/civitai.py +++ b/core/sources/civitai.py @@ -20,6 +20,7 @@ from ..matcher import ( base_model_matches as _base_model_matches, base_model_score as _base_model_score, ) +from ..type_utils import first_non_empty from ..progress import report_progress from ..log_system.log_funcs import ( log_debug, @@ -1750,15 +1751,7 @@ def _as_metadata_list(value: Any) -> List[Any]: def _first_metadata_value(*values: Any) -> Any: - for value in values: - if value is None: - continue - if isinstance(value, str) and not value.strip(): - continue - if isinstance(value, (list, tuple, dict)) and not value: - continue - return value - return None + return first_non_empty(*values, default=None) def _metadata_size_to_bytes(value: Any) -> Optional[int]: diff --git a/core/sources/huggingface.py b/core/sources/huggingface.py index 4207cba..3e91d9e 100644 --- a/core/sources/huggingface.py +++ b/core/sources/huggingface.py @@ -21,16 +21,14 @@ from ..log_system.log_funcs import ( log_error, log_exception, ) -from ..path_utils import write_json_atomic +from ..path_utils import write_json_atomic, METADATA_DIR HF_API_URL = "https://huggingface.co/api" HF_AUTHOR_FALLBACKS = ["Comfy-Org"] BRAVE_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search" HF_AUTHOR_INDEX_CACHE_TTL_SECONDS = 24 * 60 * 60 HF_AUTHOR_INDEX_CACHE_VERSION = 1 -METADATA_DIR = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "metadata" -) + HF_AUTHOR_INDEX_CACHE_PATH = os.path.join( METADATA_DIR, "huggingface-author-index.json" ) diff --git a/core/sources/model_list.py b/core/sources/model_list.py index 12708c2..85fb70c 100644 --- a/core/sources/model_list.py +++ b/core/sources/model_list.py @@ -23,10 +23,7 @@ from ..log_system.log_funcs import ( log_exception, ) -# Path to metadata directory -METADATA_DIR = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "metadata" -) +from ..path_utils import METADATA_DIR MODEL_LIST_FILE = os.path.join(METADATA_DIR, "model-list.json") MODEL_LIST_META_FILE = os.path.join(METADATA_DIR, "model-list.meta.json") MODEL_LIST_SOURCE_URL = ( diff --git a/core/sources/popular.py b/core/sources/popular.py index 2233cfa..2e7e617 100644 --- a/core/sources/popular.py +++ b/core/sources/popular.py @@ -20,10 +20,7 @@ from ..log_system.log_funcs import ( log_exception, ) -# Path to metadata directory -METADATA_DIR = os.path.join( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "metadata" -) +from ..path_utils import METADATA_DIR POPULAR_MODELS_FILE = os.path.join(METADATA_DIR, "popular-models.json") MODEL_ALIASES_FILE = os.path.join(METADATA_DIR, "model-aliases.json") BASE_MODELS_FILE = os.path.join(METADATA_DIR, "base-models.json") diff --git a/core/type_utils.py b/core/type_utils.py index 6e060c4..a0bce11 100644 --- a/core/type_utils.py +++ b/core/type_utils.py @@ -56,3 +56,32 @@ MODEL_EXTENSIONS = { ".onnx", ".gguf", } + + +def first_non_empty(*values: Any, default: Any = "") -> Any: + """ + Return the first value that is not None, not an empty/whitespace-only string, + and not an empty collection. + """ + for value in values: + if value is None: + continue + if isinstance(value, str) and not value.strip(): + continue + if isinstance(value, (list, tuple, dict, set)) and not value: + continue + return value + return default + + +def to_int(value: Any, default: Any = None) -> Any: + """ + Safely cast a value to an integer, returning a default value on failure. + """ + if value is None or value == "": + return default + try: + return int(value) + except (TypeError, ValueError): + return default +