Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
168 commits
Select commit Hold shift + click to select a range
63146cc
docs: capture shortlist-reform decisions on shortlist-reform branch
vinta Aug 15, 2026
bd657b3
docs: fix stale cap wording in shortlist ADR
vinta Aug 15, 2026
d4d518f
docs: fix stale claims in shortlist-not-catalog ADR
vinta Aug 15, 2026
9218024
docs: fold serves-Python-developers scope test into ADR-0001
vinta Aug 15, 2026
c413b8f
docs: fix stale and inaccurate definitions in CONTEXT.md
vinta Aug 15, 2026
663fad0
docs: mark Challengers by ordering, not description text
vinta Aug 15, 2026
553bf88
feat: replace acceptance lanes with shortlist admission rule
vinta Aug 15, 2026
3ca241d
clean up
vinta Aug 15, 2026
e10ff58
prune: sweep AI and Agents to the shortlist cap
vinta Aug 15, 2026
e4e70cd
feat: add claude-agent-sdk to Vendor Agent SDKs
vinta Aug 15, 2026
6cd5ffc
prune: sweep File Format Processing to the shortlist cap
vinta Aug 15, 2026
5488d08
prune: sweep Data Visualization to the shortlist cap
vinta Aug 15, 2026
5200ffa
prune: sweep Testing to the shortlist cap
vinta Aug 15, 2026
b0cdf40
feat: add verdict-preview skill for maintainer review pages
vinta Aug 15, 2026
fc88ebb
feat: add BigQuery-based PyPI downloads fetcher
vinta Aug 15, 2026
bb53dfe
docs: require live verification of keep/drop reasons
vinta Aug 15, 2026
3ae3483
docs: reinstate crewai in AI and Agents orchestration
vinta Aug 15, 2026
9290f18
feat: reinstate bokeh as Plotting challenger
vinta Aug 15, 2026
1463ba8
prune: sweep Data Analysis to the shortlist cap
vinta Aug 15, 2026
54c0c0c
prune: sweep Data Ingestion / ETL to the shortlist cap
vinta Aug 15, 2026
9103f59
prune: sweep Data Validation to the shortlist cap
vinta Aug 15, 2026
9631207
prune: sweep Geolocation to the shortlist cap
vinta Aug 15, 2026
0c1409d
prune: sweep Science to the shortlist cap
vinta Aug 15, 2026
c9abd51
style: apply challenger ordering and entry format to Quantum Computing
vinta Aug 15, 2026
846a6bd
docs: order entries by downloads/month within tiers, not alphabetically
vinta Aug 15, 2026
46b7b57
style: reorder swept sections by downloads/month within tiers
vinta Aug 15, 2026
59cc42e
docs: sync AGENTS.md with the shortlist reform rules
vinta Aug 15, 2026
83f6ceb
feat: add audit-the-list skill for recurring section audits
vinta Aug 15, 2026
7f0e923
refactor: rename verdict-preview skill to preview-verdicts
vinta Aug 15, 2026
a1ed550
fix: filter BigQuery PyPI download query on project, not file.project
vinta Aug 15, 2026
fd79813
feat: switch default PyPI download source to ClickPy
vinta Aug 15, 2026
fcac019
feat: add pepy.tech spot-check helper for download audits
vinta Aug 15, 2026
f39d00d
audit: sweep Web Frameworks to the shortlist cap
vinta Aug 15, 2026
b26c30d
audit: sweep Web APIs to the shortlist cap
vinta Aug 15, 2026
fb35807
audit: sweep Web Servers to the shortlist cap
vinta Aug 15, 2026
69e03b7
audit: sweep WebSocket to the shortlist cap
vinta Aug 15, 2026
6b9a1cc
audit: sweep Admin Panels to the shortlist cap
vinta Aug 15, 2026
4c57da8
audit: sweep CMS to the shortlist cap
vinta Aug 15, 2026
e18d521
audit: sweep Static Site Generators to the shortlist cap
vinta Aug 15, 2026
e7e17a8
style: reorder Authentication and Web Asset Management by downloads
vinta Aug 15, 2026
7447549
docs: define Sub-item in CONTEXT.md
vinta Aug 15, 2026
5297061
refactor: rename download-fetch scripts to source-suffixed scheme
vinta Aug 15, 2026
5ece46f
docs: cover awesome-* links in the Sub-item definition
vinta Aug 15, 2026
1abfb1a
refactor: split download fetchers into writer and cross-check scripts
vinta Aug 15, 2026
c2ba7d4
audit: sweep HTTP Clients to the shortlist cap
vinta Aug 15, 2026
fdf66eb
audit: sweep Web Scraping to the shortlist cap
vinta Aug 15, 2026
d2d344f
audit: sweep Email to the shortlist cap
vinta Aug 15, 2026
9fc4f00
style: rename HTTP Clients' General subcategory to Clients
vinta Aug 15, 2026
41f89f7
audit: sweep ORM to the shortlist cap
vinta Aug 15, 2026
b8dd653
style: restructure Database Drivers section
vinta Aug 15, 2026
0cc4e0e
feat: add asyncpg to Database Drivers > PostgreSQL
vinta Aug 15, 2026
774da72
feat: add clickhouse-connect to Database Drivers > ClickHouse
vinta Aug 15, 2026
9b28c56
feat: add pyodbc to Other Relational Databases
vinta Aug 15, 2026
442291d
feat: add oracledb to Database Drivers > Other Relational Databases
vinta Aug 15, 2026
3a3b20c
audit: sweep Database to the shortlist cap
vinta Aug 15, 2026
b7b633d
feat: add lancedb to Database & Storage > Vector
vinta Aug 15, 2026
02735b6
style: reorder Caching by downloads, rename python-diskcache to diskc…
vinta Aug 15, 2026
7ac2950
audit: sweep Search to the shortlist cap
vinta Aug 15, 2026
f953956
feat: add opensearch-py to Search as obvious choice
vinta Aug 15, 2026
509fcaa
feat: add meilisearch to Search as a challenger
vinta Aug 15, 2026
745347d
style: reorder Serialization by downloads
vinta Aug 15, 2026
5da1d16
docs: define Second Tier in CONTEXT.md
vinta Aug 15, 2026
bc3e7a0
docs: condense AGENTS.md and CLAUDE.md for progressive disclosure
vinta Aug 15, 2026
6c93b1e
docs: update audit-the-list SKILL.md wording
vinta Aug 15, 2026
c6957d4
docs: require WebSearch evidence for tier judgments
vinta Aug 15, 2026
36c6179
update wordings
vinta Aug 15, 2026
a2da210
feat: stamp pypi downloads cache with fetched_at and header row
vinta Aug 15, 2026
8814a85
feat: store verdict preview pages in repo-local ./tmp
vinta Aug 15, 2026
3e90482
audit: sweep Code Analysis to the shortlist cap
vinta Aug 16, 2026
5e3d778
feat: add pyright to Type Checkers
vinta Aug 16, 2026
b496053
audit: sweep Debugging Tools to the shortlist cap
vinta Aug 16, 2026
3a65a02
feat: add memray to Profiler
vinta Aug 16, 2026
d316a8e
feat: add pyinstrument to Profiler
vinta Aug 16, 2026
8125688
audit: sweep Build Tools to the shortlist cap
vinta Aug 16, 2026
71eee5d
feat: add ipython to Interactive Interpreter
vinta Aug 16, 2026
2d0f5da
feat: add mkdocs-material as sub-item under mkdocs
vinta Aug 16, 2026
166c9c9
style: reorder Algorithms and Design Patterns and Documentation by do…
vinta Aug 16, 2026
fb7383c
docs: point Build Tools readers to Package Management
vinta Aug 16, 2026
a610fce
audit: sweep DevOps Tools to the shortlist cap
vinta Aug 16, 2026
b06104c
feat: add azure-sdk-for-python to Cloud Providers
vinta Aug 16, 2026
77e7b62
feat: add google-cloud-python to Cloud Providers
vinta Aug 16, 2026
4fc3222
audit: sweep Distributed Computing to the shortlist cap
vinta Aug 16, 2026
83c723d
style: reorder Task Queues by tier and downloads
vinta Aug 16, 2026
e5ce98c
feat: add confluent-kafka to Messaging
vinta Aug 16, 2026
28b6d13
feat: add pika to Messaging
vinta Aug 16, 2026
7fd4957
feat: add paho-mqtt to Messaging
vinta Aug 16, 2026
03d9139
audit: sweep Job Schedulers to the shortlist cap
vinta Aug 16, 2026
635a735
audit: sweep Logging to the shortlist cap
vinta Aug 16, 2026
b8cacb5
audit: sweep Network Virtualization to the shortlist cap
vinta Aug 16, 2026
3290ce5
style: merge Security Linters into Linters and Formatters
vinta Aug 16, 2026
35c6898
audit: sweep CLI Development, mint TUI Frameworks, drop cement
vinta Aug 16, 2026
1a8e14c
audit: sweep CLI Tools, dissolve Productivity Tools, drop thefuck, ka…
vinta Aug 16, 2026
9bbe1cf
audit: sweep GUI Development, split Desktop into Qt and Tkinter, drop…
vinta Aug 16, 2026
41a1b4b
audit: sweep Image Processing, split into use cases, drop pymatting
vinta Aug 16, 2026
9970486
feat: add rembg to Image Processing > General
vinta Aug 16, 2026
8f9c388
audit: sweep Audio & Video Processing, re-home TTS to Speech, drop bi…
vinta Aug 16, 2026
3f391b3
audit: sweep Game Development, split into use cases, drop py-sdl2, py…
vinta Aug 16, 2026
8a3771e
feat: add pygame-ce to Game Development > Game Frameworks
vinta Aug 16, 2026
9fa72c1
audit: sweep Implementations, drop ironpython
vinta Aug 16, 2026
6af6158
audit: sweep Functional Programming, drop coconut
vinta Aug 16, 2026
19875cf
audit: sweep Asynchronous Programming, split into Async I/O and Paral…
vinta Aug 16, 2026
16077fc
style: re-tier Built-in Classes Enhancement and Date and Time
vinta Aug 16, 2026
c00094a
feat: add whenever to Date and Time
vinta Aug 16, 2026
15c4064
docs: amend Entry Ordering so standard-library modules sort first in …
vinta Aug 16, 2026
05a9dd4
style: move standard-library entries to the top of their tiers
vinta Aug 16, 2026
5f6f1f0
docs: clarify that standard-library modules lead the use case outright
vinta Aug 16, 2026
a4eba37
audit: sweep Environment Management, drop KillPy, pyenv-win becomes a…
vinta Aug 16, 2026
1b51907
audit: sweep Package Management, mint Package Managers subcategory, c…
vinta Aug 16, 2026
1a36f77
feat: add setuptools to Package Management > Build Backends
vinta Aug 16, 2026
0ec6f49
feat: add hatchling to Package Management > Build Backends
vinta Aug 16, 2026
947b939
feat: add uv-build to Package Management > Build Backends
vinta Aug 16, 2026
9f9bd8d
style: split Distribution into Executables and Obfuscation
vinta Aug 16, 2026
5a8c062
audit: sweep Configuration Files, drop python-decouple
vinta Aug 16, 2026
ee0caca
feat: add pydantic-settings to Configuration Files
vinta Aug 16, 2026
cf38a34
refactor: re-home pre-commit to Code Analysis > Git Hooks
vinta Aug 16, 2026
283f142
style: fix display-name casing to canonical PyPI names
vinta Aug 16, 2026
438881a
style: audit Cryptography and Penetration Testing — renames and re-tiers
vinta Aug 16, 2026
fe7006e
feat: add pip-audit, minting the Supply Chain Security section
vinta Aug 16, 2026
913b380
feat: add uv audit to Supply Chain Security
vinta Aug 16, 2026
16e17d6
style: drop the preview label from uv audit's description
vinta Aug 16, 2026
0194cbb
audit: sweep Hardware, drop synology-api
vinta Aug 16, 2026
f8e594a
audit: sweep Miscellaneous, drop tryton, re-home itsdangerous to Cryp…
vinta Aug 16, 2026
a265963
feat: add odoo, minting the ERP section
vinta Aug 16, 2026
660d746
style: move the ERP section to the Web Development group
vinta Aug 16, 2026
34c550b
style: split Deep Learning into Frameworks and Reinforcement Learning
vinta Aug 16, 2026
b95ce7e
feat: add gymnasium to Deep Learning > Reinforcement Learning
vinta Aug 16, 2026
7d1c5c8
audit: sweep Machine Learning, three-way split, drop h2o, mindsdb, sc…
vinta Aug 16, 2026
c83b98c
audit: sweep Natural Language Processing, drop funnlp
vinta Aug 16, 2026
67c9f7a
style: split Computer Vision into General and OCR
vinta Aug 16, 2026
da4383a
audit: sweep Text Processing, dissolve General, drop textdistance, na…
vinta Aug 16, 2026
043cb6d
feat: add charset-normalizer to Text Processing > Encoding and Unicode
vinta Aug 16, 2026
db9c262
feat: add rapidfuzz, minting Text Processing > Fuzzy Matching
vinta Aug 16, 2026
ad698c2
audit: sweep HTML Manipulation, drop html-to-markdown, pyquery, tinycss2
vinta Aug 16, 2026
15cd51c
style: re-tier File Manipulation
vinta Aug 16, 2026
5249b75
refactor: re-home fasthtml to Web Frameworks > Asynchronous
vinta Aug 16, 2026
e5f7b5b
style: update graphify link to the moved Graphify-Labs repo
vinta Aug 16, 2026
441f206
feat: add pathway to Data Ingestion / ETL
vinta Aug 16, 2026
f60d5b4
style: move fasthtml back to Web Frameworks > Synchronous
vinta Aug 16, 2026
194b386
docs: codify the Override rule in CONTRIBUTING and CONTEXT
vinta Aug 16, 2026
39eeab7
docs: warn about wrong-package PyPI download counts
vinta Aug 16, 2026
c034c88
docs: add audit log registry for maintainer overrides
vinta Aug 16, 2026
3e43fa1
docs: clarify cross-section re-homes and resources scope in audit rules
vinta Aug 16, 2026
deae0e0
docs: broaden Obvious Choice signal and note Cap has no floor
vinta Aug 16, 2026
2c50d9e
docs: document gitignored output and orphan-key behavior
vinta Aug 16, 2026
2c2ce2f
docs: document empirically verified README parsing behavior
vinta Aug 16, 2026
cfb60a6
docs: codify dual-listing policy in CONTRIBUTING.md
vinta Aug 16, 2026
cccfa18
docs: rewrite PR template to match shortlist rules
vinta Aug 16, 2026
68b15a4
docs: rephrase obvious-choice criterion in PR template
vinta Aug 16, 2026
08fdfa6
update readme
vinta Aug 16, 2026
0e9ae0c
clean up
vinta Aug 16, 2026
f13075b
refactor: re-home aws-sdk-pandas from pandas sub-item to ETL General
vinta Aug 16, 2026
63a1ead
docs: remove typeshed sub-item from under mypy
vinta Aug 16, 2026
b0434ab
refactor: promote mkdocs-material to full Documentation entry
vinta Aug 16, 2026
49b229b
refactor: promote flower from celery sub-item to full DevOps Monitori…
vinta Aug 16, 2026
ecd9dea
refactor: move pyenv-win from pyenv sub-item to full entry
vinta Aug 16, 2026
c39e4d6
docs: restrict sub-items to awesome-* also-see links
vinta Aug 16, 2026
24d6c28
build: upgrade project to Python 3.14
vinta Aug 16, 2026
0d13cd2
build: use >= floors instead of == pins for dependency groups
vinta Aug 16, 2026
e9329d4
fix: correct hydra-core repo URL to facebookresearch/hydra
vinta Aug 16, 2026
b440e64
fix: resolve PyPI download counts through curated package overrides
vinta Aug 16, 2026
cc804b1
feat: add Downloads/Month column to website table
vinta Aug 16, 2026
f3ff733
fix: add three more PyPI name remaps for NOT_FOUND triage
vinta Aug 16, 2026
fcfc65d
data: record all remaining NOT_FOUND names as explicit null overrides
vinta Aug 16, 2026
b841db6
refactor: make pypi_name_overrides.json entries self-documenting
vinta Aug 16, 2026
d3fec1f
feat: default-sort the website table by downloads per month
vinta Aug 16, 2026
da491ff
docs: rename Downloads/Month column header to PyPI Downloads
vinta Aug 16, 2026
f5f7a1c
refactor: swap PyPI Downloads and GitHub Stars column order
vinta Aug 16, 2026
bfdd087
feat: show "Not on PyPI" badge for missing download counts
vinta Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .claude/skills/audit-the-list/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: audit-the-list
description: Audit README.md sections against the shortlist rules — re-verify every entry's verdict with live data, promote or demote challengers, restructure oversized use cases, prune, and evaluate proposed additions. Fire when the maintainer asks to audit, sweep, prune, re-check, or maintain sections, or asks whether an entry (or a proposed one) deserves its slot.
argument-hint: [all or specific sections]
---

# Audit the list

An Audit re-runs the admission test over live sections of README.md: every entry re-verified against current data, tiers reassessed, structure reconsidered, then the maintainer adjudicates and the result is committed. The rules live in CONTRIBUTING.md (Admission, Evidence, Entry Ordering) and CONTEXT.md (vocabulary) — this skill is the process that applies them, not a second copy of them. Entry changes land only on the maintainer's explicit go.

## 1. Scope

Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — `AskUserQuestion` whether it means every section or only never-audited ones; prior audits are recorded in git history (`git log --oneline --grep="sweep\|audit"`). With no arguments, AskUserQuestion listing the never-audited Thematic Groups. Batch the work one Thematic Group per sitting. Done when the section list is settled.

## 2. Evidence

Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule):

- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run; header row, every row stamped with its `fetched_at` date). A cache whose `fetched_at` is within the last 7 days is current enough for verdicts — skip the sweep; older than that, re-run it (costs ~1s). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py <name> ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py <name> ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison.
- **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects.
- **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. Wrong-package cache rows are common: the sweep looks up README display names, so when the display name differs from the canonical package, the row silently measures an unrelated squatter or a dead predecessor. When a famous entry shows a count that looks off (too small, or absurdly round), verify identity before citing it, and fetch the canonical package's count via pepy.

Done when every entry in scope has downloads (or a stated no-signal reason), repo state, and a confirmed PyPI name.

## 3. Verdicts

Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed.

## 4. Review and go

Run the `preview-verdicts` skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go.

## 5. Execute

One commit per section: body lists each removal with its reason and downloads figure; restructures, tier moves, and reorders ride the same commit. Format-only outcomes (no removals) are a single style commit. `UV_PYTHON=3.13 make test` before every commit, `UV_PYTHON=3.13 make build` after the last one. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes.

## 6. Record

A conclusion that outlives the sitting goes into the repo before the audit ends: admission or evidence rules into CONTRIBUTING.md, repo process and environment facts into CLAUDE.md and AGENTS.md (kept in sync), vocabulary into CONTEXT.md. Done when nothing the next audit needs is stranded in conversation.
24 changes: 24 additions & 0 deletions .claude/skills/preview-verdicts/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: preview-verdicts
description: Generate the interactive keep/drop verdict preview (HTML page with per-row feedback controls) whenever a prune sweep, batch entry edit, or restructure needs maintainer review before touching README.md — and process the feedback JSON the maintainer pastes back.
---

# Verdict preview

Maintainer review happens through an interactive HTML page: one row per entry with your seeded verdict and reason, a Keep/Drop toggle and a reason field for the maintainer, and a **Copy feedback** button that exports only changed or commented rows as JSON. Generate the page, wait for the pasted JSON, then apply it. Entry changes land in README.md only after the review — and only on an explicit go.

## Generate the preview

1. Build the `DATA` array. A group is `[section, subcategory, rows]`; a row is `[entry, url, downloads, verdict, reason]`.
- `subcategory` may carry a note after ` — ` (rendered muted): use it for proposed splits, re-homes, or anything the maintainer should weigh for the whole group.
- `downloads` is PyPI last-month as a comma-formatted string; use `—` when no signal exists (e.g. agent skill packs), `stdlib` for standard-library modules, `fetch failed` when the lookup failed. State the fetch date in the sub-header.
- `verdict` is `keep` or `drop`, seeded from the current adjudication or dry-run.
- `reason` is plain language the maintainer reads cold — no invented shorthand. When fresh evidence contradicts the seeded verdict (a big download count on a drop, a dead repo on a keep), say so in that row's reason instead of silently changing the seed.
2. Copy `template.html` (sibling of this file) and replace the placeholders: `__TITLE__` (page title), `__SUB__` (sub-header: scope, seed provenance, fetch date, and the standing instruction to flip/comment then Copy feedback), `__KEY__` (localStorage key), `__DATA__` (the array). `__KEY__` must be unique per review — slug plus date, e.g. `awesome-python-science-2026-09-01` — because saved state under a reused key bleeds a previous review's flips into rows with the same section and entry name.
3. Write the page to `tmp/awesome-python-<slug>-preview.html` in the repo root (git-ignored; create the directory if needed), `open` it, and tell the maintainer the path and the return path: flip or comment rows (they highlight yellow), press **Copy feedback**, paste the JSON into the chat. Done when the page is open and the return path is stated.

## Process the pasted feedback

Each JSON row is `{section, subcategory, entry, my_verdict, your_verdict, reason}`. The maintainer's verdict is final — apply it, never re-argue it. An empty reason means the verdict stands unexplained; that is enough.

Before executing, surface anything the flips imply that the maintainer has not decided: a use case pushed past its cap, an entry left homeless by a proposed split, a request that is already satisfied (a no-op). Ask, then execute on their go. Done when every pasted row is either applied or surfaced back — none silently dropped.
160 changes: 160 additions & 0 deletions .claude/skills/preview-verdicts/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>__TITLE__</title>
<style>
:root {
--ink: #1c1917; --paper: #faf9f7; --muted: #78716c; --line: #e7e5e4;
--keep: #1d4ed8; --keep-bg: #eff4ff; --drop: #b42318; --drop-bg: #fdf1ef;
--changed: #fffbeb; --changed-line: #f5c518;
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--paper); color: var(--ink); font: 16px/1.5 -apple-system, "SF Pro Text", "Helvetica Neue", Arial, sans-serif; }
.wrap { padding: 2rem 2.5rem 6rem; }
h1 { font-size: 1.7rem; margin: 0 0 .3rem; }
.sub { color: var(--muted); margin: 0 0 1.6rem; font-size: 1rem; }
.sub b { color: var(--ink); }
.sub code { background: #f0eeec; padding: .1em .35em; border-radius: 4px; font-size: .92em; }
h2 { font-size: 1.35rem; margin: 2.2rem 0 .2rem; border-bottom: 2px solid var(--ink); padding-bottom: .3rem; }
h3 { font-size: 1.05rem; margin: 1.3rem 0 .4rem; color: var(--ink); }
h3 .note { font-weight: 400; color: var(--muted); font-size: .92rem; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; font-size: .82rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: .35rem .6rem; border-bottom: 1px solid var(--line); }
td { padding: .5rem .6rem; border-bottom: 1px solid var(--line); vertical-align: top; }
tr.changed td { background: var(--changed); border-bottom-color: var(--changed-line); }
td.name { white-space: nowrap; font-weight: 600; }
td.name a { color: var(--ink); text-decoration: none; border-bottom: 1px solid #c8c4c0; }
td.name a:hover { border-bottom-color: var(--ink); }
td.dl { white-space: nowrap; font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
td.mine { white-space: nowrap; }
.badge { display: inline-block; font-size: .78rem; font-weight: 700; letter-spacing: .03em; padding: .12em .5em; border-radius: 4px; }
.badge.keep { color: var(--keep); background: var(--keep-bg); }
.badge.drop { color: var(--drop); background: var(--drop-bg); }
td.reason { color: #44403c; font-size: .95rem; max-width: 34rem; }
td.yours { white-space: nowrap; }
.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.seg button { border: 0; background: #fff; padding: .3rem .8rem; font: inherit; font-size: .88rem; cursor: pointer; color: var(--muted); }
.seg button + button { border-left: 1px solid var(--line); }
.seg button.on.k { background: var(--keep); color: #fff; font-weight: 700; }
.seg button.on.d { background: var(--drop); color: #fff; font-weight: 700; }
td.fb input { width: 100%; min-width: 14rem; font: inherit; font-size: .92rem; padding: .3rem .5rem; border: 1px solid var(--line); border-radius: 6px; background: #fff; }
td.fb input:focus { outline: 2px solid var(--keep); outline-offset: 0; }
.bar { position: fixed; bottom: 0; left: 0; right: 0; background: var(--ink); color: #fafaf9; display: flex; align-items: center; gap: 1.4rem; padding: .7rem 2.5rem; font-size: .95rem; }
.bar .counts b { font-variant-numeric: tabular-nums; }
.bar button { font: inherit; font-weight: 700; padding: .45rem 1.1rem; border: 0; border-radius: 6px; background: #fafaf9; color: var(--ink); cursor: pointer; }
.bar button:hover { background: #e7e5e4; }
.bar .hint { color: #a8a29e; }
#out { display: none; width: 100%; height: 9rem; margin-top: 1rem; font: 13px/1.4 ui-monospace, monospace; }
</style>
</head>
<body>
<div class="wrap">
<h1>__TITLE__</h1>
<p class="sub">__SUB__</p>
<div id="app"></div>
<textarea id="out" readonly></textarea>
</div>
<div class="bar">
<span class="counts" id="counts"></span>
<button onclick="copyFeedback()">Copy feedback</button>
<span class="hint" id="hint">Copies only rows you changed or commented, as JSON.</span>
</div>
<script>
const DATA = __DATA__;

const KEY = "__KEY__";
let state = {};
try { state = JSON.parse(localStorage.getItem(KEY) || "{}"); } catch (e) {}

function rowId(section, name) { return section + "::" + name; }

function render() {
const app = document.getElementById("app");
let html = "", lastSection = "";
for (const [section, subcat, rows] of DATA) {
if (section !== lastSection) { html += `<h2>${section}</h2>`; lastSection = section; }
const parts = subcat.split(" — ");
html += `<h3>${parts[0]}${parts[1] ? ` <span class="note">— ${parts[1]}</span>` : ""}</h3>`;
html += `<table><tr><th>Entry</th><th style="text-align:right">Downloads/mo</th><th>My verdict</th><th>My reason</th><th>Yours</th><th>Your reason (optional)</th></tr>`;
for (const [name, url, dl, mine, reason] of rows) {
const id = rowId(section, name);
const s = state[id] || {};
const yours = s.verdict || mine;
const changed = yours !== mine || (s.feedback || "").trim() !== "";
html += `<tr id="${cssId(id)}" class="${changed ? "changed" : ""}">
<td class="name"><a href="${url}" target="_blank" rel="noopener">${name}</a></td>
<td class="dl">${dl}</td>
<td class="mine"><span class="badge ${mine}">${mine.toUpperCase()}</span></td>
<td class="reason">${reason}</td>
<td class="yours"><span class="seg">
<button class="k ${yours === "keep" ? "on" : ""}" onclick="setV('${esc(id)}','keep')">Keep</button>
<button class="d ${yours === "drop" ? "on" : ""}" onclick="setV('${esc(id)}','drop')">Drop</button>
</span></td>
<td class="fb"><input value="${(s.feedback || "").replace(/"/g, "&quot;")}" oninput="setF('${esc(id)}', this.value)" placeholder="why…"></td>
</tr>`;
}
html += `</table>`;
}
app.innerHTML = html;
updateCounts();
}

function cssId(id) { return "r-" + id.replace(/[^a-zA-Z0-9]/g, "_"); }
function esc(id) { return id.replace(/'/g, "\\'"); }

function setV(id, v) { state[id] = state[id] || {}; state[id].verdict = v; save(); render(); }
function setF(id, f) {
state[id] = state[id] || {}; state[id].feedback = f; save();
const [section, name] = id.split("::");
const mine = findMine(section, name);
const yours = state[id].verdict || mine;
document.getElementById(cssId(id)).className = (yours !== mine || f.trim() !== "") ? "changed" : "";
updateCounts();
}
function findMine(section, name) {
for (const [s, , rows] of DATA) if (s === section) for (const r of rows) if (r[0] === name) return r[3];
}
function save() { localStorage.setItem(KEY, JSON.stringify(state)); }

function collect() {
const out = [];
for (const [section, subcat, rows] of DATA) {
for (const [name, , , mine] of rows) {
const id = rowId(section, name);
const s = state[id] || {};
const yours = s.verdict || mine;
const fb = (s.feedback || "").trim();
if (yours !== mine || fb) out.push({ section, subcategory: subcat.split(" — ")[0], entry: name, my_verdict: mine, your_verdict: yours, reason: fb });
}
}
return out;
}

function updateCounts() {
let keeps = 0, total = 0, changed = 0;
for (const [section, , rows] of DATA) for (const [name, , , mine] of rows) {
total++;
const s = state[rowId(section, name)] || {};
const yours = s.verdict || mine;
if (yours === "keep") keeps++;
if (yours !== mine) changed++;
}
document.getElementById("counts").innerHTML = `<b>${keeps}</b> keep · <b>${total - keeps}</b> drop of <b>${total}</b> &nbsp;|&nbsp; <b>${changed}</b> verdicts flipped`;
}

function copyFeedback() {
const fb = collect();
const text = fb.length ? JSON.stringify(fb, null, 1) : "(no changes — all verdicts accepted as-is)";
const hint = document.getElementById("hint");
navigator.clipboard.writeText(text).then(
() => { hint.textContent = `Copied ${fb.length ? fb.length + " row(s)" : "acceptance note"} — paste into the chat.`; },
() => { const o = document.getElementById("out"); o.style.display = "block"; o.value = text; o.select(); hint.textContent = "Clipboard blocked — select & copy the box above."; }
);
}

render();
</script>
</body>
</html>
22 changes: 12 additions & 10 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
## Project

[Project Name](url)
[project-name](url)

## Checklist

- [ ] I read [CONTRIBUTING.md](https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md) - awesome-python is a shortlist, not a catalog
- [ ] One project per PR
- [ ] PR title format: `Add project-name`
- [ ] Entry format: `- [project-name](url) - Description ending with period.`
- [ ] Description is concise and short
- [ ] Entry format: `- [pypi-name](https://github.com/owner/repo) - Description ending with period.`
- [ ] Display name is the PyPI package name
- [ ] Placed in an existing use case (new sections and subcategories are maintainer-only)
- [ ] Meets all Quality Requirements: active, stable, documented, at least 1 month old

## Why This Project Is Awesome
## Which Tier

Which criterion does it meet? (pick one)
Pick one:

- [ ] **Industry Standard** - The go-to tool for a specific use case
- [ ] **Rising Star** - 5000+ stars in < 2 years, significant adoption
- [ ] **Hidden Gem** - Exceptional quality, solves niche problems elegantly
- [ ] **Obvious choice** - a tool an experienced Python developer would name when asked "what do I use for this?"
- [ ] **Challenger** - not yet the obvious choice, but a credible successor to one. Give adoption-trajectory evidence, not popularity alone.

Explain:

## How It Differs
## Displacement

If similar entries exist, what makes this one unique?
If the use case is at its cap, name the entry your project replaces and argue that yours does that entry's job better. One in, one out.
19 changes: 19 additions & 0 deletions .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ jobs:
fi
python -m json.tool website/data/github_stars.json > /dev/null

- name: Restore PyPI download data cache
uses: actions/cache/restore@v4
with:
path: website/data/pypi_downloads.tsv
key: pypi-downloads-${{ steps.date.outputs.today }}
restore-keys: pypi-downloads-

- name: Fetch PyPI downloads
id: fetch-downloads
continue-on-error: true
run: make fetch_pypi_downloads

- name: Save PyPI download data cache
if: steps.fetch-downloads.outcome == 'success'
uses: actions/cache/save@v4
with:
path: website/data/pypi_downloads.tsv
key: pypi-downloads-${{ steps.date.outputs.today }}

- name: Build website
run: make build

Expand Down
11 changes: 3 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ __pycache__/

# website
website/output/
website/data/

# docs
docs/
website/data/*
!website/data/pypi_name_overrides.json

# agents
.agents/
.claude/skills/
.superpowers/
.playwright-cli/
skills-lock.json
tmp/
Loading