feat: add Bundled badge for entries shipped inside a larger project

django.db.models, geodjango, httpx.URL and uv audit were rendering
"Not on PyPI" alongside eighteen genuinely standalone projects that
simply are not packaged on PyPI, conflating two different reasons for
a missing download count.

These four entries now carry a "(part of X)" description prefix in
README.md, mirroring the existing "(Python standard library)"
convention. build.py reads that prefix into a bundled flag that both
templates render as a "Bundled" badge.

The prefix approach was chosen over a separate data file so README.md
stays the single source of content truth, and over inferring from the
entry name because geodjango is neither dotted nor spaced and would
have been missed. Redundant tail wording was trimmed from the
httpx.URL, geodjango and uv audit descriptions now that the prefix
names the parent.

The new entry format is documented in CONTRIBUTING.md and the
vocabulary in CONTEXT.md.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Vinta Chen
2026-08-23 01:09:20 +08:00
co-authored by Claude
parent 2fc0edd565
commit 733021ae5b
7 changed files with 32 additions and 12 deletions
+4
View File
@@ -31,6 +31,8 @@ SPONSORSHIP_PATH = "/sponsorship/"
SPONSORSHIP_PUBLIC_URL = f"{SITE_URL}sponsorship/"
SPONSORSHIP_DESCRIPTION = "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use."
BUNDLED_PREFIX_RE = re.compile(r"^\(part of ")
SOURCE_TYPE_DOMAINS = {
"docs.python.org": BUILTIN_FILTER,
"gitlab.com": "GitLab",
@@ -57,6 +59,7 @@ class TemplateEntry(TypedDict):
owner: str | None
last_commit_at: str | None
source_type: str | None
bundled: bool
also_see: list[AlsoSee]
@@ -520,6 +523,7 @@ def extract_entries(
owner=None,
last_commit_at=None,
source_type=detect_source_type(entry["url"]),
bundled=bool(BUNDLED_PREFIX_RE.match(entry["description"])),
also_see=entry["also_see"],
)
seen[key] = existing
+3 -4
View File
@@ -156,10 +156,9 @@
<td class="col-downloads">
{% if entry.downloads is not none %}{{
"{:,}".format(entry.downloads) }}{% elif entry.source_type ==
'Stdlib' %}<span class="source-badge">Stdlib</span>{% else %}<span
class="source-badge"
>Not on PyPI</span
>{% endif %}
'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 %}
</td>
<td class="col-stars">
{% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{%
+3 -4
View File
@@ -193,10 +193,9 @@
<td class="col-downloads">
{% if entry.downloads is not none %}{{
"{:,}".format(entry.downloads) }}{% elif entry.source_type ==
'Stdlib' %}<span class="source-badge">Stdlib</span>{% else %}<span
class="source-badge"
>Not on PyPI</span
>{% endif %}
'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 %}
</td>
<td class="col-stars">
{% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{%
+6
View File
@@ -446,6 +446,7 @@ class TestBuild:
- [My-Lib](https://github.com/org/mylib) - On PyPI.
- [no-pypi](https://example.com/none) - Not on PyPI.
- [asyncio](https://docs.python.org/3/library/asyncio.html) - Stdlib.
- [my-lib.thing](https://github.com/org/mylib) - (part of My-Lib) A bundled feature.
# Contributing
@@ -470,6 +471,10 @@ class TestBuild:
assert "26,305,454" not in html
# Default sort: entries with download counts come first
assert html.index("My-Lib") < html.index("no-pypi")
# Each no-download entry gets the badge matching why it has no count
assert html.count('<span class="source-badge">Stdlib</span>') == 1
assert html.count('<span class="source-badge">Bundled</span>') == 1
assert html.count('<span class="source-badge">Not on PyPI</span>') == 1
def test_build_fails_when_group_and_category_slug_collide(self, tmp_path):
readme = textwrap.dedent("""\
@@ -1020,6 +1025,7 @@ def _template_entry(name: str, stars: int | None, source_type: str | None = None
owner=None,
last_commit_at=None,
source_type=source_type,
bundled=False,
also_see=[],
)