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
@@ -12,6 +12,10 @@ A single listed project: `- [name](url) - Description.` The unit that is admitte
**Sub-item**:
An indented awesome-* also-see link under an Entry (awesome-django under django, awesome-pytest under pytest). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement, and only for awesome-* lists: a companion project either earns a full Entry in its proper Use Case or is not listed.
**Bundled Entry**:
An Entry for a feature that ships inside a larger library, framework, or tool rather than as its own PyPI package (django.db.models, geodjango, httpx.URL, uv audit). Marked in README.md by a `(part of X)` description prefix, which the website reads to render a "Bundled" badge where a download count would go. It holds a normal slot and is audited like any other Entry; only its download signal is absent.
_Avoid_: Pointer entry (reform-era name)
**Thematic Group**:
A bold group line (e.g. "AI & ML", "Web Development") that clusters Sections in the TOC and the Projects body.
+8
View File
@@ -55,6 +55,14 @@ Use the **PyPI package name** as the display name so developers can copy it dire
- [module](https://docs.python.org/3/library/module.html) - (Python standard library) Description.
```
### Bundled Feature
A feature that ships inside a larger library, framework, or tool rather than as its own PyPI package. The `(part of X)` prefix is parsed by the website, which renders a "Bundled" badge in place of a download count, so write it exactly in that form and name the parent as developers say it.
```markdown
- [name](url) - (part of Parent) Description ending with period.
```
### Fork of Another Project
```markdown
+4 -4
View File
@@ -369,7 +369,7 @@ _Libraries for working with HTTP._
- [httpx2](https://github.com/pydantic/httpx2) - HTTP/1.1 and HTTP/2 client with sync and async APIs, maintained by Pydantic ([httpx](https://github.com/encode/httpx) fork).
- URL Manipulation
- [yarl](https://github.com/aio-libs/yarl) - Yet another URL library.
- [httpx.URL](https://www.python-httpx.org/api/) - The immutable URL class bundled with HTTPX.
- [httpx.URL](https://www.python-httpx.org/api/) - (part of HTTPX) The immutable URL class.
### Web Scraping
@@ -399,7 +399,7 @@ _Libraries that implement Object-Relational Mapping or data mapping techniques._
- Relational Databases
- [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) - The Python SQL Toolkit and Object Relational Mapper.
- [awesome-sqlalchemy](https://github.com/dahlia/awesome-sqlalchemy)
- [django.db.models](https://github.com/django/django) - The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/).
- [django.db.models](https://github.com/django/django) - (part of Django) The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/).
- [peewee](https://github.com/coleifer/peewee) - A small, expressive ORM.
- [sqlmodel](https://github.com/fastapi/sqlmodel) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy.
- NoSQL Databases
@@ -531,7 +531,7 @@ _Libraries for geocoding addresses and working with latitudes and longitudes._
- [geopandas](https://github.com/geopandas/geopandas) - Python tools for geographic data (GeoSeries/GeoDataFrame) built on pandas.
- [geopy](https://github.com/geopy/geopy) - Python Geocoding Toolbox.
- [geojson](https://github.com/jazzband/geojson) - Python bindings and utilities for GeoJSON.
- [geodjango](https://github.com/django/django) - A world-class geographic web framework that is part of [Django](https://docs.djangoproject.com/en/dev/ref/contrib/gis/).
- [geodjango](https://github.com/django/django) - (part of Django) A world-class [geographic web framework](https://docs.djangoproject.com/en/dev/ref/contrib/gis/).
### Science
@@ -1118,7 +1118,7 @@ _Frameworks and tools for penetration testing._
_Tools for auditing dependencies against known vulnerabilities._
- [pip-audit](https://github.com/pypa/pip-audit) - Audits Python environments and dependency trees for known vulnerabilities, using the PyPI Advisory Database and OSV.
- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - uv's built-in dependency vulnerability and malware scanning backed by OSV.
- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - (part of uv) Built-in dependency vulnerability and malware scanning backed by OSV.
### Web Security
+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=[],
)