mirror of
https://github.com/modelscope/FunASR.git
synced 2026-10-12 02:19:18 +00:00
Track PyPI download windows
Add PyPIStats 7-day and 30-day download totals to the growth patrol while keeping the run tolerant of PyPIStats rate limits.
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), but the GitHub GraphQL API currently exposes `createDiscussion` and `updateDiscussion` but no discussion pin mutation; pin manually in the GitHub Discussions UI or retry if GitHub exposes an API for discussion pinning.
|
||||
- [ ] Triage all new issues within 48 hours.
|
||||
- [ ] Convert top 3 support questions into docs.
|
||||
- [ ] Track stars, PyPI downloads, issue volume, and docs traffic.
|
||||
- [ ] 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.
|
||||
|
||||
## Release-note template
|
||||
|
||||
|
||||
@@ -328,6 +328,37 @@ def fetch_json(url: str, headers: Optional[Dict[str, str]] = None) -> Any:
|
||||
raise RuntimeError(f"GET {url} failed: {exc.reason}") from exc
|
||||
|
||||
|
||||
def collect_pypi_download_metrics(package: str) -> Dict[str, Any]:
|
||||
url = f"https://pypistats.org/api/packages/{package}/overall?mirrors=false"
|
||||
try:
|
||||
payload = fetch_json(url, {"User-Agent": "funasr-growth-metrics"})
|
||||
rows = [
|
||||
row
|
||||
for row in payload.get("data", [])
|
||||
if row.get("category") == "without_mirrors"
|
||||
and row.get("date")
|
||||
and isinstance(row.get("downloads"), int)
|
||||
]
|
||||
rows.sort(key=lambda row: row["date"])
|
||||
if not rows:
|
||||
raise RuntimeError("PyPIStats returned no daily download rows")
|
||||
return {
|
||||
"source": "pypistats.org",
|
||||
"source_url": f"https://pypistats.org/packages/{package}",
|
||||
"latest_date": rows[-1]["date"],
|
||||
"downloads_last_7_days": sum(row["downloads"] for row in rows[-7:]),
|
||||
"downloads_last_30_days": sum(row["downloads"] for row in rows[-30:]),
|
||||
"status": "available",
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
"source": "pypistats.org",
|
||||
"source_url": f"https://pypistats.org/packages/{package}",
|
||||
"status": "unavailable",
|
||||
"reason": str(exc),
|
||||
}
|
||||
|
||||
|
||||
def github_headers() -> Dict[str, str]:
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
@@ -710,6 +741,7 @@ def collect_metrics(repo: str, package: str) -> Dict[str, Any]:
|
||||
"version": pypi.get("info", {}).get("version"),
|
||||
"summary": pypi.get("info", {}).get("summary"),
|
||||
"project_url": pypi.get("info", {}).get("project_url"),
|
||||
"downloads": collect_pypi_download_metrics(package),
|
||||
},
|
||||
}
|
||||
return metrics
|
||||
@@ -752,10 +784,24 @@ def collect_ecosystem_metrics(
|
||||
"version": pypi.get("info", {}).get("version"),
|
||||
"summary": pypi.get("info", {}).get("summary"),
|
||||
"project_url": pypi.get("info", {}).get("project_url"),
|
||||
"downloads": collect_pypi_download_metrics(package),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def format_pypi_downloads(pypi: Dict[str, Any]) -> str:
|
||||
downloads = pypi.get("downloads") or {}
|
||||
if downloads.get("status") == "available":
|
||||
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')})"
|
||||
)
|
||||
if downloads.get("status") == "unavailable":
|
||||
return f"- PyPI downloads: unavailable via {downloads.get('source', 'PyPIStats')} ({downloads.get('reason')})"
|
||||
return "- PyPI downloads: n/a"
|
||||
|
||||
|
||||
def format_markdown(metrics: Dict[str, Any], star_goal: int) -> str:
|
||||
github = metrics["github"]
|
||||
pypi = metrics["pypi"]
|
||||
@@ -772,6 +818,7 @@ def format_markdown(metrics: Dict[str, Any], star_goal: int) -> str:
|
||||
if github.get("open_pull_requests") is not None
|
||||
else "- Open pull requests: n/a",
|
||||
f"- PyPI package: **{pypi.get('package')} {pypi.get('version')}**",
|
||||
format_pypi_downloads(pypi),
|
||||
f"- Last GitHub push: `{github.get('pushed_at')}`",
|
||||
"",
|
||||
"## Links",
|
||||
@@ -797,6 +844,7 @@ def format_ecosystem_markdown(metrics: Dict[str, Any]) -> str:
|
||||
f"- Target date: **{ecosystem['target_date']}** ({ecosystem['days_remaining']:,} days remaining)",
|
||||
f"- Required daily average: **{ecosystem['required_daily_average']:,}** stars/day",
|
||||
f"- PyPI package: **{pypi.get('package')} {pypi.get('version')}**",
|
||||
format_pypi_downloads(pypi),
|
||||
"",
|
||||
"## Repositories",
|
||||
"",
|
||||
|
||||
@@ -353,6 +353,51 @@ def test_collect_ecosystem_metrics_sums_repositories_and_target_gap(monkeypatch)
|
||||
]
|
||||
|
||||
|
||||
def test_collect_pypi_download_metrics_sums_recent_windows(monkeypatch):
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
def fake_fetch_json(url, headers=None):
|
||||
assert url == "https://pypistats.org/api/packages/funasr/overall?mirrors=false"
|
||||
return {
|
||||
"data": [
|
||||
{
|
||||
"category": "without_mirrors",
|
||||
"date": f"2026-07-{day:02d}",
|
||||
"downloads": day,
|
||||
}
|
||||
for day in range(1, 32)
|
||||
]
|
||||
}
|
||||
|
||||
monkeypatch.setattr(module, "fetch_json", fake_fetch_json)
|
||||
|
||||
metrics = module.collect_pypi_download_metrics("funasr")
|
||||
|
||||
assert metrics == {
|
||||
"source": "pypistats.org",
|
||||
"source_url": "https://pypistats.org/packages/funasr",
|
||||
"latest_date": "2026-07-31",
|
||||
"downloads_last_7_days": sum(range(25, 32)),
|
||||
"downloads_last_30_days": sum(range(2, 32)),
|
||||
"status": "available",
|
||||
}
|
||||
|
||||
|
||||
def test_collect_pypi_download_metrics_reports_unavailable_without_failing(monkeypatch):
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
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"] == "unavailable"
|
||||
assert metrics["source"] == "pypistats.org"
|
||||
assert "HTTP 429" in metrics["reason"]
|
||||
|
||||
|
||||
def test_collect_ecosystem_metrics_calculates_daily_target(monkeypatch):
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
@@ -396,6 +441,42 @@ def test_collect_ecosystem_metrics_calculates_daily_target(monkeypatch):
|
||||
assert metrics["ecosystem"]["required_daily_average"] == 181
|
||||
|
||||
|
||||
def test_format_ecosystem_markdown_includes_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",
|
||||
"project_url": "https://pypi.org/project/funasr/",
|
||||
"downloads": {
|
||||
"status": "available",
|
||||
"latest_date": "2026-07-22",
|
||||
"downloads_last_7_days": 70123,
|
||||
"downloads_last_30_days": 301234,
|
||||
"source_url": "https://pypistats.org/packages/funasr",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
output = module.format_ecosystem_markdown(metrics)
|
||||
|
||||
assert "- PyPI downloads: **70,123** last 7 days; **301,234** last 30 days" in output
|
||||
assert "through 2026-07-22" in output
|
||||
|
||||
|
||||
def test_collect_integration_metrics_summarizes_pull_request_checks(monkeypatch):
|
||||
module = load_growth_metrics_module()
|
||||
|
||||
|
||||
@@ -233,3 +233,19 @@ def test_growth_plan_records_homepage_entrypoint_completion():
|
||||
assert marker in text
|
||||
|
||||
assert "[ ] Update homepage hero and docs entry points" not in text
|
||||
|
||||
|
||||
def test_growth_plan_records_metrics_tracking_progress():
|
||||
text = PLAN.read_text()
|
||||
|
||||
required_markers = [
|
||||
"[ ] Track stars, PyPI downloads, issue volume, and docs traffic.",
|
||||
"PyPIStats",
|
||||
"`downloads_last_7_days`",
|
||||
"`downloads_last_30_days`",
|
||||
"`collect_growth_metrics.py`",
|
||||
"GitHub stars, open issues, and open pull requests",
|
||||
"docs traffic remains unavailable until an analytics credential or export is connected",
|
||||
]
|
||||
for marker in required_markers:
|
||||
assert marker in text
|
||||
|
||||
Reference in New Issue
Block a user