Fix manager ranking and cache defaults
This commit is contained in:
@@ -8,6 +8,7 @@ from pathlib import Path
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from tools.generate_popular_node_signatures import (
|
from tools.generate_popular_node_signatures import (
|
||||||
|
DEFAULT_CACHE_DIR,
|
||||||
build_artifact,
|
build_artifact,
|
||||||
clone_or_update_repo,
|
clone_or_update_repo,
|
||||||
extract_repo_signatures,
|
extract_repo_signatures,
|
||||||
@@ -5602,19 +5603,19 @@ class ManagerIngestionTests(unittest.TestCase):
|
|||||||
"id": "tie-b",
|
"id": "tie-b",
|
||||||
"title": "Tie B",
|
"title": "Tie B",
|
||||||
"repository": "https://github.com/example/tie-b",
|
"repository": "https://github.com/example/tie-b",
|
||||||
"metrics": {"downloads": 5},
|
"metrics": {"downloads": 5, "stars": 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "most",
|
"id": "most",
|
||||||
"title": "Most",
|
"title": "Most",
|
||||||
"repository": "https://github.com/example/most",
|
"repository": "https://github.com/example/most",
|
||||||
"metrics": {"stars": 10},
|
"metrics": {"downloads": 10},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "tie-a",
|
"id": "tie-a",
|
||||||
"title": "Tie A",
|
"title": "Tie A",
|
||||||
"repository": "https://github.com/example/tie-a",
|
"repository": "https://github.com/example/tie-a",
|
||||||
"metrics": {"favorites": 5},
|
"metrics": {"downloads": 5, "stars": 2},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "none",
|
"id": "none",
|
||||||
@@ -5629,8 +5630,36 @@ class ManagerIngestionTests(unittest.TestCase):
|
|||||||
self.assertEqual(["most", "tie-a", "tie-b", "none"], [pack["id"] for pack in ranked])
|
self.assertEqual(["most", "tie-a", "tie-b", "none"], [pack["id"] for pack in ranked])
|
||||||
self.assertEqual([1, 2, 3, 4], [pack["rank"] for pack in ranked])
|
self.assertEqual([1, 2, 3, 4], [pack["rank"] for pack in ranked])
|
||||||
|
|
||||||
|
def test_rank_packs_prioritizes_downloads_before_stars(self):
|
||||||
|
packs = [
|
||||||
|
{
|
||||||
|
"id": "many-stars",
|
||||||
|
"title": "Many Stars",
|
||||||
|
"repository": "https://github.com/example/many-stars",
|
||||||
|
"metrics": {"downloads": 1, "stars": 1000},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "many-downloads",
|
||||||
|
"title": "Many Downloads",
|
||||||
|
"repository": "https://github.com/example/many-downloads",
|
||||||
|
"metrics": {"downloads": 100, "stars": 0},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
ranked = rank_packs(packs)
|
||||||
|
|
||||||
|
self.assertEqual(["many-downloads", "many-stars"], [pack["id"] for pack in ranked])
|
||||||
|
|
||||||
|
|
||||||
class RepoCacheTests(unittest.TestCase):
|
class RepoCacheTests(unittest.TestCase):
|
||||||
|
def test_default_cache_dir_is_under_system_temp_dir(self):
|
||||||
|
temp_root = Path(tempfile.gettempdir()).resolve()
|
||||||
|
default_cache = DEFAULT_CACHE_DIR.resolve()
|
||||||
|
|
||||||
|
self.assertTrue(default_cache.is_absolute())
|
||||||
|
self.assertTrue(default_cache == temp_root or temp_root in default_cache.parents)
|
||||||
|
self.assertNotEqual(Path(".cache/utfcn-popular-node-repos"), DEFAULT_CACHE_DIR)
|
||||||
|
|
||||||
def test_repo_cache_path_is_safe_stable_and_collision_resistant(self):
|
def test_repo_cache_path_is_safe_stable_and_collision_resistant(self):
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
cache_dir = Path(tmp)
|
cache_dir = Path(tmp)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -17,7 +18,7 @@ SCHEMA_VERSION = 1
|
|||||||
MANAGER_LIST_URL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json"
|
MANAGER_LIST_URL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/custom-node-list.json"
|
||||||
REGISTRY_NODES_URL = "https://api.comfy.org/nodes"
|
REGISTRY_NODES_URL = "https://api.comfy.org/nodes"
|
||||||
DEFAULT_GENERATED_AT = "1970-01-01T00:00:00Z"
|
DEFAULT_GENERATED_AT = "1970-01-01T00:00:00Z"
|
||||||
DEFAULT_CACHE_DIR = Path(".cache/utfcn-popular-node-repos")
|
DEFAULT_CACHE_DIR = Path(tempfile.gettempdir()) / "utfcn-popular-node-repos"
|
||||||
DEFAULT_OUTPUT = Path("popular_node_signatures.json")
|
DEFAULT_OUTPUT = Path("popular_node_signatures.json")
|
||||||
USER_AGENT = "ComfyUI-UTFCN popular node signature generator"
|
USER_AGENT = "ComfyUI-UTFCN popular node signature generator"
|
||||||
|
|
||||||
@@ -56,6 +57,9 @@ _METRIC_FIELDS = (
|
|||||||
"stars",
|
"stars",
|
||||||
"github_stars",
|
"github_stars",
|
||||||
"stargazers_count",
|
"stargazers_count",
|
||||||
|
"search_ranking",
|
||||||
|
"search_rank",
|
||||||
|
"search_order",
|
||||||
"favorites",
|
"favorites",
|
||||||
"favourites",
|
"favourites",
|
||||||
"installed",
|
"installed",
|
||||||
@@ -63,6 +67,7 @@ _METRIC_FIELDS = (
|
|||||||
"install_count",
|
"install_count",
|
||||||
"count",
|
"count",
|
||||||
)
|
)
|
||||||
|
_SEARCH_RANKING_FIELDS = {"search_ranking", "search_rank", "search_order"}
|
||||||
|
|
||||||
|
|
||||||
def fetch_json(url):
|
def fetch_json(url):
|
||||||
@@ -100,6 +105,20 @@ def _coerce_int(value):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _coerce_float(value):
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return None
|
||||||
|
if isinstance(value, (int, float)):
|
||||||
|
return float(value)
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value.strip().replace(",", "")
|
||||||
|
try:
|
||||||
|
return float(text)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _slug(value, default="unnamed-pack"):
|
def _slug(value, default="unnamed-pack"):
|
||||||
text = str(value or "").strip().lower()
|
text = str(value or "").strip().lower()
|
||||||
text = re.sub(r"[^a-z0-9]+", "-", text).strip("-")
|
text = re.sub(r"[^a-z0-9]+", "-", text).strip("-")
|
||||||
@@ -216,12 +235,31 @@ def _entry_metrics(item):
|
|||||||
sources.append(value)
|
sources.append(value)
|
||||||
for source in sources:
|
for source in sources:
|
||||||
for field in _METRIC_FIELDS:
|
for field in _METRIC_FIELDS:
|
||||||
|
if field in _SEARCH_RANKING_FIELDS:
|
||||||
|
value = _coerce_float(source.get(field))
|
||||||
|
if value is not None:
|
||||||
|
metrics[field] = value
|
||||||
|
else:
|
||||||
value = _coerce_int(source.get(field))
|
value = _coerce_int(source.get(field))
|
||||||
if value:
|
if value:
|
||||||
metrics[field] = value
|
metrics[field] = value
|
||||||
return metrics
|
return metrics
|
||||||
|
|
||||||
|
|
||||||
|
def _metric_max(metrics, names):
|
||||||
|
values = [_coerce_int(metrics.get(name)) for name in names]
|
||||||
|
return max(values, default=0)
|
||||||
|
|
||||||
|
|
||||||
|
def _metric_min_float(metrics, names):
|
||||||
|
values = []
|
||||||
|
for name in names:
|
||||||
|
value = _coerce_float(metrics.get(name))
|
||||||
|
if value is not None:
|
||||||
|
values.append(value)
|
||||||
|
return min(values) if values else None
|
||||||
|
|
||||||
|
|
||||||
def _pack_id_from_repository(repository):
|
def _pack_id_from_repository(repository):
|
||||||
parsed = urlparse(repository)
|
parsed = urlparse(repository)
|
||||||
if parsed.netloc:
|
if parsed.netloc:
|
||||||
@@ -261,8 +299,22 @@ def normalise_manager_entries(raw):
|
|||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
||||||
def _popularity_score(pack):
|
def _rank_sort_key(pack):
|
||||||
return sum(_coerce_int(value) for value in pack.get("metrics", {}).values())
|
metrics = pack.get("metrics", {})
|
||||||
|
downloads = _metric_max(metrics, ("downloads", "download_count"))
|
||||||
|
stars = _metric_max(metrics, ("stars", "github_stars", "stargazers_count"))
|
||||||
|
search_ranking = _metric_min_float(metrics, ("search_ranking", "search_rank", "search_order"))
|
||||||
|
manager_order = int(pack.get("manager_order", 0))
|
||||||
|
return (
|
||||||
|
-downloads,
|
||||||
|
-stars,
|
||||||
|
1 if search_ranking is None else 0,
|
||||||
|
search_ranking if search_ranking is not None else 0.0,
|
||||||
|
manager_order,
|
||||||
|
str(pack.get("title", "")).lower(),
|
||||||
|
str(pack.get("id", "")),
|
||||||
|
str(pack.get("repository", "")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def rank_packs(packs, limit=None):
|
def rank_packs(packs, limit=None):
|
||||||
@@ -276,28 +328,10 @@ def rank_packs(packs, limit=None):
|
|||||||
if previous is None:
|
if previous is None:
|
||||||
best_by_repository[repository] = candidate
|
best_by_repository[repository] = candidate
|
||||||
continue
|
continue
|
||||||
candidate_key = (
|
if _rank_sort_key(candidate) < _rank_sort_key(previous):
|
||||||
_popularity_score(candidate),
|
|
||||||
-int(candidate.get("manager_order", 0)),
|
|
||||||
str(candidate.get("id", "")),
|
|
||||||
)
|
|
||||||
previous_key = (
|
|
||||||
_popularity_score(previous),
|
|
||||||
-int(previous.get("manager_order", 0)),
|
|
||||||
str(previous.get("id", "")),
|
|
||||||
)
|
|
||||||
if candidate_key > previous_key:
|
|
||||||
best_by_repository[repository] = candidate
|
best_by_repository[repository] = candidate
|
||||||
|
|
||||||
ranked = sorted(
|
ranked = sorted(best_by_repository.values(), key=_rank_sort_key)
|
||||||
best_by_repository.values(),
|
|
||||||
key=lambda pack: (
|
|
||||||
-_popularity_score(pack),
|
|
||||||
str(pack.get("title", "")).lower(),
|
|
||||||
str(pack.get("id", "")),
|
|
||||||
str(pack.get("repository", "")),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
ranked = ranked[:limit]
|
ranked = ranked[:limit]
|
||||||
result = []
|
result = []
|
||||||
|
|||||||
Reference in New Issue
Block a user