Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
169fafd
docs: fix wrong, outdated, and broken README entry descriptions
vinta Sep 27, 2026
6a2acf7
Merge branch 'fix/readme-todo-descriptions'
vinta Sep 27, 2026
2756fba
docs: name Ultralytics' license by family in computer-vision intro
vinta Sep 27, 2026
7a841f2
docs: fix duplicate rembg link in image-processing intro
vinta Sep 27, 2026
91927ea
Merge branch 'fix/small-fixes'
vinta Sep 27, 2026
5c4607d
refactor: reformat clickpy query string with ruff format
vinta Sep 27, 2026
f0ebe92
fix: order category page JSON-LD ItemList to match rendered rows
vinta Sep 27, 2026
0abd19a
fix: number rows in sorted order after a column sort
vinta Sep 27, 2026
ec818eb
fix: drop redundant aria-hidden on homepage description rows
vinta Sep 27, 2026
1ed83b7
docs: fix 19 entry descriptions that drifted from upstream
vinta Sep 27, 2026
edc3db3
docs: fix 6 section descriptions that misdescribed their sections
vinta Sep 27, 2026
2c0486a
docs: rewrite Data Validation category intro lead
vinta Sep 27, 2026
56aa407
docs: rewrite Date and Time category intro lead
vinta Sep 27, 2026
f293ce3
docs: rewrite Game Development category intro lead
vinta Sep 27, 2026
86d8570
docs: rewrite Web APIs category intro lead
vinta Sep 27, 2026
fc09dc7
docs: rewrite Web Frameworks category intro lead
vinta Sep 27, 2026
36880b3
docs: rewrite Web Servers category intro lead
vinta Sep 27, 2026
540f462
docs: rewrite WebSocket category intro lead
vinta Sep 27, 2026
a048332
docs: rewrite Template Engines category intro lead
vinta Sep 27, 2026
9cdd11b
docs: rewrite File Format Processing category intro lead
vinta Sep 27, 2026
6a038e3
docs: fix inaccurate entry descriptions across README
vinta Sep 27, 2026
a25bb7a
docs: remove stale nested awesome lists for pyramid and fasthtml
vinta Sep 27, 2026
6eaa8a4
Merge branch 'fix/todo-pass2'
vinta Sep 27, 2026
b674882
style: give inline code in intros a chip appearance
vinta Sep 27, 2026
ec4fd5d
Merge branch 'code-chip'
vinta Sep 27, 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
168 changes: 83 additions & 85 deletions README.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion website/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,8 +755,9 @@ def render_category(
if parent_category:
breadcrumbs.append((parent_category["name"], category_public_url(parent_category)))
breadcrumbs.append((category["name"], category_url))
page_entries = [entry for group in entry_groups for entry in group["entries"]] if entry_groups else entries
category_json_ld = json.dumps(
build_category_json_ld(category_title.removesuffix(" - Awesome Python"), category_url, category_description, entries, breadcrumbs),
build_category_json_ld(category_title.removesuffix(" - Awesome Python"), category_url, category_description, page_entries, breadcrumbs),
ensure_ascii=False,
).replace("</", "<\\/")
(page_dir / "index.html").write_text(
Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/computer-vision.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ How to choose:

OpenCV comes as [four pip packages that share the `cv2` namespace](https://github.com/opencv/opencv-python), so install only one: opencv-python for the main modules, or opencv-contrib-python to add the extra modules. If you never call `cv2.imshow` or you build your GUI with another toolkit, install the headless variant of either one, which also makes Docker images smaller.

Ultralytics YOLO covers the [whole life of a model](https://docs.ultralytics.com/modes/): train, validate, predict, export, and track, from Python or the `yolo` command. Its docs recommend [starting training from a pretrained model](https://docs.ultralytics.com/modes/train/). To deploy, [export it](https://docs.ultralytics.com/modes/export/) to ONNX, TensorRT, CoreML, or another format. The code and the models you train with it are [AGPL-3.0](https://www.ultralytics.com/license), so unless you open-source your whole project, you need an Enterprise License.
Ultralytics YOLO covers the [whole life of a model](https://docs.ultralytics.com/modes/): train, validate, predict, export, and track, from Python or the `yolo` command. Its docs recommend [starting training from a pretrained model](https://docs.ultralytics.com/modes/train/). To deploy, [export it](https://docs.ultralytics.com/modes/export/) to ONNX, TensorRT, CoreML, or another format. The code and the models you train with it are [AGPL](https://www.ultralytics.com/license), so unless you open-source your whole project, you need an Enterprise License.

Kornia is a [differentiable computer vision library like OpenCV, with strong GPU support](https://kornia.readthedocs.io/en/latest/get-started/introduction.html). Every operator works on PyTorch tensors and supports autograd, so vision ops can run on the GPU and sit inside your training loop.

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/data-validation.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Validate API input and config with Pydantic, the Python data validation library built on type hints. Use Pandera for dataframes, jsonschema for JSON Schema.
Declare API input and config as type hints, and Pydantic validates them. Pandera brings Python data validation to dataframes, and jsonschema covers JSON Schema.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/date-and-time.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Most code should handle time zones with zoneinfo and pick python-dateutil, the Python date library for parsing date strings and adding months.
Handle time zones with the built-in zoneinfo. Parsing date strings and adding months is where a Python date library earns its install: python-dateutil.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/file-format-processing.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PDF, Word, and Excel files open with pypdf, python-docx, and openpyxl, a Python file format library for each. MarkItDown turns all three into Markdown.
pypdf reads PDFs, python-docx Word files, and openpyxl Excel sheets: one Python file format library per format. MarkItDown turns any of them into Markdown.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/game-development.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
A 2D game needs a loop: write your own with pygame-ce, the Python game development library, or let Arcade run it. Panda3D does 3D, and Ren'Py visual novels.
pygame-ce leaves the 2D game loop to you, while Arcade runs it for you. For 3D, Panda3D covers Python game development, and Ren'Py builds visual novels.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/image-processing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pyvips builds a pipeline of operations and runs it only when you write the resul

Wand is a [ctypes-based ImageMagick binding](https://docs.wand-py.org/en/latest/), so install ImageMagick's MagickWand library first. Its objects are resources like open files: [use them in a `with` block](https://docs.wand-py.org/en/latest/guide/resource.html) so they get closed. Wand's docs say to [never use Wand directly in an HTTP service](https://docs.wand-py.org/en/latest/guide/security.html) or on any public server. Hand the images to a background worker through a queue, and limit ImageMagick's resources and formats in its `policy.xml`.

rembg runs as a [CLI, a Python library, an HTTP server, or a Docker container](https://github.com/danielgatis/rembg). In code, create a session once with `new_session()` and pass it to each `remove()` call, since `remove` otherwise [starts a new session every call](https://github.com/danielgatis/rembg/blob/main/USAGE.md). The model weights [carry their own licenses](https://github.com/danielgatis/rembg), separate from rembg's MIT license, so check the one you use before you ship it in a commercial product.
rembg runs as a [CLI, a Python library, an HTTP server, or a Docker container](https://github.com/danielgatis/rembg). In code, create a session once with `new_session()` and pass it to each `remove()` call, since `remove` otherwise [starts a new session every call](https://github.com/danielgatis/rembg/blob/main/USAGE.md). The model weights [carry their own licenses](https://github.com/danielgatis/rembg#models), separate from rembg's MIT license, so check the one you use before you ship it in a commercial product.

thumbor is an HTTP server: you [set the size and crop in the image URL](https://github.com/thumbor/thumbor), and it [detects faces and important features](https://thumbor.readthedocs.io/en/latest/) to crop around them. Set a `SECURITY_KEY` so [every URL is signed](https://thumbor.readthedocs.io/en/latest/security.html) and nobody can tamper with it, and build those URLs in Python with [libthumbor](https://thumbor.readthedocs.io/en/latest/libraries.html). In production, [turn off `ALLOW_UNSAFE_URL`](https://thumbor.readthedocs.io/en/latest/configuration.html) and run [more than one instance](https://thumbor.readthedocs.io/en/latest/hosting.html) behind a load balancer.

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/template-engines.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
If your templates should hold no Python code, Jinja fits: a Python template engine whose sandbox also renders untrusted templates. Mako embeds plain Python.
Whether templates may hold Python code splits the Python template engines: Jinja keeps code out and sandboxes untrusted templates, Mako embeds plain Python.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/web-apis.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
For a Django project, pick Django REST framework or Django Ninja. Outside Django, build on FastAPI, a Python API framework based on type hints.
Outside Django, declare each FastAPI endpoint with type hints. Django projects add a Python API framework on top instead: Django REST framework or Django Ninja.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/web-frameworks.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
When you want an ORM, an admin, and auth built in, use Django. Flask, a smaller Python web framework, gives you a core you extend.
How much should a Python web framework build in? Django comes with an ORM, an admin, and auth; Flask gives you a small core to extend.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/web-servers.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
In production, Gunicorn serves WSGI apps like Django, and Uvicorn serves ASGI apps like FastAPI. Waitress, a pure-Python web server, runs WSGI apps on Windows.
In production, Gunicorn serves WSGI apps like Django, and Uvicorn ASGI apps like FastAPI. Need a Python web server on Windows? Waitress runs WSGI there.

How to choose:

Expand Down
2 changes: 1 addition & 1 deletion website/data/category_intros/websocket.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
With Django, Channels is the pick; with Flask and Socket.IO clients, Flask-SocketIO. Standalone apps run on websockets, a Python WebSocket library.
Your Python WebSocket library follows your framework: Channels on Django, Flask-SocketIO on Flask with Socket.IO clients. websockets needs no framework.

How to choose:

Expand Down
7 changes: 1 addition & 6 deletions website/fetch_pypi_downloads_via_clickpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,7 @@ def collect_names(readme_text: str) -> list[str]:

def fetch_clickpy(names: list[str]) -> dict[str, int]:
in_list = ", ".join(f"'{name}'" for name in names)
query = (
"SELECT project, sum(count) AS downloads "
"FROM pypi.pypi_downloads_per_day "
f"WHERE project IN ({in_list}) AND date >= today() - 30 "
"GROUP BY project FORMAT JSON"
)
query = f"SELECT project, sum(count) AS downloads FROM pypi.pypi_downloads_per_day WHERE project IN ({in_list}) AND date >= today() - 30 GROUP BY project FORMAT JSON"
resp = httpx.post(CLICKPY_URL, content=query, timeout=60)
resp.raise_for_status()
return {row["project"]: int(row["downloads"]) for row in resp.json()["data"]}
Expand Down
4 changes: 3 additions & 1 deletion website/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ function applyFilters() {

collapseAll();

rows.forEach(function (row) {
// Number rows in their sorted DOM order, not the load order `rows` keeps
const orderedRows = tbody ? tbody.querySelectorAll("tr.row") : rows;
orderedRows.forEach(function (row) {
let show = true;

if (query) {
Expand Down
5 changes: 5 additions & 0 deletions website/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,12 @@ kbd {

.category-intro code,
.guide-body code {
font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
font-size: 0.9em;
padding: 0.08rem 0.4rem;
border-radius: 0.4rem;
background: var(--hero-line);
color: var(--hero-text);
}

.jump-links {
Expand Down
2 changes: 1 addition & 1 deletion website/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ <h2 class="sr-only">Results</h2>
<td class="col-arrow"><span class="arrow">&rarr;</span></td>
</tr>
{% if entry.description %}
<tr class="desc-row" aria-hidden="true" hidden>
<tr class="desc-row" hidden>
<td class="col-num"></td>
<td colspan="6">
<div class="desc-text">{{ entry.description | safe }}</div>
Expand Down
33 changes: 33 additions & 0 deletions website/tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,39 @@ def test_category_page_contains_json_ld(self, tmp_path):
{"@type": "ListItem", "position": 2, "name": "Widgets", "item": "https://awesome-python.com/categories/widgets/"},
]

def test_category_json_ld_follows_page_order(self, tmp_path):
readme = textwrap.dedent("""\
# Awesome Python

Intro.

## Projects

**Tools**

## Widgets

_Widget libraries._

- [zeta](https://example.com/zeta) - Listed first.
- [alpha](https://example.com/alpha) - Listed second.

# Contributing

Help!
""")
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
self._copy_real_templates(tmp_path)
build(tmp_path)

category_html = (tmp_path / "website" / "output" / "categories" / "widgets" / "index.html").read_text(encoding="utf-8")
marker = '<script type="application/ld+json">'
start = category_html.index(marker) + len(marker)
data = json.loads(category_html[start : category_html.index("</script>", start)])
collection = next(node for node in data["@graph"] if node["@type"] == "CollectionPage")
items = collection["mainEntity"]["itemListElement"]
assert [(item["position"], item["name"]) for item in items] == [(1, "zeta"), (2, "alpha")]

def test_group_page_falls_back_to_default_description_in_json_ld(self, tmp_path):
readme = textwrap.dedent("""\
# T
Expand Down
Loading