Repository: vinta/awesome-python
Branch: master
Commit: c87fdacd7b11
Files: 22
Total size: 174.5 KB
Directory structure:
gitextract_1fmgb11j/
├── .claude/
│ ├── commands/
│ │ └── review-pending-prs.md
│ └── settings.json
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── deploy-website.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SPONSORSHIP.md
├── pyproject.toml
└── website/
├── build.py
├── fetch_github_stars.py
├── readme_parser.py
├── static/
│ ├── main.js
│ └── style.css
├── templates/
│ ├── base.html
│ └── index.html
└── tests/
├── test_build.py
├── test_fetch_github_stars.py
└── test_readme_parser.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/commands/review-pending-prs.md
================================================
---
description: Review pending PRs against CONTRIBUTING.md acceptance criteria.
allowed-tools: Bash(gh api:*), Bash(gh pr close:*), Bash(gh pr diff:*), Bash(gh pr edit:*), Bash(gh pr list:*)
---
## Usage
```
/review-pending-prs
```
## Instructions
1. Fetch 10 open PRs with details: `gh pr list --repo vinta/awesome-python --limit 10 --search "-label:\"claude reviewed\"" --json number,title,author,url,body,files,mergeable,mergeStateStatus`
2. Fetch all PR diffs in parallel: `gh pr diff <number> --repo vinta/awesome-python`
3. Run quick rejection checks (no API calls needed):
- Has merge conflicts? (from `mergeable`/`mergeStateStatus`)
- Adds more than one project? (from diff)
- Duplicate entry? (from diff - URL already in README)
- Not a project submission? (from diff - e.g., random files, contributor list)
4. For PRs passing quick checks, fetch repo stats: `gh api repos/<owner>/<repo> --jq '{stars: .stargazers_count, created: .created_at, updated: .pushed_at, language: .language, archived: .archived}'`
5. Review against all criteria in [CONTRIBUTING.md](../../CONTRIBUTING.md)
6. Present summary table with recommendations
7. Ask user:
```
Would you like me to:
1. Close the rejected PRs with comments?
2. Add "claude reviewed" label to the passed PRs?
3. Do all
```
## Quick Rejection Checks
Check these rules first - if any fail, recommend rejection:
- PR has merge conflicts
- Add more than one project per PR
- Duplicate of existing entry
- Placed under an inappropriate category
- Project is archived or abandoned (no commits in 12+ months)
- No documentation or unclear use case
- Less than 100 GitHub stars AND not justified as a hidden gem
- Too niche — a thin wrapper, single-function utility, or narrow edge-case tool that most Python developers would never need
## Output Format
Provide a simple review:
1. **Rejection Check** - table with the above rules and PASS/REJECT
2. **Recommendation** - PASS or REJECT
## Close PRs
If user asks to close/reject:
```bash
gh pr close <number> --repo vinta/awesome-python --comment "<brief reason>"
```
## Mark as Passed
```bash
gh pr edit <number> --repo vinta/awesome-python --add-label "claude reviewed"
```
## Extra Instructions (If Provided)
$ARGUMENTS
================================================
FILE: .claude/settings.json
================================================
{
"permissions": {
"allow": [
"Bash(gh api:*)",
"Bash(gh pr close:*)",
"Bash(gh pr comment:*)",
"Bash(gh pr diff:*)",
"Bash(gh pr edit:*)",
"Bash(gh pr list:*)",
"Bash(gh pr view:*)",
"Bash(gh run list:*)",
"Bash(gh run rerun:*)",
"Bash(gh run view:*)",
"Bash(gh search:*)"
],
"deny": []
}
}
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Project
[Project Name](url)
## Checklist
- [ ] One project per PR
- [ ] PR title format: `Add project-name`
- [ ] Entry format: `* [project-name](url) - Description ending with period.`
- [ ] Description is concise and short
## Why This Project Is Awesome
Which criterion does it meet? (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
Explain:
## How It Differs
If similar entries exist, what makes this one unique?
================================================
FILE: .github/workflows/deploy-website.yml
================================================
name: Deploy Website
on:
push:
branches:
- master
schedule:
- cron: "0 0 * * *"
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Install dependencies
run: uv sync --group build
- name: Restore star data cache
id: cache-stars
uses: actions/cache/restore@v4
with:
path: website/data/github_stars.json
key: github-stars-${{ github.run_id }}
restore-keys: github-stars-
- name: Fetch GitHub stars
id: fetch-stars
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: make fetch_github_stars
- name: Save star data cache
if: steps.fetch-stars.outcome == 'success'
uses: actions/cache/save@v4
with:
path: website/data/github_stars.json
key: github-stars-${{ github.run_id }}
- name: Verify star data exists
run: |
if [ ! -f website/data/github_stars.json ]; then
echo "::error::github_stars.json not found. No cache and fetch failed or was skipped."
exit 1
fi
echo "Star data found: $(wc -l < website/data/github_stars.json) lines"
- name: Build site
run: make build
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: website/output/
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: https://awesome-python.com/
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .gitignore
================================================
# macOS
.DS_Store
# python
.venv/
*.py[co]
# website
website/output/
website/data/
# claude code
.claude/skills/
.superpowers/
.gstack/
skills-lock.json
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
## Repository Overview
This is the awesome-python repository - a curated list of Python frameworks, libraries, software and resources. The repository serves as a comprehensive directory about Python ecosystem.
## PR Review Guidelines
**For all PR review tasks, refer to [CONTRIBUTING.md](CONTRIBUTING.md)** which contains:
- Acceptance criteria (Industry Standard, Rising Star, Hidden Gem)
- Quality requirements
- Automatic rejection criteria
- Entry format reference
- PR description template
## Architecture & Structure
The repository follows a single-file architecture:
- **README.md**: All content in hierarchical structure (categories, subcategories, entries)
- **CONTRIBUTING.md**: Submission guidelines and review criteria
- **sort.py**: Script to enforce alphabetical ordering
Entry format: `* [project-name](url) - Concise description ending with period.`
## Key Considerations
- This is a curated list, not a code project
- Quality over quantity - only "awesome" projects
- Alphabetical ordering within categories is mandatory
- README.md is the source of truth for all content
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Quality Requirements
All submissions must satisfy **ALL** of these:
1. **Python-first**: Primarily written in Python (>50% of codebase)
2. **Active**: Commits within the last 12 months
3. **Stable**: Production-ready, not alpha/beta/experimental
4. **Documented**: Clear README with examples and use cases
5. **Unique**: Adds distinct value, not "yet another X"
6. **Established**: Repository at least 1 month old
## Acceptance Criteria
Your submission must meet **ONE** of the following criteria:
### 1. Industry Standard
- The go-to tool that almost everyone uses for a specific use case
- Examples: requests, flask, pandas, numpy
- Limit: 1-3 tools per category
### 2. Rising Star
- Rapid growth: 5,000+ GitHub stars in less than 2 years
- Significant community buzz and adoption
- Solving problems in new or better ways
- Examples: fastapi, ruff, uv
### 3. Hidden Gem
- Exceptional quality despite fewer stars (100-500 stars preferred; < 100 requires strong justification)
- Solves niche problems elegantly
- Strong recommendation from experienced developers
- **Must demonstrate real-world usage** (not a project published last week)
- Repository must be at least 6 months old with consistent activity
- Must include compelling justification in PR description
## Entry Format Reference
**Use GitHub repository URLs** whenever possible. Projects linked to a GitHub repo are ranked higher on [awesome-python.com](https://awesome-python.com/).
### Naming Convention
Use the **PyPI package name** as the display name so developers can copy it directly to `pip install`. Check the canonical name at `https://pypi.org/pypi/{package}/json`. If the project is not on PyPI, use the GitHub repository name instead.
### Standard Entry
```markdown
- [pypi-name](https://github.com/owner/repo) - Description ending with period.
```
### Standard Library Module
```markdown
- [module](https://docs.python.org/3/library/module.html) - (Python standard library) Description.
```
### Fork of Another Project
```markdown
- [new-name](https://github.com/owner/new-name) - Description ([original-name](original-url) fork).
```
### Entry with Related Awesome List
```markdown
- [project](https://github.com/owner/project) - Description.
- [awesome-project](https://github.com/someone/awesome-project)
```
### Subcategory Format
```markdown
- Subcategory Name
- [project](url) - Description.
```
## Adding a New Section
1. Add section description in italics: `*Libraries for doing X.*`
2. Add the section under the appropriate thematic group (e.g., **AI & ML**, **Web**, **Data & Science**)
3. Add the section title to the Table of Contents under its group
4. Keep entries in alphabetical order within each category
## Review Process
PRs are reviewed by automated tools and maintainers:
1. **Format Check**: Entry follows the correct format
2. **Category Check**: Placed in the appropriate category/subcategory
3. **Duplicate Check**: Not already listed or previously rejected
4. **Activity Check**: Project shows recent activity
5. **Quality Check**: Meets acceptance criteria
Search previous Pull Requests and Issues before submitting, as yours may be a duplicate.
## Automatic Rejection
PRs will be **closed** if:
- Adding multiple projects in one PR
- Duplicate of existing entry or recently-closed PR
- Empty or placeholder PR descriptions
- Placed under an inappropriate category
- Project is archived or abandoned (no commits in 12+ months)
- No documentation or unclear use case
- Less than 100 GitHub stars without Hidden Gem justification
- Repository less than 3 months old
================================================
FILE: LICENSE
================================================
Creative Commons Attribution 4.0 International License (CC BY 4.0)
http://creativecommons.org/licenses/by/4.0/
================================================
FILE: Makefile
================================================
-include .env
export
install:
uv sync
fetch_github_stars:
uv run python website/fetch_github_stars.py
test:
uv run pytest website/tests/ -v
build:
uv run python website/build.py
preview: build
@echo "Check the website on http://localhost:8000"
uv run watchmedo shell-command \
--patterns='*.md;*.html;*.css;*.js;*.py' \
--recursive \
--wait --drop \
--command='uv run python website/build.py' \
README.md website/templates website/static website/data & \
python -m http.server -b 127.0.0.1 -d website/output/ 8000
================================================
FILE: README.md
================================================
# Awesome Python
An opinionated list of awesome Python frameworks, libraries, tools, software and resources.
> The **#10 most-starred repo on GitHub**. Put your product where Python developers discover tools. [Become a sponsor](SPONSORSHIP.md).
# Categories
**AI & ML**
- [AI and Agents](#ai-and-agents)
- [Deep Learning](#deep-learning)
- [Machine Learning](#machine-learning)
- [Natural Language Processing](#natural-language-processing)
- [Computer Vision](#computer-vision)
- [Recommender Systems](#recommender-systems)
**Web**
- [Web Frameworks](#web-frameworks)
- [Web APIs](#web-apis)
- [Web Servers](#web-servers)
- [WebSocket](#websocket)
- [Template Engines](#template-engines)
- [Web Asset Management](#web-asset-management)
- [Authentication](#authentication)
- [Admin Panels](#admin-panels)
- [CMS](#cms)
- [Static Site Generators](#static-site-generators)
**HTTP & Scraping**
- [HTTP Clients](#http-clients)
- [Web Scraping](#web-scraping)
- [Email](#email)
**Database & Storage**
- [ORM](#orm)
- [Database Drivers](#database-drivers)
- [Database](#database)
- [Caching](#caching)
- [Search](#search)
- [Serialization](#serialization)
**Data & Science**
- [Data Analysis](#data-analysis)
- [Data Validation](#data-validation)
- [Data Visualization](#data-visualization)
- [Geolocation](#geolocation)
- [Science](#science)
- [Quantum Computing](#quantum-computing)
**Developer Tools**
- [Algorithms and Design Patterns](#algorithms-and-design-patterns)
- [Interactive Interpreter](#interactive-interpreter)
- [Code Analysis](#code-analysis)
- [Testing](#testing)
- [Debugging Tools](#debugging-tools)
- [Build Tools](#build-tools)
- [Documentation](#documentation)
**DevOps**
- [DevOps Tools](#devops-tools)
- [Distributed Computing](#distributed-computing)
- [Task Queues](#task-queues)
- [Job Schedulers](#job-schedulers)
- [Logging](#logging)
- [Network Virtualization](#network-virtualization)
**CLI & GUI**
- [Command-line Interface Development](#command-line-interface-development)
- [Command-line Tools](#command-line-tools)
- [GUI Development](#gui-development)
**Text & Documents**
- [Text Processing](#text-processing)
- [HTML Manipulation](#html-manipulation)
- [File Format Processing](#file-format-processing)
- [File Manipulation](#file-manipulation)
**Media**
- [Image Processing](#image-processing)
- [Audio & Video Processing](#audio--video-processing)
- [Game Development](#game-development)
**Python Language**
- [Implementations](#implementations)
- [Built-in Classes Enhancement](#built-in-classes-enhancement)
- [Functional Programming](#functional-programming)
- [Asynchronous Programming](#asynchronous-programming)
- [Date and Time](#date-and-time)
**Python Toolchain**
- [Environment Management](#environment-management)
- [Package Management](#package-management)
- [Package Repositories](#package-repositories)
- [Distribution](#distribution)
- [Configuration Files](#configuration-files)
**Security**
- [Cryptography](#cryptography)
- [Penetration Testing](#penetration-testing)
**Miscellaneous**
- [Hardware](#hardware)
- [Microsoft Windows](#microsoft-windows)
- [Miscellaneous](#miscellaneous)
---
**AI & ML**
## AI and Agents
_Libraries for building AI applications, LLM integrations, and autonomous agents._
- Frameworks
- [autogen](https://github.com/microsoft/autogen) - A programming framework for building agentic AI applications.
- [crewai](https://github.com/crewAIInc/crewAI) - A framework for orchestrating role-playing autonomous AI agents for collaborative task solving.
- [dspy](https://github.com/stanfordnlp/dspy) - A framework for programming, not prompting, language models.
- [instructor](https://github.com/567-labs/instructor) - A library for extracting structured data from LLMs, powered by Pydantic.
- [langchain](https://github.com/langchain-ai/langchain) - Building applications with LLMs through composability.
- [llama_index](https://github.com/run-llama/llama_index) - A data framework for your LLM application.
- [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas.
- Pretrained Models and Inference
- [diffusers](https://github.com/huggingface/diffusers) - A library that provides pretrained diffusion models for generating and editing images, audio, and video.
- [transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks.
- [vllm](https://github.com/vllm-project/vllm) - A high-throughput and memory-efficient inference and serving engine for LLMs.
## Deep Learning
_Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learning](https://github.com/ChristosChristofidis/awesome-deep-learning)._
- [jax](https://github.com/jax-ml/jax) - a library for high-performance numerical computing with automatic differentiation and JIT compilation.
- [keras](https://github.com/keras-team/keras) - A high-level deep learning library with support for JAX, TensorFlow, and PyTorch backends.
- [pytorch-lightning](https://github.com/Lightning-AI/pytorch-lightning) - Deep learning framework to train, deploy, and ship AI products Lightning fast.
- [pytorch](https://github.com/pytorch/pytorch) - Tensors and Dynamic neural networks in Python with strong GPU acceleration.
- [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) - PyTorch implementations of Stable Baselines (deep) reinforcement learning algorithms.
- [tensorflow](https://github.com/tensorflow/tensorflow) - The most popular Deep Learning framework created by Google.
## Machine Learning
_Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python)._
- [catboost](https://github.com/catboost/catboost) - A fast, scalable, high performance gradient boosting on decision trees library.
- [feature_engine](https://github.com/feature-engine/feature_engine) - sklearn compatible API with the widest toolset for feature engineering and selection.
- [h2o](https://github.com/h2oai/h2o-3) - Open Source Fast Scalable Machine Learning Platform.
- [lightgbm](https://github.com/lightgbm-org/LightGBM) - A fast, distributed, high performance gradient boosting framework.
- [mindsdb](https://github.com/mindsdb/mindsdb) - MindsDB is an open source AI layer for existing databases that allows you to effortlessly develop, train and deploy state-of-the-art machine learning models using standard queries.
- [pgmpy](https://github.com/pgmpy/pgmpy) - A Python library for probabilistic graphical models and Bayesian networks.
- [scikit-learn](https://github.com/scikit-learn/scikit-learn) - The most popular Python library for Machine Learning with extensive documentation and community support.
- [spark.ml](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library for distributed computing.
- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library.
## Natural Language Processing
_Libraries for working with human languages._
- General
- [gensim](https://github.com/piskvorky/gensim) - Topic Modeling for Humans.
- [nltk](https://github.com/nltk/nltk) - A leading platform for building Python programs to work with human language data.
- [spacy](https://github.com/explosion/spaCy) - A library for industrial-strength natural language processing in Python and Cython.
- [stanza](https://github.com/stanfordnlp/stanza) - The Stanford NLP Group's official Python library, supporting 60+ languages.
- Chinese
- [funnlp](https://github.com/fighting41love/funNLP) - A collection of tools and datasets for Chinese NLP.
- [jieba](https://github.com/fxsjy/jieba) - The most popular Chinese text segmentation library.
## Computer Vision
_Libraries for Computer Vision._
- [easyocr](https://github.com/JaidedAI/EasyOCR) - Ready-to-use OCR with 40+ languages supported.
- [kornia](https://github.com/kornia/kornia/) - Open Source Differentiable Computer Vision Library for PyTorch.
- [opencv](https://github.com/opencv/opencv-python) - Open Source Computer Vision Library.
- [pytesseract](https://github.com/madmaze/pytesseract) - A wrapper for [Google Tesseract OCR](https://github.com/tesseract-ocr).
## Recommender Systems
_Libraries for building recommender systems._
- [annoy](https://github.com/spotify/annoy) - Approximate Nearest Neighbors in C++/Python optimized for memory usage.
- [implicit](https://github.com/benfred/implicit) - A fast Python implementation of collaborative filtering for implicit datasets.
- [scikit-surprise](https://github.com/NicolasHug/Surprise) - A scikit for building and analyzing recommender systems.
**Web**
## Web Frameworks
_Traditional full stack web frameworks. Also see [Web APIs](#web-apis)._
- Synchronous
- [bottle](https://github.com/bottlepy/bottle) - A fast and simple micro-framework distributed as a single file with no dependencies.
- [django](https://github.com/django/django) - The most popular web framework in Python.
- [awesome-django](https://github.com/shahraizali/awesome-django)
- [flask](https://github.com/pallets/flask) - A microframework for Python.
- [awesome-flask](https://github.com/humiaozuzu/awesome-flask)
- [pyramid](https://github.com/Pylons/pyramid) - A small, fast, down-to-earth, open source Python web framework.
- [awesome-pyramid](https://github.com/uralbash/awesome-pyramid)
- [fasthtml](https://github.com/AnswerDotAI/fasthtml) - The fastest way to create an HTML app.
- [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml)
- [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework.
- Asynchronous
- [litestar](https://github.com/litestar-org/litestar) - Production-ready, capable and extensible ASGI Web framework.
- [microdot](https://github.com/miguelgrinberg/microdot) - The impossibly small web framework for Python and MicroPython.
- [reflex](https://github.com/reflex-dev/reflex) – A framework for building reactive, full-stack web applications entirely with python .
- [robyn](https://github.com/sparckles/Robyn) - A high-performance async Python web framework with a Rust runtime.
- [starlette](https://github.com/Kludex/starlette) - A lightweight ASGI framework and toolkit for building high-performance async services.
- [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library.
## Web APIs
_Libraries for building RESTful and GraphQL APIs._
- Django
- [django-ninja](https://github.com/vitalik/django-ninja) - Fast, Django REST framework based on type hints and Pydantic.
- [django-rest-framework](https://github.com/encode/django-rest-framework) - A powerful and flexible toolkit to build web APIs.
- [strawberry-django](https://github.com/strawberry-graphql/strawberry-django) - Strawberry GraphQL integration with Django.
- Flask
- [apiflask](https://github.com/apiflask/apiflask) - A lightweight Python web API framework based on Flask and Marshmallow.
- Framework Agnostic
- [connexion](https://github.com/spec-first/connexion) - A spec-first framework that automatically handles requests based on your OpenAPI specification.
- [falcon](https://github.com/falconry/falcon) - A high-performance framework for building cloud APIs and web app backends.
- [fastapi](https://github.com/fastapi/fastapi) - A modern, fast, web framework for building APIs with standard Python type hints.
- [sanic](https://github.com/sanic-org/sanic) - A Python 3.6+ web server and web framework that's written to go fast.
- [strawberry](https://github.com/strawberry-graphql/strawberry) - A GraphQL library that leverages Python type annotations for schema definition.
- [webargs](https://github.com/marshmallow-code/webargs) - A friendly library for parsing HTTP request arguments with built-in support for popular web frameworks.
## Web Servers
_ASGI and WSGI compatible web servers._
- ASGI
- [daphne](https://github.com/django/daphne) - A HTTP, HTTP2 and WebSocket protocol server for ASGI and ASGI-HTTP.
- [granian](https://github.com/emmett-framework/granian) - A Rust HTTP server for Python applications built on top of Hyper and Tokio, supporting WSGI/ASGI/RSGI.
- [hypercorn](https://github.com/pgjones/hypercorn) - An ASGI and WSGI Server based on Hyper libraries and inspired by Gunicorn.
- [uvicorn](https://github.com/Kludex/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools.
- WSGI
- [gunicorn](https://github.com/benoitc/gunicorn) - Pre-forked, ported from Ruby's Unicorn project.
- [uwsgi](https://github.com/unbit/uwsgi) - A project aims at developing a full stack for building hosting services, written in C.
- [waitress](https://github.com/Pylons/waitress) - Multi-threaded, powers Pyramid.
- RPC
- [grpcio](https://github.com/grpc/grpc) - HTTP/2-based RPC framework with Python bindings, built by Google.
- [rpyc](https://github.com/tomerfiliba-org/rpyc) (Remote Python Call) - A transparent and symmetric RPC library for Python.
## WebSocket
_Libraries for working with WebSocket._
- [autobahn-python](https://github.com/crossbario/autobahn-python) - WebSocket & WAMP for Python on Twisted and [asyncio](https://docs.python.org/3/library/asyncio.html).
- [channels](https://github.com/django/channels) - Developer-friendly asynchrony for Django.
- [flask-socketio](https://github.com/miguelgrinberg/Flask-SocketIO) - Socket.IO integration for Flask applications.
- [websockets](https://github.com/python-websockets/websockets) - A library for building WebSocket servers and clients with a focus on correctness and simplicity.
## Template Engines
_Libraries and tools for templating and lexing._
- [jinja](https://github.com/pallets/jinja) - A modern and designer friendly templating language.
- [mako](https://github.com/sqlalchemy/mako) - Hyperfast and lightweight templating for the Python platform.
## Web Asset Management
_Tools for managing, compressing and minifying website assets._
- [django-compressor](https://github.com/django-compressor/django-compressor) - Compresses linked and inline JavaScript or CSS into a single cached file.
- [django-storages](https://github.com/jschneier/django-storages) - A collection of custom storage back ends for Django.
## Authentication
_Libraries for implementing authentication schemes._
- OAuth
- [authlib](https://github.com/authlib/authlib) - JavaScript Object Signing and Encryption draft implementation.
- [django-allauth](https://github.com/pennersr/django-allauth) - Authentication app for Django that "just works."
- [django-oauth-toolkit](https://github.com/django-oauth/django-oauth-toolkit) - OAuth 2 goodies for Django.
- [oauthlib](https://github.com/oauthlib/oauthlib) - A generic and thorough implementation of the OAuth request-signing logic.
- JWT
- [pyjwt](https://github.com/jpadilla/pyjwt) - JSON Web Token implementation in Python.
- Permissions
- [django-guardian](https://github.com/django-guardian/django-guardian) - Implementation of per object permissions for Django 1.2+
- [django-rules](https://github.com/dfunckt/django-rules) - A tiny but powerful app providing object-level permissions to Django, without requiring a database.
## Admin Panels
_Libraries for administrative interfaces._
- [ajenti](https://github.com/ajenti/ajenti) - The admin panel your servers deserve.
- [django-grappelli](https://github.com/sehmaschine/django-grappelli) - A jazzy skin for the Django Admin-Interface.
- [django-unfold](https://github.com/unfoldadmin/django-unfold) - Elevate your Django admin with a stunning modern interface, powerful features, and seamless user experience.
- [flask-admin](https://github.com/pallets-eco/flask-admin) - Simple and extensible administrative interface framework for Flask.
- [func-to-web](https://github.com/offerrall/FuncToWeb) - Instantly create web UIs from Python functions using type hints. Zero frontend code required.
- [jet-bridge](https://github.com/jet-admin/jet-bridge) - Admin panel framework for any application with nice UI (ex Jet Django).
## CMS
_Content Management Systems._
- [django-cms](https://github.com/django-cms/django-cms) - The easy-to-use and developer-friendly enterprise CMS powered by Django.
- [indico](https://github.com/indico/indico) - A feature-rich event management system, made @ [CERN](https://en.wikipedia.org/wiki/CERN).
- [wagtail](https://github.com/wagtail/wagtail) - A Django content management system.
## Static Site Generators
_Static site generator is a software that takes some text + templates as input and produces HTML files on the output._
- [lektor](https://github.com/lektor/lektor) - An easy to use static CMS and blog engine.
- [nikola](https://github.com/getnikola/nikola) - A static website and blog generator.
- [pelican](https://github.com/getpelican/pelican) - Static site generator that supports Markdown and reST syntax.
**HTTP & Scraping**
## HTTP Clients
_Libraries for working with HTTP._
- [aiohttp](https://github.com/aio-libs/aiohttp) - Asynchronous HTTP client/server framework for asyncio and Python.
- [furl](https://github.com/gruns/furl) - A small Python library that makes parsing and manipulating URLs easy.
- [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python.
- [requests](https://github.com/psf/requests) - HTTP Requests for Humans.
- [urllib3](https://github.com/urllib3/urllib3) - A HTTP library with thread-safe connection pooling, file post support, sanity friendly.
## Web Scraping
_Libraries to automate web scraping and extract web content._
- Frameworks
- [browser-use](https://github.com/browser-use/browser-use) - Make websites accessible for AI agents with easy browser automation.
- [crawl4ai](https://github.com/unclecode/crawl4ai) - An open-source, LLM-friendly web crawler that provides lightning-fast, structured data extraction specifically designed for AI agents.
- [mechanicalsoup](https://github.com/MechanicalSoup/MechanicalSoup) - A Python library for automating interaction with websites.
- [scrapy](https://github.com/scrapy/scrapy) - A fast high-level screen scraping and web crawling framework.
- Content Extraction
- [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser.
- [html2text](https://github.com/Alir3z4/html2text) - Convert HTML to Markdown-formatted text.
- [micawber](https://github.com/coleifer/micawber) - A small library for extracting rich content from URLs.
- [sumy](https://github.com/miso-belica/sumy) - A module for automatic summarization of text documents and HTML pages.
- [trafilatura](https://github.com/adbar/trafilatura) - A tool for gathering text and metadata from the web, with built-in content filtering.
## Email
_Libraries for sending and parsing email, and mail server management._
- [modoboa](https://github.com/modoboa/modoboa) - A mail hosting and management platform including a modern Web UI.
- [yagmail](https://github.com/kootenpv/yagmail) - Yet another Gmail/SMTP client.
**Database & Storage**
## ORM
_Libraries that implement Object-Relational Mapping or data mapping techniques._
- Relational Databases
- [django.db.models](https://docs.djangoproject.com/en/dev/topics/db/models/) - The Django ORM.
- [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) - The Python SQL Toolkit and Object Relational Mapper.
- [awesome-sqlalchemy](https://github.com/dahlia/awesome-sqlalchemy)
- [dataset](https://github.com/pudo/dataset) - Store Python dicts in a database - works with SQLite, MySQL, and PostgreSQL.
- [peewee](https://github.com/coleifer/peewee) - A small, expressive ORM.
- [pony](https://github.com/ponyorm/pony/) - ORM that provides a generator-oriented interface to SQL.
- [sqlmodel](https://github.com/fastapi/sqlmodel) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy.
- [tortoise-orm](https://github.com/tortoise/tortoise-orm) - An easy-to-use asyncio ORM inspired by Django, with relations support.
- NoSQL Databases
- [beanie](https://github.com/BeanieODM/beanie) - An asynchronous Python object-document mapper (ODM) for MongoDB.
- [mongoengine](https://github.com/MongoEngine/mongoengine) - A Python Object-Document-Mapper for working with MongoDB.
- [pynamodb](https://github.com/pynamodb/PynamoDB) - A Pythonic interface for [Amazon DynamoDB](https://aws.amazon.com/dynamodb/).
## Database Drivers
_Libraries for connecting and operating databases._
- MySQL - [awesome-mysql](https://github.com/shlomi-noach/awesome-mysql)
- [mysqlclient](https://github.com/PyMySQL/mysqlclient) - MySQL connector with Python 3 support ([mysql-python](https://sourceforge.net/projects/mysql-python/) fork).
- [pymysql](https://github.com/PyMySQL/PyMySQL) - A pure Python MySQL driver compatible to mysql-python.
- PostgreSQL - [awesome-postgres](https://github.com/dhamaniasad/awesome-postgres)
- [psycopg](https://github.com/psycopg/psycopg) - The most popular PostgreSQL adapter for Python.
- SQlite - [awesome-sqlite](https://github.com/planetopendata/awesome-sqlite)
- [sqlite-utils](https://github.com/simonw/sqlite-utils) - Python CLI utility and library for manipulating SQLite databases.
- [sqlite3](https://docs.python.org/3/library/sqlite3.html) - (Python standard library) SQlite interface compliant with DB-API 2.0.
- Other Relational Databases
- [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse.
- [mssql-python](https://github.com/microsoft/mssql-python) - Official Microsoft driver for SQL Server and Azure SQL, built on ODBC for high performance and low memory usage.
- NoSQL Databases
- [cassandra-driver](https://github.com/apache/cassandra-python-driver) - The Python Driver for Apache Cassandra.
- [django-mongodb-backend](https://github.com/mongodb/django-mongodb-backend) - Official MongoDB database backend for Django.
- [pymongo](https://github.com/mongodb/mongo-python-driver) - The official Python client for MongoDB.
- [redis-py](https://github.com/redis/redis-py) - The Python client for Redis.
## Database
_Databases implemented in Python._
- [chromadb](https://github.com/chroma-core/chroma) - An open-source embedding database for building AI applications with embeddings and semantic search.
- [duckdb](https://github.com/duckdb/duckdb) - An in-process SQL OLAP database management system; optimized for analytics and fast queries, similar to SQLite but for analytical workloads.
- [pickledb](https://github.com/patx/pickledb) - A simple and lightweight key-value store for Python.
- [tinydb](https://github.com/msiemens/tinydb) - A tiny, document-oriented database.
- [ZODB](https://github.com/zopefoundation/ZODB) - A native object database for Python. A key-value and object graph database.
## Caching
_Libraries for caching data._
- [cachetools](https://github.com/tkem/cachetools) - Extensible memoizing collections and decorators.
- [django-cacheops](https://github.com/Suor/django-cacheops) - A slick ORM cache with automatic granular event-driven invalidation.
- [dogpile.cache](https://github.com/sqlalchemy/dogpile.cache) - dogpile.cache is a next generation replacement for Beaker made by the same authors.
- [python-diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis.
## Search
_Libraries and software for indexing and performing search queries on data._
- [django-haystack](https://github.com/django-haystack/django-haystack) - Modular search for Django.
- [elasticsearch-py](https://github.com/elastic/elasticsearch-py) - The official low-level Python client for [Elasticsearch](https://www.elastic.co/products/elasticsearch).
- [pysolr](https://github.com/django-haystack/pysolr) - A lightweight Python wrapper for [Apache Solr](https://lucene.apache.org/solr/).
## Serialization
_Libraries for serializing complex data types._
- [marshmallow](https://github.com/marshmallow-code/marshmallow) - A lightweight library for converting complex objects to and from simple Python datatypes.
- [msgpack](https://github.com/msgpack/msgpack-python) - MessagePack serializer implementation for Python.
- [orjson](https://github.com/ijl/orjson) - Fast, correct JSON library.
**Data & Science**
## Data Analysis
_Libraries for data analysis._
- General
- [aws-sdk-pandas](https://github.com/aws/aws-sdk-pandas) - Pandas on AWS.
- [datasette](https://github.com/simonw/datasette) - An open source multi-tool for exploring and publishing data.
- [desbordante](https://github.com/desbordante/desbordante-core/) - An open source data profiler for complex pattern discovery.
- [ibis](https://github.com/ibis-project/ibis) - A portable Python dataframe library with a single API for 20+ backends.
- [modin](https://github.com/modin-project/modin) - A drop-in pandas replacement that scales workflows by changing a single line of code.
- [pandas](https://github.com/pandas-dev/pandas) - A library providing high-performance, easy-to-use data structures and data analysis tools.
- [pathway](https://github.com/pathwaycom/pathway) - Real-time data processing framework for Python with reactive dataflows.
- [polars](https://github.com/pola-rs/polars) - A fast DataFrame library implemented in Rust with a Python API.
- Financial Data
- [akshare](https://github.com/akfamily/akshare) - A financial data interface library, built for human beings!
- [edgartools](https://github.com/dgunning/edgartools) - Library for downloading structured data from SEC EDGAR filings and XBRL financial statements.
- [openbb](https://github.com/OpenBB-finance/OpenBB) - A financial data platform for analysts, quants and AI agents.
- [yfinance](https://github.com/ranaroussi/yfinance) - Easy Pythonic way to download market and financial data from Yahoo Finance.
## Data Validation
_Libraries for validating data. Used for forms in many cases._
- [cerberus](https://github.com/pyeve/cerberus) - A lightweight and extensible data validation library.
- [jsonschema](https://github.com/python-jsonschema/jsonschema) - An implementation of [JSON Schema](http://json-schema.org/) for Python.
- [pandera](https://github.com/unionai-oss/pandera) - A data validation library for dataframes, with support for pandas, polars, and Spark.
- [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints.
## Data Visualization
_Libraries for visualizing data. Also see [awesome-javascript](https://github.com/sorrycc/awesome-javascript#data-visualization)._
- Plotting
- [altair](https://github.com/vega/altair) - Declarative statistical visualization library for Python.
- [bokeh](https://github.com/bokeh/bokeh) - Interactive Web Plotting for Python.
- [bqplot](https://github.com/bqplot/bqplot) - Interactive Plotting Library for the Jupyter Notebook.
- [matplotlib](https://github.com/matplotlib/matplotlib) - A Python 2D plotting library.
- [plotly](https://github.com/plotly/plotly.py) - Interactive graphing library for Python.
- [plotnine](https://github.com/has2k1/plotnine) - A grammar of graphics for Python based on ggplot2.
- [pygal](https://github.com/Kozea/pygal) - A Python SVG Charts Creator.
- [pyqtgraph](https://github.com/pyqtgraph/pyqtgraph) - Interactive and realtime 2D/3D/Image plotting and science/engineering widgets.
- [seaborn](https://github.com/mwaskom/seaborn) - Statistical data visualization using Matplotlib.
- [ultraplot](https://github.com/ultraplot/UltraPlot) - Matplotlib wrapper for publication-ready scientific figures with minimal code. Includes advanced subplot management, panel layouts, and batteries-included geoscience plotting.
- [vispy](https://github.com/vispy/vispy) - High-performance scientific visualization based on OpenGL.
- Specialized
- [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support.
- [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](http://www.graphviz.org/).
- Dashboards and Apps
- [gradio](https://github.com/gradio-app/gradio) - Build and share machine learning apps, all in Python.
- [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes.
## Geolocation
_Libraries for geocoding addresses and working with latitudes and longitudes._
- [django-countries](https://github.com/SmileyChris/django-countries) - A Django app that provides a country field for models and forms.
- [geodjango](https://docs.djangoproject.com/en/dev/ref/contrib/gis/) - A world-class geographic web framework.
- [geojson](https://github.com/jazzband/geojson) - Python bindings and utilities for GeoJSON.
- [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.
## Science
_Libraries for scientific computing. Also see [Python-for-Scientists](https://github.com/TomNicholas/Python-for-Scientists)._
- Core
- [numba](https://github.com/numba/numba) - Python JIT compiler to LLVM aimed at scientific Python.
- [numpy](https://github.com/numpy/numpy) - A fundamental package for scientific computing with Python.
- [scipy](https://github.com/scipy/scipy) - A Python-based ecosystem of open-source software for mathematics, science, and engineering.
- [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python.
- [sympy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics.
- Biology and Chemistry
- [biopython](https://github.com/biopython/biopython) - Biopython is a set of freely available tools for biological computation.
- [cclib](https://github.com/cclib/cclib) - A library for parsing and interpreting the results of computational chemistry packages.
- [openbabel](https://github.com/openbabel/openbabel) - A chemical toolbox designed to speak the many languages of chemical data.
- [rdkit](https://github.com/rdkit/rdkit) - Cheminformatics and Machine Learning Software.
- Physics and Engineering
- [astropy](https://github.com/astropy/astropy) - A community Python library for Astronomy.
- [obspy](https://github.com/obspy/obspy) - A Python toolbox for seismology.
- [pydy](https://github.com/pydy/pydy) - Short for Python Dynamics, used to assist with workflow in the modeling of dynamic motion.
- [PythonRobotics](https://github.com/AtsushiSakai/PythonRobotics) - This is a compilation of various robotics algorithms with visualizations.
- Simulation and Modeling
- [pathsim](https://github.com/pathsim/pathsim) - A block-based system modeling and simulation framework with a browser-based visual editor.
- [pymc](https://github.com/pymc-devs/pymc) - Probabilistic programming and Bayesian modeling in Python.
- [simpy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework.
- Other
- [colour](https://github.com/colour-science/colour) - Implementing a comprehensive number of colour theory transformations and algorithms.
- [manim](https://github.com/ManimCommunity/manim) - An animation engine for explanatory math videos.
- [networkx](https://github.com/networkx/networkx) - A high-productivity software for complex networks.
- [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane.
## Quantum Computing
_Libraries for quantum computing._
- [Cirq](https://github.com/quantumlib/Cirq) — A Google-developed framework focused on hardware-aware quantum circuit design for NISQ devices.
- [pennylane](https://github.com/PennyLaneAI/pennylane) — A hybrid quantum-classical machine learning library with automatic differentiation support.
- [qiskit](https://github.com/Qiskit/qiskit) — An IBM-backed quantum SDK for building, simulating, and running circuits on real quantum hardware.
- [qutip](https://github.com/qutip/qutip) - Quantum Toolbox in Python.
**Developer Tools**
## Algorithms and Design Patterns
_Python implementation of data structures, algorithms and design patterns. Also see [awesome-algorithms](https://github.com/tayllan/awesome-algorithms)._
- Algorithms
- [algorithms](https://github.com/keon/algorithms) - Minimal examples of data structures and algorithms.
- [sortedcontainers](https://github.com/grantjenks/python-sortedcontainers) - Fast and pure-Python implementation of sorted collections.
- [thealgorithms](https://github.com/TheAlgorithms/Python) - All Algorithms implemented in Python.
- Design Patterns
- [python-cqrs](https://github.com/pypatterns/python-cqrs) - Event-Driven Architecture Framework with CQRS/CQS, Transaction Outbox, Saga orchestration.
- [python-patterns](https://github.com/faif/python-patterns) - A collection of design patterns in Python.
- [transitions](https://github.com/pytransitions/transitions) - A lightweight, object-oriented finite state machine implementation.
## Interactive Interpreter
_Interactive Python interpreters (REPL)._
- [jupyter](https://github.com/jupyter/notebook) - A rich toolkit to help you make the most out of using Python interactively.
- [awesome-jupyter](https://github.com/markusschanta/awesome-jupyter)
- [marimo](https://github.com/marimo-team/marimo) - Transform data and train models, feels like a next-gen notebook, stored as Git-friendly Python.
- [ptpython](https://github.com/prompt-toolkit/ptpython) - Advanced Python REPL built on top of the [python-prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit).
## Code Analysis
_Tools of static analysis, linters and code quality checkers. Also see [awesome-static-analysis](https://github.com/analysis-tools-dev/static-analysis)._
- Code Analysis
- [code2flow](https://github.com/scottrogowski/code2flow) - Turn your Python and JavaScript code into DOT flowcharts.
- [prospector](https://github.com/prospector-dev/prospector) - A tool to analyze Python code.
- [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code.
- Code Linters
- [bandit](https://github.com/PyCQA/bandit) - A tool designed to find common security issues in Python code.
- [flake8](https://github.com/PyCQA/flake8) - A wrapper around `pycodestyle`, `pyflakes` and McCabe.
- [awesome-flake8-extensions](https://github.com/DmytroLitvinov/awesome-flake8-extensions)
- [pylint](https://github.com/pylint-dev/pylint) - A fully customizable source code analyzer.
- [ruff](https://github.com/astral-sh/ruff) - An extremely fast Python linter and code formatter.
- Code Formatters
- [black](https://github.com/psf/black) - The uncompromising Python code formatter.
- [isort](https://github.com/PyCQA/isort) - A Python utility / library to sort imports.
- Static Type Checkers, also see [awesome-python-typing](https://github.com/typeddjango/awesome-python-typing)
- [mypy](https://github.com/python/mypy) - Check variable types during compile time.
- [pyre-check](https://github.com/facebook/pyre-check) - Performant type checking.
- [ty](https://github.com/astral-sh/ty) - An extremely fast Python type checker and language server.
- [typeshed](https://github.com/python/typeshed) - Collection of library stubs for Python, with static types.
- Refactoring
- [rope](https://github.com/python-rope/rope) - Rope is a python refactoring library.
- Static Type Annotations Generators
- [monkeytype](https://github.com/Instagram/MonkeyType) - A system for Python that generates static type annotations by collecting runtime types.
- [pytype](https://github.com/google/pytype) - Pytype checks and infers types for Python code - without requiring type annotations.
## Testing
_Libraries for testing codebases and generating test data._
- Testing Frameworks
- [hypothesis](https://github.com/HypothesisWorks/hypothesis) - Hypothesis is an advanced Quickcheck style property based testing library.
- [pytest](https://github.com/pytest-dev/pytest) - A mature full-featured Python testing tool.
- [robotframework](https://github.com/robotframework/robotframework) - A generic test automation framework.
- [scanapi](https://github.com/scanapi/scanapi) - Automated Testing and Documentation for your REST API.
- [unittest](https://docs.python.org/3/library/unittest.html) - (Python standard library) Unit testing framework.
- Test Runners
- [nox](https://github.com/wntrblm/nox) - Flexible test automation for Python.
- [tox](https://github.com/tox-dev/tox) - Auto builds and tests distributions in multiple Python versions
- GUI / Web Testing
- [locust](https://github.com/locustio/locust) - Scalable user load testing tool written in Python.
- [playwright](https://github.com/microsoft/playwright-python) - Python version of the Playwright testing and automation library.
- [pyautogui](https://github.com/asweigart/pyautogui) - PyAutoGUI is a cross-platform GUI automation Python module for human beings.
- [schemathesis](https://github.com/schemathesis/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications.
- [selenium](https://github.com/SeleniumHQ/selenium) - Python bindings for [Selenium](https://selenium.dev/) [WebDriver](https://selenium.dev/documentation/webdriver/).
- Mock
- [freezegun](https://github.com/spulec/freezegun) - Travel through time by mocking the datetime module.
- [mock](https://docs.python.org/3/library/unittest.mock.html) - (Python standard library) A mocking and patching library.
- [mocket](https://github.com/mindflayer/python-mocket) - A socket mock framework with gevent/asyncio/SSL support.
- [responses](https://github.com/getsentry/responses) - A utility library for mocking out the requests Python library.
- [vcrpy](https://github.com/kevin1024/vcrpy) - Record and replay HTTP interactions on your tests.
- Object Factories
- [factory_boy](https://github.com/FactoryBoy/factory_boy) - A test fixtures replacement for Python.
- [polyfactory](https://github.com/litestar-org/polyfactory) - mock data generation library with support to classes (continuation of `pydantic-factories`)
- Code Coverage
- [coverage](https://github.com/coveragepy/coveragepy) - Code coverage measurement.
- Fake Data
- [faker](https://github.com/joke2k/faker) - A Python package that generates fake data.
- [mimesis](https://github.com/lk-geimfari/mimesis) - is a Python library that help you generate fake data.
## Debugging Tools
_Libraries for debugging code._
- pdb-like Debugger
- [ipdb](https://github.com/gotcha/ipdb) - IPython-enabled [pdb](https://docs.python.org/3/library/pdb.html).
- [pudb](https://github.com/inducer/pudb) - A full-screen, console-based Python debugger.
- Tracing
- [manhole](https://github.com/ionelmc/python-manhole) - Debugging UNIX socket connections and present the stacktraces for all threads and an interactive prompt.
- [python-hunter](https://github.com/ionelmc/python-hunter) - A flexible code tracing toolkit.
- Profiler
- [py-spy](https://github.com/benfred/py-spy) - A sampling profiler for Python programs. Written in Rust.
- [scalene](https://github.com/plasma-umass/scalene) - A high-performance, high-precision CPU, GPU, and memory profiler for Python.
- Others
- [django-debug-toolbar](https://github.com/django-commons/django-debug-toolbar) - Display various debug information for Django.
- [flask-debugtoolbar](https://github.com/pallets-eco/flask-debugtoolbar) - A port of the django-debug-toolbar to flask.
- [icecream](https://github.com/gruns/icecream) - Inspect variables, expressions, and program execution with a single, simple function call.
- [memory_graph](https://github.com/bterwijn/memory_graph) - Visualize Python data at runtime to debug references, mutability, and aliasing.
## Build Tools
_Compile software from source code._
- [bitbake](https://github.com/openembedded/bitbake) - A make-like build tool for embedded Linux.
- [invoke](https://github.com/pyinvoke/invoke) - A tool for managing shell-oriented subprocesses and organizing executable Python code into CLI-invokable tasks.
- [platformio](https://github.com/platformio/platformio-core) - A console tool to build code with different development platforms.
- [pybuilder](https://github.com/pybuilder/pybuilder) - A continuous build tool written in pure Python.
- [doit](https://github.com/pydoit/doit) - A task runner and build tool.
- [scons](https://github.com/SCons/scons) - A software construction tool.
## Documentation
_Libraries for generating project documentation._
- [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator.
- [awesome-sphinxdoc](https://github.com/ygzgxyz/awesome-sphinxdoc)
- [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code.
- [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator.
- [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries.
**DevOps**
## DevOps Tools
_Software and libraries for DevOps._
- Cloud Providers
- [awscli](https://github.com/aws/aws-cli) - Universal Command Line Interface for Amazon Web Services.
- [boto3](https://github.com/boto/boto3) - Python interface to Amazon Web Services.
- Configuration Management
- [ansible](https://github.com/ansible/ansible) - A radically simple IT automation platform.
- [cloudinit](https://github.com/canonical/cloud-init) - A multi-distribution package that handles early initialization of a cloud instance.
- [openstack](https://www.openstack.org/) - Open source software for building private and public clouds.
- [pyinfra](https://github.com/pyinfra-dev/pyinfra) - A versatile CLI tools and python libraries to automate infrastructure.
- [saltstack](https://github.com/saltstack/salt) - Infrastructure automation and management system.
- Deployment
- [chalice](https://github.com/aws/chalice) - A Python serverless microframework for AWS.
- [fabric](https://github.com/fabric/fabric) - A simple, Pythonic tool for remote execution and deployment.
- Monitoring and Processes
- [psutil](https://github.com/giampaolo/psutil) - A cross-platform process and system utilities module.
- [sentry-python](https://github.com/getsentry/sentry-python) - Sentry SDK for Python.
- [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python.
- [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX.
- Other
- [borg](https://github.com/borgbackup/borg) - A deduplicating archiver with compression and encryption.
- [chaostoolkit](https://github.com/chaostoolkit/chaostoolkit) - A Chaos Engineering toolkit & Orchestration for Developers.
- [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks.
## Distributed Computing
_Frameworks and libraries for Distributed Computing._
- Batch Processing
- [dask](https://github.com/dask/dask) - A flexible parallel computing library for analytic computing.
- [luigi](https://github.com/spotify/luigi) - A module that helps you build complex pipelines of batch jobs.
- [mpi4py](https://github.com/mpi4py/mpi4py) - Python bindings for MPI.
- [pyspark](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/) Python API.
- [joblib](https://github.com/joblib/joblib) - A set of tools to provide lightweight pipelining in Python.
- [ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem.
## Task Queues
_Libraries for working with task queues._
- [celery](https://github.com/celery/celery) - An asynchronous task queue/job queue based on distributed message passing.
- [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery.
- [dramatiq](https://github.com/Bogdanp/dramatiq) - A fast and reliable background task processing library for Python 3.
- [huey](https://github.com/coleifer/huey) - Little multi-threaded task queue.
- [rq](https://github.com/rq/rq) - Simple job queues for Python.
## Job Schedulers
_Libraries for scheduling jobs._
- [airflow](https://github.com/apache/airflow) - Airflow is a platform to programmatically author, schedule and monitor workflows.
- [apscheduler](https://github.com/agronholm/apscheduler) - A light but powerful in-process task scheduler that lets you schedule functions.
- [dagster](https://github.com/dagster-io/dagster) - An orchestration platform for the development, production, and observation of data assets.
- [prefect](https://github.com/PrefectHQ/prefect) - A modern workflow orchestration framework that makes it easy to build, schedule and monitor robust data pipelines.
- [schedule](https://github.com/dbader/schedule) - Python job scheduling for humans.
- [SpiffWorkflow](https://github.com/sartography/SpiffWorkflow) - A powerful workflow engine implemented in pure Python.
## Logging
_Libraries for generating and working with logs._
- [logging](https://docs.python.org/3/library/logging.html) - (Python standard library) Logging facility for Python.
- [loguru](https://github.com/Delgan/loguru) - Library which aims to bring enjoyable logging in Python.
- [structlog](https://github.com/hynek/structlog) - Structured logging made easy.
## Network Virtualization
_Tools and libraries for Virtual Networking and SDN (Software Defined Networking)._
- [mininet](https://github.com/mininet/mininet) - A popular network emulator and API written in Python.
- [napalm](https://github.com/napalm-automation/napalm) - Cross-vendor API to manipulate network devices.
- [scapy](https://github.com/secdev/scapy) - A brilliant packet manipulation library.
**CLI & GUI**
## Command-line Interface Development
_Libraries for building command-line applications._
- Command-line Application Development
- [argparse](https://docs.python.org/3/library/argparse.html) - (Python standard library) Command-line option and argument parsing.
- [cement](https://github.com/datafolklabs/cement) - CLI Application Framework for Python.
- [click](https://github.com/pallets/click/) - A package for creating beautiful command line interfaces in a composable way.
- [python-fire](https://github.com/google/python-fire) - A library for creating command line interfaces from absolutely any Python object.
- [python-prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) - A library for building powerful interactive command lines.
- [typer](https://github.com/fastapi/typer) - Modern CLI framework that uses Python type hints. Built on Click and Pydantic.
- Terminal Rendering
- [alive-progress](https://github.com/rsalmei/alive-progress) - A new kind of Progress Bar, with real-time throughput, eta and very cool animations.
- [asciimatics](https://github.com/peterbrittain/asciimatics) - A package to create full-screen text UIs (from interactive forms to ASCII animations).
- [colorama](https://github.com/tartley/colorama) - Cross-platform colored terminal text.
- [rich](https://github.com/Textualize/rich) - Python library for rich text and beautiful formatting in the terminal. Also provides a great `RichHandler` log handler.
- [textual](https://github.com/Textualize/textual) - A framework for building interactive user interfaces that run in the terminal and the browser.
- [tqdm](https://github.com/tqdm/tqdm) - Fast, extensible progress bar for loops and CLI.
## Command-line Tools
_Useful CLI-based tools for productivity._
- Productivity Tools
- [cookiecutter](https://github.com/cookiecutter/cookiecutter) - A command-line utility that creates projects from cookiecutters (project templates).
- [copier](https://github.com/copier-org/copier) - A library and command-line utility for rendering projects templates.
- [doitlive](https://github.com/sloria/doitlive) - A tool for live presentations in the terminal.
- [thefuck](https://github.com/nvbn/thefuck) - Correcting your previous console command.
- [tmuxp](https://github.com/tmux-python/tmuxp) - A [tmux](https://github.com/tmux/tmux) session manager.
- [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered shell. Full-featured and cross-platform.
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A command-line program to download videos from YouTube and other video sites, a fork of youtube-dl.
- CLI Enhancements
- [httpie](https://github.com/httpie/cli) - A command line HTTP client, a user-friendly cURL replacement.
- [iredis](https://github.com/laixintao/iredis) - Redis CLI with autocompletion and syntax highlighting.
- [litecli](https://github.com/dbcli/litecli) - SQLite CLI with autocompletion and syntax highlighting.
- [mycli](https://github.com/dbcli/mycli) - MySQL CLI with autocompletion and syntax highlighting.
- [pgcli](https://github.com/dbcli/pgcli) - PostgreSQL CLI with autocompletion and syntax highlighting.
## GUI Development
_Libraries for working with graphical user interface applications._
- Desktop
- [customtkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter.
- [dearpygui](https://github.com/hoffstadt/DearPyGui) - A Simple GPU accelerated Python GUI framework
- [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML.
- [kivy](https://github.com/kivy/kivy) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS.
- [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python.
- [pygobject](https://github.com/GNOME/pygobject) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3).
- [PyQt](https://www.riverbankcomputing.com/static/Docs/PyQt6/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework.
- [pyside](https://github.com/pyside/pyside-setup) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), this is same as PyQt but it's the official binding with different licensing.
- [tkinter](https://docs.python.org/3/library/tkinter.html) - (Python standard library) The standard Python interface to the Tcl/Tk GUI toolkit.
- [toga](https://github.com/beeware/toga) - A Python native, OS native GUI toolkit.
- [wxPython](https://github.com/wxWidgets/Phoenix) - A blending of the wxWidgets C++ class library with the Python.
- Web-based
- [flet](https://github.com/flet-dev/flet) - Cross-platform GUI framework for building modern apps in pure Python.
- [nicegui](https://github.com/zauberzeug/nicegui) - An easy-to-use, Python-based UI framework, which shows up in your web browser.
- [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component.
- Terminal
- [curses](https://docs.python.org/3/library/curses.html) - Built-in wrapper for [ncurses](http://www.gnu.org/software/ncurses/) used to create terminal GUI applications.
- [urwid](https://github.com/urwid/urwid) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc.
- Wrappers
- [gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line.
**Text & Documents**
## Text Processing
_Libraries for parsing and manipulating plain texts._
- General
- [babel](https://github.com/python-babel/babel) - An internationalization library for Python.
- [chardet](https://github.com/chardet/chardet) - Python 2/3 compatible character encoding detector.
- [difflib](https://docs.python.org/3/library/difflib.html) - (Python standard library) Helpers for computing deltas.
- [ftfy](https://github.com/rspeer/python-ftfy) - Makes Unicode text less broken and more consistent automagically.
- [pangu.py](https://github.com/vinta/pangu.py) - Paranoid text spacing.
- [pyfiglet](https://github.com/pwaller/pyfiglet) - An implementation of figlet written in Python.
- [pypinyin](https://github.com/mozillazg/python-pinyin) - Convert Chinese hanzi (漢字) to pinyin (拼音).
- [python-slugify](https://github.com/un33k/python-slugify) - A Python slugify library that translates unicode to ASCII.
- [textdistance](https://github.com/life4/textdistance) - Compute distance between sequences with 30+ algorithms.
- [unidecode](https://github.com/avian2/unidecode) - ASCII transliterations of Unicode text.
- Unique identifiers
- [sqids](https://github.com/sqids/sqids-python) - A library for generating short unique IDs from numbers.
- [shortuuid](https://github.com/skorokithakis/shortuuid) - A generator library for concise, unambiguous and URL-safe UUIDs.
- Parser
- [pygments](https://github.com/pygments/pygments) - A generic syntax highlighter.
- [pyparsing](https://github.com/pyparsing/pyparsing) - A general purpose framework for generating parsers.
- [python-nameparser](https://github.com/derek73/python-nameparser) - Parsing human names into their individual components.
- [python-phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) - Parsing, formatting, storing and validating international phone numbers.
- [python-user-agents](https://github.com/selwin/python-user-agents) - Browser user agent parser.
- [sqlparse](https://github.com/andialbrecht/sqlparse) - A non-validating SQL parser.
## HTML Manipulation
_Libraries for working with HTML and XML._
- [beautifulsoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) - Providing Pythonic idioms for iterating, searching, and modifying HTML or XML.
- [cssutils](https://github.com/jaraco/cssutils) - A CSS library for Python.
- [justhtml](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works.
- [lxml](https://github.com/lxml/lxml) - A very fast, easy-to-use and versatile library for handling HTML and XML.
- [markupsafe](https://github.com/pallets/markupsafe) - Implements a XML/HTML/XHTML Markup safe string for Python.
- [pyquery](https://github.com/gawel/pyquery) - A jQuery-like library for parsing HTML.
- [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON.
## File Format Processing
_Libraries for parsing and manipulating specific text formats._
- General
- [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data.
- [kreuzberg](https://github.com/kreuzberg-dev/kreuzberg) - High-performance document extraction library with a Rust core, supporting 62+ formats including PDF, Office, images with OCR, HTML, email, and archives.
- [pyelftools](https://github.com/eliben/pyelftools) - Parsing and analyzing ELF files and DWARF debugging information.
- [tablib](https://github.com/jazzband/tablib) - A module for Tabular Datasets in XLS, CSV, JSON, YAML.
- Office
- [docxtpl](https://github.com/elapouya/python-docx-template) - Editing a docx document by jinja2 template
- [openpyxl](https://openpyxl.readthedocs.io/en/stable/) - A library for reading and writing Excel 2010 xlsx/xlsm/xltx/xltm files.
- [pyexcel](https://github.com/pyexcel/pyexcel) - Providing one API for reading, manipulating and writing csv, ods, xls, xlsx and xlsm files.
- [python-docx](https://github.com/python-openxml/python-docx) - Reads, queries and modifies Microsoft Word 2007/2008 docx files.
- [python-pptx](https://github.com/scanny/python-pptx) - Python library for creating and updating PowerPoint (.pptx) files.
- [xlsxwriter](https://github.com/jmcnamara/XlsxWriter) - A Python module for creating Excel .xlsx files.
- [xlwings](https://github.com/ZoomerAnalytics/xlwings) - A BSD-licensed library that makes it easy to call Python from Excel and vice versa.
- PDF
- [pdf_oxide](https://github.com/yfedoseev/pdf_oxide) - A fast PDF library for text extraction, image extraction, and markdown conversion, powered by Rust.
- [pdfminer.six](https://github.com/pdfminer/pdfminer.six) - Pdfminer.six is a community maintained fork of the original PDFMiner.
- [pikepdf](https://github.com/pikepdf/pikepdf) - A powerful library for reading and editing PDF files, based on qpdf.
- [pypdf](https://github.com/py-pdf/pypdf) - A library capable of splitting, merging, cropping, and transforming PDF pages.
- [reportlab](https://www.reportlab.com/opensource/) - Allowing Rapid creation of rich PDF documents.
- [weasyprint](https://github.com/Kozea/WeasyPrint) - A visual rendering engine for HTML and CSS that can export to PDF.
- Markdown
- [markdown-it-py](https://github.com/executablebooks/markdown-it-py) - Markdown parser with 100% CommonMark support, extensions, and syntax plugins.
- [markdown](https://github.com/waylan/Python-Markdown) - A Python implementation of John Gruber’s Markdown.
- [markitdown](https://github.com/microsoft/markitdown) - Python tool for converting files and office documents to Markdown.
- [mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown.
- Data Formats
- [csvkit](https://github.com/wireservice/csvkit) - Utilities for converting to and working with CSV.
- [pyyaml](https://github.com/yaml/pyyaml) - YAML implementations for Python.
- [tomllib](https://docs.python.org/3/library/tomllib.html) - (Python standard library) Parse TOML files.
## File Manipulation
_Libraries for file manipulation._
- [mimetypes](https://docs.python.org/3/library/mimetypes.html) - (Python standard library) Map filenames to MIME types.
- [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) A cross-platform, object-oriented path library.
- [python-magic](https://github.com/ahupp/python-magic) - A Python interface to the libmagic file type identification library.
- [watchdog](https://github.com/gorakhargosh/watchdog) - API and shell utilities to monitor file system events.
- [watchfiles](https://github.com/samuelcolvin/watchfiles) - Simple, modern and fast file watching and code reload in python.
**Media**
## Image Processing
_Libraries for manipulating images._
- [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](http://www.pythonware.com/products/pil/) fork.
- [pymatting](https://github.com/pymatting/pymatting) - A library for alpha matting.
- [python-barcode](https://github.com/WhyNotHugo/python-barcode) - Create barcodes in Python with no extra dependencies.
- [python-qrcode](https://github.com/lincolnloop/python-qrcode) - A pure Python QR Code generator.
- [pyvips](https://github.com/libvips/pyvips) - A fast image processing library with low memory needs.
- [scikit-image](https://github.com/scikit-image/scikit-image) - A Python library for (scientific) image processing.
- [thumbor](https://github.com/thumbor/thumbor) - A smart imaging service. It enables on-demand crop, re-sizing and flipping of images.
- [wand](https://github.com/emcconville/wand) - Python bindings for [MagickWand](http://www.imagemagick.org/script/magick-wand.php), C API for ImageMagick.
## Audio & Video Processing
_Libraries for manipulating audio, video, and their metadata._
- Audio
- [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS.
- [librosa](https://github.com/librosa/librosa) - Python library for audio and music analysis.
- [matchering](https://github.com/sergree/matchering) - A library for automated reference audio mastering.
- [pydub](https://github.com/jiaaro/pydub) - Manipulate audio with a simple and easy high level interface.
- Video
- [moviepy](https://github.com/Zulko/moviepy) - A module for script-based movie editing with many formats, including animated GIFs.
- [vidgear](https://github.com/abhiTronix/vidgear) - Most Powerful multi-threaded Video Processing framework.
- Metadata
- [beets](https://github.com/beetbox/beets) - A music library manager and [MusicBrainz](https://musicbrainz.org/) tagger.
- [mutagen](https://github.com/quodlibet/mutagen) - A Python module to handle audio metadata.
- [tinytag](https://github.com/devsnd/tinytag) - A library for reading music meta data of MP3, OGG, FLAC and Wave files.
## Game Development
_Awesome game development libraries._
- [arcade](https://github.com/pythonarcade/arcade) - Arcade is a modern Python framework for crafting games with compelling graphics and sound.
- [panda3d](https://github.com/panda3d/panda3d) - 3D game engine developed by Disney.
- [py-sdl2](https://github.com/py-sdl/py-sdl2) - A ctypes based wrapper for the SDL2 library.
- [pygame](https://github.com/pygame/pygame) - Pygame is a set of Python modules designed for writing games.
- [pyopengl](https://github.com/mcfletch/pyopengl) - Python ctypes bindings for OpenGL and it's related APIs.
- [renpy](https://github.com/renpy/renpy) - A Visual Novel engine.
**Python Language**
## Implementations
_Implementations of Python._
- [cpython](https://github.com/python/cpython) - Default, most widely used implementation of the Python programming language written in C.
- [cython](https://github.com/cython/cython) - Optimizing Static Compiler for Python.
- [ironpython](https://github.com/IronLanguages/ironpython3) - Implementation of the Python programming language written in C#.
- [micropython](https://github.com/micropython/micropython) - A lean and efficient Python programming language implementation.
- [pypy](https://github.com/pypy/pypy) - A very fast and compliant implementation of the Python language.
## Built-in Classes Enhancement
_Libraries for enhancing Python built-in classes._
- [attrs](https://github.com/python-attrs/attrs) - Replacement for `__init__`, `__eq__`, `__repr__`, etc. boilerplate in class definitions.
- [bidict](https://github.com/jab/bidict) - Efficient, Pythonic bidirectional map data structures and related functionality.
- [box](https://github.com/cdgriffith/Box) - Python dictionaries with advanced dot notation access.
## Functional Programming
_Functional Programming with Python._
- [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming.
- [functools](https://docs.python.org/3/library/functools.html) - (Python standard library) Higher-order functions and operations on callable objects.
- [funcy](https://github.com/Suor/funcy) - A fancy and practical functional tools.
- [more-itertools](https://github.com/erikrose/more-itertools) - More routines for operating on iterables, beyond `itertools`.
- [returns](https://github.com/dry-python/returns) - A set of type-safe monads, transformers, and composition utilities.
- [toolz](https://github.com/pytoolz/toolz) - A collection of functional utilities for iterators, functions, and dictionaries. Also available as [cytoolz](https://github.com/pytoolz/cytoolz/) for Cython-accelerated performance.
## Asynchronous Programming
_Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio)._
- [anyio](https://github.com/agronholm/anyio) - A high-level async concurrency and networking framework that works on top of asyncio or trio.
- [asyncio](https://docs.python.org/3/library/asyncio.html) - (Python standard library) Asynchronous I/O, event loop, coroutines and tasks.
- [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio)
- [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html) - (Python standard library) A high-level interface for asynchronously executing callables.
- [gevent](https://github.com/gevent/gevent) - A coroutine-based Python networking library that uses [greenlet](https://github.com/python-greenlet/greenlet).
- [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) - (Python standard library) Process-based parallelism.
- [trio](https://github.com/python-trio/trio) - A friendly library for async concurrency and I/O.
- [twisted](https://github.com/twisted/twisted) - An event-driven networking engine.
- [uvloop](https://github.com/MagicStack/uvloop) - Ultra fast asyncio event loop.
## Date and Time
_Libraries for working with dates and times._
- [dateparser](https://github.com/scrapinghub/dateparser) - A Python parser for human-readable dates in dozens of languages.
- [dateutil](https://github.com/dateutil/dateutil) - Extensions to the standard Python [datetime](https://docs.python.org/3/library/datetime.html) module.
- [pendulum](https://github.com/python-pendulum/pendulum) - Python datetimes made easy.
- [zoneinfo](https://docs.python.org/3/library/zoneinfo.html) - (Python standard library) IANA time zone support. Brings the [tz database](https://en.wikipedia.org/wiki/Tz_database) into Python.
**Python Toolchain**
## Environment Management
_Libraries for Python version and virtual environment management._
- [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management.
- [pyenv-win](https://github.com/pyenv-win/pyenv-win) - Pyenv for Windows, Simple Python version management.
- [uv](https://github.com/astral-sh/uv) - An extremely fast Python version, package and project manager, written in Rust.
- [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments.
## Package Management
_Libraries for package and dependency management._
- [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager.
- [pip](https://github.com/pypa/pip) - The package installer for Python.
- [pipx](https://github.com/pypa/pipx) - Install and Run Python Applications in Isolated Environments. Like `npx` in Node.js.
- [poetry](https://github.com/python-poetry/poetry) - Python dependency management and packaging made easy.
- [uv](https://github.com/astral-sh/uv) - An extremely fast Python version, package and project manager, written in Rust.
## Package Repositories
_Local PyPI repository server and proxies._
- [bandersnatch](https://github.com/pypa/bandersnatch/) - PyPI mirroring tool provided by Python Packaging Authority (PyPA).
- [devpi](https://github.com/devpi/devpi) - PyPI server and packaging/testing/release tool.
- [warehouse](https://github.com/pypa/warehouse) - Next generation Python Package Repository (PyPI).
## Distribution
_Libraries to create packaged executables for release distribution._
- [cx-Freeze](https://github.com/marcelotduarte/cx_Freeze) - It is a Python tool that converts Python scripts into standalone executables and installers for Windows, macOS, and Linux.
- [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions).
- [pyarmor](https://github.com/dashingsoft/pyarmor) - A tool used to obfuscate python scripts, bind obfuscated scripts to fixed machine or expire obfuscated scripts.
- [pyinstaller](https://github.com/pyinstaller/pyinstaller) - Converts Python programs into stand-alone executables (cross-platform).
- [shiv](https://github.com/linkedin/shiv) - A command line utility for building fully self-contained zipapps (PEP 441), but with all their dependencies included.
## Configuration Files
_Libraries for storing and parsing configuration options._
- [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser.
- [dynaconf](https://github.com/dynaconf/dynaconf) - Dynaconf is a configuration manager with plugins for Django, Flask and FastAPI.
- [hydra](https://github.com/facebookresearch/hydra) - Hydra is a framework for elegantly configuring complex applications.
- [python-decouple](https://github.com/HBNetwork/python-decouple) - Strict separation of settings from code.
- [python-dotenv](https://github.com/theskumar/python-dotenv) - Reads key-value pairs from a `.env` file and sets them as environment variables.
**Security**
## Cryptography
- [cryptography](https://github.com/pyca/cryptography) - A package designed to expose cryptographic primitives and recipes to Python developers.
- [paramiko](https://github.com/paramiko/paramiko) - The leading native Python SSHv2 protocol library.
- [pynacl](https://github.com/pyca/pynacl) - Python binding to the Networking and Cryptography (NaCl) library.
## Penetration Testing
_Frameworks and tools for penetration testing._
- [mitmproxy](https://github.com/mitmproxy/mitmproxy) - An interactive TLS-capable intercepting HTTP proxy for penetration testers and software developers.
- [setoolkit](https://github.com/trustedsec/social-engineer-toolkit) - A toolkit for social engineering.
- [sherlock](https://github.com/sherlock-project/sherlock) - Hunt down social media accounts by username across social networks.
- [sqlmap](https://github.com/sqlmapproject/sqlmap) - Automatic SQL injection and database takeover tool.
**Miscellaneous**
## Hardware
_Libraries for programming with hardware._
- [bleak](https://github.com/hbldh/bleak) - A cross platform Bluetooth Low Energy Client for Python using asyncio.
- [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices.
## Microsoft Windows
_Python programming on Microsoft Windows._
- [pythonnet](https://github.com/pythonnet/pythonnet) - Python Integration with the .NET Common Language Runtime (CLR).
- [pywin32](https://github.com/mhammond/pywin32) - Python Extensions for Windows.
- [winpython](https://github.com/winpython/winpython) - Portable development environment for Windows 10/11.
## Miscellaneous
_Useful libraries or tools that don't fit in the categories above._
- [blinker](https://github.com/jek/blinker) - A fast Python in-process signal/event dispatching system.
- [boltons](https://github.com/mahmoud/boltons) - A set of pure-Python utilities.
- [itsdangerous](https://github.com/pallets/itsdangerous) - Various helpers to pass trusted data to untrusted environments.
- [tryton](https://github.com/tryton/tryton) - A general-purpose business framework.
# Resources
Where to discover learning resources or new Python libraries.
## Newsletters
- [Awesome Python Newsletter](http://python.libhunt.com/newsletter)
- [Pycoder's Weekly](https://pycoders.com/)
- [Python Tricks](https://realpython.com/python-tricks/)
- [Python Weekly](https://www.pythonweekly.com/)
## Podcasts
- [Django Chat](https://djangochat.com/)
- [PyPodcats](https://pypodcats.live)
- [Python Bytes](https://pythonbytes.fm)
- [Python Test](https://podcast.pythontest.com/)
- [Talk Python To Me](https://talkpython.fm/)
- [The Real Python Podcast](https://realpython.com/podcasts/rpp/)
# Contributing
Your contributions are always welcome! Please take a look at the [contribution guidelines](https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md) first.
---
If you have any question about this opinionated list, do not hesitate to contact [@VintaChen](https://twitter.com/VintaChen) on Twitter.
================================================
FILE: SPONSORSHIP.md
================================================
# Sponsor awesome-python
**The #10 most-starred repository on all of GitHub.**
awesome-python is where Python developers go to discover tools. When someone searches Google for "best Python libraries," they land here. When ChatGPT recommends Python tools, it references this list. When developers evaluate frameworks, this is the list they check.
Your sponsorship puts your product in front of developers at the exact moment they're choosing what to use.
## By the Numbers
| Metric | Value |
| ------------ | ---------------------------------------------------------------------------------------------------- |
| Stars |  |
| Forks |  |
| Watchers |  |
| Contributors |  |
Top referrers: GitHub, Google Search, YouTube, Reddit, ChatGPT — developers actively searching for and evaluating Python tools.
## Sponsorship Tiers
### Logo Sponsor — $500/month (2 slots)
Your logo and a one-line description at the top of the README, seen by every visitor.
### Link Sponsor — $150/month (5 slots)
A text link with your product name at the top of the README, right below logo sponsors.
## Past Sponsors
- [Warp](https://www.warp.dev/) - https://github.com/vinta/awesome-python/pull/2766
## Get Started
Email [vinta.chen@gmail.com](mailto:vinta.chen@gmail.com?subject=awesome-python%20Sponsorship) with your company name and preferred tier. Most sponsors are set up within 24 hours.
================================================
FILE: pyproject.toml
================================================
[project]
name = "awesome-python"
version = "0.1.0"
description = "An opinionated list of awesome Python frameworks, libraries, software and resources."
authors = [{ name = "Vinta Chen", email = "vinta.chen@gmail.com" }]
readme = "README.md"
license = "MIT"
requires-python = ">=3.13"
dependencies = []
[project.urls]
Homepage = "https://awesome-python.com/"
Repository = "https://github.com/vinta/awesome-python"
[dependency-groups]
build = ["httpx==0.28.1", "jinja2==3.1.6", "markdown-it-py==4.0.0"]
lint = ["ruff==0.15.6"]
test = ["pytest==9.0.2"]
dev = [
{ include-group = "build" },
{ include-group = "lint" },
{ include-group = "test" },
"watchdog==6.0.0",
]
[tool.pytest.ini_options]
testpaths = ["website/tests"]
pythonpath = ["website"]
[tool.ruff]
line-length = 200
================================================
FILE: website/build.py
================================================
#!/usr/bin/env python3
"""Build a single-page HTML site from README.md for the awesome-python website."""
import json
import re
import shutil
from pathlib import Path
from typing import TypedDict
from jinja2 import Environment, FileSystemLoader
from readme_parser import parse_readme, slugify
def group_categories(
parsed_groups: list[dict],
resources: list[dict],
) -> list[dict]:
"""Combine parsed groups with resources for template rendering."""
groups = list(parsed_groups)
if resources:
groups.append(
{
"name": "Resources",
"slug": slugify("Resources"),
"categories": list(resources),
}
)
return groups
class Entry(TypedDict):
name: str
url: str
description: str
category: str
group: str
stars: int | None
owner: str | None
last_commit_at: str | None
class StarData(TypedDict):
stars: int
owner: str
last_commit_at: str
fetched_at: str
GITHUB_REPO_URL_RE = re.compile(r"^https?://github\.com/([^/]+/[^/]+?)(?:\.git)?/?$")
def extract_github_repo(url: str) -> str | None:
"""Extract owner/repo from a GitHub repo URL. Returns None for non-GitHub URLs."""
m = GITHUB_REPO_URL_RE.match(url)
return m.group(1) if m else None
def load_stars(path: Path) -> dict[str, StarData]:
"""Load star data from JSON. Returns empty dict if file doesn't exist or is corrupt."""
if path.exists():
try:
return json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
return {}
return {}
def sort_entries(entries: list[dict]) -> list[dict]:
"""Sort entries by stars descending, then name ascending. No-star entries go last."""
def sort_key(entry: dict) -> tuple[int, int, str]:
stars = entry["stars"]
name = entry["name"].lower()
if stars is None:
return (1, 0, name)
return (0, -stars, name)
return sorted(entries, key=sort_key)
def extract_entries(
categories: list[dict],
groups: list[dict],
) -> list[dict]:
"""Flatten categories into individual library entries for table display.
Entries appearing in multiple categories are merged into a single entry
with lists of categories and groups.
"""
cat_to_group: dict[str, str] = {}
for group in groups:
for cat in group["categories"]:
cat_to_group[cat["name"]] = group["name"]
seen: dict[str, dict] = {} # url -> entry
entries: list[dict] = []
for cat in categories:
group_name = cat_to_group.get(cat["name"], "Other")
for entry in cat["entries"]:
url = entry["url"]
if url in seen:
existing = seen[url]
if cat["name"] not in existing["categories"]:
existing["categories"].append(cat["name"])
if group_name not in existing["groups"]:
existing["groups"].append(group_name)
else:
merged = {
"name": entry["name"],
"url": url,
"description": entry["description"],
"categories": [cat["name"]],
"groups": [group_name],
"stars": None,
"owner": None,
"last_commit_at": None,
"also_see": entry["also_see"],
}
seen[url] = merged
entries.append(merged)
return entries
def build(repo_root: str) -> None:
"""Main build: parse README, render single-page HTML via Jinja2 templates."""
repo = Path(repo_root)
website = repo / "website"
readme_text = (repo / "README.md").read_text(encoding="utf-8")
subtitle = ""
for line in readme_text.split("\n"):
stripped = line.strip()
if stripped and not stripped.startswith("#"):
subtitle = stripped
break
parsed_groups, resources = parse_readme(readme_text)
categories = [cat for g in parsed_groups for cat in g["categories"]]
total_entries = sum(c["entry_count"] for c in categories)
groups = group_categories(parsed_groups, resources)
entries = extract_entries(categories, groups)
stars_data = load_stars(website / "data" / "github_stars.json")
for entry in entries:
repo_key = extract_github_repo(entry["url"])
if repo_key and repo_key in stars_data:
sd = stars_data[repo_key]
entry["stars"] = sd["stars"]
entry["owner"] = sd["owner"]
entry["last_commit_at"] = sd.get("last_commit_at", "")
entries = sort_entries(entries)
env = Environment(
loader=FileSystemLoader(website / "templates"),
autoescape=True,
)
site_dir = website / "output"
if site_dir.exists():
shutil.rmtree(site_dir)
site_dir.mkdir(parents=True)
tpl_index = env.get_template("index.html")
(site_dir / "index.html").write_text(
tpl_index.render(
categories=categories,
resources=resources,
groups=groups,
subtitle=subtitle,
entries=entries,
total_entries=total_entries,
total_categories=len(categories),
),
encoding="utf-8",
)
static_src = website / "static"
static_dst = site_dir / "static"
if static_src.exists():
shutil.copytree(static_src, static_dst, dirs_exist_ok=True)
shutil.copy(repo / "README.md", site_dir / "llms.txt")
print(f"Built single page with {len(parsed_groups)} groups, {len(categories)} categories + {len(resources)} resources")
print(f"Total entries: {total_entries}")
print(f"Output: {site_dir}")
if __name__ == "__main__":
build(str(Path(__file__).parent.parent))
================================================
FILE: website/fetch_github_stars.py
================================================
#!/usr/bin/env python3
"""Fetch GitHub star counts and owner info for all GitHub repos in README.md."""
import json
import os
import re
import sys
from datetime import datetime, timezone
from pathlib import Path
import httpx
from build import extract_github_repo, load_stars
CACHE_MAX_AGE_HOURS = 12
DATA_DIR = Path(__file__).parent / "data"
CACHE_FILE = DATA_DIR / "github_stars.json"
README_PATH = Path(__file__).parent.parent / "README.md"
GRAPHQL_URL = "https://api.github.com/graphql"
BATCH_SIZE = 50
def extract_github_repos(text: str) -> set[str]:
"""Extract unique owner/repo pairs from GitHub URLs in markdown text."""
repos = set()
for url in re.findall(r"https?://github\.com/[^\s)\]]+", text):
repo = extract_github_repo(url.split("#")[0].rstrip("/"))
if repo:
repos.add(repo)
return repos
def save_cache(cache: dict) -> None:
"""Write the star cache to disk, creating data/ dir if needed."""
DATA_DIR.mkdir(parents=True, exist_ok=True)
CACHE_FILE.write_text(
json.dumps(cache, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
def build_graphql_query(repos: list[str]) -> str:
"""Build a GraphQL query with aliases for up to 100 repos."""
if not repos:
return ""
parts = []
for i, repo in enumerate(repos):
owner, name = repo.split("/", 1)
if '"' in owner or '"' in name:
continue
parts.append(
f'repo_{i}: repository(owner: "{owner}", name: "{name}") '
f"{{ stargazerCount owner {{ login }} defaultBranchRef {{ target {{ ... on Commit {{ committedDate }} }} }} }}"
)
if not parts:
return ""
return "query { " + " ".join(parts) + " }"
def parse_graphql_response(
data: dict,
repos: list[str],
) -> dict[str, dict]:
"""Parse GraphQL response into {owner/repo: {stars, owner}} dict."""
result = {}
for i, repo in enumerate(repos):
node = data.get(f"repo_{i}")
if node is None:
continue
default_branch = node.get("defaultBranchRef") or {}
target = default_branch.get("target") or {}
result[repo] = {
"stars": node.get("stargazerCount", 0),
"owner": node.get("owner", {}).get("login", ""),
"last_commit_at": target.get("committedDate", ""),
}
return result
def fetch_batch(
repos: list[str], *, client: httpx.Client,
) -> dict[str, dict]:
"""Fetch star data for a batch of repos via GitHub GraphQL API."""
query = build_graphql_query(repos)
if not query:
return {}
resp = client.post(GRAPHQL_URL, json={"query": query})
resp.raise_for_status()
result = resp.json()
if "errors" in result:
for err in result["errors"]:
print(f" Warning: {err.get('message', err)}", file=sys.stderr)
data = result.get("data", {})
return parse_graphql_response(data, repos)
def main() -> None:
"""Fetch GitHub stars for all repos in README.md, updating the JSON cache."""
token = os.environ.get("GITHUB_TOKEN", "")
if not token:
print("Error: GITHUB_TOKEN environment variable is required.", file=sys.stderr)
sys.exit(1)
readme_text = README_PATH.read_text(encoding="utf-8")
current_repos = extract_github_repos(readme_text)
print(f"Found {len(current_repos)} GitHub repos in README.md")
cache = load_stars(CACHE_FILE)
now = datetime.now(timezone.utc)
# Prune entries not in current README
pruned = {k: v for k, v in cache.items() if k in current_repos}
if len(pruned) < len(cache):
print(f"Pruned {len(cache) - len(pruned)} stale cache entries")
cache = pruned
# Determine which repos need fetching (missing or stale)
to_fetch = []
for repo in sorted(current_repos):
entry = cache.get(repo)
if entry and "fetched_at" in entry:
fetched = datetime.fromisoformat(entry["fetched_at"])
age_hours = (now - fetched).total_seconds() / 3600
if age_hours < CACHE_MAX_AGE_HOURS:
continue
to_fetch.append(repo)
print(f"{len(to_fetch)} repos to fetch ({len(current_repos) - len(to_fetch)} cached)")
if not to_fetch:
save_cache(cache)
print("Cache is up to date.")
return
# Fetch in batches
fetched_count = 0
skipped_repos: list[str] = []
with httpx.Client(
headers={"Authorization": f"bearer {token}", "Content-Type": "application/json"},
transport=httpx.HTTPTransport(retries=2),
timeout=30,
) as client:
for i in range(0, len(to_fetch), BATCH_SIZE):
batch = to_fetch[i : i + BATCH_SIZE]
batch_num = i // BATCH_SIZE + 1
total_batches = (len(to_fetch) + BATCH_SIZE - 1) // BATCH_SIZE
print(f"Fetching batch {batch_num}/{total_batches} ({len(batch)} repos)...")
try:
results = fetch_batch(batch, client=client)
except httpx.HTTPStatusError as e:
print(f"HTTP error {e.response.status_code}", file=sys.stderr)
if e.response.status_code == 401:
print("Error: Invalid GITHUB_TOKEN.", file=sys.stderr)
sys.exit(1)
print("Saving partial cache and exiting.", file=sys.stderr)
save_cache(cache)
sys.exit(1)
now_iso = now.isoformat()
for repo in batch:
if repo in results:
cache[repo] = {
"stars": results[repo]["stars"],
"owner": results[repo]["owner"],
"last_commit_at": results[repo]["last_commit_at"],
"fetched_at": now_iso,
}
fetched_count += 1
else:
skipped_repos.append(repo)
# Save after each batch in case of interruption
save_cache(cache)
if skipped_repos:
print(f"Skipped {len(skipped_repos)} repos (deleted/private/renamed)")
print(f"Done. Fetched {fetched_count} repos, {len(cache)} total cached.")
if __name__ == "__main__":
main()
================================================
FILE: website/readme_parser.py
================================================
"""Parse README.md into structured section data using markdown-it-py AST."""
from __future__ import annotations
import re
from typing import TypedDict
from markdown_it import MarkdownIt
from markdown_it.tree import SyntaxTreeNode
from markupsafe import escape
class AlsoSee(TypedDict):
name: str
url: str
class ParsedEntry(TypedDict):
name: str
url: str
description: str # inline HTML, properly escaped
also_see: list[AlsoSee]
class ParsedSection(TypedDict):
name: str
slug: str
description: str # plain text, links resolved to text
entries: list[ParsedEntry]
entry_count: int
preview: str
content_html: str # rendered HTML, properly escaped
class ParsedGroup(TypedDict):
name: str
slug: str
categories: list[ParsedSection]
# --- Slugify ----------------------------------------------------------------
_SLUG_NON_ALNUM_RE = re.compile(r"[^a-z0-9\s-]")
_SLUG_WHITESPACE_RE = re.compile(r"[\s]+")
_SLUG_MULTI_DASH_RE = re.compile(r"-+")
def slugify(name: str) -> str:
"""Convert a category name to a URL-friendly slug."""
slug = name.lower()
slug = _SLUG_NON_ALNUM_RE.sub("", slug)
slug = _SLUG_WHITESPACE_RE.sub("-", slug.strip())
slug = _SLUG_MULTI_DASH_RE.sub("-", slug)
return slug
# --- Inline renderers -------------------------------------------------------
def render_inline_html(children: list[SyntaxTreeNode]) -> str:
"""Render inline AST nodes to HTML with proper escaping."""
parts: list[str] = []
for child in children:
match child.type:
case "text":
parts.append(str(escape(child.content)))
case "softbreak":
parts.append(" ")
case "link":
href = str(escape(child.attrGet("href") or ""))
inner = render_inline_html(child.children)
parts.append(
f'<a href="{href}" target="_blank" rel="noopener">{inner}</a>'
)
case "em":
parts.append(f"<em>{render_inline_html(child.children)}</em>")
case "strong":
parts.append(f"<strong>{render_inline_html(child.children)}</strong>")
case "code_inline":
parts.append(f"<code>{escape(child.content)}</code>")
case "html_inline":
parts.append(str(escape(child.content)))
return "".join(parts)
def render_inline_text(children: list[SyntaxTreeNode]) -> str:
"""Render inline AST nodes to plain text (links become their text)."""
parts: list[str] = []
for child in children:
match child.type:
case "text":
parts.append(child.content)
case "softbreak":
parts.append(" ")
case "code_inline":
parts.append(child.content)
case "em" | "strong" | "link":
parts.append(render_inline_text(child.children))
return "".join(parts)
# --- AST helpers -------------------------------------------------------------
def _heading_text(node: SyntaxTreeNode) -> str:
"""Extract plain text from a heading node."""
for child in node.children:
if child.type == "inline":
return render_inline_text(child.children)
return ""
def _extract_description(nodes: list[SyntaxTreeNode]) -> str:
"""Extract description from the first paragraph if it's a single <em> block.
Pattern: _Libraries for foo._ -> "Libraries for foo."
"""
if not nodes:
return ""
first = nodes[0]
if first.type != "paragraph":
return ""
for child in first.children:
if child.type == "inline" and len(child.children) == 1:
em = child.children[0]
if em.type == "em":
return render_inline_text(em.children)
return ""
# --- Entry extraction --------------------------------------------------------
_DESC_SEP_RE = re.compile(r"^\s*[-\u2013\u2014]\s*")
def _find_child(node: SyntaxTreeNode, child_type: str) -> SyntaxTreeNode | None:
"""Find first direct child of a given type."""
for child in node.children:
if child.type == child_type:
return child
return None
def _find_inline(node: SyntaxTreeNode) -> SyntaxTreeNode | None:
"""Find the inline node in a list_item's paragraph."""
para = _find_child(node, "paragraph")
if para is None:
return None
return _find_child(para, "inline")
def _find_first_link(inline: SyntaxTreeNode) -> SyntaxTreeNode | None:
"""Find the first link node among inline children."""
for child in inline.children:
if child.type == "link":
return child
return None
def _is_leading_link(inline: SyntaxTreeNode, link: SyntaxTreeNode) -> bool:
"""Check if the link is the first child of inline (a real entry, not a subcategory label)."""
return bool(inline.children) and inline.children[0] is link
def _extract_description_html(inline: SyntaxTreeNode, first_link: SyntaxTreeNode) -> str:
"""Extract description HTML from inline content after the first link.
AST: [link("name"), text(" - Description.")] -> "Description."
The separator (- / en-dash / em-dash) is stripped.
"""
link_idx = next((i for i, c in enumerate(inline.children) if c is first_link), None)
if link_idx is None:
return ""
desc_children = inline.children[link_idx + 1 :]
if not desc_children:
return ""
html = render_inline_html(desc_children)
return _DESC_SEP_RE.sub("", html)
def _parse_list_entries(bullet_list: SyntaxTreeNode) -> list[ParsedEntry]:
"""Extract entries from a bullet_list AST node.
Handles three patterns:
- Text-only list_item -> subcategory label -> recurse into nested list
- Link list_item with nested link-only items -> entry with also_see
- Link list_item without nesting -> simple entry
"""
entries: list[ParsedEntry] = []
for list_item in bullet_list.children:
if list_item.type != "list_item":
continue
inline = _find_inline(list_item)
if inline is None:
continue
first_link = _find_first_link(inline)
if first_link is None or not _is_leading_link(inline, first_link):
# Subcategory label (plain text or text-before-link) — recurse into nested list
nested = _find_child(list_item, "bullet_list")
if nested:
entries.extend(_parse_list_entries(nested))
continue
# Entry with a link
name = render_inline_text(first_link.children)
url = first_link.attrGet("href") or ""
desc_html = _extract_description_html(inline, first_link)
# Collect also_see from nested bullet_list
also_see: list[AlsoSee] = []
nested = _find_child(list_item, "bullet_list")
if nested:
for sub_item in nested.children:
if sub_item.type != "list_item":
continue
sub_inline = _find_inline(sub_item)
if sub_inline:
sub_link = _find_first_link(sub_inline)
if sub_link:
also_see.append(AlsoSee(
name=render_inline_text(sub_link.children),
url=sub_link.attrGet("href") or "",
))
entries.append(ParsedEntry(
name=name,
url=url,
description=desc_html,
also_see=also_see,
))
return entries
def _parse_section_entries(content_nodes: list[SyntaxTreeNode]) -> list[ParsedEntry]:
"""Extract all entries from a section's content nodes."""
entries: list[ParsedEntry] = []
for node in content_nodes:
if node.type == "bullet_list":
entries.extend(_parse_list_entries(node))
return entries
# --- Content HTML rendering --------------------------------------------------
def _render_bullet_list_html(
bullet_list: SyntaxTreeNode,
*,
is_sub: bool = False,
) -> str:
"""Render a bullet_list node to HTML with entry/entry-sub/subcat classes."""
out: list[str] = []
for list_item in bullet_list.children:
if list_item.type != "list_item":
continue
inline = _find_inline(list_item)
if inline is None:
continue
first_link = _find_first_link(inline)
if first_link is None or not _is_leading_link(inline, first_link):
# Subcategory label (plain text or text-before-link)
label = str(escape(render_inline_text(inline.children)))
out.append(f'<div class="subcat">{label}</div>')
nested = _find_child(list_item, "bullet_list")
if nested:
out.append(_render_bullet_list_html(nested, is_sub=False))
continue
# Entry with a link
name = str(escape(render_inline_text(first_link.children)))
url = str(escape(first_link.attrGet("href") or ""))
if is_sub:
out.append(f'<div class="entry-sub"><a href="{url}">{name}</a></div>')
else:
desc = _extract_description_html(inline, first_link)
if desc:
out.append(
f'<div class="entry"><a href="{url}">{name}</a>'
f'<span class="sep">—</span>{desc}</div>'
)
else:
out.append(f'<div class="entry"><a href="{url}">{name}</a></div>')
# Nested items under an entry with a link are sub-entries
nested = _find_child(list_item, "bullet_list")
if nested:
out.append(_render_bullet_list_html(nested, is_sub=True))
return "\n".join(out)
def _render_section_html(content_nodes: list[SyntaxTreeNode]) -> str:
"""Render a section's content nodes to HTML."""
parts: list[str] = []
for node in content_nodes:
if node.type == "bullet_list":
parts.append(_render_bullet_list_html(node))
return "\n".join(parts)
# --- Section splitting -------------------------------------------------------
def _build_section(name: str, body: list[SyntaxTreeNode]) -> ParsedSection:
"""Build a ParsedSection from a heading name and its body nodes."""
desc = _extract_description(body)
content_nodes = body[1:] if desc else body
entries = _parse_section_entries(content_nodes)
entry_count = len(entries) + sum(len(e["also_see"]) for e in entries)
preview = ", ".join(e["name"] for e in entries[:4])
content_html = _render_section_html(content_nodes)
return ParsedSection(
name=name,
slug=slugify(name),
description=desc,
entries=entries,
entry_count=entry_count,
preview=preview,
content_html=content_html,
)
def _group_by_h2(
nodes: list[SyntaxTreeNode],
) -> list[ParsedSection]:
"""Group AST nodes into sections by h2 headings."""
sections: list[ParsedSection] = []
current_name: str | None = None
current_body: list[SyntaxTreeNode] = []
def flush() -> None:
nonlocal current_name
if current_name is None:
return
sections.append(_build_section(current_name, current_body))
current_name = None
for node in nodes:
if node.type == "heading" and node.tag == "h2":
flush()
current_name = _heading_text(node)
current_body = []
elif current_name is not None:
current_body.append(node)
flush()
return sections
def _is_bold_marker(node: SyntaxTreeNode) -> str | None:
"""Detect a bold-only paragraph used as a group marker.
Pattern: a paragraph whose only content is **Group Name** (possibly
surrounded by empty text nodes in the AST).
Returns the group name text, or None if not a group marker.
"""
if node.type != "paragraph":
return None
for child in node.children:
if child.type != "inline":
continue
# Filter out empty text nodes that markdown-it inserts around strong
meaningful = [c for c in child.children if not (c.type == "text" and c.content == "")]
if len(meaningful) == 1 and meaningful[0].type == "strong":
return render_inline_text(meaningful[0].children)
return None
def _parse_grouped_sections(
nodes: list[SyntaxTreeNode],
) -> list[ParsedGroup]:
"""Parse nodes into groups of categories using bold markers as group boundaries.
Bold-only paragraphs (**Group Name**) delimit groups. H2 headings under each
bold marker become categories within that group. Categories appearing before
any bold marker go into an "Other" group.
"""
groups: list[ParsedGroup] = []
current_group_name: str | None = None
current_group_cats: list[ParsedSection] = []
current_cat_name: str | None = None
current_cat_body: list[SyntaxTreeNode] = []
def flush_cat() -> None:
nonlocal current_cat_name
if current_cat_name is None:
return
current_group_cats.append(_build_section(current_cat_name, current_cat_body))
current_cat_name = None
def flush_group() -> None:
nonlocal current_group_name, current_group_cats
if not current_group_cats:
current_group_name = None
current_group_cats = []
return
name = current_group_name or "Other"
groups.append(ParsedGroup(
name=name,
slug=slugify(name),
categories=list(current_group_cats),
))
current_group_name = None
current_group_cats = []
for node in nodes:
bold_name = _is_bold_marker(node)
if bold_name is not None:
flush_cat()
flush_group()
current_group_name = bold_name
current_cat_body = []
elif node.type == "heading" and node.tag == "h2":
flush_cat()
current_cat_name = _heading_text(node)
current_cat_body = []
elif current_cat_name is not None:
current_cat_body.append(node)
flush_cat()
flush_group()
return groups
def parse_readme(text: str) -> tuple[list[ParsedGroup], list[ParsedSection]]:
"""Parse README.md text into grouped categories and resources.
Returns (groups, resources) where groups is a list of ParsedGroup dicts
containing nested categories, and resources is a flat list of ParsedSection.
"""
md = MarkdownIt("commonmark")
tokens = md.parse(text)
root = SyntaxTreeNode(tokens)
children = root.children
# Find thematic break (---), # Resources, and # Contributing in one pass
hr_idx = None
resources_idx = None
contributing_idx = None
for i, node in enumerate(children):
if hr_idx is None and node.type == "hr":
hr_idx = i
elif node.type == "heading" and node.tag == "h1":
text_content = _heading_text(node)
if text_content == "Resources":
resources_idx = i
elif text_content == "Contributing":
contributing_idx = i
if hr_idx is None:
return [], []
# Slice into category and resource ranges
cat_end = resources_idx or contributing_idx or len(children)
cat_nodes = children[hr_idx + 1 : cat_end]
res_nodes: list[SyntaxTreeNode] = []
if resources_idx is not None:
res_end = contributing_idx or len(children)
res_nodes = children[resources_idx + 1 : res_end]
groups = _parse_grouped_sections(cat_nodes)
resources = _group_by_h2(res_nodes)
return groups, resources
================================================
FILE: website/static/main.js
================================================
// State
var activeFilter = null; // { type: "cat"|"group", value: "..." }
var activeSort = { col: 'stars', order: 'desc' };
var searchInput = document.querySelector('.search');
var filterBar = document.querySelector('.filter-bar');
var filterValue = document.querySelector('.filter-value');
var filterClear = document.querySelector('.filter-clear');
var noResults = document.querySelector('.no-results');
var rows = document.querySelectorAll('.table tbody tr.row');
var tags = document.querySelectorAll('.tag');
var tbody = document.querySelector('.table tbody');
// Relative time formatting
function relativeTime(isoStr) {
var date = new Date(isoStr);
var now = new Date();
var diffMs = now - date;
var diffHours = Math.floor(diffMs / 3600000);
var diffDays = Math.floor(diffMs / 86400000);
if (diffHours < 1) return 'just now';
if (diffHours < 24) return diffHours === 1 ? '1 hour ago' : diffHours + ' hours ago';
if (diffDays === 1) return 'yesterday';
if (diffDays < 30) return diffDays + ' days ago';
var diffMonths = Math.floor(diffDays / 30);
if (diffMonths < 12) return diffMonths === 1 ? '1 month ago' : diffMonths + ' months ago';
var diffYears = Math.floor(diffDays / 365);
return diffYears === 1 ? '1 year ago' : diffYears + ' years ago';
}
// Format all commit date cells
document.querySelectorAll('.col-commit[data-commit]').forEach(function (td) {
var time = td.querySelector('time');
if (time) time.textContent = relativeTime(td.dataset.commit);
});
// Store original row order for sort reset
rows.forEach(function (row, i) {
row._origIndex = i;
row._expandRow = row.nextElementSibling;
});
function collapseAll() {
var openRows = document.querySelectorAll('.table tbody tr.row.open');
openRows.forEach(function (row) {
row.classList.remove('open');
row.setAttribute('aria-expanded', 'false');
});
}
function applyFilters() {
var query = searchInput ? searchInput.value.toLowerCase().trim() : '';
var visibleCount = 0;
// Collapse all expanded rows on filter/search change
collapseAll();
rows.forEach(function (row) {
var show = true;
// Category/group filter
if (activeFilter) {
var attr = activeFilter.type === 'cat' ? row.dataset.cats : row.dataset.groups;
show = attr ? attr.split('||').indexOf(activeFilter.value) !== -1 : false;
}
// Text search
if (show && query) {
if (!row._searchText) {
var text = row.textContent.toLowerCase();
var next = row.nextElementSibling;
if (next && next.classList.contains('expand-row')) {
text += ' ' + next.textContent.toLowerCase();
}
row._searchText = text;
}
show = row._searchText.includes(query);
}
if (row.hidden !== !show) row.hidden = !show;
if (show) {
visibleCount++;
var numCell = row.cells[0];
if (numCell.textContent !== String(visibleCount)) {
numCell.textContent = String(visibleCount);
}
}
});
if (noResults) noResults.hidden = visibleCount > 0;
// Update tag highlights
tags.forEach(function (tag) {
var isActive = activeFilter
&& tag.dataset.type === activeFilter.type
&& tag.dataset.value === activeFilter.value;
tag.classList.toggle('active', isActive);
});
// Filter bar
if (filterBar) {
if (activeFilter) {
filterBar.hidden = false;
if (filterValue) filterValue.textContent = activeFilter.value;
} else {
filterBar.hidden = true;
}
}
updateURL();
}
function updateURL() {
var params = new URLSearchParams();
var query = searchInput ? searchInput.value.trim() : '';
if (query) params.set('q', query);
if (activeFilter) {
params.set(activeFilter.type === 'cat' ? 'category' : 'group', activeFilter.value);
}
if (activeSort.col !== 'stars' || activeSort.order !== 'desc') {
params.set('sort', activeSort.col);
params.set('order', activeSort.order);
}
var qs = params.toString();
history.replaceState(null, '', qs ? '?' + qs : location.pathname);
}
function getSortValue(row, col) {
if (col === 'name') {
return row.querySelector('.col-name a').textContent.trim().toLowerCase();
}
if (col === 'stars') {
var text = row.querySelector('.col-stars').textContent.trim().replace(/,/g, '');
var num = parseInt(text, 10);
return isNaN(num) ? -1 : num;
}
if (col === 'commit-time') {
var attr = row.querySelector('.col-commit').getAttribute('data-commit');
return attr ? new Date(attr).getTime() : 0;
}
return 0;
}
function sortRows() {
var arr = Array.prototype.slice.call(rows);
if (activeSort) {
arr.sort(function (a, b) {
var aVal = getSortValue(a, activeSort.col);
var bVal = getSortValue(b, activeSort.col);
if (activeSort.col === 'name') {
var cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
if (cmp === 0) return a._origIndex - b._origIndex;
return activeSort.order === 'desc' ? -cmp : cmp;
}
if (aVal <= 0 && bVal <= 0) return a._origIndex - b._origIndex;
if (aVal <= 0) return 1;
if (bVal <= 0) return -1;
var cmp = aVal - bVal;
if (cmp === 0) return a._origIndex - b._origIndex;
return activeSort.order === 'desc' ? -cmp : cmp;
});
} else {
arr.sort(function (a, b) { return a._origIndex - b._origIndex; });
}
arr.forEach(function (row) {
tbody.appendChild(row);
tbody.appendChild(row._expandRow);
});
applyFilters();
}
function updateSortIndicators() {
document.querySelectorAll('th[data-sort]').forEach(function (th) {
th.classList.remove('sort-asc', 'sort-desc');
if (activeSort && th.dataset.sort === activeSort.col) {
th.classList.add('sort-' + activeSort.order);
}
});
}
// Expand/collapse: event delegation on tbody
if (tbody) {
tbody.addEventListener('click', function (e) {
// Don't toggle if clicking a link or tag button
if (e.target.closest('a') || e.target.closest('.tag')) return;
var row = e.target.closest('tr.row');
if (!row) return;
var isOpen = row.classList.contains('open');
if (isOpen) {
row.classList.remove('open');
row.setAttribute('aria-expanded', 'false');
} else {
row.classList.add('open');
row.setAttribute('aria-expanded', 'true');
}
});
// Keyboard: Enter or Space on focused .row toggles expand
tbody.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var row = e.target.closest('tr.row');
if (!row) return;
e.preventDefault();
row.click();
});
}
// Tag click: filter by category or group
tags.forEach(function (tag) {
tag.addEventListener('click', function (e) {
e.preventDefault();
var type = tag.dataset.type;
var value = tag.dataset.value;
// Toggle: click same filter again to clear
if (activeFilter && activeFilter.type === type && activeFilter.value === value) {
activeFilter = null;
} else {
activeFilter = { type: type, value: value };
}
applyFilters();
});
});
// Clear filter
if (filterClear) {
filterClear.addEventListener('click', function () {
activeFilter = null;
applyFilters();
});
}
// Column sorting
document.querySelectorAll('th[data-sort]').forEach(function (th) {
th.addEventListener('click', function () {
var col = th.dataset.sort;
var defaultOrder = col === 'name' ? 'asc' : 'desc';
var altOrder = defaultOrder === 'asc' ? 'desc' : 'asc';
if (activeSort && activeSort.col === col) {
if (activeSort.order === defaultOrder) activeSort = { col: col, order: altOrder };
else activeSort = { col: 'stars', order: 'desc' };
} else {
activeSort = { col: col, order: defaultOrder };
}
sortRows();
updateSortIndicators();
});
});
// Search input
if (searchInput) {
var searchTimer;
searchInput.addEventListener('input', function () {
clearTimeout(searchTimer);
searchTimer = setTimeout(applyFilters, 150);
});
// Keyboard shortcuts
document.addEventListener('keydown', function (e) {
if (e.key === '/' && !['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName) && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
searchInput.focus();
}
if (e.key === 'Escape' && document.activeElement === searchInput) {
searchInput.value = '';
activeFilter = null;
applyFilters();
searchInput.blur();
}
});
}
// Restore state from URL
(function () {
var params = new URLSearchParams(location.search);
var q = params.get('q');
var cat = params.get('category');
var group = params.get('group');
var sort = params.get('sort');
var order = params.get('order');
if (q && searchInput) searchInput.value = q;
if (cat) activeFilter = { type: 'cat', value: cat };
else if (group) activeFilter = { type: 'group', value: group };
if ((sort === 'name' || sort === 'stars' || sort === 'commit-time') && (order === 'desc' || order === 'asc')) {
activeSort = { col: sort, order: order };
}
if (q || cat || group || sort) {
sortRows();
}
updateSortIndicators();
})();
================================================
FILE: website/static/style.css
================================================
/* === Reset & Base === */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--font-display: Georgia, "Noto Serif", "Times New Roman", serif;
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--text-xs: 0.9375rem;
--text-sm: 1rem;
--text-base: 1.125rem;
--bg: oklch(99.5% 0.003 240);
--bg-hover: oklch(97% 0.008 240);
--text: oklch(15% 0.005 240);
--text-secondary: oklch(35% 0.005 240);
--text-muted: oklch(50% 0.005 240);
--border: oklch(90% 0.005 240);
--border-strong: oklch(75% 0.008 240);
--border-heavy: oklch(25% 0.01 240);
--bg-input: oklch(94.5% 0.035 240);
--accent: oklch(42% 0.14 240);
--accent-hover: oklch(32% 0.16 240);
--accent-light: oklch(97% 0.015 240);
--highlight: oklch(93% 0.10 90);
--highlight-text: oklch(35% 0.10 90);
--tag-text: oklch(45% 0.06 240);
--tag-hover-bg: oklch(93% 0.025 240);
}
html { font-size: 16px; }
body {
font-family: var(--font-body);
background: var(--bg);
color: var(--text);
line-height: 1.55;
min-height: 100vh;
display: flex;
flex-direction: column;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: var(--accent); text-decoration: none; text-underline-offset: 0.15em; }
a:hover { color: var(--accent-hover); text-decoration: underline; }
/* === Skip Link === */
.skip-link {
position: absolute;
left: -9999px;
top: 0;
padding: 0.5rem 1rem;
background: var(--text);
color: var(--bg);
font-size: var(--text-xs);
font-weight: 700;
z-index: 200;
}
.skip-link:focus { left: 0; }
/* === Hero === */
.hero {
max-width: 1400px;
margin: 0 auto;
padding: 3.5rem 2rem 1.5rem;
}
.hero-main {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
}
.hero-submit {
flex-shrink: 0;
padding: 0.4rem 1rem;
border: 1px solid var(--border-strong);
border-radius: 4px;
font-size: var(--text-sm);
color: var(--text);
text-decoration: none;
white-space: nowrap;
transition: border-color 0.2s, background 0.2s, color 0.2s;
}
.hero-submit:hover {
border-color: var(--accent);
background: var(--accent-light);
color: var(--accent);
text-decoration: none;
}
.hero-submit:active {
transform: scale(0.97);
}
.hero-submit:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.hero h1 {
font-family: var(--font-display);
font-size: clamp(2rem, 5vw, 3rem);
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.1;
text-wrap: balance;
color: var(--accent);
margin-bottom: 0.75rem;
}
.hero-sub {
font-size: var(--text-base);
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 0.5rem;
text-wrap: pretty;
}
.hero-sub a { color: var(--text-secondary); font-weight: 600; }
.hero-sub a:hover { color: var(--accent); }
.hero-gh {
font-size: var(--text-sm);
color: var(--text-muted);
font-weight: 500;
}
.hero-gh:hover { color: var(--accent); }
/* === Controls === */
.controls {
max-width: 1400px;
margin: 0 auto;
padding: 0 2rem 1rem;
}
.search-wrap {
position: relative;
margin-bottom: 0.75rem;
}
.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
pointer-events: none;
}
.search {
width: 100%;
padding: 0.65rem 1rem 0.65rem 2.75rem;
border: 1px solid transparent;
border-radius: 4px;
background: var(--bg-input);
font-family: var(--font-body);
font-size: var(--text-sm);
color: var(--text);
transition: border-color 0.15s, background 0.15s;
}
.search::placeholder { color: var(--text-muted); }
.search:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-color: var(--accent);
background: var(--bg);
}
.filter-bar[hidden] { display: none; }
.filter-bar {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0;
font-size: var(--text-sm);
color: var(--text-secondary);
}
.filter-bar strong {
color: var(--text);
}
.filter-clear {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.35rem 0.65rem;
font-family: inherit;
font-size: var(--text-xs);
color: var(--text-muted);
cursor: pointer;
transition: border-color 0.15s, color 0.15s;
}
.filter-clear:active {
transform: scale(0.97);
}
.filter-clear:hover {
border-color: var(--text-muted);
color: var(--text);
}
.filter-clear:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* === Table === */
.table-wrap {
width: 100%;
padding: 0;
overflow-x: auto;
}
.table-wrap:focus {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: var(--text-sm);
}
.table thead th {
text-align: left;
font-weight: 700;
font-size: var(--text-base);
color: var(--text);
padding: 0.65rem 0.75rem;
border-bottom: 2px solid var(--border-heavy);
position: sticky;
top: 0;
background: var(--bg);
z-index: 10;
white-space: nowrap;
}
.table thead th:first-child,
.table tbody td:first-child {
padding-left: max(2rem, calc(50vw - 700px + 2rem));
}
.table thead th:last-child,
.table tbody td:last-child {
padding-right: max(2rem, calc(50vw - 700px + 2rem));
}
.table tbody td {
padding: 0.7rem 0.75rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
transition: background 0.15s;
}
.table tbody tr.row:not(.open):hover td {
background: var(--bg-hover);
}
.table tbody tr[hidden] { display: none; }
.col-num {
width: 3rem;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
text-align: left;
}
.col-name {
width: 30%;
overflow-wrap: anywhere;
}
.col-name > a {
font-weight: 500;
color: var(--accent);
text-decoration: none;
}
.col-name > a:hover { text-decoration: underline; color: var(--accent-hover); }
/* === Sortable Headers === */
th[data-sort] {
cursor: pointer;
user-select: none;
}
th[data-sort]:hover {
color: var(--accent);
}
th[data-sort]::after {
content: " ▼";
opacity: 0;
transition: opacity 0.15s;
}
th[data-sort="name"]::after {
content: " ▲";
}
th[data-sort]:hover::after {
opacity: 1;
}
th[data-sort].sort-desc::after {
content: " ▼";
opacity: 1;
}
th[data-sort].sort-asc::after {
content: " ▲";
opacity: 1;
}
/* === Stars Column === */
.col-stars {
width: 5rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
color: var(--text-secondary);
text-align: right;
}
/* === Arrow Column === */
.col-arrow {
width: 2.5rem;
text-align: center;
}
.arrow {
display: inline-block;
font-size: 0.8rem;
color: var(--accent);
transition: transform 0.15s ease;
}
.row.open .arrow {
transform: rotate(90deg);
}
/* === Row Click === */
.row { cursor: pointer; }
.row:active td { background: var(--bg-hover); }
.row:focus-visible td {
outline: none;
background: var(--bg-hover);
box-shadow: inset 2px 0 0 var(--accent);
}
/* === Expand Row === */
.expand-row {
display: none;
}
.row.open + .expand-row {
display: table-row;
}
.row.open td {
background: var(--accent-light);
border-bottom-color: transparent;
padding-bottom: 0.1rem;
}
.expand-row td {
padding: 0.15rem 0.75rem 0.75rem;
background: var(--accent-light);
border-bottom: 1px solid var(--border);
}
@keyframes expand-in {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.expand-content {
font-size: var(--text-sm);
color: var(--text-secondary);
line-height: 1.6;
text-wrap: pretty;
animation: expand-in 0.2s cubic-bezier(0.25, 1, 0.5, 1);
}
.expand-tags {
display: flex;
gap: 0.4rem;
margin-bottom: 0.4rem;
}
.expand-tag {
font-size: var(--text-xs);
color: var(--tag-text);
background: var(--bg);
padding: 0.15rem 0.4rem;
border-radius: 3px;
}
.expand-also-see {
margin-top: 0.25rem;
font-size: var(--text-xs);
color: var(--text-muted);
}
.expand-also-see a {
color: var(--accent);
text-decoration: none;
}
.expand-also-see a:hover {
text-decoration: underline;
}
.expand-meta {
margin-top: 0.25rem;
font-size: var(--text-xs);
color: var(--text-muted);
font-weight: normal;
}
.expand-meta a {
color: var(--accent);
text-decoration: none;
}
.expand-meta a:hover {
text-decoration: underline;
}
.expand-sep {
margin: 0 0.25rem;
color: var(--border);
}
.col-cat {
white-space: nowrap;
}
.col-cat .tag + .tag {
margin-left: 0.35rem;
}
/* === Last Commit Column === */
.col-commit {
width: 9rem;
white-space: nowrap;
color: var(--text-muted);
}
/* === Tags === */
.tag {
position: relative;
background: var(--accent-light);
border: none;
font-family: inherit;
font-size: var(--text-xs);
color: var(--tag-text);
cursor: pointer;
padding: 0.25rem 0.5rem;
border-radius: 3px;
white-space: nowrap;
transition: background 0.15s, color 0.15s;
}
/* Expand touch target to 44x44px minimum */
.tag::after {
content: "";
position: absolute;
inset: -0.5rem -0.25rem;
}
.tag:active {
transform: scale(0.95);
}
.tag:hover {
background: var(--tag-hover-bg);
color: var(--accent);
}
.tag:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
.tag.active {
background: var(--highlight);
color: var(--highlight-text);
font-weight: 600;
}
/* === Noscript === */
.noscript-msg {
text-align: center;
padding: 1rem;
color: var(--text-muted);
}
/* === No Results === */
.no-results {
max-width: 1400px;
margin: 0 auto;
padding: 3rem 2rem;
font-size: var(--text-base);
color: var(--text-muted);
text-align: center;
}
/* === Footer === */
.footer {
margin-top: auto;
border-top: none;
width: 100%;
padding: 1.25rem 2rem;
font-size: var(--text-xs);
color: var(--text-muted);
background: var(--bg-input);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
}
.footer a { color: var(--accent); text-decoration: none; }
.footer a:hover { color: var(--accent-hover); text-decoration: underline; }
.footer-sep { color: var(--border-strong); }
/* === Responsive === */
@media (max-width: 900px) {
.col-commit { display: none; }
.tag-group { display: none; }
.col-name { width: 50%; }
}
@media (max-width: 640px) {
.hero { padding: 2rem 1.25rem 1rem; }
.controls { padding: 0 1.25rem 0.75rem; }
.table { table-layout: auto; }
.table thead th,
.table tbody td {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.table thead th:first-child,
.table tbody td:first-child { padding-left: 0.25rem; }
.table thead th:last-child,
.table tbody td:last-child { padding-right: 0.25rem; }
.table thead th { font-size: var(--text-sm); }
.col-num { width: 2rem; }
.col-stars { width: 4.75rem; }
.col-arrow { width: 1.25rem; }
.col-cat { display: none; }
.col-name {
width: auto;
white-space: normal;
}
.footer { padding: 1.25rem; justify-content: center; flex-wrap: wrap; }
}
/* === Screen Reader Only === */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* === Reduced Motion === */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
================================================
FILE: website/templates/base.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}Awesome Python{% endblock %}</title>
<meta
name="description"
content="{% block description %}An opinionated list of awesome Python frameworks, libraries, software and resources. {{ total_entries }} libraries across {{ categories | length }} categories.{% endblock %}"
/>
<link rel="canonical" href="https://awesome-python.com/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Awesome Python" />
<meta
property="og:description"
content="An opinionated list of awesome Python frameworks, libraries, software and resources."
/>
<meta property="og:url" content="https://awesome-python.com/" />
<meta name="twitter:card" content="summary" />
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="/static/style.css" />
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-0LMLYE0HER"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-0LMLYE0HER");
</script>
</head>
<body>
<a href="#content" class="skip-link">Skip to content</a>
<main id="content">{% block content %}{% endblock %}</main>
<footer class="footer">
<span
>Made by
<a href="https://vinta.ws/" target="_blank" rel="noopener"
>Vinta</a
></span
>
<span class="footer-sep">/</span>
<a href="https://github.com/vinta" target="_blank" rel="noopener"
>GitHub</a
>
<span class="footer-sep">/</span>
<a href="https://twitter.com/vinta" target="_blank" rel="noopener"
>Twitter</a
>
</footer>
<noscript
><p class="noscript-msg">
JavaScript is needed for search and filtering.
</p></noscript
>
<script src="/static/main.js"></script>
</body>
</html>
================================================
FILE: website/templates/index.html
================================================
{% extends "base.html" %} {% block content %}
<header class="hero">
<div class="hero-main">
<div>
<h1>Awesome Python</h1>
<p class="hero-sub">
{{ subtitle }}<br />Maintained by
<a href="https://github.com/vinta" target="_blank" rel="noopener"
>@vinta</a
>
and
<a
href="https://github.com/JinyangWang27"
target="_blank"
rel="noopener"
>@JinyangWang27</a
>.
</p>
<a
href="https://github.com/vinta/awesome-python"
class="hero-gh"
target="_blank"
rel="noopener"
>awesome-python on GitHub →</a
>
</div>
<a
href="https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md"
class="hero-submit"
target="_blank"
rel="noopener"
>Submit a Project</a
>
</div>
</header>
<h2 class="sr-only">Search and filter</h2>
<div class="controls">
<div class="search-wrap">
<svg
class="search-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
<input
type="search"
class="search"
placeholder="Search {{ entries | length }} libraries across {{ total_categories }} categories..."
aria-label="Search libraries"
/>
</div>
<div class="filter-bar" hidden>
<span>Showing <strong class="filter-value"></strong></span>
<button class="filter-clear" aria-label="Clear filter">
× Clear
</button>
</div>
</div>
<h2 class="sr-only">Results</h2>
<div class="table-wrap" tabindex="0" role="region" aria-label="Libraries table">
<table class="table">
<thead>
<tr>
<th class="col-num"><span class="sr-only">#</span></th>
<th class="col-name" data-sort="name">Project Name</th>
<th class="col-stars" data-sort="stars">GitHub Stars</th>
<th class="col-commit" data-sort="commit-time">Last Commit</th>
<th class="col-cat">Category</th>
<th class="col-arrow"><span class="sr-only">Details</span></th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr
class="row"
role="button"
data-cats="{{ entry.categories | join('||') }}"
data-groups="{{ entry.groups | join('||') }}"
tabindex="0"
aria-expanded="false"
aria-controls="expand-{{ loop.index }}"
>
<td class="col-num">{{ loop.index }}</td>
<td class="col-name">
<a href="{{ entry.url }}" target="_blank" rel="noopener"
>{{ entry.name }}</a
>
</td>
<td class="col-stars">
{% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{%
else %}—{% endif %}
</td>
<td
class="col-commit"
{%
if
entry.last_commit_at
%}data-commit="{{ entry.last_commit_at }}"
{%
endif
%}
>
{% if entry.last_commit_at %}<time
datetime="{{ entry.last_commit_at }}"
>{{ entry.last_commit_at[:10] }}</time
>{% else %}—{% endif %}
</td>
<td class="col-cat">
{% for cat in entry.categories %}
<button class="tag" data-type="cat" data-value="{{ cat }}">
{{ cat }}
</button>
{% endfor %}
<button class="tag tag-group" data-type="group" data-value="{{ entry.groups[0] }}">
{{ entry.groups[0] }}
</button>
</td>
<td class="col-arrow"><span class="arrow">→</span></td>
</tr>
<tr class="expand-row" id="expand-{{ loop.index }}">
<td></td>
<td colspan="4">
<div class="expand-content">
{% if entry.description %}
<div class="expand-desc">{{ entry.description | safe }}</div>
{% endif %} {% if entry.also_see %}
<div class="expand-also-see">
Also see: {% for see in entry.also_see %}<a
href="{{ see.url }}"
target="_blank"
rel="noopener"
>{{ see.name }}</a
>{% if not loop.last %}, {% endif %}{% endfor %}
</div>
{% endif %}
<div class="expand-meta">
{% if entry.owner %}<a
href="https://github.com/{{ entry.owner }}"
target="_blank"
rel="noopener"
>{{ entry.owner }}</a
><span class="expand-sep">/</span>{% endif %}<a
href="{{ entry.url }}"
target="_blank"
rel="noopener"
>{{ entry.url | replace("https://", "") }}</a
>
</div>
</div>
</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="no-results" hidden>No libraries match your search.</div>
{% endblock %}
================================================
FILE: website/tests/test_build.py
================================================
"""Tests for the build module."""
import json
import shutil
import textwrap
from pathlib import Path
from build import (
build,
extract_github_repo,
group_categories,
load_stars,
sort_entries,
)
from readme_parser import slugify
# ---------------------------------------------------------------------------
# slugify
# ---------------------------------------------------------------------------
class TestSlugify:
def test_simple(self):
assert slugify("Admin Panels") == "admin-panels"
def test_uppercase_acronym(self):
assert slugify("RESTful API") == "restful-api"
def test_all_caps(self):
assert slugify("CMS") == "cms"
def test_hyphenated_input(self):
assert slugify("Command-line Tools") == "command-line-tools"
def test_special_chars(self):
assert slugify("Editor Plugins and IDEs") == "editor-plugins-and-ides"
def test_single_word(self):
assert slugify("Audio") == "audio"
def test_extra_spaces(self):
assert slugify(" Date and Time ") == "date-and-time"
# ---------------------------------------------------------------------------
# group_categories
# ---------------------------------------------------------------------------
class TestGroupCategories:
def test_appends_resources(self):
parsed_groups = [
{"name": "G1", "slug": "g1", "categories": [{"name": "Cat1"}]},
]
resources = [{"name": "Newsletters", "slug": "newsletters"}]
groups = group_categories(parsed_groups, resources)
group_names = [g["name"] for g in groups]
assert "G1" in group_names
assert "Resources" in group_names
def test_no_resources_no_extra_group(self):
parsed_groups = [
{"name": "G1", "slug": "g1", "categories": [{"name": "Cat1"}]},
]
groups = group_categories(parsed_groups, [])
assert len(groups) == 1
assert groups[0]["name"] == "G1"
def test_preserves_group_order(self):
parsed_groups = [
{"name": "Second", "slug": "second", "categories": [{"name": "C2"}]},
{"name": "First", "slug": "first", "categories": [{"name": "C1"}]},
]
groups = group_categories(parsed_groups, [])
assert groups[0]["name"] == "Second"
assert groups[1]["name"] == "First"
# ---------------------------------------------------------------------------
# build (integration)
# ---------------------------------------------------------------------------
class TestBuild:
def _make_repo(self, tmp_path, readme):
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
tpl_dir = tmp_path / "website" / "templates"
tpl_dir.mkdir(parents=True)
(tpl_dir / "base.html").write_text(
"<!DOCTYPE html><html lang='en'><head><title>{% block title %}{% endblock %}</title>"
"<meta name='description' content='{% block description %}{% endblock %}'>"
"</head><body>{% block content %}{% endblock %}</body></html>",
encoding="utf-8",
)
(tpl_dir / "index.html").write_text(
'{% extends "base.html" %}{% block content %}'
"{% for group in groups %}"
'<section class="group">'
"<h2>{{ group.name }}</h2>"
"{% for cat in group.categories %}"
'<div class="row" id="{{ cat.slug }}">'
"<span>{{ cat.name }}</span>"
"<span>{{ cat.preview }}</span>"
"<span>{{ cat.entry_count }}</span>"
'<div class="row-content" hidden>{{ cat.content_html | safe }}</div>'
"</div>"
"{% endfor %}"
"</section>"
"{% endfor %}"
"{% endblock %}",
encoding="utf-8",
)
def test_build_creates_single_page(self, tmp_path):
readme = textwrap.dedent("""\
# Awesome Python
Intro.
---
**Tools**
## Widgets
_Widget libraries._
- [w1](https://example.com) - A widget.
## Gadgets
_Gadget tools._
- [g1](https://example.com) - A gadget.
# Resources
Info.
## Newsletters
- [NL](https://example.com)
# Contributing
Help!
""")
self._make_repo(tmp_path, readme)
build(str(tmp_path))
site = tmp_path / "website" / "output"
assert (site / "index.html").exists()
# No category sub-pages
assert not (site / "categories").exists()
def test_build_cleans_stale_output(self, tmp_path):
readme = textwrap.dedent("""\
# T
---
## Only
- [x](https://x.com) - X.
# Contributing
Done.
""")
self._make_repo(tmp_path, readme)
stale = tmp_path / "website" / "output" / "categories" / "stale"
stale.mkdir(parents=True)
(stale / "index.html").write_text("old", encoding="utf-8")
build(str(tmp_path))
assert not (tmp_path / "website" / "output" / "categories" / "stale").exists()
def test_index_contains_category_names(self, tmp_path):
readme = textwrap.dedent("""\
# T
---
**Group A**
## Alpha
- [a](https://x.com) - A.
**Group B**
## Beta
- [b](https://x.com) - B.
# Contributing
Done.
""")
self._make_repo(tmp_path, readme)
build(str(tmp_path))
index_html = (tmp_path / "website" / "output" / "index.html").read_text()
assert "Alpha" in index_html
assert "Beta" in index_html
assert "Group A" in index_html
assert "Group B" in index_html
def test_index_contains_preview_text(self, tmp_path):
readme = textwrap.dedent("""\
# T
---
## Stuff
- [django](https://x.com) - A framework.
- [flask](https://x.com) - A micro.
# Contributing
Done.
""")
self._make_repo(tmp_path, readme)
build(str(tmp_path))
index_html = (tmp_path / "website" / "output" / "index.html").read_text()
assert "django" in index_html
assert "flask" in index_html
def test_build_with_stars_sorts_by_stars(self, tmp_path):
readme = textwrap.dedent("""\
# T
---
## Stuff
- [low-stars](https://github.com/org/low) - Low.
- [high-stars](https://github.com/org/high) - High.
- [no-stars](https://example.com/none) - None.
# Contributing
Done.
""")
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
# Copy real templates
real_tpl = Path(__file__).parent / ".." / "templates"
tpl_dir = tmp_path / "website" / "templates"
shutil.copytree(real_tpl, tpl_dir)
# Create mock star data
data_dir = tmp_path / "website" / "data"
data_dir.mkdir(parents=True)
stars = {
"org/high": {"stars": 5000, "owner": "org", "fetched_at": "2026-01-01T00:00:00+00:00"},
"org/low": {"stars": 100, "owner": "org", "fetched_at": "2026-01-01T00:00:00+00:00"},
}
(data_dir / "github_stars.json").write_text(json.dumps(stars), encoding="utf-8")
build(str(tmp_path))
html = (tmp_path / "website" / "output" / "index.html").read_text(encoding="utf-8")
# Star-sorted: high-stars (5000) before low-stars (100) before no-stars (None)
assert html.index("high-stars") < html.index("low-stars")
assert html.index("low-stars") < html.index("no-stars")
# Formatted star counts
assert "5,000" in html
assert "100" in html
# Expand content present
assert "expand-content" in html
# ---------------------------------------------------------------------------
# extract_github_repo
# ---------------------------------------------------------------------------
class TestExtractGithubRepo:
def test_github_url(self):
assert extract_github_repo("https://github.com/psf/requests") == "psf/requests"
def test_non_github_url(self):
assert extract_github_repo("https://foss.heptapod.net/pypy/pypy") is None
def test_github_io_url(self):
assert extract_github_repo("https://user.github.io/proj") is None
def test_trailing_slash(self):
assert extract_github_repo("https://github.com/org/repo/") == "org/repo"
def test_deep_path(self):
assert extract_github_repo("https://github.com/org/repo/tree/main") is None
def test_dot_git_suffix(self):
assert extract_github_repo("https://github.com/org/repo.git") == "org/repo"
def test_org_only(self):
assert extract_github_repo("https://github.com/org") is None
# ---------------------------------------------------------------------------
# load_stars
# ---------------------------------------------------------------------------
class TestLoadStars:
def test_returns_empty_when_missing(self, tmp_path):
result = load_stars(tmp_path / "nonexistent.json")
assert result == {}
def test_loads_valid_json(self, tmp_path):
data = {"psf/requests": {"stars": 52467, "owner": "psf", "fetched_at": "2026-01-01T00:00:00+00:00"}}
f = tmp_path / "stars.json"
f.write_text(json.dumps(data), encoding="utf-8")
result = load_stars(f)
assert result["psf/requests"]["stars"] == 52467
def test_returns_empty_on_corrupt_json(self, tmp_path):
f = tmp_path / "stars.json"
f.write_text("not json", encoding="utf-8")
result = load_stars(f)
assert result == {}
# ---------------------------------------------------------------------------
# sort_entries
# ---------------------------------------------------------------------------
class TestSortEntries:
def test_sorts_by_stars_descending(self):
entries = [
{"name": "a", "stars": 100, "url": ""},
{"name": "b", "stars": 500, "url": ""},
{"name": "c", "stars": 200, "url": ""},
]
result = sort_entries(entries)
assert [e["name"] for e in result] == ["b", "c", "a"]
def test_equal_stars_sorted_alphabetically(self):
entries = [
{"name": "beta", "stars": 100, "url": ""},
{"name": "alpha", "stars": 100, "url": ""},
]
result = sort_entries(entries)
assert [e["name"] for e in result] == ["alpha", "beta"]
def test_no_stars_go_to_bottom(self):
entries = [
{"name": "no-stars", "stars": None, "url": ""},
{"name": "has-stars", "stars": 50, "url": ""},
]
result = sort_entries(entries)
assert [e["name"] for e in result] == ["has-stars", "no-stars"]
def test_no_stars_sorted_alphabetically(self):
entries = [
{"name": "zebra", "stars": None, "url": ""},
{"name": "apple", "stars": None, "url": ""},
]
result = sort_entries(entries)
assert [e["name"] for e in result] == ["apple", "zebra"]
================================================
FILE: website/tests/test_fetch_github_stars.py
================================================
"""Tests for fetch_github_stars module."""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from fetch_github_stars import (
build_graphql_query,
extract_github_repos,
parse_graphql_response,
save_cache,
)
class TestExtractGithubRepos:
def test_extracts_owner_repo_from_github_url(self):
readme = "* [requests](https://github.com/psf/requests) - HTTP lib."
result = extract_github_repos(readme)
assert result == {"psf/requests"}
def test_multiple_repos(self):
readme = (
"* [requests](https://github.com/psf/requests) - HTTP.\n"
"* [flask](https://github.com/pallets/flask) - Micro."
)
result = extract_github_repos(readme)
assert result == {"psf/requests", "pallets/flask"}
def test_ignores_non_github_urls(self):
readme = "* [pypy](https://foss.heptapod.net/pypy/pypy) - Fast Python."
result = extract_github_repos(readme)
assert result == set()
def test_ignores_github_io_urls(self):
readme = "* [docs](https://user.github.io/project) - Docs site."
result = extract_github_repos(readme)
assert result == set()
def test_ignores_github_wiki_and_blob_urls(self):
readme = (
"* [wiki](https://github.com/org/repo/wiki) - Wiki.\n"
"* [file](https://github.com/org/repo/blob/main/f.py) - File."
)
result = extract_github_repos(readme)
assert result == set()
def test_handles_trailing_slash(self):
readme = "* [lib](https://github.com/org/repo/) - Lib."
result = extract_github_repos(readme)
assert result == {"org/repo"}
def test_deduplicates(self):
readme = (
"* [a](https://github.com/org/repo) - A.\n"
"* [b](https://github.com/org/repo) - B."
)
result = extract_github_repos(readme)
assert result == {"org/repo"}
def test_strips_fragment(self):
readme = "* [lib](https://github.com/org/repo#section) - Lib."
result = extract_github_repos(readme)
assert result == {"org/repo"}
class TestSaveCache:
def test_creates_directory_and_writes_json(self, tmp_path, monkeypatch):
data_dir = tmp_path / "data"
cache_file = data_dir / "stars.json"
monkeypatch.setattr("fetch_github_stars.DATA_DIR", data_dir)
monkeypatch.setattr("fetch_github_stars.CACHE_FILE", cache_file)
save_cache({"a/b": {"stars": 1}})
assert cache_file.exists()
assert json.loads(cache_file.read_text(encoding="utf-8")) == {"a/b": {"stars": 1}}
class TestBuildGraphqlQuery:
def test_single_repo(self):
query = build_graphql_query(["psf/requests"])
assert "repository" in query
assert 'owner: "psf"' in query
assert 'name: "requests"' in query
assert "stargazerCount" in query
def test_multiple_repos_use_aliases(self):
query = build_graphql_query(["psf/requests", "pallets/flask"])
assert "repo_0:" in query
assert "repo_1:" in query
def test_empty_list(self):
query = build_graphql_query([])
assert query == ""
def test_skips_repos_with_quotes_in_name(self):
query = build_graphql_query(['org/"bad"'])
assert query == ""
def test_skips_only_bad_repos(self):
query = build_graphql_query(["good/repo", 'bad/"repo"'])
assert "good" in query
assert "bad" not in query
class TestParseGraphqlResponse:
def test_parses_star_count_and_owner(self):
data = {
"repo_0": {
"stargazerCount": 52467,
"owner": {"login": "psf"},
}
}
repos = ["psf/requests"]
result = parse_graphql_response(data, repos)
assert result["psf/requests"]["stars"] == 52467
assert result["psf/requests"]["owner"] == "psf"
def test_skips_null_repos(self):
data = {"repo_0": None}
repos = ["deleted/repo"]
result = parse_graphql_response(data, repos)
assert result == {}
def test_handles_missing_owner(self):
data = {"repo_0": {"stargazerCount": 100}}
repos = ["org/repo"]
result = parse_graphql_response(data, repos)
assert result["org/repo"]["owner"] == ""
def test_multiple_repos(self):
data = {
"repo_0": {"stargazerCount": 100, "owner": {"login": "a"}},
"repo_1": {"stargazerCount": 200, "owner": {"login": "b"}},
}
repos = ["a/x", "b/y"]
result = parse_graphql_response(data, repos)
assert len(result) == 2
assert result["a/x"]["stars"] == 100
assert result["b/y"]["stars"] == 200
class TestMainSkipsFreshCache:
"""Verify that main() skips fetching when all cache entries are fresh."""
def test_skips_fetch_when_cache_is_fresh(self, tmp_path, monkeypatch, capsys):
from datetime import datetime, timedelta, timezone
from fetch_github_stars import main
# Set up a minimal README with one repo
readme = tmp_path / "README.md"
readme.write_text("* [req](https://github.com/psf/requests) - HTTP.\n")
monkeypatch.setattr("fetch_github_stars.README_PATH", readme)
# Pre-populate cache with a fresh entry (1 hour ago)
data_dir = tmp_path / "data"
data_dir.mkdir()
cache_file = data_dir / "github_stars.json"
now = datetime.now(timezone.utc)
fresh_cache = {
"psf/requests": {
"stars": 52000,
"owner": "psf",
"last_commit_at": "2025-01-01T00:00:00+00:00",
"fetched_at": (now - timedelta(hours=1)).isoformat(),
}
}
cache_file.write_text(json.dumps(fresh_cache), encoding="utf-8")
monkeypatch.setattr("fetch_github_stars.CACHE_FILE", cache_file)
monkeypatch.setattr("fetch_github_stars.DATA_DIR", data_dir)
monkeypatch.setenv("GITHUB_TOKEN", "fake-token")
main()
output = capsys.readouterr().out
assert "0 repos to fetch" in output
assert "Cache is up to date" in output
def test_fetches_when_cache_is_stale(self, tmp_path, monkeypatch, capsys):
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock
from fetch_github_stars import main
# Set up a minimal README with one repo
readme = tmp_path / "README.md"
readme.write_text("* [req](https://github.com/psf/requests) - HTTP.\n")
monkeypatch.setattr("fetch_github_stars.README_PATH", readme)
# Pre-populate cache with a stale entry (24 hours ago)
data_dir = tmp_path / "data"
data_dir.mkdir()
cache_file = data_dir / "github_stars.json"
now = datetime.now(timezone.utc)
stale_cache = {
"psf/requests": {
"stars": 52000,
"owner": "psf",
"last_commit_at": "2025-01-01T00:00:00+00:00",
"fetched_at": (now - timedelta(hours=24)).isoformat(),
}
}
cache_file.write_text(json.dumps(stale_cache), encoding="utf-8")
monkeypatch.setattr("fetch_github_stars.CACHE_FILE", cache_file)
monkeypatch.setattr("fetch_github_stars.DATA_DIR", data_dir)
monkeypatch.setenv("GITHUB_TOKEN", "fake-token")
# Mock httpx.Client to avoid real API calls
mock_response = MagicMock()
mock_response.json.return_value = {
"data": {
"repo_0": {
"stargazerCount": 53000,
"owner": {"login": "psf"},
"defaultBranchRef": {"target": {"committedDate": "2025-06-01T00:00:00Z"}},
}
}
}
mock_response.raise_for_status = MagicMock()
mock_client = MagicMock()
mock_client.__enter__ = MagicMock(return_value=mock_client)
mock_client.__exit__ = MagicMock(return_value=False)
mock_client.post.return_value = mock_response
monkeypatch.setattr("fetch_github_stars.httpx.Client", lambda **kwargs: mock_client)
main()
output = capsys.readouterr().out
assert "1 repos to fetch" in output
assert "Done. Fetched 1 repos" in output
mock_client.post.assert_called_once()
================================================
FILE: website/tests/test_readme_parser.py
================================================
"""Tests for the readme_parser module."""
import os
import textwrap
import pytest
from readme_parser import (
_parse_section_entries,
_render_section_html,
parse_readme,
render_inline_html,
render_inline_text,
)
from markdown_it import MarkdownIt
from markdown_it.tree import SyntaxTreeNode
def _parse_inline(md_text: str) -> list[SyntaxTreeNode]:
"""Helper: parse a single paragraph and return its inline children."""
md = MarkdownIt("commonmark")
root = SyntaxTreeNode(md.parse(md_text))
# root > paragraph > inline > children
return root.children[0].children[0].children
class TestRenderInlineHtml:
def test_plain_text_escapes_html(self):
children = _parse_inline("Hello <world> & friends")
assert render_inline_html(children) == "Hello <world> & friends"
def test_link_with_target(self):
children = _parse_inline("[name](https://example.com)")
html = render_inline_html(children)
assert 'href="https://example.com"' in html
assert 'target="_blank"' in html
assert 'rel="noopener"' in html
assert ">name</a>" in html
def test_emphasis(self):
children = _parse_inline("*italic* text")
assert "<em>italic</em>" in render_inline_html(children)
def test_strong(self):
children = _parse_inline("**bold** text")
assert "<strong>bold</strong>" in render_inline_html(children)
def test_code_inline(self):
children = _parse_inline("`some code`")
assert "<code>some code</code>" in render_inline_html(children)
def test_mixed_link_and_text(self):
children = _parse_inline("See [foo](https://x.com) for details.")
html = render_inline_html(children)
assert "See " in html
assert ">foo</a>" in html
assert " for details." in html
class TestRenderInlineText:
def test_plain_text(self):
children = _parse_inline("Hello world")
assert render_inline_text(children) == "Hello world"
def test_link_becomes_text(self):
children = _parse_inline("See [awesome-algos](https://github.com/x/y).")
assert render_inline_text(children) == "See awesome-algos."
def test_emphasis_stripped(self):
children = _parse_inline("*italic* text")
assert render_inline_text(children) == "italic text"
def test_code_inline_kept(self):
children = _parse_inline("`code` here")
assert render_inline_text(children) == "code here"
MINIMAL_README = textwrap.dedent("""\
# Awesome Python
Some intro text.
---
## Alpha
_Libraries for alpha stuff._
- [lib-a](https://example.com/a) - Does A.
- [lib-b](https://example.com/b) - Does B.
## Beta
_Tools for beta._
- [lib-c](https://example.com/c) - Does C.
# Resources
Where to discover resources.
## Newsletters
- [News One](https://example.com/n1)
- [News Two](https://example.com/n2)
## Podcasts
- [Pod One](https://example.com/p1)
# Contributing
Please contribute!
""")
GROUPED_README = textwrap.dedent("""\
# Awesome Python
Some intro text.
---
**Group One**
## Alpha
_Libraries for alpha stuff._
- [lib-a](https://example.com/a) - Does A.
- [lib-b](https://example.com/b) - Does B.
**Group Two**
## Beta
_Tools for beta._
- [lib-c](https://example.com/c) - Does C.
## Gamma
- [lib-d](https://example.com/d) - Does D.
# Resources
Where to discover resources.
## Newsletters
- [News One](https://example.com/n1)
# Contributing
Please contribute!
""")
class TestParseReadmeSections:
def test_ungrouped_categories_go_to_other(self):
groups, resources = parse_readme(MINIMAL_README)
assert len(groups) == 1
assert groups[0]["name"] == "Other"
assert len(groups[0]["categories"]) == 2
def test_ungrouped_category_names(self):
groups, _ = parse_readme(MINIMAL_README)
cats = groups[0]["categories"]
assert cats[0]["name"] == "Alpha"
assert cats[1]["name"] == "Beta"
def test_resource_count(self):
_, resources = parse_readme(MINIMAL_README)
assert len(resources) == 2
def test_category_slugs(self):
groups, _ = parse_readme(MINIMAL_README)
cats = groups[0]["categories"]
assert cats[0]["slug"] == "alpha"
assert cats[1]["slug"] == "beta"
def test_category_description(self):
groups, _ = parse_readme(MINIMAL_README)
cats = groups[0]["categories"]
assert cats[0]["description"] == "Libraries for alpha stuff."
assert cats[1]["description"] == "Tools for beta."
def test_resource_names(self):
_, resources = parse_readme(MINIMAL_README)
assert resources[0]["name"] == "Newsletters"
assert resources[1]["name"] == "Podcasts"
def test_contributing_skipped(self):
groups, resources = parse_readme(MINIMAL_README)
all_names = []
for g in groups:
all_names.extend(c["name"] for c in g["categories"])
all_names.extend(r["name"] for r in resources)
assert "Contributing" not in all_names
def test_no_separator(self):
groups, resources = parse_readme("# Just a heading\n\nSome text.\n")
assert groups == []
assert resources == []
def test_no_description(self):
readme = textwrap.dedent("""\
# Title
---
## NullDesc
- [item](https://x.com) - Thing.
# Resources
## Tips
- [tip](https://x.com)
# Contributing
Done.
""")
groups, resources = parse_readme(readme)
cats = groups[0]["categories"]
assert cats[0]["description"] == ""
assert cats[0]["entries"][0]["name"] == "item"
def test_description_with_link_stripped(self):
readme = textwrap.dedent("""\
# T
---
## Algos
_Algorithms. Also see [awesome-algos](https://example.com)._
- [lib](https://x.com) - Lib.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
cats = groups[0]["categories"]
assert cats[0]["description"] == "Algorithms. Also see awesome-algos."
class TestParseGroupedReadme:
def test_group_count(self):
groups, _ = parse_readme(GROUPED_README)
assert len(groups) == 2
def test_group_names(self):
groups, _ = parse_readme(GROUPED_README)
assert groups[0]["name"] == "Group One"
assert groups[1]["name"] == "Group Two"
def test_group_slugs(self):
groups, _ = parse_readme(GROUPED_README)
assert groups[0]["slug"] == "group-one"
assert groups[1]["slug"] == "group-two"
def test_group_one_has_one_category(self):
groups, _ = parse_readme(GROUPED_README)
assert len(groups[0]["categories"]) == 1
assert groups[0]["categories"][0]["name"] == "Alpha"
def test_group_two_has_two_categories(self):
groups, _ = parse_readme(GROUPED_README)
assert len(groups[1]["categories"]) == 2
assert groups[1]["categories"][0]["name"] == "Beta"
assert groups[1]["categories"][1]["name"] == "Gamma"
def test_resources_still_parsed(self):
_, resources = parse_readme(GROUPED_README)
assert len(resources) == 1
assert resources[0]["name"] == "Newsletters"
def test_empty_group_skipped(self):
readme = textwrap.dedent("""\
# T
---
**Empty**
**HasCats**
## Cat
- [x](https://x.com) - X.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
assert len(groups) == 1
assert groups[0]["name"] == "HasCats"
def test_bold_with_extra_text_not_group_marker(self):
readme = textwrap.dedent("""\
# T
---
**Note:** This is not a group marker.
## Cat
- [x](https://x.com) - X.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
# "Note:" has text after the strong node, so it's not a group marker
# Category goes into "Other"
assert len(groups) == 1
assert groups[0]["name"] == "Other"
def test_categories_before_any_group_marker(self):
readme = textwrap.dedent("""\
# T
---
## Orphan
- [x](https://x.com) - X.
**A Group**
## Grouped
- [y](https://x.com) - Y.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
assert len(groups) == 2
assert groups[0]["name"] == "Other"
assert groups[0]["categories"][0]["name"] == "Orphan"
assert groups[1]["name"] == "A Group"
assert groups[1]["categories"][0]["name"] == "Grouped"
def _content_nodes(md_text: str) -> list[SyntaxTreeNode]:
"""Helper: parse markdown and return all block nodes."""
md = MarkdownIt("commonmark")
root = SyntaxTreeNode(md.parse(md_text))
return root.children
class TestParseSectionEntries:
def test_flat_entries(self):
nodes = _content_nodes(
"- [django](https://example.com/d) - A web framework.\n"
"- [flask](https://example.com/f) - A micro framework.\n"
)
entries = _parse_section_entries(nodes)
assert len(entries) == 2
assert entries[0]["name"] == "django"
assert entries[0]["url"] == "https://example.com/d"
assert "web framework" in entries[0]["description"]
assert entries[0]["also_see"] == []
assert entries[1]["name"] == "flask"
def test_link_only_entry(self):
nodes = _content_nodes("- [tool](https://x.com)\n")
entries = _parse_section_entries(nodes)
assert len(entries) == 1
assert entries[0]["name"] == "tool"
assert entries[0]["description"] == ""
def test_subcategorized_entries(self):
nodes = _content_nodes(
"- Algorithms\n"
" - [algos](https://x.com/a) - Algo lib.\n"
" - [sorts](https://x.com/s) - Sort lib.\n"
"- Design Patterns\n"
" - [patterns](https://x.com/p) - Pattern lib.\n"
)
entries = _parse_section_entries(nodes)
assert len(entries) == 3
assert entries[0]["name"] == "algos"
assert entries[2]["name"] == "patterns"
def test_text_before_link_is_subcategory(self):
nodes = _content_nodes(
"- MySQL - [awesome-mysql](http://example.com/awesome-mysql/)\n"
" - [mysqlclient](https://example.com/mysqlclient) - MySQL connector.\n"
" - [pymysql](https://example.com/pymysql) - Pure Python MySQL driver.\n"
)
entries = _parse_section_entries(nodes)
# awesome-mysql is a subcategory label, not an entry
assert len(entries) == 2
names = [e["name"] for e in entries]
assert "awesome-mysql" not in names
assert "mysqlclient" in names
assert "pymysql" in names
def test_also_see_sub_entries(self):
nodes = _content_nodes(
"- [asyncio](https://docs.python.org/3/library/asyncio.html) - Async I/O.\n"
" - [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio)\n"
"- [trio](https://github.com/python-trio/trio) - Friendly async.\n"
)
entries = _parse_section_entries(nodes)
assert len(entries) == 2
assert entries[0]["name"] == "asyncio"
assert len(entries[0]["also_see"]) == 1
assert entries[0]["also_see"][0]["name"] == "awesome-asyncio"
assert entries[1]["name"] == "trio"
assert entries[1]["also_see"] == []
def test_entry_count_includes_also_see(self):
readme = textwrap.dedent("""\
# T
---
## Async
- [asyncio](https://x.com) - Async I/O.
- [awesome-asyncio](https://y.com)
- [trio](https://z.com) - Friendly async.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
cats = groups[0]["categories"]
# 2 main entries + 1 also_see = 3
assert cats[0]["entry_count"] == 3
def test_preview_first_four_names(self):
readme = textwrap.dedent("""\
# T
---
## Libs
- [alpha](https://x.com) - A.
- [beta](https://x.com) - B.
- [gamma](https://x.com) - C.
- [delta](https://x.com) - D.
- [epsilon](https://x.com) - E.
# Contributing
Done.
""")
groups, _ = parse_readme(readme)
cats = groups[0]["categories"]
assert cats[0]["preview"] == "alpha, beta, gamma, delta"
def test_description_html_escapes_xss(self):
nodes = _content_nodes('- [lib](https://x.com) - A <script>alert(1)</script> lib.\n')
entries = _parse_section_entries(nodes)
assert "<script>" not in entries[0]["description"]
assert "<script>" in entries[0]["description"]
class TestRenderSectionHtml:
def test_basic_entry(self):
nodes = _content_nodes("- [django](https://example.com) - A web framework.\n")
html = _render_section_html(nodes)
assert 'class="entry"' in html
assert 'href="https://example.com"' in html
assert "django" in html
assert "A web framework." in html
def test_subcategory_label(self):
nodes = _content_nodes(
"- Synchronous\n - [django](https://x.com) - Framework.\n"
)
html = _render_section_html(nodes)
assert 'class="subcat"' in html
assert "Synchronous" in html
assert 'class="entry"' in html
def test_sub_entry(self):
nodes = _content_nodes(
"- [django](https://x.com) - Framework.\n"
" - [awesome-django](https://y.com)\n"
)
html = _render_section_html(nodes)
assert 'class="entry-sub"' in html
assert "awesome-django" in html
def test_link_only_entry(self):
nodes = _content_nodes("- [tool](https://x.com)\n")
html = _render_section_html(nodes)
assert 'class="entry"' in html
assert 'href="https://x.com"' in html
assert "tool" in html
def test_xss_escaped_in_name(self):
nodes = _content_nodes('- [<img onerror=alert(1)>](https://x.com) - Bad.\n')
html = _render_section_html(nodes)
assert "onerror" not in html or "&" in html
def test_xss_escaped_in_subcat(self):
nodes = _content_nodes("- <script>alert(1)</script>\n")
html = _render_section_html(nodes)
assert "<script>" not in html
class TestParseRealReadme:
@pytest.fixture(autouse=True)
def load_readme(self):
readme_path = os.path.join(os.path.dirname(__file__), "..", "..", "README.md")
with open(readme_path, encoding="utf-8") as f:
self.readme_text = f.read()
self.groups, self.resources = parse_readme(self.readme_text)
self.cats = [c for g in self.groups for c in g["categories"]]
def test_at_least_11_groups(self):
assert len(self.groups) >= 11
def test_first_group_is_ai_ml(self):
assert self.groups[0]["name"] == "AI & ML"
def test_at_least_76_categories(self):
assert len(self.cats) >= 76
def test_resources_has_newsletters_and_podcasts(self):
names = [r["name"] for r in self.resources]
assert "Newsletters" in names
assert "Podcasts" in names
def test_contributing_not_in_results(self):
all_names = [c["name"] for c in self.cats] + [r["name"] for r in self.resources]
assert "Contributing" not in all_names
def test_first_category_is_ai_and_agents(self):
assert self.cats[0]["name"] == "AI and Agents"
assert self.cats[0]["slug"] == "ai-and-agents"
def test_web_apis_slug(self):
slugs = [c["slug"] for c in self.cats]
assert "web-apis" in slugs
def test_descriptions_extracted(self):
ai = next(c for c in self.cats if c["name"] == "AI and Agents")
assert "AI applications" in ai["description"]
def test_entry_counts_nonzero(self):
for cat in self.cats:
assert cat["entry_count"] > 0, f"{cat['name']} has 0 entries"
def test_previews_nonempty(self):
for cat in self.cats:
assert cat["preview"], f"{cat['name']} has empty preview"
def test_content_html_nonempty(self):
for cat in self.cats:
assert cat["content_html"], f"{cat['name']} has empty content_html"
def test_algorithms_has_subcategories(self):
algos = next(c for c in self.cats if c["name"] == "Algorithms and Design Patterns")
assert 'class="subcat"' in algos["content_html"]
def test_async_has_also_see(self):
async_cat = next(c for c in self.cats if c["name"] == "Asynchronous Programming")
asyncio_entry = next(e for e in async_cat["entries"] if e["name"] == "asyncio")
assert len(asyncio_entry["also_see"]) >= 1
assert asyncio_entry["also_see"][0]["name"] == "awesome-asyncio"
def test_description_links_stripped_to_text(self):
algos = next(c for c in self.cats if c["name"] == "Algorithms and Design Patterns")
assert "awesome-algorithms" in algos["description"]
assert "https://" not in algos["description"]
def test_miscellaneous_in_own_group(self):
misc_group = next((g for g in self.groups if g["name"] == "Miscellaneous"), None)
assert misc_group is not None
assert any(c["name"] == "Miscellaneous" for c in misc_group["categories"])
gitextract_1fmgb11j/
├── .claude/
│ ├── commands/
│ │ └── review-pending-prs.md
│ └── settings.json
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── deploy-website.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── SPONSORSHIP.md
├── pyproject.toml
└── website/
├── build.py
├── fetch_github_stars.py
├── readme_parser.py
├── static/
│ ├── main.js
│ └── style.css
├── templates/
│ ├── base.html
│ └── index.html
└── tests/
├── test_build.py
├── test_fetch_github_stars.py
└── test_readme_parser.py
SYMBOL INDEX (173 symbols across 7 files)
FILE: website/build.py
function group_categories (line 14) | def group_categories(
class Entry (line 33) | class Entry(TypedDict):
class StarData (line 44) | class StarData(TypedDict):
function extract_github_repo (line 54) | def extract_github_repo(url: str) -> str | None:
function load_stars (line 60) | def load_stars(path: Path) -> dict[str, StarData]:
function sort_entries (line 70) | def sort_entries(entries: list[dict]) -> list[dict]:
function extract_entries (line 83) | def extract_entries(
function build (line 126) | def build(repo_root: str) -> None:
FILE: website/fetch_github_stars.py
function extract_github_repos (line 23) | def extract_github_repos(text: str) -> set[str]:
function save_cache (line 33) | def save_cache(cache: dict) -> None:
function build_graphql_query (line 42) | def build_graphql_query(repos: list[str]) -> str:
function parse_graphql_response (line 60) | def parse_graphql_response(
function fetch_batch (line 80) | def fetch_batch(
function main (line 97) | def main() -> None:
FILE: website/readme_parser.py
class AlsoSee (line 13) | class AlsoSee(TypedDict):
class ParsedEntry (line 18) | class ParsedEntry(TypedDict):
class ParsedSection (line 25) | class ParsedSection(TypedDict):
class ParsedGroup (line 35) | class ParsedGroup(TypedDict):
function slugify (line 48) | def slugify(name: str) -> str:
function render_inline_html (line 60) | def render_inline_html(children: list[SyntaxTreeNode]) -> str:
function render_inline_text (line 86) | def render_inline_text(children: list[SyntaxTreeNode]) -> str:
function _heading_text (line 105) | def _heading_text(node: SyntaxTreeNode) -> str:
function _extract_description (line 113) | def _extract_description(nodes: list[SyntaxTreeNode]) -> str:
function _find_child (line 136) | def _find_child(node: SyntaxTreeNode, child_type: str) -> SyntaxTreeNode...
function _find_inline (line 144) | def _find_inline(node: SyntaxTreeNode) -> SyntaxTreeNode | None:
function _find_first_link (line 152) | def _find_first_link(inline: SyntaxTreeNode) -> SyntaxTreeNode | None:
function _is_leading_link (line 160) | def _is_leading_link(inline: SyntaxTreeNode, link: SyntaxTreeNode) -> bool:
function _extract_description_html (line 165) | def _extract_description_html(inline: SyntaxTreeNode, first_link: Syntax...
function _parse_list_entries (line 181) | def _parse_list_entries(bullet_list: SyntaxTreeNode) -> list[ParsedEntry]:
function _parse_section_entries (line 239) | def _parse_section_entries(content_nodes: list[SyntaxTreeNode]) -> list[...
function _render_bullet_list_html (line 251) | def _render_bullet_list_html(
function _render_section_html (line 302) | def _render_section_html(content_nodes: list[SyntaxTreeNode]) -> str:
function _build_section (line 314) | def _build_section(name: str, body: list[SyntaxTreeNode]) -> ParsedSection:
function _group_by_h2 (line 333) | def _group_by_h2(
function _is_bold_marker (line 360) | def _is_bold_marker(node: SyntaxTreeNode) -> str | None:
function _parse_grouped_sections (line 379) | def _parse_grouped_sections(
function parse_readme (line 435) | def parse_readme(text: str) -> tuple[list[ParsedGroup], list[ParsedSecti...
FILE: website/static/main.js
function relativeTime (line 14) | function relativeTime(isoStr) {
function collapseAll (line 42) | function collapseAll() {
function applyFilters (line 50) | function applyFilters() {
function updateURL (line 113) | function updateURL() {
function getSortValue (line 128) | function getSortValue(row, col) {
function sortRows (line 144) | function sortRows() {
function updateSortIndicators (line 172) | function updateSortIndicators() {
FILE: website/tests/test_build.py
class TestSlugify (line 22) | class TestSlugify:
method test_simple (line 23) | def test_simple(self):
method test_uppercase_acronym (line 26) | def test_uppercase_acronym(self):
method test_all_caps (line 29) | def test_all_caps(self):
method test_hyphenated_input (line 32) | def test_hyphenated_input(self):
method test_special_chars (line 35) | def test_special_chars(self):
method test_single_word (line 38) | def test_single_word(self):
method test_extra_spaces (line 41) | def test_extra_spaces(self):
class TestGroupCategories (line 50) | class TestGroupCategories:
method test_appends_resources (line 51) | def test_appends_resources(self):
method test_no_resources_no_extra_group (line 61) | def test_no_resources_no_extra_group(self):
method test_preserves_group_order (line 69) | def test_preserves_group_order(self):
class TestBuild (line 84) | class TestBuild:
method _make_repo (line 85) | def _make_repo(self, tmp_path, readme):
method test_build_creates_single_page (line 114) | def test_build_creates_single_page(self, tmp_path):
method test_build_cleans_stale_output (line 156) | def test_build_cleans_stale_output(self, tmp_path):
method test_index_contains_category_names (line 180) | def test_index_contains_category_names(self, tmp_path):
method test_index_contains_preview_text (line 211) | def test_index_contains_preview_text(self, tmp_path):
method test_build_with_stars_sorts_by_stars (line 233) | def test_build_with_stars_sorts_by_stars(self, tmp_path):
class TestExtractGithubRepo (line 283) | class TestExtractGithubRepo:
method test_github_url (line 284) | def test_github_url(self):
method test_non_github_url (line 287) | def test_non_github_url(self):
method test_github_io_url (line 290) | def test_github_io_url(self):
method test_trailing_slash (line 293) | def test_trailing_slash(self):
method test_deep_path (line 296) | def test_deep_path(self):
method test_dot_git_suffix (line 299) | def test_dot_git_suffix(self):
method test_org_only (line 302) | def test_org_only(self):
class TestLoadStars (line 311) | class TestLoadStars:
method test_returns_empty_when_missing (line 312) | def test_returns_empty_when_missing(self, tmp_path):
method test_loads_valid_json (line 316) | def test_loads_valid_json(self, tmp_path):
method test_returns_empty_on_corrupt_json (line 323) | def test_returns_empty_on_corrupt_json(self, tmp_path):
class TestSortEntries (line 335) | class TestSortEntries:
method test_sorts_by_stars_descending (line 336) | def test_sorts_by_stars_descending(self):
method test_equal_stars_sorted_alphabetically (line 345) | def test_equal_stars_sorted_alphabetically(self):
method test_no_stars_go_to_bottom (line 353) | def test_no_stars_go_to_bottom(self):
method test_no_stars_sorted_alphabetically (line 361) | def test_no_stars_sorted_alphabetically(self):
FILE: website/tests/test_fetch_github_stars.py
class TestExtractGithubRepos (line 16) | class TestExtractGithubRepos:
method test_extracts_owner_repo_from_github_url (line 17) | def test_extracts_owner_repo_from_github_url(self):
method test_multiple_repos (line 22) | def test_multiple_repos(self):
method test_ignores_non_github_urls (line 30) | def test_ignores_non_github_urls(self):
method test_ignores_github_io_urls (line 35) | def test_ignores_github_io_urls(self):
method test_ignores_github_wiki_and_blob_urls (line 40) | def test_ignores_github_wiki_and_blob_urls(self):
method test_handles_trailing_slash (line 48) | def test_handles_trailing_slash(self):
method test_deduplicates (line 53) | def test_deduplicates(self):
method test_strips_fragment (line 61) | def test_strips_fragment(self):
class TestSaveCache (line 67) | class TestSaveCache:
method test_creates_directory_and_writes_json (line 68) | def test_creates_directory_and_writes_json(self, tmp_path, monkeypatch):
class TestBuildGraphqlQuery (line 78) | class TestBuildGraphqlQuery:
method test_single_repo (line 79) | def test_single_repo(self):
method test_multiple_repos_use_aliases (line 86) | def test_multiple_repos_use_aliases(self):
method test_empty_list (line 91) | def test_empty_list(self):
method test_skips_repos_with_quotes_in_name (line 95) | def test_skips_repos_with_quotes_in_name(self):
method test_skips_only_bad_repos (line 99) | def test_skips_only_bad_repos(self):
class TestParseGraphqlResponse (line 105) | class TestParseGraphqlResponse:
method test_parses_star_count_and_owner (line 106) | def test_parses_star_count_and_owner(self):
method test_skips_null_repos (line 118) | def test_skips_null_repos(self):
method test_handles_missing_owner (line 124) | def test_handles_missing_owner(self):
method test_multiple_repos (line 130) | def test_multiple_repos(self):
class TestMainSkipsFreshCache (line 142) | class TestMainSkipsFreshCache:
method test_skips_fetch_when_cache_is_fresh (line 145) | def test_skips_fetch_when_cache_is_fresh(self, tmp_path, monkeypatch, ...
method test_fetches_when_cache_is_stale (line 179) | def test_fetches_when_cache_is_stale(self, tmp_path, monkeypatch, caps...
FILE: website/tests/test_readme_parser.py
function _parse_inline (line 20) | def _parse_inline(md_text: str) -> list[SyntaxTreeNode]:
class TestRenderInlineHtml (line 28) | class TestRenderInlineHtml:
method test_plain_text_escapes_html (line 29) | def test_plain_text_escapes_html(self):
method test_link_with_target (line 33) | def test_link_with_target(self):
method test_emphasis (line 41) | def test_emphasis(self):
method test_strong (line 45) | def test_strong(self):
method test_code_inline (line 49) | def test_code_inline(self):
method test_mixed_link_and_text (line 53) | def test_mixed_link_and_text(self):
class TestRenderInlineText (line 61) | class TestRenderInlineText:
method test_plain_text (line 62) | def test_plain_text(self):
method test_link_becomes_text (line 66) | def test_link_becomes_text(self):
method test_emphasis_stripped (line 70) | def test_emphasis_stripped(self):
method test_code_inline_kept (line 74) | def test_code_inline_kept(self):
class TestParseReadmeSections (line 160) | class TestParseReadmeSections:
method test_ungrouped_categories_go_to_other (line 161) | def test_ungrouped_categories_go_to_other(self):
method test_ungrouped_category_names (line 167) | def test_ungrouped_category_names(self):
method test_resource_count (line 173) | def test_resource_count(self):
method test_category_slugs (line 177) | def test_category_slugs(self):
method test_category_description (line 183) | def test_category_description(self):
method test_resource_names (line 189) | def test_resource_names(self):
method test_contributing_skipped (line 194) | def test_contributing_skipped(self):
method test_no_separator (line 202) | def test_no_separator(self):
method test_no_description (line 207) | def test_no_description(self):
method test_description_with_link_stripped (line 232) | def test_description_with_link_stripped(self):
class TestParseGroupedReadme (line 253) | class TestParseGroupedReadme:
method test_group_count (line 254) | def test_group_count(self):
method test_group_names (line 258) | def test_group_names(self):
method test_group_slugs (line 263) | def test_group_slugs(self):
method test_group_one_has_one_category (line 268) | def test_group_one_has_one_category(self):
method test_group_two_has_two_categories (line 273) | def test_group_two_has_two_categories(self):
method test_resources_still_parsed (line 279) | def test_resources_still_parsed(self):
method test_empty_group_skipped (line 284) | def test_empty_group_skipped(self):
method test_bold_with_extra_text_not_group_marker (line 306) | def test_bold_with_extra_text_not_group_marker(self):
method test_categories_before_any_group_marker (line 328) | def test_categories_before_any_group_marker(self):
function _content_nodes (line 356) | def _content_nodes(md_text: str) -> list[SyntaxTreeNode]:
class TestParseSectionEntries (line 363) | class TestParseSectionEntries:
method test_flat_entries (line 364) | def test_flat_entries(self):
method test_link_only_entry (line 377) | def test_link_only_entry(self):
method test_subcategorized_entries (line 384) | def test_subcategorized_entries(self):
method test_text_before_link_is_subcategory (line 397) | def test_text_before_link_is_subcategory(self):
method test_also_see_sub_entries (line 411) | def test_also_see_sub_entries(self):
method test_entry_count_includes_also_see (line 425) | def test_entry_count_includes_also_see(self):
method test_preview_first_four_names (line 446) | def test_preview_first_four_names(self):
method test_description_html_escapes_xss (line 468) | def test_description_html_escapes_xss(self):
class TestRenderSectionHtml (line 475) | class TestRenderSectionHtml:
method test_basic_entry (line 476) | def test_basic_entry(self):
method test_subcategory_label (line 484) | def test_subcategory_label(self):
method test_sub_entry (line 493) | def test_sub_entry(self):
method test_link_only_entry (line 502) | def test_link_only_entry(self):
method test_xss_escaped_in_name (line 509) | def test_xss_escaped_in_name(self):
method test_xss_escaped_in_subcat (line 514) | def test_xss_escaped_in_subcat(self):
class TestParseRealReadme (line 520) | class TestParseRealReadme:
method load_readme (line 522) | def load_readme(self):
method test_at_least_11_groups (line 529) | def test_at_least_11_groups(self):
method test_first_group_is_ai_ml (line 532) | def test_first_group_is_ai_ml(self):
method test_at_least_76_categories (line 535) | def test_at_least_76_categories(self):
method test_resources_has_newsletters_and_podcasts (line 538) | def test_resources_has_newsletters_and_podcasts(self):
method test_contributing_not_in_results (line 543) | def test_contributing_not_in_results(self):
method test_first_category_is_ai_and_agents (line 547) | def test_first_category_is_ai_and_agents(self):
method test_web_apis_slug (line 551) | def test_web_apis_slug(self):
method test_descriptions_extracted (line 555) | def test_descriptions_extracted(self):
method test_entry_counts_nonzero (line 559) | def test_entry_counts_nonzero(self):
method test_previews_nonempty (line 563) | def test_previews_nonempty(self):
method test_content_html_nonempty (line 567) | def test_content_html_nonempty(self):
method test_algorithms_has_subcategories (line 571) | def test_algorithms_has_subcategories(self):
method test_async_has_also_see (line 575) | def test_async_has_also_see(self):
method test_description_links_stripped_to_text (line 581) | def test_description_links_stripped_to_text(self):
method test_miscellaneous_in_own_group (line 586) | def test_miscellaneous_in_own_group(self):
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (187K chars).
[
{
"path": ".claude/commands/review-pending-prs.md",
"chars": 2255,
"preview": "---\ndescription: Review pending PRs against CONTRIBUTING.md acceptance criteria.\nallowed-tools: Bash(gh api:*), Bash(gh "
},
{
"path": ".claude/settings.json",
"chars": 374,
"preview": "{\n \"permissions\": {\n \"allow\": [\n \"Bash(gh api:*)\",\n \"Bash(gh pr close:*)\",\n \"Bash(gh pr comment:*)\",\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 606,
"preview": "## Project\n\n[Project Name](url)\n\n## Checklist\n\n- [ ] One project per PR\n- [ ] PR title format: `Add project-name`\n- [ ] "
},
{
"path": ".github/workflows/deploy-website.yml",
"chars": 1904,
"preview": "name: Deploy Website\n\non:\n push:\n branches:\n - master\n schedule:\n - cron: \"0 0 * * *\"\n\npermissions:\n conte"
},
{
"path": ".gitignore",
"chars": 156,
"preview": "# macOS\n.DS_Store\n\n# python\n.venv/\n*.py[co]\n\n# website\nwebsite/output/\nwebsite/data/\n\n# claude code\n.claude/skills/\n.sup"
},
{
"path": "CLAUDE.md",
"chars": 1112,
"preview": "# CLAUDE.md\n\n## Repository Overview\n\nThis is the awesome-python repository - a curated list of Python frameworks, librar"
},
{
"path": "CONTRIBUTING.md",
"chars": 3623,
"preview": "# Contributing\n\n## Quality Requirements\n\nAll submissions must satisfy **ALL** of these:\n\n1. **Python-first**: Primarily "
},
{
"path": "LICENSE",
"chars": 112,
"preview": "Creative Commons Attribution 4.0 International License (CC BY 4.0)\n\nhttp://creativecommons.org/licenses/by/4.0/\n"
},
{
"path": "Makefile",
"chars": 536,
"preview": "-include .env\nexport\n\ninstall:\n\tuv sync\n\nfetch_github_stars:\n\tuv run python website/fetch_github_stars.py\n\ntest:\n\tuv run"
},
{
"path": "README.md",
"chars": 71570,
"preview": "# Awesome Python\n\nAn opinionated list of awesome Python frameworks, libraries, tools, software and resources.\n\n> The **#"
},
{
"path": "SPONSORSHIP.md",
"chars": 1906,
"preview": "# Sponsor awesome-python\n\n**The #10 most-starred repository on all of GitHub.**\n\nawesome-python is where Python develope"
},
{
"path": "pyproject.toml",
"chars": 796,
"preview": "[project]\nname = \"awesome-python\"\nversion = \"0.1.0\"\ndescription = \"An opinionated list of awesome Python frameworks, lib"
},
{
"path": "website/build.py",
"chars": 5872,
"preview": "#!/usr/bin/env python3\n\"\"\"Build a single-page HTML site from README.md for the awesome-python website.\"\"\"\n\nimport json\ni"
},
{
"path": "website/fetch_github_stars.py",
"chars": 6252,
"preview": "#!/usr/bin/env python3\n\"\"\"Fetch GitHub star counts and owner info for all GitHub repos in README.md.\"\"\"\n\nimport json\nimp"
},
{
"path": "website/readme_parser.py",
"chars": 15739,
"preview": "\"\"\"Parse README.md into structured section data using markdown-it-py AST.\"\"\"\n\nfrom __future__ import annotations\n\nimport"
},
{
"path": "website/static/main.js",
"chars": 9140,
"preview": "// State\nvar activeFilter = null; // { type: \"cat\"|\"group\", value: \"...\" }\nvar activeSort = { col: 'stars', order: 'desc"
},
{
"path": "website/static/style.css",
"chars": 11522,
"preview": "/* === Reset & Base === */\n*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n:root {\n --font-d"
},
{
"path": "website/templates/base.html",
"chars": 2153,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-w"
},
{
"path": "website/templates/index.html",
"chars": 5171,
"preview": "{% extends \"base.html\" %} {% block content %}\n<header class=\"hero\">\n <div class=\"hero-main\">\n <div>\n <h1>Awesom"
},
{
"path": "website/tests/test_build.py",
"chars": 11358,
"preview": "\"\"\"Tests for the build module.\"\"\"\n\nimport json\nimport shutil\nimport textwrap\nfrom pathlib import Path\n\nfrom build import"
},
{
"path": "website/tests/test_fetch_github_stars.py",
"chars": 8440,
"preview": "\"\"\"Tests for fetch_github_stars module.\"\"\"\n\nimport json\nimport os\nimport sys\n\nsys.path.insert(0, os.path.join(os.path.di"
},
{
"path": "website/tests/test_readme_parser.py",
"chars": 18041,
"preview": "\"\"\"Tests for the readme_parser module.\"\"\"\n\nimport os\nimport textwrap\n\nimport pytest\n\nfrom readme_parser import (\n _pa"
}
]
About this extraction
This page contains the full source code of the vinta/awesome-python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (174.5 KB), approximately 45.5k tokens, and a symbol index with 173 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.