mirror of
https://github.com/vinta/awesome-python.git
synced 2026-11-08 02:15:12 -06:00
feat: add "Multiple on PyPI" badge for SDK monorepos
azure-sdk-for-python and google-cloud-python were rendering "Not on PyPI", which is misleading. Both do ship on PyPI, just as many per-service packages (azure-identity, azure-storage-blob, google-cloud-storage, etc.) rather than under the repo name. pypi_name_overrides.json already recorded that distinction in its reason field; those two entries now carry an optional "badge" value that build.py reads into the PyPI Downloads column. The other sixteen no-count entries (cpython, renpy, agent skill repos, etc.) keep "Not on PyPI" since that remains accurate for them. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -11,7 +11,7 @@ from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
from fetch_pypi_downloads_via_clickpy import normalize
|
||||
from fetch_pypi_downloads_via_clickpy import OVERRIDES_FILE, normalize
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from readme_parser import AlsoSee, ParsedGroup, ParsedSection, parse_readme, parse_sponsors, slugify
|
||||
|
||||
@@ -60,6 +60,7 @@ class TemplateEntry(TypedDict):
|
||||
last_commit_at: str | None
|
||||
source_type: str | None
|
||||
bundled: bool
|
||||
badge: str | None
|
||||
also_see: list[AlsoSee]
|
||||
|
||||
|
||||
@@ -101,6 +102,17 @@ def load_stars(path: Path) -> dict[str, dict]:
|
||||
return {}
|
||||
|
||||
|
||||
def load_pypi_badges() -> dict[str, str]:
|
||||
"""Load the badge each entry shows in place of a download count.
|
||||
|
||||
Keyed by normalized README name. Only entries whose missing count needs
|
||||
explaining beyond "Not on PyPI" carry one, e.g. the SDK monorepos that do
|
||||
ship on PyPI, just as many per-service packages.
|
||||
"""
|
||||
raw = json.loads(OVERRIDES_FILE.read_text())
|
||||
return {name: entry["badge"] for name, entry in raw.items() if entry.get("badge")}
|
||||
|
||||
|
||||
def load_downloads(path: Path) -> dict[str, int]:
|
||||
"""Load last-30-day download counts from the TSV cache, keyed by normalized README name.
|
||||
|
||||
@@ -524,6 +536,7 @@ def extract_entries(
|
||||
last_commit_at=None,
|
||||
source_type=detect_source_type(entry["url"]),
|
||||
bundled=bool(BUNDLED_PREFIX_RE.match(entry["description"])),
|
||||
badge=None,
|
||||
also_see=entry["also_see"],
|
||||
)
|
||||
seen[key] = existing
|
||||
@@ -576,6 +589,7 @@ def build(repo_root: Path) -> None:
|
||||
|
||||
stars_data = load_stars(website / "data" / "github_stars.json")
|
||||
downloads_data = load_downloads(website / "data" / "pypi_downloads.tsv")
|
||||
pypi_badges = load_pypi_badges()
|
||||
|
||||
repo_self = stars_data.get("vinta/awesome-python", {})
|
||||
repo_stars = None
|
||||
@@ -595,6 +609,7 @@ def build(repo_root: Path) -> None:
|
||||
# Stdlib entries would hit same-named PyPI backports (e.g. asyncio), not the stdlib.
|
||||
if entry.get("source_type") != BUILTIN_FILTER:
|
||||
entry["downloads"] = downloads_data.get(normalize(entry["name"]))
|
||||
entry["badge"] = pypi_badges.get(normalize(entry["name"]))
|
||||
|
||||
entries = sort_entries(entries)
|
||||
category_urls = {cat["name"]: category_path(cat) for cat in categories}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"autobahn-python": { "package": "autobahn", "reason": null },
|
||||
"azure-sdk-for-python": { "package": null, "reason": "monorepo umbrella; ships as many azure-* packages, no single package represents it" },
|
||||
"azure-sdk-for-python": { "package": null, "reason": "monorepo umbrella; ships as many azure-* packages, no single package represents it", "badge": "Multiple on PyPI" },
|
||||
"cloud-init": { "package": null, "reason": "installed via distro images, not pip" },
|
||||
"concurrent-futures": { "package": null, "reason": "stdlib module" },
|
||||
"cpython": { "package": null, "reason": "not pip-installable; the cpython PyPI name was squatted" },
|
||||
@@ -12,7 +12,7 @@
|
||||
"django-rules": { "package": "rules", "reason": "PyPI django-rules is an abandoned unrelated fork; dfunckt's project publishes as rules" },
|
||||
"fasthtml": { "package": "python-fasthtml", "reason": null },
|
||||
"geodjango": { "package": null, "reason": "part of Django (django.contrib.gis), not a package" },
|
||||
"google-cloud-python": { "package": null, "reason": "monorepo umbrella; ships as many google-cloud-* packages" },
|
||||
"google-cloud-python": { "package": null, "reason": "monorepo umbrella; ships as many google-cloud-* packages", "badge": "Multiple on PyPI" },
|
||||
"graphify": { "package": null, "reason": "GitHub-only so far; young project, remap if it publishes" },
|
||||
"httpx-url": { "package": null, "reason": "class in the httpx package, not a package" },
|
||||
"jinja": { "package": "jinja2", "reason": "PyPI jinja is Jinja1; current releases publish as jinja2" },
|
||||
|
||||
@@ -56,8 +56,10 @@ def normalize(name: str) -> str:
|
||||
def load_overrides() -> dict[str, str | None]:
|
||||
"""Load curated overrides: normalized README name -> PyPI package to measure (None = never query).
|
||||
|
||||
Each file entry is {"package": str | null, "reason": str | null}; the reason is
|
||||
documentation only — required for null packages, optional for remaps.
|
||||
Each file entry is {"package": str | null, "reason": str | null} plus an
|
||||
optional "badge" the website renders in place of a download count; the
|
||||
reason is documentation only — required for null packages, optional for
|
||||
remaps.
|
||||
"""
|
||||
raw = json.loads(OVERRIDES_FILE.read_text())
|
||||
return {name: entry["package"] for name, entry in raw.items()}
|
||||
|
||||
@@ -157,8 +157,9 @@
|
||||
{% if entry.downloads is not none %}{{
|
||||
"{:,}".format(entry.downloads) }}{% elif entry.source_type ==
|
||||
'Stdlib' %}<span class="source-badge">Stdlib</span>{% elif
|
||||
entry.bundled %}<span class="source-badge">Bundled</span>{% else
|
||||
%}<span class="source-badge">Not on PyPI</span>{% endif %}
|
||||
entry.bundled %}<span class="source-badge">Bundled</span>{% elif
|
||||
entry.badge %}<span class="source-badge">{{ entry.badge }}</span>{%
|
||||
else %}<span class="source-badge">Not on PyPI</span>{% endif %}
|
||||
</td>
|
||||
<td class="col-stars">
|
||||
{% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{%
|
||||
|
||||
@@ -194,8 +194,9 @@
|
||||
{% if entry.downloads is not none %}{{
|
||||
"{:,}".format(entry.downloads) }}{% elif entry.source_type ==
|
||||
'Stdlib' %}<span class="source-badge">Stdlib</span>{% elif
|
||||
entry.bundled %}<span class="source-badge">Bundled</span>{% else
|
||||
%}<span class="source-badge">Not on PyPI</span>{% endif %}
|
||||
entry.bundled %}<span class="source-badge">Bundled</span>{% elif
|
||||
entry.badge %}<span class="source-badge">{{ entry.badge }}</span>{%
|
||||
else %}<span class="source-badge">Not on PyPI</span>{% endif %}
|
||||
</td>
|
||||
<td class="col-stars">
|
||||
{% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{%
|
||||
|
||||
@@ -18,6 +18,7 @@ from build import (
|
||||
extract_entries,
|
||||
extract_github_repo,
|
||||
load_downloads,
|
||||
load_pypi_badges,
|
||||
load_stars,
|
||||
sort_entries,
|
||||
subcategory_path,
|
||||
@@ -1012,6 +1013,16 @@ class TestLoadStars:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestLoadPypiBadges:
|
||||
def test_only_entries_with_a_badge_are_returned(self):
|
||||
badges = load_pypi_badges()
|
||||
assert badges["azure-sdk-for-python"] == "Multiple on PyPI"
|
||||
assert badges["google-cloud-python"] == "Multiple on PyPI"
|
||||
# Entries whose missing count needs no explaining stay off the map
|
||||
assert "cpython" not in badges
|
||||
assert "tomllib" not in badges
|
||||
|
||||
|
||||
def _template_entry(name: str, stars: int | None, source_type: str | None = None) -> TemplateEntry:
|
||||
return TemplateEntry(
|
||||
name=name,
|
||||
@@ -1026,6 +1037,7 @@ def _template_entry(name: str, stars: int | None, source_type: str | None = None
|
||||
last_commit_at=None,
|
||||
source_type=source_type,
|
||||
bundled=False,
|
||||
badge=None,
|
||||
also_see=[],
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user