Commit Graph
51 Commits
Author SHA1 Message Date
Vinta ChenandClaude c10264977e feat: serve redirect stubs for renamed and dissolved category slugs
Renamed or dissolved category slugs (e.g. /categories/web-servers/rpc/, /categories/code-analysis/code-linters/) returned 404, Search Console lists 7 of them, and each audit re-home was dropping the old URL's ranking.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-26 18:45:53 +08:00
Vinta ChenandClaude 0f677911f0 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>
2026-08-23 01:28:33 +08:00
Vinta ChenandClaude 733021ae5b 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>
2026-08-23 01:09:20 +08:00
Vinta ChenandClaude 2fc0edd565 feat: rename Built-in source type to Stdlib and show it in downloads column
Standard-library entries rendered "Not on PyPI" in the PyPI Downloads
column, which read like missing data rather than a deliberate category
— the build already forces downloads to None for them so they never
pick up a same-named PyPI backport. They now render a "Stdlib" badge
instead, while genuine non-PyPI entries keep "Not on PyPI".

The filter tag is renamed to match, so the source-type value, the row
filter tag, and the synthetic category heading all read Stdlib now.
The literal "Built-in" strings scattered through build.py are routed
through the existing BUILTIN_FILTER constant so the label lives in one
place. The category page slug stays "built-in" so the public URL
/categories/built-in/ does not break.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 01:01:17 +08:00
Vinta Chen ee8089d0d5 add build date to llms.txt 2026-08-16 20:16:05 +08:00
Vinta ChenandClaude 30c9f18bc2 feat: annotate llms.txt entries with PyPI download counts
Entries now end with "(PyPI downloads/month: N, GitHub stars: M)" where
known, replacing the stars-only note, since download counts are the
list's stated primary evidence signal. annotate_entries_with_stars is
renamed to annotate_entries_with_stats and looks downloads up by the
first link's display name, skipping category-index bullets (which link
into the site itself) and Built-in entries (which would otherwise hit
same-named PyPI backports like logging or asyncio).

The intro now mirrors the README subtitle verbatim with the
project/category totals on their own line below, and the "opinionated
catalog" wording is gone since the shortlist ADR is literally titled
"shortlist, not a catalog".

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-16 19:43:52 +08:00
Vinta ChenandClaude d3fec1f7fa feat: default-sort the website table by downloads per month
Entries with a download count now sort first (descending), with
stars, then Built-in, then name as fallback tiers for entries that
lack a count. main.js mirrors this in its default activeSort, clean
URL check, and third-click reset target. Sorting by stars remains one
header click away.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-16 19:15:21 +08:00
Vinta ChenandClaude cc804b1de2 feat: add Downloads/Month column to website table
Sourced from website/data/pypi_downloads.tsv the same way
github_stars.json feeds the stars column. The new sortable column
sits between GitHub Stars and Last Commit on the homepage and
category pages, formatted with thousands separators like stars, with
an em dash when no PyPI data exists. Rows are matched by normalized
README display name; Built-in entries never show counts since
same-named PyPI packages are stdlib backports (e.g. the asyncio
package).

Below 960px the column hides and the count moves into the expand
row, mirroring the existing Last Commit treatment. main.js gains the
downloads sort branch and URL param.

The deploy workflow fetches the TSV via the new
make fetch_pypi_downloads target with a daily actions/cache
fallback, mirroring the stars fetch, but non-fatal: the column
degrades to dashes when the fetch fails, unlike stars which the
build requires.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-16 17:52:04 +08:00
Vinta Chen c0b80fd75f fix: surface sponsorship link in header
Deploy Website / deploy (push) Has been cancelled
CI / test (push) Has been cancelled
2026-06-07 03:22:28 +08:00
Vinta Chen 32ae78fd96 fix: improve website SEO metadata 2026-06-07 03:08:43 +08:00
Vinta Chen 10c06fb26d add category links in llms.txt 2026-05-07 20:00:56 +08:00
Vinta Chen 6c18b6447e feat: use explicit Projects section in README
Deploy Website / deploy (push) Has been cancelled
CI / test (push) Has been cancelled
2026-05-04 21:24:57 +08:00
Vinta Chen 921d47b455 remove index.md 2026-05-04 17:11:34 +08:00
Vinta Chen 3510db9df9 update llms.txt 2026-05-04 17:05:05 +08:00
Vinta Chen 509ebaff7a use file modification time as lastmod in sitemap 2026-05-04 16:24:52 +08:00
Vinta ChenandClaude 28b61a9212 style(seo): switch category page title separator from pipe to hyphen
Google truncates pipe separators and treats hyphens as cleaner word
boundaries in SERP titles.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 20:03:29 +08:00
Vinta ChenandClaude c886e470b6 feat(website): lead category meta description with real description when present, count first as fallback
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 19:57:31 +08:00
Vinta ChenandClaude 2f398acefb fix(seo): align JSON-LD with Yoast/RankMath conventions
- Wrap category pages in a self-contained @graph (WebSite + CollectionPage)
- Set canonical @id on CollectionPage to its URL (no hash fragment)
- Expand isPartOf to typed object {"@type": "WebSite", "@id": ...}
- Extract _website_node() and ISPARTOF_WEBSITE constants to avoid repetition
- Update tests to assert @graph structure on category pages

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 19:31:14 +08:00
Vinta ChenandClaude 86d2aa7e01 feat(website): add CollectionPage JSON-LD to category, group, and subcategory pages
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 19:23:14 +08:00
Vinta ChenandClaude b2910d59c8 feat(website): add homepage JSON-LD with WebSite, CollectionPage, ItemList for SEO/AEO
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 19:18:15 +08:00
Vinta ChenandClaude d3f35a9d21 test(website): remove redundant and brittle tests
Drops tests that either duplicate coverage already provided by adjacent
cases (single-word slugify, trailing-slash checks) or hard-code first-
category names and specific description strings that break whenever the
README content shifts.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 12:19:32 +08:00
Vinta Chen a068219684 fix(website): type build template entries 2026-05-03 12:08:41 +08:00
Vinta ChenandClaude c68b985d7c feat(website): add /sponsorship/ landing page
Adds a dedicated sponsorship page at /sponsorship/ built from the Jinja2
template, with hero stats, tier cards, and CSS. Updates the index.html
sponsor sidebar link to point to /sponsorship/ instead of the GitHub
SPONSORSHIP.md. Adds the URL to the sitemap and test fixtures.

Also renames .impeccable.md to DESIGN.md.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 09:35:39 +08:00
Vinta ChenandClaude d64b47b910 feat(website): mirror index layout on category pages
Add search input, filter chips, no-results block, and back-to-top
button to category/group/subcategory pages. Pass filter_urls_json to
all page types so tag-chip navigation works site-wide. Fix JS so
filter-clear and no-results-clear redirect to / on non-index pages
instead of trying to filter a non-existent local table. Remove the
now-redundant .category-results CSS overrides.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 08:26:37 +08:00
Vinta ChenandClaude Opus 4.7 04a04a136b feat(website): add data-url to tag buttons for client-side routing
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:44:43 +08:00
Vinta ChenandClaude Opus 4.7 704332271b fix(website): escape </script> in embedded filter URLs JSON
`| safe` bypasses Jinja autoescape. If a category name ever contained
"</script>", the literal substring would close the script block early,
leaking JSON content into the DOM and creating an XSS vector. Replace
"</" with "<\\/" (still valid JSON) and pass ensure_ascii=False so
non-ASCII names render readably. Also add a group_path() helper to
parallel category_path()/subcategory_path() and reuse category_urls
when seeding filter_urls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:40:52 +08:00
Vinta ChenandClaude e0e7fc9168 feat(website): embed filter-to-url map in index for client routing
Adds filter_urls dict (categories, groups, subcategories) in build.py,
passes filter_urls_json to the template, and injects a JSON script block
before the results section in index.html. Covered by a new test that
verifies all three URL types are present and correctly resolved.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:36:41 +08:00
Vinta ChenandClaude Opus 4.7 e320ba7278 test(website): restore exact sitemap URL list and lastmod count check
Membership-only assertions wouldn't catch phantom URLs added by future
build changes. Tighten back to an exact-list assertion now that we know
the fixture's exact output, and assert lastmod count tracks loc count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:35:11 +08:00
Vinta ChenandClaude fe7fd35e18 feat(website): include group and subcategory URLs in sitemap
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:32:21 +08:00
Vinta ChenandClaude Opus 4.7 20df47e1e9 style(website): add CSS for category-breadcrumb and assert absence on parent
Mirrors the .category-subtitle a underline style for visual cohesion in
the hero, and locks in the gating behavior with a negative assertion so
a regression that drops the page_kind guard would be caught.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:30:56 +08:00
Vinta ChenandClaude 03702231af feat(website): show parent category breadcrumb on subcategory pages
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:27:22 +08:00
Vinta ChenandClaude eeecacc3bd feat(website): generate static pages for subcategories
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:23:14 +08:00
Vinta ChenandClaude 532d93d436 feat(website): generate static pages for groups under /categories/
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:19:14 +08:00
Vinta ChenandClaude Opus 4.7 cee1e65fb3 test(website): hoist pytest import to module level
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:17:53 +08:00
Vinta ChenandClaude Opus 4.7 583d5e7c51 feat(website): assert unique slugs across categories and groups
Categories and groups will share the /categories/ URL namespace.
Fail the build with a clear error message if a future README change
introduces a collision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:15:25 +08:00
Vinta ChenandClaude 39d4b3db4b feat(website): add subcategory_path and subcategory_public_url helpers
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:08:04 +08:00
Vinta ChenandClaude 4005c2ea82 feat(website): add slug and url to subcategory entries
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 00:05:02 +08:00
Vinta Chen 7fadbaf6fe feat(website): add homepage category directory 2026-05-02 23:44:27 +08:00
Vinta Chen b00395a301 add missing links of category descriptions 2026-05-02 23:35:24 +08:00
Vinta Chen e11afd1730 feat(website): generate static category pages 2026-05-02 23:31:08 +08:00
Vinta ChenandClaude 429c9b3d12 feat: generate llms.txt from template and annotate entries with star counts
Deploy Website / deploy (push) Has been cancelled
CI / test (push) Has been cancelled
- Add llms.txt Jinja2 template with a categories_md placeholder
- Extract categories body from README and inject it into the template
- Annotate bullet-entry lines with GitHub star counts (N GitHub stars)
  for the main index.md and bare numbers for llms.txt
- Add TestAnnotateEntriesWithStars unit tests

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-02 02:32:18 +08:00
Vinta ChenandClaude d9f26a8635 Improve SEO/AEO discovery surface for awesome-python.com (#3103)
* update gitignore

* feat: tighten homepage metadata

* fix: trim generated HTML whitespace

* feat(website): add discovery files and markdown alternate

* feat(website): add sitemap lastmod

* feat(seo): add Content-Signal directive to robots.txt

Signals search, ai-input, and ai-train to crawlers
via the experimental Content-Signal header in robots.txt.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-02 01:53:19 +08:00
Vinta ChenandClaude 39b65bc994 refactor(build): inline format_stars_short into its call site
The helper only appeared once and the logic is two lines, so the named
function added indirection without clarity. Removed the four dedicated
unit tests that covered the function directly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-19 22:00:45 +08:00
Vinta ChenandClaude c85f81bb24 refactor(build): accept Path directly in build() signature
Remove internal str->Path conversion; callers and tests now pass
Path objects directly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-19 21:56:06 +08:00
Vinta ChenandClaude e71f38ef4e test: add coverage for detect_source_type, format_stars_short, extract_entries, and last_commit_at parsing
Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-23 02:25:44 +08:00
Vinta ChenandClaude 25a3f4d903 refactor(parser): remove resources parsing, preview, and content_html fields
parse_readme now returns list[ParsedGroup] instead of a tuple. The
resources section (Newsletters, Podcasts), preview string, and
content_html rendering are no longer produced by the parser or consumed
by the build. Removes _render_section_html, _group_by_h2, and the
associated dead code and tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-23 01:43:19 +08:00
Vinta ChenandClaude df2191fc05 refactor(build): remove unused group_categories wrapper
group_categories only ever appended a Resources group when the
resources list was non-empty. All call sites passed an empty list,
making it a no-op indirection. Inline parsed_groups directly and
remove the dead code along with its tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-22 15:58:42 +08:00
Vinta ChenandClaude 4322026817 refactor: parse thematic groups from README bold markers instead of hardcoding them
The website builder previously relied on a hardcoded SECTION_GROUPS list in
build.py to organize categories into thematic groups. This was fragile: any
rename or addition to README.md required a matching code change.

Replace this with a parser-driven approach:
- readme_parser.py now detects bold-only paragraphs (**Group Name**) as
  group boundary markers and groups H2 categories beneath them into
  ParsedGroup structs.
- build.py drops SECTION_GROUPS entirely; group_categories() now just
  passes parsed groups through and appends the Resources group.
- sort.py is removed as it relied on the old flat section model.
- Tests updated throughout to reflect the new (groups, resources) return
  shape and to cover the new grouping logic.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-20 18:43:09 +08:00
Vinta ChenandClaude 280f250ce0 feat: migrate README parser to markdown-it-py and refresh website
Switch readme_parser.py from regex-based parsing to markdown-it-py for
more robust and maintainable Markdown AST traversal. Update build pipeline,
templates, styles, and JS to support the new parser output. Refresh GitHub
stars data and update tests to match new parser behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-18 20:33:36 +08:00
Vinta ChenandClaude 0f374970dd refactor: extract parsing logic from build.py into readme_parser module
slugify, parse_readme, count_entries, extract_preview, render_content_html,
and related helpers are moved to a dedicated readme_parser module.
build.py now imports from readme_parser rather than defining these inline.
Tests for the removed functions are dropped from test_build.py since they
now live with the module they test.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-18 17:27:14 +08:00