mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
Cache PyPI download metrics for patrols (#3387)
Co-authored-by: LauraGPT <[email protected]>
This commit is contained in:
@@ -370,7 +370,7 @@ Paste the Markdown output into launch notes, weekly community updates, or releas
|
||||
- [ ] Pin a GitHub discussion for the release. The release discussion exists at [`modelscope/FunASR#3376`](https://github.com/modelscope/FunASR/discussions/3376) with GraphQL node `D_kwDOIfjis84An-Vp` in the `Announcements` category. A 2026-07-23 GraphQL schema recheck found discussion mutations `createDiscussion`, `updateDiscussion`, `closeDiscussion`, `reopenDiscussion`, `deleteDiscussion`, and discussion-comment mutations, but still no `pinDiscussion` / `unpinDiscussion`; only `pinIssue`, `pinIssueComment`, and `pinEnvironment` are exposed. Pin manually in the GitHub Discussions UI or retry if GitHub adds a discussion pin mutation.
|
||||
- [x] Triage all new issues within 48 hours. On 2026-07-23, all four repositories were patrolled with the GitHub token on `ind-gpu8`: `modelscope/FunASR`, `FunAudioLLM/Fun-ASR`, `FunAudioLLM/SenseVoice`, and `modelscope/FunClip` all had `0` open pull requests; Fun-ASR, SenseVoice, and FunClip had `0` open issues. The only open FunASR issues were already triaged: #3302 is labeled `question` + `needs feedback` and is waiting for the reporter to retest the merged hotword/postprocess fix, while #3104 is labeled `enhancement` + `needs maintainer decision` and is waiting on release-scope confirmation for RL training code. No unlabelled or unowned open issue remained in the 48-hour window.
|
||||
- [x] Convert top 3 support questions into docs. On 2026-07-23, the latest 80 FunASR issues were triaged into three repeat support blockers: install / hub / model-id selection (#3321, #3045, #3042, #2973, #2976), llama.cpp / GGUF / CUDA-Vulkan runtime packages (#3298, #3297, #3296, #3289, #3243), and realtime / VAD / vLLM / server output behavior (#3101, #3109, #3038, #3031, #2968, #2965). `docs/troubleshooting.md` and `docs/troubleshooting_zh.md` now start with a Top support questions section that maps those three issue clusters to the existing install, model-download, OpenAI-compatible server, WebSocket, and llama.cpp / GGUF FAQ anchors.
|
||||
- [ ] Track stars, PyPI downloads, issue volume, and docs traffic. `collect_growth_metrics.py` now tracks GitHub stars, open issues, and open pull requests for the four core repositories and adds PyPIStats download windows for `funasr` as `downloads_last_7_days` and `downloads_last_30_days` when `pypistats.org` is reachable. The script keeps the patrol usable when PyPIStats is rate-limited by reporting downloads as unavailable instead of failing the run. GitHub/PyPI tracking is now covered; docs traffic remains unavailable until an analytics credential or export is connected.
|
||||
- [ ] Track stars, PyPI downloads, issue volume, and docs traffic. `collect_growth_metrics.py` now tracks GitHub stars, open issues, and open pull requests for the four core repositories and adds PyPIStats download windows for `funasr` as `downloads_last_7_days` and `downloads_last_30_days`. On 2026-07-23, the live PyPIStats window was refreshed at `116,407` downloads in 7 days and `442,556` downloads in 30 days through `2026-07-21`; successful runs now write `/cpfs_speech/user/zhifu.gzf/.cache/funasr-ops/pypi-downloads-funasr.json`, and failed live runs fall back to that cache with status `stale_available` instead of dropping the download metric during SSL/429 outages. GitHub/PyPI tracking is now covered; docs traffic remains unavailable until an analytics credential or export is connected.
|
||||
|
||||
## Release-note template
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ DEFAULT_BASELINE_STARS = 31224
|
||||
DEFAULT_TARGET_ADDITIONAL_STARS = 20000
|
||||
DEFAULT_TARGET_DATE = "2026-09-30"
|
||||
OPS_GITHUB_TOKEN_PATH = Path("/cpfs_speech/user/zhifu.gzf/.config/funasr-ops/github_token")
|
||||
DEFAULT_PYPI_DOWNLOAD_CACHE = Path("/cpfs_speech/user/zhifu.gzf/.cache/funasr-ops/pypi-downloads-funasr.json")
|
||||
DEFAULT_INTEGRATION_PRS = [
|
||||
"huggingface/transformers#46180",
|
||||
"yuekaizhang/Fun-ASR-vllm#21",
|
||||
@@ -328,6 +329,45 @@ def fetch_json(url: str, headers: Optional[Dict[str, str]] = None) -> Any:
|
||||
raise RuntimeError(f"GET {url} failed: {exc.reason}") from exc
|
||||
|
||||
|
||||
def pypi_download_cache_path(package: str) -> Path:
|
||||
if package == DEFAULT_PACKAGE:
|
||||
return DEFAULT_PYPI_DOWNLOAD_CACHE
|
||||
return DEFAULT_PYPI_DOWNLOAD_CACHE.with_name(f"pypi-downloads-{package}.json")
|
||||
|
||||
|
||||
def write_pypi_download_cache(package: str, metrics: Dict[str, Any]) -> None:
|
||||
cache_path = pypi_download_cache_path(package)
|
||||
try:
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.write_text(
|
||||
json.dumps({"package": package, "metrics": metrics}, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def read_pypi_download_cache(package: str, reason: str) -> Optional[Dict[str, Any]]:
|
||||
cache_path = pypi_download_cache_path(package)
|
||||
try:
|
||||
cached = json.loads(cache_path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
if cached.get("package") != package:
|
||||
return None
|
||||
metrics = cached.get("metrics")
|
||||
if not isinstance(metrics, dict):
|
||||
return None
|
||||
required = {"latest_date", "downloads_last_7_days", "downloads_last_30_days", "source_url"}
|
||||
if not required.issubset(metrics):
|
||||
return None
|
||||
stale = dict(metrics)
|
||||
stale["status"] = "stale_available"
|
||||
stale["cache_path"] = str(cache_path)
|
||||
stale["reason"] = reason
|
||||
return stale
|
||||
|
||||
|
||||
def collect_pypi_download_metrics(package: str) -> Dict[str, Any]:
|
||||
url = f"https://pypistats.org/api/packages/{package}/overall?mirrors=false"
|
||||
try:
|
||||
@@ -342,7 +382,7 @@ def collect_pypi_download_metrics(package: str) -> Dict[str, Any]:
|
||||
rows.sort(key=lambda row: row["date"])
|
||||
if not rows:
|
||||
raise RuntimeError("PyPIStats returned no daily download rows")
|
||||
return {
|
||||
metrics = {
|
||||
"source": "pypistats.org",
|
||||
"source_url": f"https://pypistats.org/packages/{package}",
|
||||
"latest_date": rows[-1]["date"],
|
||||
@@ -350,12 +390,18 @@ def collect_pypi_download_metrics(package: str) -> Dict[str, Any]:
|
||||
"downloads_last_30_days": sum(row["downloads"] for row in rows[-30:]),
|
||||
"status": "available",
|
||||
}
|
||||
write_pypi_download_cache(package, metrics)
|
||||
return metrics
|
||||
except Exception as exc:
|
||||
reason = str(exc)
|
||||
cached = read_pypi_download_cache(package, reason)
|
||||
if cached:
|
||||
return cached
|
||||
return {
|
||||
"source": "pypistats.org",
|
||||
"source_url": f"https://pypistats.org/packages/{package}",
|
||||
"status": "unavailable",
|
||||
"reason": str(exc),
|
||||
"reason": reason,
|
||||
}
|
||||
|
||||
|
||||
@@ -791,11 +837,14 @@ def collect_ecosystem_metrics(
|
||||
|
||||
def format_pypi_downloads(pypi: Dict[str, Any]) -> str:
|
||||
downloads = pypi.get("downloads") or {}
|
||||
if downloads.get("status") == "available":
|
||||
if downloads.get("status") in {"available", "stale_available"}:
|
||||
cache_note = ""
|
||||
if downloads.get("status") == "stale_available":
|
||||
cache_note = f"; cached after live API failure: {downloads.get('reason')}"
|
||||
return (
|
||||
f"- PyPI downloads: **{downloads.get('downloads_last_7_days', 0):,}** last 7 days; "
|
||||
f"**{downloads.get('downloads_last_30_days', 0):,}** last 30 days "
|
||||
f"(through {downloads.get('latest_date')}, {downloads.get('source_url')})"
|
||||
f"(through {downloads.get('latest_date')}, {downloads.get('source_url')}{cache_note})"
|
||||
)
|
||||
if downloads.get("status") == "unavailable":
|
||||
return f"- PyPI downloads: unavailable via {downloads.get('source', 'PyPIStats')} ({downloads.get('reason')})"
|
||||
|
||||
@@ -353,8 +353,9 @@ def test_collect_ecosystem_metrics_sums_repositories_and_target_gap(monkeypatch)
|
||||
]
|
||||
|
||||
|
||||
def test_collect_pypi_download_metrics_sums_recent_windows(monkeypatch):
|
||||
def test_collect_pypi_download_metrics_sums_recent_windows(monkeypatch, tmp_path):
|
||||
module = load_growth_metrics_module()
|
||||
monkeypatch.setattr(module, "DEFAULT_PYPI_DOWNLOAD_CACHE", tmp_path / "pypi-downloads-funasr.json")
|
||||
|
||||
def fake_fetch_json(url, headers=None):
|
||||
assert url == "https://pypistats.org/api/packages/funasr/overall?mirrors=false"
|
||||
@@ -381,10 +382,44 @@ def test_collect_pypi_download_metrics_sums_recent_windows(monkeypatch):
|
||||
"downloads_last_30_days": sum(range(2, 32)),
|
||||
"status": "available",
|
||||
}
|
||||
cached = json.loads((tmp_path / "pypi-downloads-funasr.json").read_text())
|
||||
assert cached["package"] == "funasr"
|
||||
assert cached["metrics"] == metrics
|
||||
|
||||
|
||||
def test_collect_pypi_download_metrics_reports_unavailable_without_failing(monkeypatch):
|
||||
def test_collect_pypi_download_metrics_uses_cache_after_live_failure(monkeypatch, tmp_path):
|
||||
module = load_growth_metrics_module()
|
||||
cache_path = tmp_path / "pypi-downloads-funasr.json"
|
||||
monkeypatch.setattr(module, "DEFAULT_PYPI_DOWNLOAD_CACHE", cache_path)
|
||||
cache_path.write_text(json.dumps({
|
||||
"package": "funasr",
|
||||
"metrics": {
|
||||
"source": "pypistats.org",
|
||||
"source_url": "https://pypistats.org/packages/funasr",
|
||||
"latest_date": "2026-07-21",
|
||||
"downloads_last_7_days": 116407,
|
||||
"downloads_last_30_days": 442556,
|
||||
"status": "available",
|
||||
},
|
||||
}))
|
||||
|
||||
def fake_fetch_json(url, headers=None):
|
||||
raise RuntimeError("HTTP 429")
|
||||
|
||||
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
||||
|
||||
metrics = module.collect_pypi_download_metrics("funasr")
|
||||
|
||||
assert metrics["status"] == "stale_available"
|
||||
assert metrics["downloads_last_7_days"] == 116407
|
||||
assert metrics["downloads_last_30_days"] == 442556
|
||||
assert metrics["cache_path"] == str(cache_path)
|
||||
assert "HTTP 429" in metrics["reason"]
|
||||
|
||||
|
||||
def test_collect_pypi_download_metrics_reports_unavailable_without_cache(monkeypatch, tmp_path):
|
||||
module = load_growth_metrics_module()
|
||||
monkeypatch.setattr(module, "DEFAULT_PYPI_DOWNLOAD_CACHE", tmp_path / "missing-cache.json")
|
||||
|
||||
def fake_fetch_json(url, headers=None):
|
||||
raise RuntimeError("HTTP 429")
|
||||
@@ -441,6 +476,42 @@ def test_collect_ecosystem_metrics_calculates_daily_target(monkeypatch):
|
||||
assert metrics["ecosystem"]["required_daily_average"] == 181
|
||||
|
||||
|
||||
def test_format_ecosystem_markdown_marks_cached_pypi_download_windows():
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
metrics = {
|
||||
"collected_at_utc": "2026-07-23T00:00:00+00:00",
|
||||
"ecosystem": {
|
||||
"repositories": [],
|
||||
"total_stars": 35787,
|
||||
"baseline_stars": 31224,
|
||||
"target_additional_stars": 20000,
|
||||
"added_stars": 4563,
|
||||
"remaining_to_target": 15437,
|
||||
"target_date": "2026-09-30",
|
||||
"days_remaining": 70,
|
||||
"required_daily_average": 221,
|
||||
},
|
||||
"pypi": {
|
||||
"package": "funasr",
|
||||
"version": "1.3.26",
|
||||
"downloads": {
|
||||
"status": "stale_available",
|
||||
"latest_date": "2026-07-21",
|
||||
"downloads_last_7_days": 116407,
|
||||
"downloads_last_30_days": 442556,
|
||||
"source_url": "https://pypistats.org/packages/funasr",
|
||||
"reason": "HTTP 429",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output = module.format_ecosystem_markdown(metrics)
|
||||
|
||||
assert "- PyPI downloads: **116,407** last 7 days; **442,556** last 30 days" in output
|
||||
assert "cached after live API failure: HTTP 429" in output
|
||||
|
||||
|
||||
def test_format_ecosystem_markdown_includes_pypi_download_windows():
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
|
||||
@@ -377,6 +377,12 @@ def test_growth_plan_records_metrics_tracking_progress():
|
||||
"`downloads_last_30_days`",
|
||||
"`collect_growth_metrics.py`",
|
||||
"GitHub stars, open issues, and open pull requests",
|
||||
"`116,407` downloads in 7 days",
|
||||
"`442,556` downloads in 30 days",
|
||||
"`2026-07-21`",
|
||||
"pypi-downloads-funasr.json",
|
||||
"`stale_available`",
|
||||
"SSL/429 outages",
|
||||
"docs traffic remains unavailable until an analytics credential or export is connected",
|
||||
]
|
||||
for marker in required_markers:
|
||||
|
||||
Reference in New Issue
Block a user