From 2750cb452447db4bc865ac273ba722b3b69b1d38 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Sat, 30 Apr 2022 16:22:18 -0400 Subject: [PATCH 0001/1130] Add Hatch --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 17bbbe2d..bfd92e25 100644 --- a/README.md +++ b/README.md @@ -929,6 +929,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pip-tools](https://github.com/jazzband/pip-tools) - A set of tools to keep your pinned Python dependencies fresh. * [PyPI](https://pypi.org/) * [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. +* [Hatch](https://github.com/ofek/hatch) - Modern, extensible Python project management. * [poetry](https://github.com/sdispater/poetry) - Python dependency management and packaging made easy. ## Package Repositories From cee5f3e3983a00d55232b24832a4815d742aa308 Mon Sep 17 00:00:00 2001 From: Ofek Lev Date: Thu, 11 Aug 2022 23:54:07 -0400 Subject: [PATCH 0002/1130] Apply suggestions from code review --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bfd92e25..f7090d3a 100644 --- a/README.md +++ b/README.md @@ -929,7 +929,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pip-tools](https://github.com/jazzband/pip-tools) - A set of tools to keep your pinned Python dependencies fresh. * [PyPI](https://pypi.org/) * [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. -* [Hatch](https://github.com/ofek/hatch) - Modern, extensible Python project management. +* [Hatch](https://github.com/pypa/hatch) - Modern, extensible Python project management. * [poetry](https://github.com/sdispater/poetry) - Python dependency management and packaging made easy. ## Package Repositories From 71499de54e7e281b6b09f25a30d8336ebfbb2d10 Mon Sep 17 00:00:00 2001 From: Bruno Rocha Date: Wed, 17 Apr 2024 10:34:35 +0100 Subject: [PATCH 0003/1130] Add dynaconf adding dynaconf https://dynaconf.com --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a99fed92..4c0b9a75 100644 --- a/README.md +++ b/README.md @@ -303,6 +303,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. * [configobj](https://github.com/DiffSK/configobj) - INI file parser with validation. +* [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. From c344c59d7f4493912cd7b97ed91888d03e6f4ba5 Mon Sep 17 00:00:00 2001 From: K <51281148+K-dash@users.noreply.github.com> Date: Sun, 28 Jul 2024 12:22:47 +0900 Subject: [PATCH 0004/1130] Add watchfiles --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..d7ec30a7 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). * [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. ## Functional Programming From aaacefa3299dd5f65b95d301feccb4c113fe3151 Mon Sep 17 00:00:00 2001 From: Juan F Gonzalez Date: Sun, 11 Aug 2024 05:34:49 -0600 Subject: [PATCH 0005/1130] Add FastHTML --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5a7f5f89..504dd944 100644 --- a/README.md +++ b/README.md @@ -1151,6 +1151,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pyramid](https://pylonsproject.org/) - A small, fast, down-to-earth, open source Python web framework. * [awesome-pyramid](https://github.com/uralbash/awesome-pyramid) * [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. + * [fastHTML](https://github.com/AnswerDotAI/fasthtml) - The fastest way to create an HTML app. + * [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) * Asynchronous * [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. From adc907ed077b836f96f772f5a90c8672e6a6b25a Mon Sep 17 00:00:00 2001 From: Hector Canto Date: Sat, 31 Aug 2024 15:24:03 +0200 Subject: [PATCH 0006/1130] add polifactory to Testing:Object factory --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..2cbbb569 100644 --- a/README.md +++ b/README.md @@ -1035,6 +1035,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [factory_boy](https://github.com/FactoryBoy/factory_boy) - A test fixtures replacement for Python. * [mixer](https://github.com/klen/mixer) - Another fixtures replacement. Supports Django, Flask, SQLAlchemy, Peewee and etc. * [model_mommy](https://github.com/vandersonmota/model_mommy) - Creating random fixtures for testing in Django. + * [polyfactory](https://github.com/litestar-org/polyfactory) - mock data generation library with support to classes (continuation of `pydantic-factories`) * Code Coverage * [coverage](https://pypi.org/project/coverage/) - Code coverage measurement. * Fake Data From b600ab27a331825f8b96afc7b7c9ea921a1cc290 Mon Sep 17 00:00:00 2001 From: Noa Tamir <6564007+noatamir@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:56:56 +0200 Subject: [PATCH 0007/1130] Added PyPodcats Podcast --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..55738205 100644 --- a/README.md +++ b/README.md @@ -1185,6 +1185,7 @@ Where to discover learning resources or new Python libraries. ## Podcasts * [Django Chat](https://djangochat.com/) +* [PyPodcats](https://pypodcats.live) - Hidden Figures of Python: Stories from the underrepresented group members of the Python community. * [Python Bytes](https://pythonbytes.fm) * [Talk Python To Me](https://talkpython.fm/) * [Python Test](https://podcast.pythontest.com/) From 247f5ceb507892aafaea32c0cb971c4a9abbbb7d Mon Sep 17 00:00:00 2001 From: Khawar Zamman Wani Date: Thu, 5 Sep 2024 22:33:47 +0530 Subject: [PATCH 0008/1130] add jax --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..4c5017bf 100644 --- a/README.md +++ b/README.md @@ -413,6 +413,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learning](https://github.com/ChristosChristofidis/awesome-deep-learning).* +* [jax](https://github.com/google/jax) - a library for high-performance numerical computing with automatic differentiation and JIT compilation. * [keras](https://github.com/keras-team/keras) - A high-level neural networks library and capable of running on top of either TensorFlow or Theano. * [pytorch](https://github.com/pytorch/pytorch) - Tensors and Dynamic neural networks in Python with strong GPU acceleration. * [pytorch-lightning](https://github.com/Lightning-AI/pytorch-lightning) - Deep learning framework to train, deploy, and ship AI products Lightning fast. From 0bcbb50821d3af6a86bf769b85514523f3310aea Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Fri, 17 Jan 2025 15:13:00 +0600 Subject: [PATCH 0009/1130] Update README.md: new xonsh description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a7f5f89..296f7ce2 100644 --- a/README.md +++ b/README.md @@ -935,7 +935,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Shells based on Python.* -* [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered, cross-platform, Unix-gazing shell language and command prompt. +* [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered shell. Full-featured and cross-platform. ## Specific Formats Processing From cd9811c31624bcbd66c56791213c920c726417d9 Mon Sep 17 00:00:00 2001 From: Shay Molcho <152275799+shaymolcho@users.noreply.github.com> Date: Thu, 23 Jan 2025 12:16:20 +0200 Subject: [PATCH 0010/1130] Ensure consistency by adding missing period in README Added a missing period at the end of a specific line in the README to maintain consistency with similar lines. All other lines of this type included a period, and this correction ensures uniform formatting and readability. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a7f5f89..f04d7dbc 100644 --- a/README.md +++ b/README.md @@ -917,7 +917,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). ## Serialization -*Libraries for serializing complex data types* +*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. * [pysimdjson](https://github.com/TkTech/pysimdjson) - A Python bindings for [simdjson](https://github.com/lemire/simdjson). From 07f4b90ea5475205e5ee4ff0e37344e0b1679ece Mon Sep 17 00:00:00 2001 From: Fuad Hasan Date: Sun, 2 Feb 2025 15:12:10 +0600 Subject: [PATCH 0011/1130] Update README.md Added a robust small framework that works with embedded systems like esp32 with micropython firmware. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..a529c775 100644 --- a/README.md +++ b/README.md @@ -1153,6 +1153,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. * Asynchronous * [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. + * [microdot](https://github.com/miguelgrinberg/microdot) - The impossibly small web framework for Python and MicroPython. ## WebSocket From b82e3e2abfed28d341b155e4e3a9b506fea646a7 Mon Sep 17 00:00:00 2001 From: Pei-Lun Huang Date: Fri, 7 Feb 2025 10:29:48 +0800 Subject: [PATCH 0012/1130] Remove Orator ORM which is no longer maintained --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5a7f5f89..91e0c143 100644 --- a/README.md +++ b/README.md @@ -771,7 +771,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [SQLAlchemy](https://www.sqlalchemy.org/) - 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. - * [orator](https://github.com/sdispater/orator) - The Orator ORM provides a simple yet beautiful ActiveRecord implementation. * [orm](https://github.com/encode/orm) - An async ORM. * [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. From 3cc9d3c057125eb43fd644cd244a2385dbc4b1d0 Mon Sep 17 00:00:00 2001 From: KunchaChennikaWangmai <49652856+KunchaChennikaWangmai@users.noreply.github.com> Date: Wed, 2 Apr 2025 19:21:52 +0530 Subject: [PATCH 0013/1130] Update README.md corrected a few spellings --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5a7f5f89..f2c3291d 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). ## Authentication -*Libraries for implementing authentications schemes.* +*Libraries for implementing authentication schemes.* * OAuth * [authlib](https://github.com/lepture/authlib) - JavaScript Object Signing and Encryption draft implementation. @@ -230,8 +230,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * Code Analysis * [code2flow](https://github.com/scottrogowski/code2flow) - Turn your Python and JavaScript code into DOT flowcharts. - * [prospector](https://github.com/PyCQA/prospector) - A tool to analyse Python code. - * [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analysing dead Python code. + * [prospector](https://github.com/PyCQA/prospector) - A tool to analyze Python code. + * [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. * Code Linters * [flake8](https://github.com/PyCQA/flake8) - A wrapper around `pycodestyle`, `pyflakes` and McCabe. * [awesome-flake8-extensions](https://github.com/DmytroLitvinov/awesome-flake8-extensions) @@ -314,7 +314,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). ## Data Analysis -*Libraries for data analyzing.* +*Libraries for data analysis.* * [pandas](http://pandas.pydata.org/) - A library providing high-performance, easy-to-use data structures and data analysis tools. * [aws-sdk-pandas](https://github.com/aws/aws-sdk-pandas) - Pandas on AWS. @@ -725,7 +725,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [itsdangerous](https://github.com/pallets/itsdangerous) - Various helpers to pass trusted data to untrusted environments. * [magenta](https://github.com/magenta/magenta) - A tool to generate music and art using artificial intelligence. * [pluginbase](https://github.com/mitsuhiko/pluginbase) - A simple but flexible plugin system for Python. -* [tryton](http://www.tryton.org/) - A general purpose business framework. +* [tryton](http://www.tryton.org/) - A general-purpose business framework. ## Natural Language Processing From a235359bd3373da28619fa3ca436c0d5db7c3223 Mon Sep 17 00:00:00 2001 From: v479038280 Date: Tue, 15 Apr 2025 23:48:40 +0800 Subject: [PATCH 0014/1130] Update README.md Add NiceGui --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..e91fc2fd 100644 --- a/README.md +++ b/README.md @@ -557,6 +557,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [urwid](http://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. * [wxPython](https://wxpython.org/) - A blending of the wxWidgets C++ class library with the Python. * [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework +* [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. ## GraphQL From 4f5c48d1f8c37fcc9c2a23de72d0b614533f75dd Mon Sep 17 00:00:00 2001 From: Dennis Handy <106592296+d-handy@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:37:32 +0200 Subject: [PATCH 0015/1130] Added django-unfold to admin panels Added django-unfold to admin panels --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..ce7d589d 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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/flask-admin/flask-admin) - Simple and extensible administrative interface framework for Flask. * [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. * [jet-bridge](https://github.com/jet-admin/jet-bridge) - Admin panel framework for any application with nice UI (ex Jet Django). From 6a31ced62aad3e2a13744496f4388c0a26e4282a Mon Sep 17 00:00:00 2001 From: ajinkya Date: Tue, 29 Apr 2025 07:31:06 +0530 Subject: [PATCH 0016/1130] Add DuckDB to the database section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..deb14d41 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. +* [DuckDB](https://duckdb.org/) - An in-process SQL OLAP database management system; optimized for analytics and fast queries, similar to SQLite but for analytical workloads. ## Database Drivers From 33751092499481ac7ab0d3140c300abef2ebdc6a Mon Sep 17 00:00:00 2001 From: Jayden Chao Date: Sat, 17 May 2025 23:53:40 +0800 Subject: [PATCH 0017/1130] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..a892702c 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [py2exe](https://github.com/py2exe/py2exe) - Freezes Python scripts (Windows). * [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). +* [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). * [shiv](https://github.com/linkedin/shiv) - A command line utility for building fully self-contained zipapps (PEP 441), but with all their dependencies included. ## Documentation From e0152380d4da96812cc88f8ac0e32763443a4aa7 Mon Sep 17 00:00:00 2001 From: Soledad Galli Date: Sat, 28 Jun 2025 11:17:17 -0400 Subject: [PATCH 0018/1130] add machine learning library Adding feature-engine to the machine learning collection. Features: - 320k + downloads per month - 2k+ github stars - 50 contributors - 10k+ doc views per month --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5a7f5f89..89975d15 100644 --- a/README.md +++ b/README.md @@ -697,6 +697,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python).* * [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. +* [Feature-engine](https://feature-engine.trainindata.com/) - 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. * [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. * [NuPIC](https://github.com/numenta/nupic) - Numenta Platform for Intelligent Computing. From 86d4e647da80aa6e716f612bc1fa02eee6b295c4 Mon Sep 17 00:00:00 2001 From: Ronyda Essam Date: Sat, 26 Jul 2025 14:30:24 +0300 Subject: [PATCH 0019/1130] Remove Eel from python GUI Development libraries (no longer maintained + repo archived) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 79624204..dce6029e 100644 --- a/README.md +++ b/README.md @@ -543,7 +543,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for working with graphical user interface applications.* * [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. -* [Eel](https://github.com/ChrisKnott/Eel) - A library for making simple Electron-like offline HTML/JS GUI apps. * [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. * [Flexx](https://github.com/zoofIO/flexx) - Flexx is a pure Python toolkit for creating GUI's, that uses web technology for its rendering. * [Gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. From 49f0d0655f62931064af728125f968511b5b85e0 Mon Sep 17 00:00:00 2001 From: Christopher Hillenbrand Date: Wed, 30 Jul 2025 19:42:29 -0400 Subject: [PATCH 0020/1130] Add mpi4py --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79624204..a514c3e9 100644 --- a/README.md +++ b/README.md @@ -448,6 +448,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * 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. * [Ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem. * Stream Processing From 8863a9815ada843e8e083b2348f7042f1352288c Mon Sep 17 00:00:00 2001 From: focus zheng Date: Fri, 15 Aug 2025 10:37:45 +0800 Subject: [PATCH 0021/1130] add granian --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79624204..fc88672f 100644 --- a/README.md +++ b/README.md @@ -122,14 +122,14 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -## ASGI Servers +## Servers *[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers.* * [daphne](https://github.com/django/daphne) - A HTTP, HTTP2 and WebSocket protocol server for ASGI and ASGI-HTTP. * [uvicorn](https://github.com/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. * [hypercorn](https://github.com/pgjones/hypercorn) - An ASGI and WSGI Server based on Hyper libraries and inspired by Gunicorn. - +* [granian](https://github.com/emmett-framework/granian) - Granian is a Rust HTTP server for Python applications built on top of Hyper and Tokio,supporting WSGI/ASGI/RSGI. ## Asynchronous Programming *Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio).* From 9358397d8c1118b3b863e14c2e4e15690532014f Mon Sep 17 00:00:00 2001 From: focus zheng Date: Fri, 15 Aug 2025 10:43:26 +0800 Subject: [PATCH 0022/1130] titile --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc88672f..8247ab7b 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -## Servers +## ASGI Servers *[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers.* From 53a45001ce37eefd863e40f4b3a448f361280eba Mon Sep 17 00:00:00 2001 From: TS-SAHANA <115524513+TS-SAHANA@users.noreply.github.com> Date: Sun, 17 Aug 2025 21:57:15 +0530 Subject: [PATCH 0023/1130] Add Typer to Command-line Interface Development section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79624204..5ae62e7d 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [cliff](https://github.com/openstack/cliff) - A framework for creating command-line programs with multi-level commands. * [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/tiangolo/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). From 66d7655580aff18ccd6af20fbf54b4bb366f6f52 Mon Sep 17 00:00:00 2001 From: Johnny Chin <154020378+hyjc@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:42:55 -0700 Subject: [PATCH 0024/1130] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79624204..3070984b 100644 --- a/README.md +++ b/README.md @@ -658,6 +658,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [bpython](https://github.com/bpython/bpython) - A fancy interface to the Python interpreter. * [Jupyter Notebook (IPython)](https://jupyter.org) - 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) - A reactive notebook: reproducible, Git-friendly, AI-native, SQL built-in, executable as a script, shareable as an app. * [ptpython](https://github.com/jonathanslenders/ptpython) - Advanced Python REPL built on top of the [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit). ## Internationalization From dbc69ac29e17ba71bcf0763dc086ef9c4e11a79f Mon Sep 17 00:00:00 2001 From: Johnny Chin <154020378+hyjc@users.noreply.github.com> Date: Wed, 27 Aug 2025 22:46:46 -0700 Subject: [PATCH 0025/1130] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3070984b..077c36f4 100644 --- a/README.md +++ b/README.md @@ -658,7 +658,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [bpython](https://github.com/bpython/bpython) - A fancy interface to the Python interpreter. * [Jupyter Notebook (IPython)](https://jupyter.org) - 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) - A reactive notebook: reproducible, Git-friendly, AI-native, SQL built-in, executable as a script, shareable as an app. +* [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/jonathanslenders/ptpython) - Advanced Python REPL built on top of the [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit). ## Internationalization From 6efba6a24249f95ba2a3a3ff9afca3915329aeae Mon Sep 17 00:00:00 2001 From: NajeebFarhan Date: Mon, 8 Sep 2025 02:54:29 +0530 Subject: [PATCH 0026/1130] Add SQLModel to ORM section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 79624204..74f0452b 100644 --- a/README.md +++ b/README.md @@ -771,6 +771,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [Django Models](https://docs.djangoproject.com/en/dev/topics/db/models/) - The Django ORM. * [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL Toolkit and Object Relational Mapper. * [awesome-sqlalchemy](https://github.com/dahlia/awesome-sqlalchemy) + * [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. * [dataset](https://github.com/pudo/dataset) - Store Python dicts in a database - works with SQLite, MySQL, and PostgreSQL. * [orator](https://github.com/sdispater/orator) - The Orator ORM provides a simple yet beautiful ActiveRecord implementation. * [orm](https://github.com/encode/orm) - An async ORM. From b274cb04b800998d3c7f35389121c20ccadd4e08 Mon Sep 17 00:00:00 2001 From: Kim Date: Sun, 19 Oct 2025 11:46:42 +0900 Subject: [PATCH 0027/1130] Update WinPython description for Windows version compatibility --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93d20bb1..e6879268 100644 --- a/README.md +++ b/README.md @@ -727,7 +727,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pythonlibs](http://www.lfd.uci.edu/~gohlke/pythonlibs/) - Unofficial Windows binaries for Python extension packages. * [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://winpython.github.io/) - Portable development environment for Windows 7/8. +* [WinPython](https://winpython.github.io/) - Portable development environment for Windows 10/11. ## Miscellaneous From a1cdf60c0cbfee7ee1b38500c4a3e3b4d8ddbb8b Mon Sep 17 00:00:00 2001 From: offerrall Date: Sun, 26 Oct 2025 15:16:30 +0100 Subject: [PATCH 0028/1130] Add func-to-web to Admin Panels section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 93d20bb1..c85c5511 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [django-grappelli](https://github.com/sehmaschine/django-grappelli) - A jazzy skin for the Django Admin-Interface. * [flask-admin](https://github.com/flask-admin/flask-admin) - Simple and extensible administrative interface framework for Flask. * [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. +* [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). * [wooey](https://github.com/wooey/wooey) - A Django app which creates automatic web UIs for Python scripts. * [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes. From 0d351f7f03b7c5100b17da34cb443904e19d36ed Mon Sep 17 00:00:00 2001 From: hasan Date: Thu, 6 Nov 2025 11:03:19 +0000 Subject: [PATCH 0029/1130] Add Pathway to Data Analysis section --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 93d20bb1..1c2edc74 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. * [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. +* [pathway](https://github.com/pathwaycom/pathway) - Real-time data processing framework for Python with reactive dataflows. + ## Data Validation From d7e656ae00bf2c3b942510ec98248bbba210d685 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Sat, 6 Dec 2025 21:28:08 +1000 Subject: [PATCH 0030/1130] add ultraplot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b56fdefe..f3c50c44 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](http://www.graphviz.org/). * [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. ## Database @@ -1213,4 +1214,3 @@ Your contributions are always welcome! Please take a look at the [contribution g - - - If you have any question about this opinionated list, do not hesitate to contact me [@VintaChen](https://twitter.com/VintaChen) on Twitter or open an issue on GitHub. - From 8e5454b7d53b8d02290efb0eca8b3974babc1e2b Mon Sep 17 00:00:00 2001 From: Thiago Bellini Ribeiro Date: Sat, 6 Dec 2025 22:46:25 +0100 Subject: [PATCH 0031/1130] Add strawberry-django --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..925598c0 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for working with GraphQL.* * [graphene](https://github.com/graphql-python/graphene/) - GraphQL framework for Python. +* [strawberry-django](https://github.com/strawberry-graphql/strawberry-django) - Strawberry GraphQL integration with Django. ## Game Development From c976bf8061f8c876ca6704db6f70808784e0ce74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Stenstr=C3=B6m?= Date: Tue, 9 Dec 2025 23:51:41 +0100 Subject: [PATCH 0032/1130] Add JustHTML library to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..7f5309d9 100644 --- a/README.md +++ b/README.md @@ -609,6 +609,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [bleach](https://github.com/mozilla/bleach) - A whitelist-based HTML sanitization and text linkification library. * [cssutils](https://pypi.org/project/cssutils/) - A CSS library for Python. * [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. +* [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. No C extensions to compile. No system dependencies to install. No complex API to learn. * [lxml](http://lxml.de/) - 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. From fed97490ca7bd6fd9f50a813865bf0139920217b Mon Sep 17 00:00:00 2001 From: qooxzuub <154452450+qooxzuub@users.noreply.github.com> Date: Wed, 10 Dec 2025 07:23:41 +0000 Subject: [PATCH 0033/1130] Add pikepdf --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..62e361b4 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [xlwt](https://github.com/python-excel/xlwt) / [xlrd](https://github.com/python-excel/xlrd) - Writing and reading data and formatting information from Excel files. * PDF * [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. * [PyPDF2](https://github.com/mstamy2/PyPDF2) - A library capable of splitting, merging and transforming PDF pages. * [ReportLab](https://www.reportlab.com/opensource/) - Allowing Rapid creation of rich PDF documents. * Markdown From 294b5f2206573ec74c48c65cab1411930049cc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammet=20Berke=20A=C4=9Faya?= Date: Thu, 18 Dec 2025 11:53:49 +0300 Subject: [PATCH 0034/1130] Add Polars to Data Analysis section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..7dac4534 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for data analyzing.* * [pandas](http://pandas.pydata.org/) - A library providing high-performance, easy-to-use data structures and data analysis tools. +* [polars](https://github.com/pola-rs/polars) - A fast DataFrame library implemented in Rust with a Python API. * [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. From fe8f366534dae1e43312b345d8809004989081d3 Mon Sep 17 00:00:00 2001 From: Mervin Praison Date: Thu, 18 Dec 2025 20:14:40 +0000 Subject: [PATCH 0035/1130] Add PraisonAI to Machine Learning section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..05cc6294 100644 --- a/README.md +++ b/README.md @@ -719,6 +719,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [vowpal_porpoise](https://github.com/josephreisinger/vowpal_porpoise) - A lightweight Python wrapper for [Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit/). * [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. * [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. +* [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. ## Microsoft Windows From 5cabd273d067f968a001aab1ba2575950c42f2ed Mon Sep 17 00:00:00 2001 From: Bas Terwijn Date: Wed, 24 Dec 2025 00:35:24 +0100 Subject: [PATCH 0036/1130] Add memory-graph --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b56fdefe..0cd0e3d9 100644 --- a/README.md +++ b/README.md @@ -420,6 +420,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [django-debug-toolbar](https://github.com/jazzband/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. * [pyelftools](https://github.com/eliben/pyelftools) - Parsing and analyzing ELF files and DWARF debugging information. ## Deep Learning From 6936494d3b97b7516b6c35d22293a07892ec674e Mon Sep 17 00:00:00 2001 From: Ishani Arora Date: Thu, 25 Dec 2025 21:29:02 +0530 Subject: [PATCH 0037/1130] Fix punctuation in Refactoring section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b56fdefe..45cd0dbd 100644 --- a/README.md +++ b/README.md @@ -854,7 +854,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). ## Refactoring -*Refactoring tools and libraries for Python* +*Refactoring tools and libraries for Python.* * [Bicycle Repair Man](http://bicyclerepair.sourceforge.net/) - Bicycle Repair Man, a refactoring tool for Python. * [Bowler](https://pybowler.io/) - Safe code refactoring for modern Python. From a652ecb6338713c402c76685f97d4627157523d0 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 11:12:03 +0800 Subject: [PATCH 0038/1130] docs: restructure CONTRIBUTING.md with detailed acceptance criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand contributing guidelines with: - Quick checklist for submissions - Three-tier acceptance criteria (Industry Standard, Rising Star, Hidden Gem) - Quality requirements and automatic rejection rules - Entry format reference with examples - PR description template This provides clear, actionable guidance for contributors and reduces ambiguity in the review process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CONTRIBUTING.md | 136 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 14 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 943f93cb..a9cd757d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,19 +2,127 @@ Your contributions are always welcome! -## Guidelines +## Quick Checklist -* Add one link per Pull Request. - * Make sure the PR title is in the format of `Add project-name`. - * Write down the reason why the library is awesome. -* Add the link: `* [project-name](http://example.com/) - A short description ends with a period.` - * Keep descriptions concise and **short**. -* Add a section if needed. - * Add the section description. - * Add the section title to Table of Contents. -* Search previous Pull Requests or Issues before making a new one, as yours may be a duplicate. -* Don't mention `Python` in the description as it's implied. -* Check your spelling and grammar. -* Remove any trailing whitespace. +Before submitting a PR, verify: -Just a gentle reminder: **Try not to submit your own project. Instead, wait for someone finds it useful and submits it for you.** +- [ ] One link per Pull Request +- [ ] PR title format: `Add project-name` +- [ ] Entry format: `* [project-name](url) - A short description ending with a period.` +- [ ] Alphabetical order maintained (run `python sort.py`) +- [ ] No mention of "Python" in description (it's implied) +- [ ] Description is concise (one sentence) +- [ ] No trailing whitespace +- [ ] Spelling and grammar checked + +## 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 (may have <500 stars) +- Solves niche problems elegantly +- Strong recommendation from experienced developers +- Must include compelling justification in PR description + +## 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" + +## Automatic Rejection + +PRs will be **closed without review** if: + +- Project is archived or abandoned (no commits in 12+ months) +- Duplicate of existing entry without clear advantages +- No documentation or unclear use case +- Alpha/beta/experimental status +- Self-promotion (wait for someone else to find it useful and submit) +- Less than 500 GitHub stars AND not justified as a hidden gem + +## Entry Format Reference + +### Standard Entry + +```markdown +- [project-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 + +If adding a new category: + +1. Add section description in italics: `*Libraries for doing X.*` +2. Add the section title to the Table of Contents +3. Keep sections in alphabetical order + +## PR Description Template + +Please include the following in your PR description: + +``` +## Why This Project Is Awesome + +[Explain which criterion it meets: Industry Standard / Rising Star / Hidden Gem] + +## How It Differs + +[If similar entries exist, explain what makes this one unique] +``` + +## Review Process + +PRs are reviewed by automated tools and maintainers: + +1. **Format Check**: Entry follows the correct format +2. **Duplicate Check**: Not already listed or previously rejected +3. **Activity Check**: Project shows recent activity +4. **Quality Check**: Meets acceptance criteria + +Search previous Pull Requests and Issues before submitting, as yours may be a duplicate. From 2ade49720b069d06791d30438e423d6b9876b74e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 11:12:11 +0800 Subject: [PATCH 0039/1130] docs: add CLAUDE.md for AI-assisted curation context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provides repository overview and guidelines for AI tools: - Repository architecture and structure - Reference to CONTRIBUTING.md for PR review criteria - Key curation considerations This document enables AI assistants to understand the project context and apply consistent review standards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..81d13417 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,32 @@ +# 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 From 65eba38143bef9c1536ec9a3bb3811cc786f7bed Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 11:23:58 +0800 Subject: [PATCH 0040/1130] ci: add Claude Code GitHub Actions workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two workflow configurations for AI-assisted development: - claude-code-review.yml: Automatic PR reviews on new/updated PRs - claude.yml: Interactive Claude assistant via @claude mentions These workflows integrate with Claude Code to automate PR reviews and provide on-demand AI assistance in issues and PR comments. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-code-review.yml | 43 +++++++++++++++++++++ .github/workflows/claude.yml | 48 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 00000000..cc0afd75 --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,43 @@ +name: Claude Code Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + # Optional: Only run on specific file changes + # paths: + # - "src/**/*.ts" + # - "src/**/*.tsx" + # - "src/**/*.js" + # - "src/**/*.jsx" + +jobs: + claude-review: + # Optional: Filter by PR author + # if: | + # github.event.pull_request.user.login == 'external-contributor' || + # github.event.pull_request.user.login == 'new-developer' || + # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' + + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + plugin_marketplaces: "https://github.com/anthropics/claude-code.git" + plugins: "code-review@claude-code-plugins" + prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}" + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..31c35622 --- /dev/null +++ b/.github/workflows/claude.yml @@ -0,0 +1,48 @@ +name: Claude Code + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + issues: read + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + + # This is an optional setting that allows Claude to read CI results on PRs + additional_permissions: | + actions: read + # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. + # prompt: 'Update the pull request description to include a summary of changes.' + + # Optional: Add claude_args to customize behavior and configuration + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + # or https://code.claude.com/docs/en/cli-reference for available options + # claude_args: '--allowed-tools Bash(gh pr:*)' From e803fd3e48b5cfcf036bb564aae126f572e20a2c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:02:57 +0800 Subject: [PATCH 0041/1130] ci: remove generic Claude workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove claude.yml and claude-code-review.yml in preparation for more focused workflow separation. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-code-review.yml | 43 --------------------- .github/workflows/claude.yml | 48 ------------------------ 2 files changed, 91 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml delete mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index cc0afd75..00000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - plugin_marketplaces: "https://github.com/anthropics/claude-code.git" - plugins: "code-review@claude-code-plugins" - prompt: "/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}" - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 31c35622..00000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' From e65cc4917f7c60ff46fd3cef7b7520e91960307b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:03:05 +0800 Subject: [PATCH 0042/1130] ci: add focused Claude workflows with context-aware prompts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add claude-mention.yml for @claude mentions in issues/PRs and claude-pr-review.yml for automatic PR reviews. Key improvements: - claude-mention: Triggers on @claude with project-specific prompt - claude-pr-review: Auto-reviews PRs from new contributors with CONTRIBUTING.md criteria - Both workflows: Restricted permissions (read-only contents, no merge capability) 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-mention.yml | 45 ++++++++++++++++++++++++++ .github/workflows/claude-pr-review.yml | 43 ++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 .github/workflows/claude-mention.yml create mode 100644 .github/workflows/claude-pr-review.yml diff --git a/.github/workflows/claude-mention.yml b/.github/workflows/claude-mention.yml new file mode 100644 index 00000000..eba29d27 --- /dev/null +++ b/.github/workflows/claude-mention.yml @@ -0,0 +1,45 @@ +name: Claude Mention + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +jobs: + claude-mention: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + + runs-on: ubuntu-latest + + permissions: + contents: read # Cannot merge PR + pull-requests: write + issues: read + actions: read # Required for Claude to read CI results on PRs + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + additional_permissions: | + actions: read + prompt: | + If this is a PR adding a project or library, review against CONTRIBUTING.md criteria. + Otherwise, follow the instructions in the comment or issue that tagged you. diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml new file mode 100644 index 00000000..5e7702dc --- /dev/null +++ b/.github/workflows/claude-pr-review.yml @@ -0,0 +1,43 @@ +name: Claude PR Review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened, labeled] + paths: + - "README.md" + +jobs: + claude-pr-review: + if: | + github.event.label.name == 'claude-review' || + github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' || + github.event.pull_request.author_association == 'FIRST_TIMER' || + github.event.pull_request.author_association == 'NONE' + + runs-on: ubuntu-latest + + permissions: + contents: read # Cannot merge PR + pull-requests: write + issues: read + actions: read # Required for Claude to read CI results on PRs + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Run Claude Code Review + id: claude-review + uses: anthropics/claude-code-action@v1 + with: + # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + plugin_marketplaces: "https://github.com/anthropics/claude-code.git" + plugins: "code-review@claude-code-plugins" + prompt: | + Review this PR against the acceptance criteria in CONTRIBUTING.md. + Focus on: Industry Standard, Rising Star, or Hidden Gem criteria. + Check for automatic rejection reasons (abandoned, duplicates, spam). + /code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }} From f9a0444b5edd1f746f407cbeaa786bba9e719ee7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:07:04 +0800 Subject: [PATCH 0043/1130] chore: remove redundant issue templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both templates only redirect users to open PRs instead, which is unnecessary overhead. Contributors can directly open PRs without going through issue template selection. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/ISSUE_TEMPLATE/add-new-library-to-this-list.md | 10 ---------- .github/ISSUE_TEMPLATE/add-xxx.md | 10 ---------- 2 files changed, 20 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/add-new-library-to-this-list.md delete mode 100644 .github/ISSUE_TEMPLATE/add-xxx.md diff --git a/.github/ISSUE_TEMPLATE/add-new-library-to-this-list.md b/.github/ISSUE_TEMPLATE/add-new-library-to-this-list.md deleted file mode 100644 index eba33ff6..00000000 --- a/.github/ISSUE_TEMPLATE/add-new-library-to-this-list.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Add new library to this list -about: '' -title: '' -labels: '' -assignees: '' - ---- - -**Please open a Pull Request instead.** diff --git a/.github/ISSUE_TEMPLATE/add-xxx.md b/.github/ISSUE_TEMPLATE/add-xxx.md deleted file mode 100644 index 585f4919..00000000 --- a/.github/ISSUE_TEMPLATE/add-xxx.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Add XXX -about: Add new item to the list -title: '' -labels: '' -assignees: '' - ---- - -**Please open a Pull Request instead.** From a5146453f93caba3412cb7f1944bb8203da2422f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:07:11 +0800 Subject: [PATCH 0044/1130] docs: remove manual alphabetical sorting requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alphabetical ordering will be handled by automation (sort.py in CI), removing this manual burden from contributors. Updated PR template with structured checklist aligned to acceptance criteria. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/PULL_REQUEST_TEMPLATE.md | 21 +++++++++++++++------ CONTRIBUTING.md | 1 - 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fdacb788..ab34f964 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,20 @@ -## What is this Python project? +## Checklist -Describe features. +- [ ] One link per Pull Request +- [ ] PR title format: `Add project-name` +- [ ] Entry format: `* [project-name](url) - Description ending with period.` +- [ ] Description is concise (no mention of "Python") -## What's the difference between this Python project and similar ones? +## Why This Project Is Awesome -Enumerate comparisons. +Which criterion does it meet? (pick one) --- +- [ ] **Industry Standard** - The go-to tool for a specific use case +- [ ] **Rising Star** - 5,000+ stars in <2 years, significant adoption +- [ ] **Hidden Gem** - Exceptional quality, solves niche problems elegantly -Anyone who agrees with this pull request could submit an *Approve* review to it. +Explain: + +## How It Differs + +If similar entries exist, what makes this one unique? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9cd757d..a4a9c605 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,6 @@ Before submitting a PR, verify: - [ ] One link per Pull Request - [ ] PR title format: `Add project-name` - [ ] Entry format: `* [project-name](url) - A short description ending with a period.` -- [ ] Alphabetical order maintained (run `python sort.py`) - [ ] No mention of "Python" in description (it's implied) - [ ] Description is concise (one sentence) - [ ] No trailing whitespace From e5d4d4f2b74b5d777769ce2a17607ff327ab8665 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:08:39 +0800 Subject: [PATCH 0045/1130] ci: remove Claude mention workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes the generic Claude mention workflow in favor of more focused, context-aware workflows already in place. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-mention.yml | 45 ---------------------------- 1 file changed, 45 deletions(-) delete mode 100644 .github/workflows/claude-mention.yml diff --git a/.github/workflows/claude-mention.yml b/.github/workflows/claude-mention.yml deleted file mode 100644 index eba29d27..00000000 --- a/.github/workflows/claude-mention.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Claude Mention - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude-mention: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - - runs-on: ubuntu-latest - - permissions: - contents: read # Cannot merge PR - pull-requests: write - issues: read - actions: read # Required for Claude to read CI results on PRs - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - additional_permissions: | - actions: read - prompt: | - If this is a PR adding a project or library, review against CONTRIBUTING.md criteria. - Otherwise, follow the instructions in the comment or issue that tagged you. From 7d215c37a53bcfe9b891491576c34885110a7dba Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:14:04 +0800 Subject: [PATCH 0046/1130] ci: add id-token write permission for OIDC authentication MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable OIDC authentication in Claude PR review workflow by adding id-token write permission. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 5e7702dc..740fa33b 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -21,6 +21,7 @@ jobs: pull-requests: write issues: read actions: read # Required for Claude to read CI results on PRs + id-token: write # Required for OIDC authentication with GitHub steps: - name: Checkout repository From 43046d9848caae698a226a81a988bb53e207d326 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:38:09 +0800 Subject: [PATCH 0047/1130] ci: update workflow trigger label to 'claude review' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change label name from 'claude-review' to 'claude review' for consistency with GitHub's standard label naming convention using spaces. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 740fa33b..03358e69 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -9,7 +9,7 @@ on: jobs: claude-pr-review: if: | - github.event.label.name == 'claude-review' || + github.event.label.name == 'claude review' || github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' || github.event.pull_request.author_association == 'FIRST_TIMER' || github.event.pull_request.author_association == 'NONE' From e05cd240c94362319fd39e65ae0f353b6d34408a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 12:38:19 +0800 Subject: [PATCH 0048/1130] docs: clarify id-token permission comment in workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update comment to better reflect the actual purpose of the id-token permission for Claude GitHub app functionality. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 03358e69..53a51d28 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -21,7 +21,7 @@ jobs: pull-requests: write issues: read actions: read # Required for Claude to read CI results on PRs - id-token: write # Required for OIDC authentication with GitHub + id-token: write # Required for Claude GitHub app to function steps: - name: Checkout repository From 436e6cea3f507c9e588a22827dca733bf631fe98 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 13:13:46 +0800 Subject: [PATCH 0049/1130] docs: restructure CONTRIBUTING.md review process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorganize quick checklist for better clarity - Move category placement check higher in priority - Restructure automatic rejection section to end for better flow - Lower star threshold from 500 to 100 for hidden gem consideration - Add emphasis on one project per PR rule 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- CONTRIBUTING.md | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4a9c605..62b8ca0b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,5 @@ # Contributing -Your contributions are always welcome! - ## Quick Checklist Before submitting a PR, verify: @@ -9,8 +7,8 @@ Before submitting a PR, verify: - [ ] One link per Pull Request - [ ] PR title format: `Add project-name` - [ ] Entry format: `* [project-name](url) - A short description ending with a period.` -- [ ] No mention of "Python" in description (it's implied) - [ ] Description is concise (one sentence) +- [ ] Placed in the appropriate category/subcategory - [ ] No trailing whitespace - [ ] Spelling and grammar checked @@ -48,17 +46,6 @@ All submissions must satisfy **ALL** of these: 4. **Documented**: Clear README with examples and use cases 5. **Unique**: Adds distinct value, not "yet another X" -## Automatic Rejection - -PRs will be **closed without review** if: - -- Project is archived or abandoned (no commits in 12+ months) -- Duplicate of existing entry without clear advantages -- No documentation or unclear use case -- Alpha/beta/experimental status -- Self-promotion (wait for someone else to find it useful and submit) -- Less than 500 GitHub stars AND not justified as a hidden gem - ## Entry Format Reference ### Standard Entry @@ -120,8 +107,20 @@ Please include the following in your PR description: PRs are reviewed by automated tools and maintainers: 1. **Format Check**: Entry follows the correct format -2. **Duplicate Check**: Not already listed or previously rejected -3. **Activity Check**: Project shows recent activity -4. **Quality Check**: Meets acceptance criteria +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: + +- Add more than one project per PR +- Duplicate of existing entry +- Self-promotion (wait for someone else to find it useful and submit) +- Less than 100 GitHub stars AND not justified as a hidden gem +- Project is archived or abandoned (no commits in 12+ months) +- No documentation or unclear use case From e87d33b2f4aca25d94a55616383ab6893fb91fe4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 13:13:51 +0800 Subject: [PATCH 0050/1130] chore: add Claude Code configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add local settings with GitHub CLI permissions for PR review - Add custom /review-pr command for automated PR review workflow 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/commands/review-pr.md | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .claude/commands/review-pr.md diff --git a/.claude/commands/review-pr.md b/.claude/commands/review-pr.md new file mode 100644 index 00000000..4f7f9807 --- /dev/null +++ b/.claude/commands/review-pr.md @@ -0,0 +1,48 @@ +# Review PR Command + +Review a PR against [CONTRIBUTING.md](../../CONTRIBUTING.md) acceptance criteria. + +## Usage + +``` +/review-pr +``` + +## Instructions + +1. Read [CONTRIBUTING.md](../../CONTRIBUTING.md) for current review criteria +2. Extract PR number from argument (supports full URL or just number) +3. Fetch PR details: `gh pr view --repo vinta/awesome-python --json title,body,author,files,url` +4. Fetch PR diff: `gh pr diff --repo vinta/awesome-python` +5. For each project added, fetch repo stats: `gh api repos// --jq '{stars: .stargazers_count, created: .created_at, updated: .pushed_at, language: .language, archived: .archived}'` +6. Review against all criteria in CONTRIBUTING.md + +## Quick Rejection Checks + +Check these rules first - if any fail, recommend rejection: + +- Add more than one project per PR +- Duplicate of existing entry +- Self-promotion (wait for someone else to find it useful and submit) +- Less than 100 GitHub stars AND not justified as a hidden gem +- Project is archived or abandoned (no commits in 12+ months) +- No documentation or unclear use case + +## Output Format + +Provide a simple review: + +1. **Rejection Check** - table with the 3 rules and PASS/FAIL +2. **Recommendation** - APPROVE for further review, or REJECT with reason + +## Closing PRs + +If user asks to close/reject: + +```bash +gh pr close --repo vinta/awesome-python --comment "" +``` + +Keep rejection comments short and reference CONTRIBUTING.md. + +$ARGUMENTS From 3aabfb8ec82c71d749c776c8a8cf3c33d8517aec Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 13:26:57 +0800 Subject: [PATCH 0051/1130] docs: update automatic rejection criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace self-promotion rule with category placement check to better reflect actual review priorities. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/commands/review-pr.md | 4 ++-- CONTRIBUTING.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.claude/commands/review-pr.md b/.claude/commands/review-pr.md index 4f7f9807..04410012 100644 --- a/.claude/commands/review-pr.md +++ b/.claude/commands/review-pr.md @@ -23,10 +23,10 @@ Check these rules first - if any fail, recommend rejection: - Add more than one project per PR - Duplicate of existing entry -- Self-promotion (wait for someone else to find it useful and submit) -- Less than 100 GitHub stars AND not justified as a hidden gem +- 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 ## Output Format diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62b8ca0b..f9feac7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,7 +120,7 @@ PRs will be **closed** if: - Add more than one project per PR - Duplicate of existing entry -- Self-promotion (wait for someone else to find it useful and submit) -- Less than 100 GitHub stars AND not justified as a hidden gem +- 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 From 80cbbfe25623d8c32ecd0b7a2c26f00c3d0e8763 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 14:55:47 +0800 Subject: [PATCH 0052/1130] refactor: replace single PR review with bulk review command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace review-pr command with review-pending-prs to enable efficient batch review of open PRs. New command fetches up to 10 unreviewed PRs, performs automated checks, and provides bulk actions (close rejected, label approved, request fixes). 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 65 ++++++++++++++++++++++++++ .claude/commands/review-pr.md | 48 ------------------- 2 files changed, 65 insertions(+), 48 deletions(-) create mode 100644 .claude/commands/review-pending-prs.md delete mode 100644 .claude/commands/review-pr.md diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md new file mode 100644 index 00000000..e66527b5 --- /dev/null +++ b/.claude/commands/review-pending-prs.md @@ -0,0 +1,65 @@ +# Review Pending PRs + +Review pending PRs against [CONTRIBUTING.md](../../CONTRIBUTING.md) acceptance criteria. + +## Usage + +``` +/review-pending-prs +``` + +## Instructions + +1. Fetch open PRs (skip reviewed): `gh pr list --repo vinta/awesome-python --limit 20 --json number,title,author,url,labels --jq '[.[] | select(.labels | map(.name) | index("claude reviewed") | not)] | .[:10]'` +2. For each PR: + - Fetch PR details: `gh pr view --repo vinta/awesome-python --json title,body,author,files,url,mergeable,mergeStateStatus` + - Fetch PR diff: `gh pr diff --repo vinta/awesome-python` + - For each project added, fetch repo stats: `gh api repos// --jq '{stars: .stargazers_count, created: .created_at, updated: .pushed_at, language: .language, archived: .archived}'` +3. Review against all criteria in [CONTRIBUTING.md](../../CONTRIBUTING.md) +4. Present summary table of 10 PRs with recommendations +5. Ask user: + +``` +Would you like me to: + +1. Close the rejected PRs with comments? +2. Add "claude reviewed" label to the approved PRs? +3. Add comments to PRs need to be fixed? +``` + +## Quick Rejection Checks + +Check these rules first - if any fail, recommend rejection: + +- 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 +- PR has merge conflicts (notify author to resolve) + +## Output Format + +Provide a simple review: + +1. **Rejection Check** - table with the 3 rules and PASS/FAIL +2. **Recommendation** - APPROVE for further review, or REJECT with reason + +## Closing PRs + +If user asks to close/reject: + +```bash +gh pr close --repo vinta/awesome-python --comment "" +``` + +## Mark as Reviewed + +```bash +gh pr edit --repo vinta/awesome-python --add-label "claude reviewed" +``` + +## Extra Instructions (If Provided) + +$ARGUMENTS diff --git a/.claude/commands/review-pr.md b/.claude/commands/review-pr.md deleted file mode 100644 index 04410012..00000000 --- a/.claude/commands/review-pr.md +++ /dev/null @@ -1,48 +0,0 @@ -# Review PR Command - -Review a PR against [CONTRIBUTING.md](../../CONTRIBUTING.md) acceptance criteria. - -## Usage - -``` -/review-pr -``` - -## Instructions - -1. Read [CONTRIBUTING.md](../../CONTRIBUTING.md) for current review criteria -2. Extract PR number from argument (supports full URL or just number) -3. Fetch PR details: `gh pr view --repo vinta/awesome-python --json title,body,author,files,url` -4. Fetch PR diff: `gh pr diff --repo vinta/awesome-python` -5. For each project added, fetch repo stats: `gh api repos// --jq '{stars: .stargazers_count, created: .created_at, updated: .pushed_at, language: .language, archived: .archived}'` -6. Review against all criteria in CONTRIBUTING.md - -## Quick Rejection Checks - -Check these rules first - if any fail, recommend rejection: - -- 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 - -## Output Format - -Provide a simple review: - -1. **Rejection Check** - table with the 3 rules and PASS/FAIL -2. **Recommendation** - APPROVE for further review, or REJECT with reason - -## Closing PRs - -If user asks to close/reject: - -```bash -gh pr close --repo vinta/awesome-python --comment "" -``` - -Keep rejection comments short and reference CONTRIBUTING.md. - -$ARGUMENTS From 4117b23614cb4245f2366fb6e83c589b15dd8c87 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 15:32:52 +0800 Subject: [PATCH 0053/1130] docs: improve PR review workflow command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify gh pr list query using --search flag to filter by label, reorganize rejection criteria with merge conflicts first, and streamline output format instructions. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md index e66527b5..5aae5630 100644 --- a/.claude/commands/review-pending-prs.md +++ b/.claude/commands/review-pending-prs.md @@ -10,7 +10,7 @@ Review pending PRs against [CONTRIBUTING.md](../../CONTRIBUTING.md) acceptance c ## Instructions -1. Fetch open PRs (skip reviewed): `gh pr list --repo vinta/awesome-python --limit 20 --json number,title,author,url,labels --jq '[.[] | select(.labels | map(.name) | index("claude reviewed") | not)] | .[:10]'` +1. Fetch 10 open PRs (skip reviewed): `gh pr list --repo vinta/awesome-python --limit 10 --search "-label:\"claude reviewed\"" --json number,title,author,url` 2. For each PR: - Fetch PR details: `gh pr view --repo vinta/awesome-python --json title,body,author,files,url,mergeable,mergeStateStatus` - Fetch PR diff: `gh pr diff --repo vinta/awesome-python` @@ -24,27 +24,27 @@ Would you like me to: 1. Close the rejected PRs with comments? 2. Add "claude reviewed" label to the approved PRs? -3. Add comments to PRs need to be fixed? +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 -- PR has merge conflicts (notify author to resolve) ## Output Format Provide a simple review: -1. **Rejection Check** - table with the 3 rules and PASS/FAIL -2. **Recommendation** - APPROVE for further review, or REJECT with reason +1. **Rejection Check** - table with the above rules and PASS/FAIL +2. **Recommendation** - APPROVE or REJECT ## Closing PRs From 5682e60934ff79751bb451993e427ff1ed14e79f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 15:32:53 +0800 Subject: [PATCH 0054/1130] config: add Claude permissions settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define allowed Bash operations for gh CLI commands used in PR review automation workflows. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/settings.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..73febfbf --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,18 @@ +{ + "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": [] + } +} From 6a7a1d63e7dfd854693497b8f16a60c1b76c56b6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:00:31 +0800 Subject: [PATCH 0055/1130] config: add frontmatter metadata to PR review command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add YAML frontmatter with description and allowed-tools permissions to the review-pending-prs command file. This configures tool access restrictions for GitHub PR operations. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md index 5aae5630..c691abba 100644 --- a/.claude/commands/review-pending-prs.md +++ b/.claude/commands/review-pending-prs.md @@ -1,6 +1,7 @@ -# Review Pending PRs - -Review pending PRs against [CONTRIBUTING.md](../../CONTRIBUTING.md) acceptance criteria. +--- +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:*), Bash(gh pr view:*) +--- ## Usage From 0d53c4f0f18a3674229b9828b3cc82e06bc3fb3e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:13:42 +0800 Subject: [PATCH 0056/1130] docs: optimize PR review workflow for efficiency Improve the PR review command workflow to reduce API calls and clarify the review process: - Fetch PR details in single call (combine list + view data) - Add quick rejection checks before API calls (conflicts, duplicates) - Simplify terminology (APPROVE/REJECT -> PASS/REJECT) - Streamline section headers for clarity - Remove gh pr view from allowed tools (redundant with list) This reduces unnecessary GitHub API calls and makes the review process more efficient. Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 31 ++++++++++++++------------ 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md index c691abba..76d0a527 100644 --- a/.claude/commands/review-pending-prs.md +++ b/.claude/commands/review-pending-prs.md @@ -1,6 +1,6 @@ --- 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:*), Bash(gh pr view:*) +allowed-tools: Bash(gh api:*), Bash(gh pr close:*), Bash(gh pr diff:*), Bash(gh pr edit:*), Bash(gh pr list:*) --- ## Usage @@ -11,20 +11,23 @@ allowed-tools: Bash(gh api:*), Bash(gh pr close:*), Bash(gh pr diff:*), Bash(gh ## Instructions -1. Fetch 10 open PRs (skip reviewed): `gh pr list --repo vinta/awesome-python --limit 10 --search "-label:\"claude reviewed\"" --json number,title,author,url` -2. For each PR: - - Fetch PR details: `gh pr view --repo vinta/awesome-python --json title,body,author,files,url,mergeable,mergeStateStatus` - - Fetch PR diff: `gh pr diff --repo vinta/awesome-python` - - For each project added, fetch repo stats: `gh api repos// --jq '{stars: .stargazers_count, created: .created_at, updated: .pushed_at, language: .language, archived: .archived}'` -3. Review against all criteria in [CONTRIBUTING.md](../../CONTRIBUTING.md) -4. Present summary table of 10 PRs with recommendations -5. Ask user: +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 --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// --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 approved PRs? +2. Add "claude reviewed" label to the passed PRs? 3. Do all ``` @@ -44,10 +47,10 @@ Check these rules first - if any fail, recommend rejection: Provide a simple review: -1. **Rejection Check** - table with the above rules and PASS/FAIL -2. **Recommendation** - APPROVE or REJECT +1. **Rejection Check** - table with the above rules and PASS/REJECT +2. **Recommendation** - PASS or REJECT -## Closing PRs +## Close PRs If user asks to close/reject: @@ -55,7 +58,7 @@ If user asks to close/reject: gh pr close --repo vinta/awesome-python --comment "" ``` -## Mark as Reviewed +## Mark as Passed ```bash gh pr edit --repo vinta/awesome-python --add-label "claude reviewed" From d2a562aaeb76c8c727b9d7bb36b6c3bf6cb616bb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:35:58 +0800 Subject: [PATCH 0057/1130] chore: remove Travis CI configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Travis CI is no longer used for this project. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .travis.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e1c0a23d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python - -python: - - "3.6" - -script: - - python sort.py - - cp README.md docs/index.md - - mkdocs build - -deploy: - provider: pages - local-dir: site - skip-cleanup: true - keep-history: true - github-token: $GITHUB_TOKEN - on: - branch: master From 3ae2cdfc7f02fb940263e1ba5145a2d4f5d28b46 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:35:59 +0800 Subject: [PATCH 0058/1130] chore: remove GitHub funding configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Sponsors funding configuration is no longer needed. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/FUNDING.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index aa22b9d0..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -# These are supported funding model platforms From dbb805c6244221c5eaa876bd80b8cdabcf016417 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:46:33 +0800 Subject: [PATCH 0059/1130] feat: add structured decision format and auto-close for rejected PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance the Claude PR review workflow with: - Remove redundant criteria mention (already in CONTRIBUTING.md) - Add file access restrictions to prevent unnecessary file reads - Require structured DECISION output format for automation - Auto-close rejected PRs with explanatory comment 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 53a51d28..1a5c4068 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -39,6 +39,20 @@ jobs: plugins: "code-review@claude-code-plugins" prompt: | Review this PR against the acceptance criteria in CONTRIBUTING.md. - Focus on: Industry Standard, Rising Star, or Hidden Gem criteria. Check for automatic rejection reasons (abandoned, duplicates, spam). + + IMPORTANT: Only read README.md and CONTRIBUTING.md. Do not read or access any other files. + /code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }} + + After your review, output your final decision on the LAST line in this exact format: + DECISION: REJECT or DECISION: PASS + + - name: Close PR if rejected + if: contains(steps.claude-review.outputs.response, 'DECISION: REJECT') + env: + GH_TOKEN: ${{ github.token }} + run: | + gh pr close ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --comment "This PR has been automatically closed based on the review. Please address the feedback and re-open the PR." From d6a6bdccfc972153035ed4e773402563a8e0c704 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:51:07 +0800 Subject: [PATCH 0060/1130] chore: enforce alphabetical ordering of entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run sort.py to ensure all entries are alphabetically sorted within their respective categories. This maintains consistency and improves readability throughout the list. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- README.md | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 40ae3c30..b646b2e2 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. * [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). -* [wooey](https://github.com/wooey/wooey) - A Django app which creates automatic web UIs for Python scripts. * [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes. +* [wooey](https://github.com/wooey/wooey) - A Django app which creates automatic web UIs for Python scripts. ## Algorithms and Design Patterns @@ -140,9 +140,9 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers.* * [daphne](https://github.com/django/daphne) - A HTTP, HTTP2 and WebSocket protocol server for ASGI and ASGI-HTTP. -* [uvicorn](https://github.com/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. -* [hypercorn](https://github.com/pgjones/hypercorn) - An ASGI and WSGI Server based on Hyper libraries and inspired by Gunicorn. * [granian](https://github.com/emmett-framework/granian) - Granian is 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/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. ## Asynchronous Programming *Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio).* @@ -150,20 +150,20 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. +* [eventlet](https://github.com/eventlet/eventlet) - Asynchronous framework with WSGI support. +* [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. -* [eventlet](https://github.com/eventlet/eventlet) - Asynchronous framework with WSGI support. -* [gevent](https://github.com/gevent/gevent) - A coroutine-based Python networking library that uses [greenlet](https://github.com/python-greenlet/greenlet). ## Audio *Libraries for manipulating audio and its metadata.* * Audio - * [audioread](https://github.com/beetbox/audioread) - Cross-library (GStreamer + Core Audio + MAD + FFmpeg) audio decoding. * [audioFlux](https://github.com/libAudioFlux/audioFlux) - A library for audio and music analysis, feature extraction. + * [audioread](https://github.com/beetbox/audioread) - Cross-library (GStreamer + Core Audio + MAD + FFmpeg) audio decoding. * [dejavu](https://github.com/worldveil/dejavu) - Audio fingerprinting and recognition. * [kapre](https://github.com/keunwoochoi/kapre) - Keras Audio Preprocessors. * [librosa](https://github.com/librosa/librosa) - Python library for audio and music analysis. @@ -286,8 +286,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Useful CLI-based tools for productivity.* * Productivity Tools - * [copier](https://github.com/copier-org/copier) - A library and command-line utility for rendering projects templates. * [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. * [howdoi](https://github.com/gleitz/howdoi) - Instant coding answers via the command line. * [invoke](https://github.com/pyinvoke/invoke) - A tool for managing shell-oriented subprocesses and organizing executable Python code into CLI-invokable tasks. @@ -316,8 +316,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for storing and parsing configuration options.* -* [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. * [configobj](https://github.com/DiffSK/configobj) - INI file parser with validation. +* [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. * [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. @@ -331,13 +331,13 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for data analysis.* -* [pandas](http://pandas.pydata.org/) - A library providing high-performance, easy-to-use data structures and data analysis tools. -* [polars](https://github.com/pola-rs/polars) - A fast DataFrame library implemented in Rust with a Python API. * [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. * [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. +* [pandas](http://pandas.pydata.org/) - 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. ## Data Validation @@ -347,10 +347,10 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [cerberus](https://github.com/pyeve/cerberus) - A lightweight and extensible data validation library. * [colander](https://github.com/Pylons/colander) - Validating and deserializing data obtained via XML, JSON, an HTML form post. * [jsonschema](https://github.com/python-jsonschema/jsonschema) - An implementation of [JSON Schema](http://json-schema.org/) for Python. +* [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints. * [schema](https://github.com/keleshev/schema) - A library for validating Python data structures. * [schematics](https://github.com/schematics/schematics) - Data Structure Validation. * [voluptuous](https://github.com/alecthomas/voluptuous) - A Python data validation library. -* [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints. ## Data Visualization @@ -374,10 +374,10 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Databases implemented in Python.* +* [DuckDB](https://duckdb.org/) - 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. -* [DuckDB](https://duckdb.org/) - An in-process SQL OLAP database management system; optimized for analytics and fast queries, similar to SQLite but for analytical workloads. ## Database Drivers @@ -389,17 +389,17 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * 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) - * [sqlite3](https://docs.python.org/3/library/sqlite3.html) - (Python standard library) SQlite interface compliant with DB-API 2.0. * [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 - * [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. * [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. + * [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. * NoSQL Databases * [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. * [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. * [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. - * [pymongo](https://github.com/mongodb/mongo-python-driver) - The official Python client for MongoDB. * [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. + * [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. ## Date and Time @@ -437,8 +437,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [jax](https://github.com/google/jax) - a library for high-performance numerical computing with automatic differentiation and JIT compilation. * [keras](https://github.com/keras-team/keras) - A high-level neural networks library and capable of running on top of either TensorFlow or Theano. -* [pytorch](https://github.com/pytorch/pytorch) - Tensors and Dynamic neural networks in Python with strong GPU acceleration. * [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. * [theano](https://github.com/Theano/Theano) - A library for fast numerical computation. @@ -481,11 +481,11 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries to create packaged executables for release distribution.* +* [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). * [py2app](https://github.com/ronaldoussoren/py2app) - Freezes Python scripts (Mac OS X). * [py2exe](https://github.com/py2exe/py2exe) - Freezes Python scripts (Windows). * [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). -* [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). * [shiv](https://github.com/linkedin/shiv) - A command line utility for building fully self-contained zipapps (PEP 441), but with all their dependencies included. ## Documentation @@ -546,8 +546,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *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) An cross-platform, object-oriented path library. * [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). +* [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) An 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. @@ -557,10 +557,10 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Functional Programming with Python.* * [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming. +* [cytoolz](https://github.com/pytoolz/cytoolz/) - Cython implementation of `Toolz`: High performance functional utilities. * [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. -* [cytoolz](https://github.com/pytoolz/cytoolz/) - Cython implementation of `Toolz`: High performance functional utilities. * [toolz](https://github.com/pytoolz/toolz) - A collection of functional utilities for iterators, functions, and dictionaries. ## GUI Development @@ -568,11 +568,13 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for working with graphical user interface applications.* * [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. +* [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework * [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. * [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. * [Flexx](https://github.com/zoofIO/flexx) - Flexx is a pure Python toolkit for creating GUI's, that uses web technology for its rendering. * [Gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. * [kivy](https://kivy.org/) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS. +* [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. * [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. * [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). * [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. @@ -582,8 +584,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. * [urwid](http://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. * [wxPython](https://wxpython.org/) - A blending of the wxWidgets C++ class library with the Python. -* [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework -* [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. ## GraphQL @@ -655,11 +655,11 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for manipulating images.* * [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](http://www.pythonware.com/products/pil/) fork. -* [python-barcode](https://github.com/WhyNotHugo/python-barcode) - Create barcodes in Python with no extra dependencies. * [pymatting](http://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. -* [pywal](https://github.com/dylanaraps/pywal) - A tool that generates color schemes from images. * [pyvips](https://github.com/libvips/pyvips) - A fast image processing library with low memory needs. +* [pywal](https://github.com/dylanaraps/pywal) - A tool that generates color schemes from images. * [quads](https://github.com/fogleman/Quads) - Computer art based on quadtrees. * [scikit-image](http://scikit-image.org/) - 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. @@ -669,9 +669,9 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Implementations of Python.* +* [clpython](https://github.com/metawilm/cl-python) - Implementation of the Python programming language written in Common Lisp. * [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. -* [clpython](https://github.com/metawilm/cl-python) - Implementation of the Python programming language written in Common Lisp. * [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. * [numba](https://github.com/numba/numba) - Python JIT compiler to LLVM aimed at scientific Python. @@ -729,13 +729,13 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. * [H2O](https://github.com/h2oai/h2o-3) - Open Source Fast Scalable Machine Learning Platform. * [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. +* [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. * [NuPIC](https://github.com/numenta/nupic) - Numenta Platform for Intelligent Computing. +* [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. * [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. * [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. * [vowpal_porpoise](https://github.com/josephreisinger/vowpal_porpoise) - A lightweight Python wrapper for [Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit/). * [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. -* [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. -* [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. ## Microsoft Windows @@ -801,12 +801,12 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [Django Models](https://docs.djangoproject.com/en/dev/topics/db/models/) - The Django ORM. * [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL Toolkit and Object Relational Mapper. * [awesome-sqlalchemy](https://github.com/dahlia/awesome-sqlalchemy) - * [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. * [dataset](https://github.com/pudo/dataset) - Store Python dicts in a database - works with SQLite, MySQL, and PostgreSQL. * [orm](https://github.com/encode/orm) - An async ORM. * [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. * [pydal](https://github.com/web2py/pydal/) - A pure Python Database Abstraction Layer. + * [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. * NoSQL Databases * [hot-redis](https://github.com/stephenmcd/hot-redis) - Rich Python data types for Redis. * [mongoengine](https://github.com/MongoEngine/mongoengine) - A Python Object-Document-Mapper for working with MongoDB. @@ -1006,8 +1006,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *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. -* [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. * [makesite](https://github.com/sunainapai/makesite) - Simple, lightweight, and magic-free static site/blog generator (< 130 lines). +* [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. * [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. @@ -1185,12 +1185,12 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [awesome-flask](https://github.com/humiaozuzu/awesome-flask) * [pyramid](https://pylonsproject.org/) - A small, fast, down-to-earth, open source Python web framework. * [awesome-pyramid](https://github.com/uralbash/awesome-pyramid) - * [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. * [fastHTML](https://github.com/AnswerDotAI/fasthtml) - The fastest way to create an HTML app. + * [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. * [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) * Asynchronous - * [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. * [microdot](https://github.com/miguelgrinberg/microdot) - The impossibly small web framework for Python and MicroPython. + * [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. ## WebSocket @@ -1225,8 +1225,8 @@ Where to discover learning resources or new Python libraries. * [Django Chat](https://djangochat.com/) * [PyPodcats](https://pypodcats.live) - Hidden Figures of Python: Stories from the underrepresented group members of the Python community. * [Python Bytes](https://pythonbytes.fm) -* [Talk Python To Me](https://talkpython.fm/) * [Python Test](https://podcast.pythontest.com/) +* [Talk Python To Me](https://talkpython.fm/) * [The Real Python Podcast](https://realpython.com/podcasts/rpp/) # Contributing @@ -1236,3 +1236,4 @@ Your contributions are always welcome! Please take a look at the [contribution g - - - If you have any question about this opinionated list, do not hesitate to contact me [@VintaChen](https://twitter.com/VintaChen) on Twitter or open an issue on GitHub. + From f143318cfd89ce008e24f66ce8764c743ea98292 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 16:54:07 +0800 Subject: [PATCH 0061/1130] style: remove extra blank line in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove redundant blank line between Data Analysis and Data Validation sections for consistent spacing. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b646b2e2..799bf87a 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. - ## Data Validation *Libraries for validating data. Used for forms in many cases.* From cb08c2e40171e2f83eb6dd4b721c09ce34defa77 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:00:01 +0800 Subject: [PATCH 0062/1130] remove theano: deprecated since 2017 MILA stopped development after v1.0. Succeeded by PyTensor (maintained by PyMC team). See github.com/Theano/Theano Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 799bf87a..ea541a7f 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -* [theano](https://github.com/Theano/Theano) - A library for fast numerical computation. ## DevOps Tools From 6791185357c90c912fb90ca566149ffe900b60a5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:00:22 +0800 Subject: [PATCH 0063/1130] remove model_mommy: archived, renamed to model_bakery Project archived in 2019. Renamed to avoid gender stereotypes. Use model_bakery instead. See github.com/model-bakers/model_bakery Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ea541a7f..2aa9507c 100644 --- a/README.md +++ b/README.md @@ -1066,7 +1066,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * Object Factories * [factory_boy](https://github.com/FactoryBoy/factory_boy) - A test fixtures replacement for Python. * [mixer](https://github.com/klen/mixer) - Another fixtures replacement. Supports Django, Flask, SQLAlchemy, Peewee and etc. - * [model_mommy](https://github.com/vandersonmota/model_mommy) - Creating random fixtures for testing in Django. * [polyfactory](https://github.com/litestar-org/polyfactory) - mock data generation library with support to classes (continuation of `pydantic-factories`) * Code Coverage * [coverage](https://pypi.org/project/coverage/) - Code coverage measurement. From bf0d530aab19d643c3978780452277e1f8296617 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:00:45 +0800 Subject: [PATCH 0064/1130] remove PySimpleGUI: changed to commercial license in 2024 No longer open source. Requires $99 license for commercial use. Project development stopped. Consider PySide6 or FreeSimpleGUI fork. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2aa9507c..a9478428 100644 --- a/README.md +++ b/README.md @@ -576,7 +576,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. * [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). * [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. -* [PySimpleGUI](https://github.com/PySimpleGUI/PySimpleGUI) - Wrapper for tkinter, Qt, WxPython and Remi. * [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. * [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. * [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. From fe2cf06967f457d88a731f8d27ccdf0baaa2fbdd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:01:07 +0800 Subject: [PATCH 0065/1130] remove NuPIC: maintenance mode since 2019, Python 2 only No PyPI releases in 12+ months. Requires Python 2 which is EOL. Numenta pivoted to commercial NuPIC platform for CPU inference. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index a9478428..77bf9002 100644 --- a/README.md +++ b/README.md @@ -727,7 +727,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [H2O](https://github.com/h2oai/h2o-3) - Open Source Fast Scalable Machine Learning Platform. * [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. * [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. -* [NuPIC](https://github.com/numenta/nupic) - Numenta Platform for Intelligent Computing. * [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. * [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. * [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. From ff18e9c6c56f9cd5a591aed2aef38ea47cde6acd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:01:32 +0800 Subject: [PATCH 0066/1130] remove vowpal_porpoise: abandoned since 2013 Last commit was August 2013 (12+ years ago). Library interface disabled, incompatible with modern Python. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 77bf9002..80a05f78 100644 --- a/README.md +++ b/README.md @@ -730,7 +730,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. * [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. * [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. -* [vowpal_porpoise](https://github.com/josephreisinger/vowpal_porpoise) - A lightweight Python wrapper for [Vowpal Wabbit](https://github.com/JohnLangford/vowpal_wabbit/). * [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. ## Microsoft Windows From 33ecf167c03960dc0998a71ed077023b923f2b3f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:01:53 +0800 Subject: [PATCH 0067/1130] remove Python(x,y): discontinued in 2015, Python 2 only Last release was v2.7.10.0 in June 2015. Python 2 reached EOL in January 2020. Use Anaconda, Miniconda, or WinPython instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 80a05f78..e2834cb3 100644 --- a/README.md +++ b/README.md @@ -736,7 +736,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Python programming on Microsoft Windows.* -* [Python(x,y)](http://python-xy.github.io/) - Scientific-applications-oriented Python Distribution based on Qt and Spyder. * [pythonlibs](http://www.lfd.uci.edu/~gohlke/pythonlibs/) - Unofficial Windows binaries for Python extension packages. * [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. From f0495a542097b8850a0efb8e5af2a0bd674a029e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:02:17 +0800 Subject: [PATCH 0068/1130] remove fake2db: unmaintained since 2019 Last release March 2019, 5+ years without updates. Multiple unresolved issues. Use faker or mimesis instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index e2834cb3..1fac7b9a 100644 --- a/README.md +++ b/README.md @@ -1066,7 +1066,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * Code Coverage * [coverage](https://pypi.org/project/coverage/) - Code coverage measurement. * Fake Data - * [fake2db](https://github.com/emirozer/fake2db) - Fake database generator. * [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. * [radar](https://pypi.org/project/radar/) - Generate random datetime / time. From 75332fba67028f83a4a742ab326f2ffb5583c79e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:02:34 +0800 Subject: [PATCH 0069/1130] remove fileconveyor: inactive since 2021, dead link Last commit February 2021. Original website (wimleers.com) is dead. Bachelor thesis project from 2009, effectively abandoned. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1fac7b9a..146d5bde 100644 --- a/README.md +++ b/README.md @@ -1138,7 +1138,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [django-pipeline](https://github.com/jazzband/django-pipeline) - An asset packaging library for Django. * [django-storages](https://github.com/jschneier/django-storages) - A collection of custom storage back ends for Django. * [fanstatic](http://www.fanstatic.org/en/latest/) - Packages, optimizes, and serves static file dependencies as Python packages. -* [fileconveyor](http://wimleers.com/fileconveyor) - A daemon to detect and sync files to CDNs, S3 and FTP. * [flask-assets](https://github.com/miracle2k/flask-assets) - Helps you integrate webassets into your Flask app. * [webassets](https://github.com/miracle2k/webassets) - Bundles, optimizes, and manages unique cache-busting URLs for static resources. From eabcc4d86e207cc992610b9a16d641a3955f37c1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:03:01 +0800 Subject: [PATCH 0070/1130] remove localshop: inactive for 12+ months No new PyPI releases in over a year. Multiple unresolved issues. Use devpi or bandersnatch instead for local PyPI hosting. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 146d5bde..4f57906c 100644 --- a/README.md +++ b/README.md @@ -824,7 +824,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -* [localshop](https://github.com/jazzband/localshop) - Local PyPI server (custom packages and auto-mirroring of pypi). * [warehouse](https://github.com/pypa/warehouse) - Next generation Python Package Repository (PyPI). ## Penetration Testing From aaeec57982e5d76431414bfb3720fd2194d8f5d3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:03:20 +0800 Subject: [PATCH 0071/1130] remove bleach: deprecated by Mozilla in January 2023 Official deprecation announced in GitHub issue #698. Maintenance releases continue but no new development. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4f57906c..5efae442 100644 --- a/README.md +++ b/README.md @@ -617,7 +617,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *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. -* [bleach](https://github.com/mozilla/bleach) - A whitelist-based HTML sanitization and text linkification library. * [cssutils](https://pypi.org/project/cssutils/) - A CSS library for Python. * [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. * [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. No C extensions to compile. No system dependencies to install. No complex API to learn. From 0cbcdba635e0cffe4a0f74b7721426b908a780af Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:03:37 +0800 Subject: [PATCH 0072/1130] remove eventlet: deprecated, maintenance mode only Project is on "life support" with no new feature development. Compatibility issues with Python 3.10+. Use gevent instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5efae442..9ec87e22 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -* [eventlet](https://github.com/eventlet/eventlet) - Asynchronous framework with WSGI support. * [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. From aa03c4be5feeb160e2c0b403ab1ab8b0be313185 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:10:07 +0800 Subject: [PATCH 0073/1130] remove pythonlibs: Gohlke retired, site discontinued in 2022 Christoph Gohlke stopped maintaining the Windows binaries service. Use conda-forge or pip with wheel packages instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9ec87e22..d2f14432 100644 --- a/README.md +++ b/README.md @@ -734,7 +734,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Python programming on Microsoft Windows.* -* [pythonlibs](http://www.lfd.uci.edu/~gohlke/pythonlibs/) - Unofficial Windows binaries for Python extension packages. * [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://winpython.github.io/) - Portable development environment for Windows 10/11. From 848afca77cb20cef472f784a72b854d959453b5c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:10:25 +0800 Subject: [PATCH 0074/1130] remove Bicycle Repair Man: unmaintained since 2003 Ancient project from 2000-2003, hosted on SourceForge. Use Rope or IDE-integrated refactoring tools instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d2f14432..80ae62a7 100644 --- a/README.md +++ b/README.md @@ -863,7 +863,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Refactoring tools and libraries for Python.* - * [Bicycle Repair Man](http://bicyclerepair.sourceforge.net/) - Bicycle Repair Man, a refactoring tool for Python. * [Bowler](https://pybowler.io/) - Safe code refactoring for modern Python. * [Rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. From 44baf266f03b0b24b4773c167aab311bfbdb18fe Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:10:43 +0800 Subject: [PATCH 0075/1130] remove hot-redis: inactive, no releases in 12+ months No new PyPI versions, limited development activity. Use redis-py directly or Redis OM for object mapping. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 80ae62a7..61926b00 100644 --- a/README.md +++ b/README.md @@ -799,7 +799,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pydal](https://github.com/web2py/pydal/) - A pure Python Database Abstraction Layer. * [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. * NoSQL Databases - * [hot-redis](https://github.com/stephenmcd/hot-redis) - Rich Python data types for Redis. * [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/). * [redisco](https://github.com/kiddouk/redisco) - A Python Library for Simple Models and Containers Persisted in Redis. From 49a9a3a963966906da42f37085b6a771a78ed1fa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:10:57 +0800 Subject: [PATCH 0076/1130] remove redisco: inactive, discontinued project No new PyPI releases in 12+ months, no recent activity. Use Redis OM (redis-om-python) for Redis object mapping. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 61926b00..d6a6c3bb 100644 --- a/README.md +++ b/README.md @@ -801,7 +801,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * NoSQL Databases * [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/). - * [redisco](https://github.com/kiddouk/redisco) - A Python Library for Simple Models and Containers Persisted in Redis. ## Package Management From 87046cb6fe84f9e80b61727683d5ca5c8465a0bd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:11:14 +0800 Subject: [PATCH 0077/1130] remove sixpack: inactive with security vulnerabilities No new releases in 12+ months, 19 known vulnerabilities. Consider modern A/B testing solutions instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d6a6c3bb..d3ce404c 100644 --- a/README.md +++ b/README.md @@ -1041,7 +1041,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [PyAutoGUI](https://github.com/asweigart/pyautogui) - PyAutoGUI is a cross-platform GUI automation Python module for human beings. * [Schemathesis](https://github.com/kiwicom/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications. * [Selenium](https://pypi.org/project/selenium/) - Python bindings for [Selenium](https://selenium.dev/) [WebDriver](https://selenium.dev/documentation/webdriver/). - * [sixpack](https://github.com/seatgeek/sixpack) - A language-agnostic A/B Testing framework. * [splinter](https://github.com/cobrateam/splinter) - Open source tool for testing web applications. * Mock * [doublex](https://pypi.org/project/doublex/) - Powerful test doubles framework for Python. From 4204126299615ebdf78948723bb62593533efe87 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:11:33 +0800 Subject: [PATCH 0078/1130] remove gunnery: inactive since March 2022 No development activity in 2+ years. Use Ansible, Prefect, or other modern task runners. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d3ce404c..d4968a79 100644 --- a/README.md +++ b/README.md @@ -699,7 +699,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [APScheduler](http://apscheduler.readthedocs.io/en/latest/) - A light but powerful in-process task scheduler that lets you schedule functions. * [django-schedule](https://github.com/thauber/django-schedule) - A calendaring app for Django. * [doit](http://pydoit.org/) - A task runner and build tool. -* [gunnery](https://github.com/gunnery/gunnery) - Multipurpose task execution tool for distributed systems with web-based interface. * [Joblib](https://joblib.readthedocs.io/) - A set of tools to provide lightweight pipelining in Python. * [Plan](https://github.com/fengsp/plan) - Writing crontab file in Python like a charm. * [Prefect](https://github.com/PrefectHQ/prefect) - A modern workflow orchestration framework that makes it easy to build, schedule and monitor robust data pipelines. From 5e55b37cb73928c757a14f4026a3f8310ceb26ff Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:11:50 +0800 Subject: [PATCH 0079/1130] remove radar: inactive, no releases in 12+ months Limited downloads (1.7k/week), no recent development. Use faker for datetime generation instead. Co-Authored-By: Claude Opus 4.5 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d4968a79..4a9287c9 100644 --- a/README.md +++ b/README.md @@ -1059,7 +1059,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * 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. - * [radar](https://pypi.org/project/radar/) - Generate random datetime / time. ## Text Processing From bfc06bc0ada1b3acdd00d2f3a542f1801b08042c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:18:10 +0800 Subject: [PATCH 0080/1130] remove Tagging category: too thin (1 entry) Only contained django-taggit. Single-entry categories don't add enough value to a curated list. Co-Authored-By: Claude Opus 4.5 --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 4a9287c9..98d18dc8 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Shell](#shell) - [Specific Formats Processing](#specific-formats-processing) - [Static Site Generator](#static-site-generator) - - [Tagging](#tagging) - [Task Queues](#task-queues) - [Template Engine](#template-engine) - [Testing](#testing) @@ -997,12 +996,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -## Tagging - -*Libraries for tagging items.* - -* [django-taggit](https://github.com/jazzband/django-taggit) - Simple tagging for Django. - ## Task Queues *Libraries for working with task queues.* From 93404f0c5176b2edc30011864858c639668b0281 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:21:24 +0800 Subject: [PATCH 0081/1130] chore: remove thin categories and reorganize DevOps section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove ChatOps Tools category (only 1 entry: errbot) - Remove News Feed category (only 2 entries) - Add Cloud Providers subsection under DevOps with boto3 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- README.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 98d18dc8..5ce5a140 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Build Tools](#build-tools) - [Built-in Classes Enhancement](#built-in-classes-enhancement) - [Caching](#caching) - - [ChatOps Tools](#chatops-tools) - [CMS](#cms) - [Code Analysis](#code-analysis) - [Command-line Interface Development](#command-line-interface-development) @@ -67,7 +66,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Miscellaneous](#miscellaneous) - [Natural Language Processing](#natural-language-processing) - [Network Virtualization](#network-virtualization) - - [News Feed](#news-feed) - [ORM](#orm) - [Package Management](#package-management) - [Package Repositories](#package-repositories) @@ -229,12 +227,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [pylibmc](https://github.com/lericson/pylibmc) - A Python wrapper around the [libmemcached](https://libmemcached.org/libMemcached.html) interface. * [python-diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis. -## ChatOps Tools - -*Libraries for chatbot development.* - -* [errbot](https://github.com/errbotio/errbot/) - The easiest and most popular chatbot to implement ChatOps. - ## Code Analysis *Tools of static analysis, linters and code quality checkers. Also see [awesome-static-analysis](https://github.com/mre/awesome-static-analysis).* @@ -443,6 +435,8 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Software and libraries for DevOps.* +* Cloud Providers + * [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. @@ -775,13 +769,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [napalm](https://github.com/napalm-automation/napalm) - Cross-vendor API to manipulate network devices. * [pox](https://github.com/noxrepo/pox) - A Python-based SDN control applications, such as OpenFlow SDN controllers. -## News Feed - -*Libraries for building user's activities.* - -* [django-activity-stream](https://github.com/justquick/django-activity-stream) - Generating generic activity streams from the actions on your site. -* [Stream Framework](https://github.com/tschellenbach/Stream-Framework) - Building news feed and notification systems using Cassandra and Redis. - ## ORM *Libraries that implement Object-Relational Mapping or data mapping techniques.* From 2d9842608ee52171623e4f83b02f2bdf58f47886 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:22:25 +0800 Subject: [PATCH 0082/1130] remove Third-party APIs category: redundant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Already links to comprehensive external list. Many entries outdated (Twitter→X, Facebook SDK deprecated). boto3 moved to DevOps/Cloud Providers section. Co-Authored-By: Claude Opus 4.5 --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index 5ce5a140..6e5ab6a3 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Template Engine](#template-engine) - [Testing](#testing) - [Text Processing](#text-processing) - - [Third-party APIs](#third-party-apis) - [URL Manipulation](#url-manipulation) - [Video](#video) - [Web Asset Management](#web-asset-management) @@ -1071,18 +1070,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). * [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. -## Third-party APIs - -*Libraries for accessing third party services APIs. Also see [List of Python API Wrappers and Libraries](https://github.com/realpython/list-of-python-api-wrappers).* - -* [apache-libcloud](https://libcloud.apache.org/) - One Python library for all clouds. -* [boto3](https://github.com/boto/boto3) - Python interface to Amazon Web Services. -* [django-wordpress](https://github.com/istrategylabs/django-wordpress) - WordPress models and views for Django. -* [facebook-sdk](https://github.com/mobolic/facebook-sdk) - Facebook Platform Python SDK. -* [google-api-python-client](https://github.com/google/google-api-python-client) - Google APIs Client Library for Python. -* [gspread](https://github.com/burnash/gspread) - Google Spreadsheets Python API. -* [twython](https://github.com/ryanmcgrath/twython) - A Python wrapper for the Twitter API. - ## URL Manipulation *Libraries for parsing URLs.* From 464bb4639ce8aa3af56e85c9da28e5ad16f8e931 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 8 Jan 2026 17:28:50 +0800 Subject: [PATCH 0083/1130] style: standardize markdown formatting in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve consistency and readability by: - Normalize list indentation from 4 to 2 spaces - Use hyphens instead of asterisks for list markers - Standardize italic syntax using underscores - Add consistent spacing between sections These formatting changes follow common Markdown conventions without altering any content. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- README.md | 1642 ++++++++++++++++++++++++++--------------------------- 1 file changed, 821 insertions(+), 821 deletions(-) diff --git a/README.md b/README.md index 6e5ab6a3..ed2e4bd2 100644 --- a/README.md +++ b/README.md @@ -16,1146 +16,1147 @@ An opinionated list of awesome Python frameworks, libraries, software and resour Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Awesome Python](#awesome-python) - - [Admin Panels](#admin-panels) - - [Algorithms and Design Patterns](#algorithms-and-design-patterns) - - [ASGI Servers](#asgi-servers) - - [Asynchronous Programming](#asynchronous-programming) - - [Audio](#audio) - - [Authentication](#authentication) - - [Build Tools](#build-tools) - - [Built-in Classes Enhancement](#built-in-classes-enhancement) - - [Caching](#caching) - - [CMS](#cms) - - [Code Analysis](#code-analysis) - - [Command-line Interface Development](#command-line-interface-development) - - [Command-line Tools](#command-line-tools) - - [Computer Vision](#computer-vision) - - [Configuration Files](#configuration-files) - - [Cryptography](#cryptography) - - [Data Analysis](#data-analysis) - - [Data Validation](#data-validation) - - [Data Visualization](#data-visualization) - - [Database Drivers](#database-drivers) - - [Database](#database) - - [Date and Time](#date-and-time) - - [Debugging Tools](#debugging-tools) - - [Deep Learning](#deep-learning) - - [DevOps Tools](#devops-tools) - - [Distributed Computing](#distributed-computing) - - [Distribution](#distribution) - - [Documentation](#documentation) - - [Downloader](#downloader) - - [Editor Plugins and IDEs](#editor-plugins-and-ides) - - [Email](#email) - - [Environment Management](#environment-management) - - [File Manipulation](#file-manipulation) - - [Functional Programming](#functional-programming) - - [Game Development](#game-development) - - [Geolocation](#geolocation) - - [GUI Development](#gui-development) - - [Hardware](#hardware) - - [HTML Manipulation](#html-manipulation) - - [HTTP Clients](#http-clients) - - [Image Processing](#image-processing) - - [Implementations](#implementations) - - [Interactive Interpreter](#interactive-interpreter) - - [Internationalization](#internationalization) - - [Job Scheduler](#job-scheduler) - - [Logging](#logging) - - [Machine Learning](#machine-learning) - - [Miscellaneous](#miscellaneous) - - [Natural Language Processing](#natural-language-processing) - - [Network Virtualization](#network-virtualization) - - [ORM](#orm) - - [Package Management](#package-management) - - [Package Repositories](#package-repositories) - - [Penetration testing](#penetration-testing) - - [Permissions](#permissions) - - [Processes](#processes) - - [Recommender Systems](#recommender-systems) - - [Refactoring](#refactoring) - - [RESTful API](#restful-api) - - [Robotics](#robotics) - - [RPC Servers](#rpc-servers) - - [Science](#science) - - [Search](#search) - - [Serialization](#serialization) - - [Serverless Frameworks](#serverless-frameworks) - - [Shell](#shell) - - [Specific Formats Processing](#specific-formats-processing) - - [Static Site Generator](#static-site-generator) - - [Task Queues](#task-queues) - - [Template Engine](#template-engine) - - [Testing](#testing) - - [Text Processing](#text-processing) - - [URL Manipulation](#url-manipulation) - - [Video](#video) - - [Web Asset Management](#web-asset-management) - - [Web Content Extracting](#web-content-extracting) - - [Web Crawling](#web-crawling) - - [Web Frameworks](#web-frameworks) - - [WebSocket](#websocket) - - [WSGI Servers](#wsgi-servers) + - [Admin Panels](#admin-panels) + - [Algorithms and Design Patterns](#algorithms-and-design-patterns) + - [ASGI Servers](#asgi-servers) + - [Asynchronous Programming](#asynchronous-programming) + - [Audio](#audio) + - [Authentication](#authentication) + - [Build Tools](#build-tools) + - [Built-in Classes Enhancement](#built-in-classes-enhancement) + - [Caching](#caching) + - [CMS](#cms) + - [Code Analysis](#code-analysis) + - [Command-line Interface Development](#command-line-interface-development) + - [Command-line Tools](#command-line-tools) + - [Computer Vision](#computer-vision) + - [Configuration Files](#configuration-files) + - [Cryptography](#cryptography) + - [Data Analysis](#data-analysis) + - [Data Validation](#data-validation) + - [Data Visualization](#data-visualization) + - [Database Drivers](#database-drivers) + - [Database](#database) + - [Date and Time](#date-and-time) + - [Debugging Tools](#debugging-tools) + - [Deep Learning](#deep-learning) + - [DevOps Tools](#devops-tools) + - [Distributed Computing](#distributed-computing) + - [Distribution](#distribution) + - [Documentation](#documentation) + - [Downloader](#downloader) + - [Editor Plugins and IDEs](#editor-plugins-and-ides) + - [Email](#email) + - [Environment Management](#environment-management) + - [File Manipulation](#file-manipulation) + - [Functional Programming](#functional-programming) + - [Game Development](#game-development) + - [Geolocation](#geolocation) + - [GUI Development](#gui-development) + - [Hardware](#hardware) + - [HTML Manipulation](#html-manipulation) + - [HTTP Clients](#http-clients) + - [Image Processing](#image-processing) + - [Implementations](#implementations) + - [Interactive Interpreter](#interactive-interpreter) + - [Internationalization](#internationalization) + - [Job Scheduler](#job-scheduler) + - [Logging](#logging) + - [Machine Learning](#machine-learning) + - [Miscellaneous](#miscellaneous) + - [Natural Language Processing](#natural-language-processing) + - [Network Virtualization](#network-virtualization) + - [ORM](#orm) + - [Package Management](#package-management) + - [Package Repositories](#package-repositories) + - [Penetration testing](#penetration-testing) + - [Permissions](#permissions) + - [Processes](#processes) + - [Recommender Systems](#recommender-systems) + - [Refactoring](#refactoring) + - [RESTful API](#restful-api) + - [Robotics](#robotics) + - [RPC Servers](#rpc-servers) + - [Science](#science) + - [Search](#search) + - [Serialization](#serialization) + - [Serverless Frameworks](#serverless-frameworks) + - [Shell](#shell) + - [Specific Formats Processing](#specific-formats-processing) + - [Static Site Generator](#static-site-generator) + - [Task Queues](#task-queues) + - [Template Engine](#template-engine) + - [Testing](#testing) + - [Text Processing](#text-processing) + - [URL Manipulation](#url-manipulation) + - [Video](#video) + - [Web Asset Management](#web-asset-management) + - [Web Content Extracting](#web-content-extracting) + - [Web Crawling](#web-crawling) + - [Web Frameworks](#web-frameworks) + - [WebSocket](#websocket) + - [WSGI Servers](#wsgi-servers) - [Resources](#resources) - - [Newsletters](#newsletters) - - [Podcasts](#podcasts) + - [Newsletters](#newsletters) + - [Podcasts](#podcasts) - [Contributing](#contributing) --- ## Admin Panels -*Libraries for administrative interfaces.* +_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/flask-admin/flask-admin) - Simple and extensible administrative interface framework for Flask. -* [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. -* [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). -* [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes. -* [wooey](https://github.com/wooey/wooey) - A Django app which creates automatic web UIs for Python scripts. +- [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/flask-admin/flask-admin) - Simple and extensible administrative interface framework for Flask. +- [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. +- [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). +- [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes. +- [wooey](https://github.com/wooey/wooey) - A Django app which creates automatic web UIs for Python scripts. ## Algorithms and Design Patterns -*Python implementation of data structures, algorithms and design patterns. Also see [awesome-algorithms](https://github.com/tayllan/awesome-algorithms).* +_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. - * [python-ds](https://github.com/prabhupant/python-ds) - A collection of data structure and algorithms for coding interviews. - * [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 - * [pypattyrn](https://github.com/tylerlaberge/PyPattyrn) - A simple yet effective library for implementing common design patterns. - * [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. +- Algorithms + - [algorithms](https://github.com/keon/algorithms) - Minimal examples of data structures and algorithms. + - [python-ds](https://github.com/prabhupant/python-ds) - A collection of data structure and algorithms for coding interviews. + - [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 + - [pypattyrn](https://github.com/tylerlaberge/PyPattyrn) - A simple yet effective library for implementing common design patterns. + - [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. ## ASGI Servers -*[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers.* +_[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers._ + +- [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) - Granian is 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/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. -* [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) - Granian is 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/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. ## Asynchronous Programming -*Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio).* +_Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio)._ -* [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. +- [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. ## Audio -*Libraries for manipulating audio and its metadata.* +_Libraries for manipulating audio and its metadata._ -* Audio - * [audioFlux](https://github.com/libAudioFlux/audioFlux) - A library for audio and music analysis, feature extraction. - * [audioread](https://github.com/beetbox/audioread) - Cross-library (GStreamer + Core Audio + MAD + FFmpeg) audio decoding. - * [dejavu](https://github.com/worldveil/dejavu) - Audio fingerprinting and recognition. - * [kapre](https://github.com/keunwoochoi/kapre) - Keras Audio Preprocessors. - * [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. - * [mingus](http://bspaans.github.io/python-mingus/) - An advanced music theory and notation package with MIDI file and playback support. - * [pyaudioanalysis](https://github.com/tyiannak/pyAudioAnalysis) - Audio feature extraction, classification, segmentation and applications. - * [pydub](https://github.com/jiaaro/pydub) - Manipulate audio with a simple and easy high level interface. - * [timeside](https://github.com/Parisson/TimeSide) - Open web audio processing framework. -* Metadata - * [beets](https://github.com/beetbox/beets) - A music library manager and [MusicBrainz](https://musicbrainz.org/) tagger. - * [eyed3](https://github.com/nicfit/eyeD3) - A tool for working with audio files, specifically MP3 files containing ID3 metadata. - * [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. +- Audio + - [audioFlux](https://github.com/libAudioFlux/audioFlux) - A library for audio and music analysis, feature extraction. + - [audioread](https://github.com/beetbox/audioread) - Cross-library (GStreamer + Core Audio + MAD + FFmpeg) audio decoding. + - [dejavu](https://github.com/worldveil/dejavu) - Audio fingerprinting and recognition. + - [kapre](https://github.com/keunwoochoi/kapre) - Keras Audio Preprocessors. + - [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. + - [mingus](http://bspaans.github.io/python-mingus/) - An advanced music theory and notation package with MIDI file and playback support. + - [pyaudioanalysis](https://github.com/tyiannak/pyAudioAnalysis) - Audio feature extraction, classification, segmentation and applications. + - [pydub](https://github.com/jiaaro/pydub) - Manipulate audio with a simple and easy high level interface. + - [timeside](https://github.com/Parisson/TimeSide) - Open web audio processing framework. +- Metadata + - [beets](https://github.com/beetbox/beets) - A music library manager and [MusicBrainz](https://musicbrainz.org/) tagger. + - [eyed3](https://github.com/nicfit/eyeD3) - A tool for working with audio files, specifically MP3 files containing ID3 metadata. + - [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. ## Authentication -*Libraries for implementing authentication schemes.* +_Libraries for implementing authentication schemes._ -* OAuth - * [authlib](https://github.com/lepture/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/jazzband/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. - * [python-jose](https://github.com/mpdavis/python-jose/) - A JOSE implementation in Python. +- OAuth + - [authlib](https://github.com/lepture/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/jazzband/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. + - [python-jose](https://github.com/mpdavis/python-jose/) - A JOSE implementation in Python. ## Build Tools -*Compile software from source code.* +_Compile software from source code._ -* [bitbake](https://github.com/openembedded/bitbake) - A make-like build tool for embedded Linux. -* [buildout](https://github.com/buildout/buildout) - A build system for creating, assembling and deploying applications from multiple parts. -* [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. -* [scons](https://github.com/SCons/scons) - A software construction tool. +- [bitbake](https://github.com/openembedded/bitbake) - A make-like build tool for embedded Linux. +- [buildout](https://github.com/buildout/buildout) - A build system for creating, assembling and deploying applications from multiple parts. +- [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. +- [scons](https://github.com/SCons/scons) - A software construction tool. ## Built-in Classes Enhancement -*Libraries for enhancing Python built-in classes.* +_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. -* [dataclasses](https://docs.python.org/3/library/dataclasses.html) - (Python standard library) Data classes. -* [dotteddict](https://github.com/carlosescri/DottedDict) - A library that provides a method of accessing lists and dicts with a dotted path notation. +- [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. +- [dataclasses](https://docs.python.org/3/library/dataclasses.html) - (Python standard library) Data classes. +- [dotteddict](https://github.com/carlosescri/DottedDict) - A library that provides a method of accessing lists and dicts with a dotted path notation. ## CMS -*Content Management Systems.* +_Content Management Systems._ -* [feincms](https://github.com/feincms/feincms) - One of the most advanced Content Management Systems built on 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. +- [feincms](https://github.com/feincms/feincms) - One of the most advanced Content Management Systems built on 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. ## Caching -*Libraries for caching data.* +_Libraries for caching data._ -* [beaker](https://github.com/bbangert/beaker) - A WSGI middleware for sessions and caching. -* [django-cache-machine](https://github.com/django-cache-machine/django-cache-machine) - Automatic caching and invalidation for Django models. -* [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. -* [hermescache](https://pypi.org/project/HermesCache/) - Python caching library with tag-based invalidation and dogpile effect prevention. -* [pylibmc](https://github.com/lericson/pylibmc) - A Python wrapper around the [libmemcached](https://libmemcached.org/libMemcached.html) interface. -* [python-diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis. +- [beaker](https://github.com/bbangert/beaker) - A WSGI middleware for sessions and caching. +- [django-cache-machine](https://github.com/django-cache-machine/django-cache-machine) - Automatic caching and invalidation for Django models. +- [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. +- [hermescache](https://pypi.org/project/HermesCache/) - Python caching library with tag-based invalidation and dogpile effect prevention. +- [pylibmc](https://github.com/lericson/pylibmc) - A Python wrapper around the [libmemcached](https://libmemcached.org/libMemcached.html) interface. +- [python-diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis. ## Code Analysis -*Tools of static analysis, linters and code quality checkers. Also see [awesome-static-analysis](https://github.com/mre/awesome-static-analysis).* +_Tools of static analysis, linters and code quality checkers. Also see [awesome-static-analysis](https://github.com/mre/awesome-static-analysis)._ -* Code Analysis - * [code2flow](https://github.com/scottrogowski/code2flow) - Turn your Python and JavaScript code into DOT flowcharts. - * [prospector](https://github.com/PyCQA/prospector) - A tool to analyze Python code. - * [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. -* Code Linters - * [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/timothycrosley/isort) - A Python utility / library to sort imports. - * [yapf](https://github.com/google/yapf) - Yet another Python code formatter from Google. -* 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. - * [typeshed](https://github.com/python/typeshed) - Collection of library stubs for Python, with static types. -* 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. +- Code Analysis + - [code2flow](https://github.com/scottrogowski/code2flow) - Turn your Python and JavaScript code into DOT flowcharts. + - [prospector](https://github.com/PyCQA/prospector) - A tool to analyze Python code. + - [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. +- Code Linters + - [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/timothycrosley/isort) - A Python utility / library to sort imports. + - [yapf](https://github.com/google/yapf) - Yet another Python code formatter from Google. +- 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. + - [typeshed](https://github.com/python/typeshed) - Collection of library stubs for Python, with static types. +- 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. ## Command-line Interface Development -*Libraries for building command-line applications.* +_Libraries for building command-line applications._ -* Command-line Application Development - * [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. - * [cliff](https://github.com/openstack/cliff) - A framework for creating command-line programs with multi-level commands. - * [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/tiangolo/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). - * [bashplotlib](https://github.com/glamp/bashplotlib) - Making basic plots in the terminal. - * [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. - * [tqdm](https://github.com/tqdm/tqdm) - Fast, extensible progress bar for loops and CLI. +- Command-line Application Development + - [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. + - [cliff](https://github.com/openstack/cliff) - A framework for creating command-line programs with multi-level commands. + - [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/tiangolo/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). + - [bashplotlib](https://github.com/glamp/bashplotlib) - Making basic plots in the terminal. + - [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. + - [tqdm](https://github.com/tqdm/tqdm) - Fast, extensible progress bar for loops and CLI. ## Command-line Tools -*Useful CLI-based tools for productivity.* +_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. - * [howdoi](https://github.com/gleitz/howdoi) - Instant coding answers via the command line. - * [invoke](https://github.com/pyinvoke/invoke) - A tool for managing shell-oriented subprocesses and organizing executable Python code into CLI-invokable tasks. - * [pathpicker](https://github.com/facebook/PathPicker) - Select files out of bash output. - * [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. - * [try](https://github.com/timofurrer/try) - A dead simple CLI to try out python packages - it's never been easier. -* 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. +- 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. + - [howdoi](https://github.com/gleitz/howdoi) - Instant coding answers via the command line. + - [invoke](https://github.com/pyinvoke/invoke) - A tool for managing shell-oriented subprocesses and organizing executable Python code into CLI-invokable tasks. + - [pathpicker](https://github.com/facebook/PathPicker) - Select files out of bash output. + - [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. + - [try](https://github.com/timofurrer/try) - A dead simple CLI to try out python packages - it's never been easier. +- 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. ## Computer Vision -*Libraries for 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://opencv.org/) - Open Source Computer Vision Library. -* [pytesseract](https://github.com/madmaze/pytesseract) - A wrapper for [Google Tesseract OCR](https://github.com/tesseract-ocr). -* [tesserocr](https://github.com/sirfz/tesserocr) - Another simple, Pillow-friendly, wrapper around the `tesseract-ocr` API for OCR. +- [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://opencv.org/) - Open Source Computer Vision Library. +- [pytesseract](https://github.com/madmaze/pytesseract) - A wrapper for [Google Tesseract OCR](https://github.com/tesseract-ocr). +- [tesserocr](https://github.com/sirfz/tesserocr) - Another simple, Pillow-friendly, wrapper around the `tesseract-ocr` API for OCR. ## Configuration Files -*Libraries for storing and parsing configuration options.* +_Libraries for storing and parsing configuration options._ -* [configobj](https://github.com/DiffSK/configobj) - INI file parser with validation. -* [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. -* [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. +- [configobj](https://github.com/DiffSK/configobj) - INI file parser with validation. +- [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. +- [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. ## 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. +- [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. ## Data Analysis -*Libraries for data analysis.* +_Libraries for data analysis._ -* [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. -* [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. -* [pandas](http://pandas.pydata.org/) - 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. +- [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. +- [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. +- [pandas](http://pandas.pydata.org/) - 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. ## Data Validation -*Libraries for validating data. Used for forms in many cases.* +_Libraries for validating data. Used for forms in many cases._ -* [cerberus](https://github.com/pyeve/cerberus) - A lightweight and extensible data validation library. -* [colander](https://github.com/Pylons/colander) - Validating and deserializing data obtained via XML, JSON, an HTML form post. -* [jsonschema](https://github.com/python-jsonschema/jsonschema) - An implementation of [JSON Schema](http://json-schema.org/) for Python. -* [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints. -* [schema](https://github.com/keleshev/schema) - A library for validating Python data structures. -* [schematics](https://github.com/schematics/schematics) - Data Structure Validation. -* [voluptuous](https://github.com/alecthomas/voluptuous) - A Python data validation library. +- [cerberus](https://github.com/pyeve/cerberus) - A lightweight and extensible data validation library. +- [colander](https://github.com/Pylons/colander) - Validating and deserializing data obtained via XML, JSON, an HTML form post. +- [jsonschema](https://github.com/python-jsonschema/jsonschema) - An implementation of [JSON Schema](http://json-schema.org/) for Python. +- [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints. +- [schema](https://github.com/keleshev/schema) - A library for validating Python data structures. +- [schematics](https://github.com/schematics/schematics) - Data Structure Validation. +- [voluptuous](https://github.com/alecthomas/voluptuous) - A Python data validation library. ## Data Visualization -*Libraries for visualizing data. Also see [awesome-javascript](https://github.com/sorrycc/awesome-javascript#data-visualization).* +_Libraries for visualizing data. Also see [awesome-javascript](https://github.com/sorrycc/awesome-javascript#data-visualization)._ -* [altair](https://github.com/altair-viz/altair) - Declarative statistical visualization library for Python. -* [bokeh](https://github.com/bokeh/bokeh) - Interactive Web Plotting for Python. -* [bqplot](https://github.com/bloomberg/bqplot) - Interactive Plotting Library for the Jupyter Notebook. -* [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. -* [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. -* [matplotlib](https://github.com/matplotlib/matplotlib) - A Python 2D plotting library. -* [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. -* [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](http://www.graphviz.org/). -* [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. +- [altair](https://github.com/altair-viz/altair) - Declarative statistical visualization library for Python. +- [bokeh](https://github.com/bokeh/bokeh) - Interactive Web Plotting for Python. +- [bqplot](https://github.com/bloomberg/bqplot) - Interactive Plotting Library for the Jupyter Notebook. +- [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. +- [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. +- [matplotlib](https://github.com/matplotlib/matplotlib) - A Python 2D plotting library. +- [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. +- [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](http://www.graphviz.org/). +- [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. ## Database -*Databases implemented in Python.* +_Databases implemented in Python._ -* [DuckDB](https://duckdb.org/) - 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. +- [DuckDB](https://duckdb.org/) - 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. ## Database Drivers -*Libraries for connecting and operating databases.* +_Libraries for connecting and operating databases._ -* MySQL - [awesome-mysql](http://shlomi-noach.github.io/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. - * [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. -* NoSQL Databases - * [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. - * [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. - * [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. - * [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. - * [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. +- MySQL - [awesome-mysql](http://shlomi-noach.github.io/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. + - [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. +- NoSQL Databases + - [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. + - [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. + - [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. + - [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. + - [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. ## Date and Time -*Libraries for working with dates and times.* +_Libraries for working with dates and times._ -* [arrow](https://github.com/arrow-py/arrow) - A Python library that offers a sensible and human-friendly approach to creating, manipulating, formatting and converting dates, times and timestamps. -* [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/sdispater/pendulum) - Python datetimes made easy. -* [pytz](https://pypi.org/project/pytz/) - World timezone definitions, modern and historical. Brings the [tz database](https://en.wikipedia.org/wiki/Tz_database) into Python. +- [arrow](https://github.com/arrow-py/arrow) - A Python library that offers a sensible and human-friendly approach to creating, manipulating, formatting and converting dates, times and timestamps. +- [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/sdispater/pendulum) - Python datetimes made easy. +- [pytz](https://pypi.org/project/pytz/) - World timezone definitions, modern and historical. Brings the [tz database](https://en.wikipedia.org/wiki/Tz_database) into Python. ## Debugging Tools -*Libraries for debugging code.* +_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. - * [vprof](https://github.com/nvdv/vprof) - Visual Python profiler. -* Others - * [django-debug-toolbar](https://github.com/jazzband/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. - * [pyelftools](https://github.com/eliben/pyelftools) - Parsing and analyzing ELF files and DWARF debugging information. +- 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. + - [vprof](https://github.com/nvdv/vprof) - Visual Python profiler. +- Others + - [django-debug-toolbar](https://github.com/jazzband/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. + - [pyelftools](https://github.com/eliben/pyelftools) - Parsing and analyzing ELF files and DWARF debugging information. ## Deep Learning -*Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learning](https://github.com/ChristosChristofidis/awesome-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/google/jax) - a library for high-performance numerical computing with automatic differentiation and JIT compilation. -* [keras](https://github.com/keras-team/keras) - A high-level neural networks library and capable of running on top of either TensorFlow or Theano. -* [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. +- [jax](https://github.com/google/jax) - a library for high-performance numerical computing with automatic differentiation and JIT compilation. +- [keras](https://github.com/keras-team/keras) - A high-level neural networks library and capable of running on top of either TensorFlow or Theano. +- [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. ## DevOps Tools -*Software and libraries for DevOps.* +_Software and libraries for DevOps._ -* Cloud Providers - * [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. -* SSH-style Deployment - * [cuisine](https://github.com/sebastien/cuisine) - Chef-like functionality for Fabric. - * [fabric](https://github.com/fabric/fabric) - A simple, Pythonic tool for remote execution and deployment. -* Process Management - * [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX. -* Monitoring - * [psutil](https://github.com/giampaolo/psutil) - A cross-platform process and system utilities module. -* Backup - * [borg](https://github.com/borgbackup/borg) - A deduplicating archiver with compression and encryption. +- Cloud Providers + - [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. +- SSH-style Deployment + - [cuisine](https://github.com/sebastien/cuisine) - Chef-like functionality for Fabric. + - [fabric](https://github.com/fabric/fabric) - A simple, Pythonic tool for remote execution and deployment. +- Process Management + - [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX. +- Monitoring + - [psutil](https://github.com/giampaolo/psutil) - A cross-platform process and system utilities module. +- Backup + - [borg](https://github.com/borgbackup/borg) - A deduplicating archiver with compression and encryption. ## Distributed Computing -*Frameworks and libraries for 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. - * [Ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem. -* Stream Processing - * [faust](https://github.com/robinhood/faust) - A stream processing library, porting the ideas from [Kafka Streams](https://kafka.apache.org/documentation/streams/) to Python. - * [streamparse](https://github.com/Parsely/streamparse) - Run Python code against real-time streams of data via [Apache Storm](http://storm.apache.org/). +- 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. + - [Ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem. +- Stream Processing + - [faust](https://github.com/robinhood/faust) - A stream processing library, porting the ideas from [Kafka Streams](https://kafka.apache.org/documentation/streams/) to Python. + - [streamparse](https://github.com/Parsely/streamparse) - Run Python code against real-time streams of data via [Apache Storm](http://storm.apache.org/). ## Distribution -*Libraries to create packaged executables for release distribution.* +_Libraries to create packaged executables for release distribution._ -* [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). -* [py2app](https://github.com/ronaldoussoren/py2app) - Freezes Python scripts (Mac OS X). -* [py2exe](https://github.com/py2exe/py2exe) - Freezes Python scripts (Windows). -* [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. +- [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). +- [py2app](https://github.com/ronaldoussoren/py2app) - Freezes Python scripts (Mac OS X). +- [py2exe](https://github.com/py2exe/py2exe) - Freezes Python scripts (Windows). +- [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. ## Documentation -*Libraries for generating project documentation.* +_Libraries for generating project documentation._ -* [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. - * [awesome-sphinxdoc](https://github.com/yoloseem/awesome-sphinxdoc) -* [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. +- [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. + - [awesome-sphinxdoc](https://github.com/yoloseem/awesome-sphinxdoc) +- [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. ## Downloader -*Libraries for downloading.* +_Libraries for downloading._ -* [akshare](https://github.com/jindaxiang/akshare) - A financial data interface library, built for human beings! -* [s3cmd](https://github.com/s3tools/s3cmd) - A command line tool for managing Amazon S3 and CloudFront. -* [youtube-dl](https://github.com/ytdl-org/youtube-dl/) - A command-line program to download videos from YouTube and other video sites. +- [akshare](https://github.com/jindaxiang/akshare) - A financial data interface library, built for human beings! +- [s3cmd](https://github.com/s3tools/s3cmd) - A command line tool for managing Amazon S3 and CloudFront. +- [youtube-dl](https://github.com/ytdl-org/youtube-dl/) - A command-line program to download videos from YouTube and other video sites. ## Editor Plugins and IDEs -* Emacs - * [elpy](https://github.com/jorgenschaefer/elpy) - Emacs Python Development Environment. -* Vim - * [jedi-vim](https://github.com/davidhalter/jedi-vim) - Vim bindings for the Jedi auto-completion library for Python. - * [python-mode](https://github.com/python-mode/python-mode) - An all in one plugin for turning Vim into a Python IDE. - * [YouCompleteMe](https://github.com/Valloric/YouCompleteMe) - Includes [Jedi](https://github.com/davidhalter/jedi)-based completion engine for Python. -* Visual Studio - * [PTVS](https://github.com/Microsoft/PTVS) - Python Tools for Visual Studio. -* Visual Studio Code - * [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - The official VSCode extension with rich support for Python. -* IDE - * [PyCharm](https://www.jetbrains.com/pycharm/) - Commercial Python IDE by JetBrains. Has free community edition available. - * [spyder](https://github.com/spyder-ide/spyder) - Open Source Python IDE. +- Emacs + - [elpy](https://github.com/jorgenschaefer/elpy) - Emacs Python Development Environment. +- Vim + - [jedi-vim](https://github.com/davidhalter/jedi-vim) - Vim bindings for the Jedi auto-completion library for Python. + - [python-mode](https://github.com/python-mode/python-mode) - An all in one plugin for turning Vim into a Python IDE. + - [YouCompleteMe](https://github.com/Valloric/YouCompleteMe) - Includes [Jedi](https://github.com/davidhalter/jedi)-based completion engine for Python. +- Visual Studio + - [PTVS](https://github.com/Microsoft/PTVS) - Python Tools for Visual Studio. +- Visual Studio Code + - [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - The official VSCode extension with rich support for Python. +- IDE + - [PyCharm](https://www.jetbrains.com/pycharm/) - Commercial Python IDE by JetBrains. Has free community edition available. + - [spyder](https://github.com/spyder-ide/spyder) - Open Source Python IDE. ## Email -*Libraries for sending and parsing email.* +_Libraries for sending and parsing email._ -* Mail Servers - * [modoboa](https://github.com/modoboa/modoboa) - A mail hosting and management platform including a modern Web UI. - * [salmon](https://github.com/moggers87/salmon) - A Python Mail Server. -* Clients - * [imbox](https://github.com/martinrusev/imbox) - Python IMAP for Humans. - * [yagmail](https://github.com/kootenpv/yagmail) - Yet another Gmail/SMTP client. -* Others - * [flanker](https://github.com/mailgun/flanker) - An email address and Mime parsing library. - * [mailer](https://github.com/marrow/mailer) - High-performance extensible mail delivery framework. +- Mail Servers + - [modoboa](https://github.com/modoboa/modoboa) - A mail hosting and management platform including a modern Web UI. + - [salmon](https://github.com/moggers87/salmon) - A Python Mail Server. +- Clients + - [imbox](https://github.com/martinrusev/imbox) - Python IMAP for Humans. + - [yagmail](https://github.com/kootenpv/yagmail) - Yet another Gmail/SMTP client. +- Others + - [flanker](https://github.com/mailgun/flanker) - An email address and Mime parsing library. + - [mailer](https://github.com/marrow/mailer) - High-performance extensible mail delivery framework. ## Environment Management -*Libraries for Python version and virtual environment management.* +_Libraries for Python version and virtual environment management._ -* [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management. -* [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. +- [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management. +- [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. ## File Manipulation -*Libraries for file manipulation.* +_Libraries for file manipulation._ -* [mimetypes](https://docs.python.org/3/library/mimetypes.html) - (Python standard library) Map filenames to MIME types. -* [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). -* [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) An 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. +- [mimetypes](https://docs.python.org/3/library/mimetypes.html) - (Python standard library) Map filenames to MIME types. +- [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). +- [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) An 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. ## Functional Programming -*Functional Programming with Python.* +_Functional Programming with Python._ -* [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming. -* [cytoolz](https://github.com/pytoolz/cytoolz/) - Cython implementation of `Toolz`: High performance functional utilities. -* [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. +- [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming. +- [cytoolz](https://github.com/pytoolz/cytoolz/) - Cython implementation of `Toolz`: High performance functional utilities. +- [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. ## GUI Development -*Libraries for working with graphical user interface applications.* +_Libraries for working with graphical user interface applications._ -* [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. -* [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework -* [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. -* [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. -* [Flexx](https://github.com/zoofIO/flexx) - Flexx is a pure Python toolkit for creating GUI's, that uses web technology for its rendering. -* [Gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. -* [kivy](https://kivy.org/) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS. -* [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. -* [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. -* [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). -* [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. -* [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. -* [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. -* [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. -* [urwid](http://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. -* [wxPython](https://wxpython.org/) - A blending of the wxWidgets C++ class library with the Python. +- [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. +- [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework +- [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. +- [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. +- [Flexx](https://github.com/zoofIO/flexx) - Flexx is a pure Python toolkit for creating GUI's, that uses web technology for its rendering. +- [Gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. +- [kivy](https://kivy.org/) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS. +- [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. +- [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. +- [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). +- [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. +- [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. +- [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. +- [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. +- [urwid](http://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. +- [wxPython](https://wxpython.org/) - A blending of the wxWidgets C++ class library with the Python. ## GraphQL -*Libraries for working with GraphQL.* +_Libraries for working with GraphQL._ -* [graphene](https://github.com/graphql-python/graphene/) - GraphQL framework for Python. -* [strawberry-django](https://github.com/strawberry-graphql/strawberry-django) - Strawberry GraphQL integration with Django. +- [graphene](https://github.com/graphql-python/graphene/) - GraphQL framework for Python. +- [strawberry-django](https://github.com/strawberry-graphql/strawberry-django) - Strawberry GraphQL integration with Django. ## Game Development -*Awesome game development libraries.* +_Awesome game development libraries._ -* [Arcade](https://api.arcade.academy/en/latest/) - Arcade is a modern Python framework for crafting games with compelling graphics and sound. -* [Cocos2d](https://www.cocos.com/en/cocos2d-x) - cocos2d is a framework for building 2D games, demos, and other graphical/interactive applications. -* [Harfang3D](http://www.harfang3d.com) - Python framework for 3D, VR and game development. -* [Panda3D](https://www.panda3d.org/) - 3D game engine developed by Disney. -* [Pygame](http://www.pygame.org/news.html) - Pygame is a set of Python modules designed for writing games. -* [PyOgre](http://www.ogre3d.org/tikiwiki/PyOgre) - Python bindings for the Ogre 3D render engine, can be used for games, simulations, anything 3D. -* [PyOpenGL](http://pyopengl.sourceforge.net/) - Python ctypes bindings for OpenGL and it's related APIs. -* [PySDL2](https://pysdl2.readthedocs.io) - A ctypes based wrapper for the SDL2 library. -* [RenPy](https://www.renpy.org/) - A Visual Novel engine. +- [Arcade](https://api.arcade.academy/en/latest/) - Arcade is a modern Python framework for crafting games with compelling graphics and sound. +- [Cocos2d](https://www.cocos.com/en/cocos2d-x) - cocos2d is a framework for building 2D games, demos, and other graphical/interactive applications. +- [Harfang3D](http://www.harfang3d.com) - Python framework for 3D, VR and game development. +- [Panda3D](https://www.panda3d.org/) - 3D game engine developed by Disney. +- [Pygame](http://www.pygame.org/news.html) - Pygame is a set of Python modules designed for writing games. +- [PyOgre](http://www.ogre3d.org/tikiwiki/PyOgre) - Python bindings for the Ogre 3D render engine, can be used for games, simulations, anything 3D. +- [PyOpenGL](http://pyopengl.sourceforge.net/) - Python ctypes bindings for OpenGL and it's related APIs. +- [PySDL2](https://pysdl2.readthedocs.io) - A ctypes based wrapper for the SDL2 library. +- [RenPy](https://www.renpy.org/) - A Visual Novel engine. ## Geolocation -*Libraries for geocoding addresses and working with latitudes and longitudes.* +_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. -* [geopy](https://github.com/geopy/geopy) - Python Geocoding Toolbox. +- [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. +- [geopy](https://github.com/geopy/geopy) - Python Geocoding Toolbox. ## HTML Manipulation -*Libraries for working with HTML and XML.* +_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://pypi.org/project/cssutils/) - A CSS library for Python. -* [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. -* [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. No C extensions to compile. No system dependencies to install. No complex API to learn. -* [lxml](http://lxml.de/) - 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. -* [untangle](https://github.com/stchris/untangle) - Converts XML documents to Python objects for easy access. -* [WeasyPrint](http://weasyprint.org) - A visual rendering engine for HTML and CSS that can export to PDF. -* [xmldataset](https://xmldataset.readthedocs.io/en/latest/) - Simple XML Parsing. -* [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON. +- [beautifulsoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) - Providing Pythonic idioms for iterating, searching, and modifying HTML or XML. +- [cssutils](https://pypi.org/project/cssutils/) - A CSS library for Python. +- [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. +- [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. No C extensions to compile. No system dependencies to install. No complex API to learn. +- [lxml](http://lxml.de/) - 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. +- [untangle](https://github.com/stchris/untangle) - Converts XML documents to Python objects for easy access. +- [WeasyPrint](http://weasyprint.org) - A visual rendering engine for HTML and CSS that can export to PDF. +- [xmldataset](https://xmldataset.readthedocs.io/en/latest/) - Simple XML Parsing. +- [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON. ## HTTP Clients -*Libraries for working with HTTP.* +_Libraries for working with HTTP._ -* [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python. -* [requests](https://github.com/psf/requests) - HTTP Requests for Humans. -* [treq](https://github.com/twisted/treq) - Python requests like API built on top of Twisted's HTTP client. -* [urllib3](https://github.com/urllib3/urllib3) - A HTTP library with thread-safe connection pooling, file post support, sanity friendly. +- [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python. +- [requests](https://github.com/psf/requests) - HTTP Requests for Humans. +- [treq](https://github.com/twisted/treq) - Python requests like API built on top of Twisted's HTTP client. +- [urllib3](https://github.com/urllib3/urllib3) - A HTTP library with thread-safe connection pooling, file post support, sanity friendly. ## Hardware -*Libraries for programming with hardware.* +_Libraries for programming with hardware._ -* [keyboard](https://github.com/boppreh/keyboard) - Hook and simulate global keyboard events on Windows and Linux. -* [mouse](https://github.com/boppreh/mouse) - Hook and simulate global mouse events on Windows and Linux. -* [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices. -* [scapy](https://github.com/secdev/scapy) - A brilliant packet manipulation library. +- [keyboard](https://github.com/boppreh/keyboard) - Hook and simulate global keyboard events on Windows and Linux. +- [mouse](https://github.com/boppreh/mouse) - Hook and simulate global mouse events on Windows and Linux. +- [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices. +- [scapy](https://github.com/secdev/scapy) - A brilliant packet manipulation library. ## Image Processing -*Libraries for manipulating images.* +_Libraries for manipulating images._ -* [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](http://www.pythonware.com/products/pil/) fork. -* [pymatting](http://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. -* [pywal](https://github.com/dylanaraps/pywal) - A tool that generates color schemes from images. -* [quads](https://github.com/fogleman/Quads) - Computer art based on quadtrees. -* [scikit-image](http://scikit-image.org/) - 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. +- [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](http://www.pythonware.com/products/pil/) fork. +- [pymatting](http://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. +- [pywal](https://github.com/dylanaraps/pywal) - A tool that generates color schemes from images. +- [quads](https://github.com/fogleman/Quads) - Computer art based on quadtrees. +- [scikit-image](http://scikit-image.org/) - 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. ## Implementations -*Implementations of Python.* +_Implementations of Python._ -* [clpython](https://github.com/metawilm/cl-python) - Implementation of the Python programming language written in Common Lisp. -* [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. -* [numba](https://github.com/numba/numba) - Python JIT compiler to LLVM aimed at scientific Python. -* [peachpy](https://github.com/Maratyszcza/PeachPy) - x86-64 assembler embedded in Python. -* [pypy](https://foss.heptapod.net/pypy/pypy) - A very fast and compliant implementation of the Python language. -* [pyston](https://github.com/pyston/pyston/) - A Python implementation using JIT techniques. +- [clpython](https://github.com/metawilm/cl-python) - Implementation of the Python programming language written in Common Lisp. +- [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. +- [numba](https://github.com/numba/numba) - Python JIT compiler to LLVM aimed at scientific Python. +- [peachpy](https://github.com/Maratyszcza/PeachPy) - x86-64 assembler embedded in Python. +- [pypy](https://foss.heptapod.net/pypy/pypy) - A very fast and compliant implementation of the Python language. +- [pyston](https://github.com/pyston/pyston/) - A Python implementation using JIT techniques. ## Interactive Interpreter -*Interactive Python interpreters (REPL).* +_Interactive Python interpreters (REPL)._ -* [bpython](https://github.com/bpython/bpython) - A fancy interface to the Python interpreter. -* [Jupyter Notebook (IPython)](https://jupyter.org) - 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/jonathanslenders/ptpython) - Advanced Python REPL built on top of the [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit). +- [bpython](https://github.com/bpython/bpython) - A fancy interface to the Python interpreter. +- [Jupyter Notebook (IPython)](https://jupyter.org) - 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/jonathanslenders/ptpython) - Advanced Python REPL built on top of the [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit). ## Internationalization -*Libraries for working with i18n.* +_Libraries for working with i18n._ -* [Babel](http://babel.pocoo.org/en/latest/) - An internationalization library for Python. -* [PyICU](https://github.com/ovalhub/pyicu) - A wrapper of International Components for Unicode C++ library ([ICU](http://site.icu-project.org/)). +- [Babel](http://babel.pocoo.org/en/latest/) - An internationalization library for Python. +- [PyICU](https://github.com/ovalhub/pyicu) - A wrapper of International Components for Unicode C++ library ([ICU](http://site.icu-project.org/)). ## Job Scheduler -*Libraries for scheduling jobs.* +_Libraries for scheduling jobs._ -* [Airflow](https://airflow.apache.org/) - Airflow is a platform to programmatically author, schedule and monitor workflows. -* [APScheduler](http://apscheduler.readthedocs.io/en/latest/) - A light but powerful in-process task scheduler that lets you schedule functions. -* [django-schedule](https://github.com/thauber/django-schedule) - A calendaring app for Django. -* [doit](http://pydoit.org/) - A task runner and build tool. -* [Joblib](https://joblib.readthedocs.io/) - A set of tools to provide lightweight pipelining in Python. -* [Plan](https://github.com/fengsp/plan) - Writing crontab file in Python like a charm. -* [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. -* [Spiff](https://github.com/knipknap/SpiffWorkflow) - A powerful workflow engine implemented in pure Python. -* [TaskFlow](https://docs.openstack.org/developer/taskflow/) - A Python library that helps to make task execution easy, consistent and reliable. +- [Airflow](https://airflow.apache.org/) - Airflow is a platform to programmatically author, schedule and monitor workflows. +- [APScheduler](http://apscheduler.readthedocs.io/en/latest/) - A light but powerful in-process task scheduler that lets you schedule functions. +- [django-schedule](https://github.com/thauber/django-schedule) - A calendaring app for Django. +- [doit](http://pydoit.org/) - A task runner and build tool. +- [Joblib](https://joblib.readthedocs.io/) - A set of tools to provide lightweight pipelining in Python. +- [Plan](https://github.com/fengsp/plan) - Writing crontab file in Python like a charm. +- [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. +- [Spiff](https://github.com/knipknap/SpiffWorkflow) - A powerful workflow engine implemented in pure Python. +- [TaskFlow](https://docs.openstack.org/developer/taskflow/) - A Python library that helps to make task execution easy, consistent and reliable. ## Logging -*Libraries for generating and working with logs.* +_Libraries for generating and working with logs._ -* [logbook](http://logbook.readthedocs.io/en/stable/) - Logging replacement for Python. -* [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. -* [sentry-python](https://github.com/getsentry/sentry-python) - Sentry SDK for Python. -* [structlog](https://www.structlog.org/en/stable/) - Structured logging made easy. +- [logbook](http://logbook.readthedocs.io/en/stable/) - Logging replacement for Python. +- [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. +- [sentry-python](https://github.com/getsentry/sentry-python) - Sentry SDK for Python. +- [structlog](https://www.structlog.org/en/stable/) - Structured logging made easy. ## Machine Learning -*Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python).* +_Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python)._ -* [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. -* [H2O](https://github.com/h2oai/h2o-3) - Open Source Fast Scalable Machine Learning Platform. -* [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. -* [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. -* [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. -* [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. -* [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. -* [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. +- [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. +- [H2O](https://github.com/h2oai/h2o-3) - Open Source Fast Scalable Machine Learning Platform. +- [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. +- [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. +- [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. +- [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. +- [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. +- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. ## Microsoft Windows -*Python programming on 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://winpython.github.io/) - Portable development environment for Windows 10/11. +- [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://winpython.github.io/) - Portable development environment for Windows 10/11. ## Miscellaneous -*Useful libraries or tools that don't fit in the categories above.* +_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. -* [magenta](https://github.com/magenta/magenta) - A tool to generate music and art using artificial intelligence. -* [pluginbase](https://github.com/mitsuhiko/pluginbase) - A simple but flexible plugin system for Python. -* [tryton](http://www.tryton.org/) - A general-purpose business framework. +- [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. +- [magenta](https://github.com/magenta/magenta) - A tool to generate music and art using artificial intelligence. +- [pluginbase](https://github.com/mitsuhiko/pluginbase) - A simple but flexible plugin system for Python. +- [tryton](http://www.tryton.org/) - A general-purpose business framework. ## Natural Language Processing -*Libraries for working with human languages.* +_Libraries for working with human languages._ - General - * [gensim](https://github.com/RaRe-Technologies/gensim) - Topic Modeling for Humans. - * [langid.py](https://github.com/saffsd/langid.py) - Stand-alone language identification system. - * [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. - * [pattern](https://github.com/clips/pattern) - A web mining module. - * [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. - * [pytext](https://github.com/facebookresearch/pytext) - A natural language modeling framework based on PyTorch. - * [PyTorch-NLP](https://github.com/PetrochukM/PyTorch-NLP) - A toolkit enabling rapid deep learning NLP prototyping for research. - * [spacy](https://spacy.io/) - 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. + - [gensim](https://github.com/RaRe-Technologies/gensim) - Topic Modeling for Humans. + - [langid.py](https://github.com/saffsd/langid.py) - Stand-alone language identification system. + - [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. + - [pattern](https://github.com/clips/pattern) - A web mining module. + - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. + - [pytext](https://github.com/facebookresearch/pytext) - A natural language modeling framework based on PyTorch. + - [PyTorch-NLP](https://github.com/PetrochukM/PyTorch-NLP) - A toolkit enabling rapid deep learning NLP prototyping for research. + - [spacy](https://spacy.io/) - 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. - * [pkuseg-python](https://github.com/lancopku/pkuseg-python) - A toolkit for Chinese word segmentation in various domains. - * [snownlp](https://github.com/isnowfy/snownlp) - A library for processing Chinese text. + - [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. + - [pkuseg-python](https://github.com/lancopku/pkuseg-python) - A toolkit for Chinese word segmentation in various domains. + - [snownlp](https://github.com/isnowfy/snownlp) - A library for processing Chinese text. ## Network Virtualization -*Tools and libraries for Virtual Networking and SDN (Software Defined Networking).* +_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. -* [pox](https://github.com/noxrepo/pox) - A Python-based SDN control applications, such as OpenFlow SDN controllers. +- [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. +- [pox](https://github.com/noxrepo/pox) - A Python-based SDN control applications, such as OpenFlow SDN controllers. ## ORM -*Libraries that implement Object-Relational Mapping or data mapping techniques.* +_Libraries that implement Object-Relational Mapping or data mapping techniques._ -* Relational Databases - * [Django Models](https://docs.djangoproject.com/en/dev/topics/db/models/) - The Django ORM. - * [SQLAlchemy](https://www.sqlalchemy.org/) - 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. - * [orm](https://github.com/encode/orm) - An async ORM. - * [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. - * [pydal](https://github.com/web2py/pydal/) - A pure Python Database Abstraction Layer. - * [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. -* NoSQL Databases - * [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/). +- Relational Databases + - [Django Models](https://docs.djangoproject.com/en/dev/topics/db/models/) - The Django ORM. + - [SQLAlchemy](https://www.sqlalchemy.org/) - 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. + - [orm](https://github.com/encode/orm) - An async ORM. + - [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. + - [pydal](https://github.com/web2py/pydal/) - A pure Python Database Abstraction Layer. + - [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. +- NoSQL Databases + - [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/). ## Package Management -*Libraries for package and dependency management.* +_Libraries for package and dependency management._ -* [pip](https://pip.pypa.io/en/stable/) - The package installer for Python. - * [pip-tools](https://github.com/jazzband/pip-tools) - A set of tools to keep your pinned Python dependencies fresh. - * [PyPI](https://pypi.org/) -* [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. -* [poetry](https://github.com/sdispater/poetry) - Python dependency management and packaging made easy. -* [uv](https://github.com/astral-sh/uv) - An extremely fast Python package and project manager, written in Rust. +- [pip](https://pip.pypa.io/en/stable/) - The package installer for Python. + - [pip-tools](https://github.com/jazzband/pip-tools) - A set of tools to keep your pinned Python dependencies fresh. + - [PyPI](https://pypi.org/) +- [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. +- [poetry](https://github.com/sdispater/poetry) - Python dependency management and packaging made easy. +- [uv](https://github.com/astral-sh/uv) - An extremely fast Python package and project manager, written in Rust. ## Package Repositories -*Local PyPI repository server and proxies.* +_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). +- [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). ## Penetration Testing -*Frameworks and tools for penetration testing.* +_Frameworks and tools for penetration testing._ -* [fsociety](https://github.com/Manisso/fsociety) - A Penetration testing framework. -* [setoolkit](https://github.com/trustedsec/social-engineer-toolkit) - A toolkit for social engineering. -* [sqlmap](https://github.com/sqlmapproject/sqlmap) - Automatic SQL injection and database takeover tool. +- [fsociety](https://github.com/Manisso/fsociety) - A Penetration testing framework. +- [setoolkit](https://github.com/trustedsec/social-engineer-toolkit) - A toolkit for social engineering. +- [sqlmap](https://github.com/sqlmapproject/sqlmap) - Automatic SQL injection and database takeover tool. ## Permissions -*Libraries that allow or deny users access to data or functionality.* +_Libraries that allow or deny users access to data or functionality._ -* [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. +- [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. ## Processes -*Libraries for starting and communicating with OS processes.* +_Libraries for starting and communicating with OS processes._ -* [delegator.py](https://github.com/amitt001/delegator.py) - [Subprocesses](https://docs.python.org/3/library/subprocess.html) for Humans 2.0. -* [sarge](https://sarge.readthedocs.io/en/latest/) - Yet another wrapper for subprocess. -* [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. +- [delegator.py](https://github.com/amitt001/delegator.py) - [Subprocesses](https://docs.python.org/3/library/subprocess.html) for Humans 2.0. +- [sarge](https://sarge.readthedocs.io/en/latest/) - Yet another wrapper for subprocess. +- [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. ## Recommender Systems -*Libraries for building recommender systems.* +_Libraries for building recommender systems._ -* [annoy](https://github.com/spotify/annoy) - Approximate Nearest Neighbors in C++/Python optimized for memory usage. -* [fastFM](https://github.com/ibayer/fastFM) - A library for Factorization Machines. -* [implicit](https://github.com/benfred/implicit) - A fast Python implementation of collaborative filtering for implicit datasets. -* [libffm](https://github.com/guestwalk/libffm) - A library for Field-aware Factorization Machine (FFM). -* [lightfm](https://github.com/lyst/lightfm) - A Python implementation of a number of popular recommendation algorithms. -* [spotlight](https://github.com/maciejkula/spotlight) - Deep recommender models using PyTorch. -* [Surprise](https://github.com/NicolasHug/Surprise) - A scikit for building and analyzing recommender systems. -* [tensorrec](https://github.com/jfkirk/tensorrec) - A Recommendation Engine Framework in TensorFlow. +- [annoy](https://github.com/spotify/annoy) - Approximate Nearest Neighbors in C++/Python optimized for memory usage. +- [fastFM](https://github.com/ibayer/fastFM) - A library for Factorization Machines. +- [implicit](https://github.com/benfred/implicit) - A fast Python implementation of collaborative filtering for implicit datasets. +- [libffm](https://github.com/guestwalk/libffm) - A library for Field-aware Factorization Machine (FFM). +- [lightfm](https://github.com/lyst/lightfm) - A Python implementation of a number of popular recommendation algorithms. +- [spotlight](https://github.com/maciejkula/spotlight) - Deep recommender models using PyTorch. +- [Surprise](https://github.com/NicolasHug/Surprise) - A scikit for building and analyzing recommender systems. +- [tensorrec](https://github.com/jfkirk/tensorrec) - A Recommendation Engine Framework in TensorFlow. ## Refactoring -*Refactoring tools and libraries for Python.* +_Refactoring tools and libraries for Python._ - * [Bowler](https://pybowler.io/) - Safe code refactoring for modern Python. - * [Rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. +- [Bowler](https://pybowler.io/) - Safe code refactoring for modern Python. +- [Rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. ## RESTful API -*Libraries for building RESTful APIs.* +_Libraries for building RESTful APIs._ -* Django - * [django-rest-framework](https://github.com/encode/django-rest-framework) - A powerful and flexible toolkit to build web APIs. - * [django-tastypie](https://github.com/django-tastypie/django-tastypie) - Creating delicious APIs for Django apps. -* Flask - * [eve](https://github.com/pyeve/eve) - REST API framework powered by Flask, MongoDB and good intentions. - * [flask-api](https://github.com/flask-api/flask-api) - Browsable Web APIs for Flask. - * [flask-restful](https://github.com/flask-restful/flask-restful) - Quickly building REST APIs for Flask. -* Pyramid - * [cornice](https://github.com/Cornices/cornice) - A RESTful framework for Pyramid. -* Framework agnostic - * [falcon](https://github.com/falconry/falcon) - A high-performance framework for building cloud APIs and web app backends. - * [fastapi](https://github.com/tiangolo/fastapi) - A modern, fast, web framework for building APIs with Python 3.6+ based on standard Python type hints. - * [hug](https://github.com/hugapi/hug) - A Python 3 framework for cleanly exposing APIs. - * [sandman2](https://github.com/jeffknupp/sandman2) - Automated REST APIs for existing database-driven systems. - * [sanic](https://github.com/sanic-org/sanic) - A Python 3.6+ web server and web framework that's written to go fast. +- Django + - [django-rest-framework](https://github.com/encode/django-rest-framework) - A powerful and flexible toolkit to build web APIs. + - [django-tastypie](https://github.com/django-tastypie/django-tastypie) - Creating delicious APIs for Django apps. +- Flask + - [eve](https://github.com/pyeve/eve) - REST API framework powered by Flask, MongoDB and good intentions. + - [flask-api](https://github.com/flask-api/flask-api) - Browsable Web APIs for Flask. + - [flask-restful](https://github.com/flask-restful/flask-restful) - Quickly building REST APIs for Flask. +- Pyramid + - [cornice](https://github.com/Cornices/cornice) - A RESTful framework for Pyramid. +- Framework agnostic + - [falcon](https://github.com/falconry/falcon) - A high-performance framework for building cloud APIs and web app backends. + - [fastapi](https://github.com/tiangolo/fastapi) - A modern, fast, web framework for building APIs with Python 3.6+ based on standard Python type hints. + - [hug](https://github.com/hugapi/hug) - A Python 3 framework for cleanly exposing APIs. + - [sandman2](https://github.com/jeffknupp/sandman2) - Automated REST APIs for existing database-driven systems. + - [sanic](https://github.com/sanic-org/sanic) - A Python 3.6+ web server and web framework that's written to go fast. ## Robotics -*Libraries for robotics.* +_Libraries for robotics._ -* [PythonRobotics](https://github.com/AtsushiSakai/PythonRobotics) - This is a compilation of various robotics algorithms with visualizations. -* [rospy](http://wiki.ros.org/rospy) - This is a library for ROS (Robot Operating System). +- [PythonRobotics](https://github.com/AtsushiSakai/PythonRobotics) - This is a compilation of various robotics algorithms with visualizations. +- [rospy](http://wiki.ros.org/rospy) - This is a library for ROS (Robot Operating System). ## RPC Servers -*RPC-compatible servers.* +_RPC-compatible servers._ -* [RPyC](https://github.com/tomerfiliba/rpyc) (Remote Python Call) - A transparent and symmetric RPC library for Python -* [zeroRPC](https://github.com/0rpc/zerorpc-python) - zerorpc is a flexible RPC implementation based on [ZeroMQ](http://zeromq.org/) and [MessagePack](http://msgpack.org/). +- [RPyC](https://github.com/tomerfiliba/rpyc) (Remote Python Call) - A transparent and symmetric RPC library for Python +- [zeroRPC](https://github.com/0rpc/zerorpc-python) - zerorpc is a flexible RPC implementation based on [ZeroMQ](http://zeromq.org/) and [MessagePack](http://msgpack.org/). ## Science -*Libraries for scientific computing. Also see [Python-for-Scientists](https://github.com/TomNicholas/Python-for-Scientists).* +_Libraries for scientific computing. Also see [Python-for-Scientists](https://github.com/TomNicholas/Python-for-Scientists)._ -* [astropy](http://www.astropy.org/) - A community Python library for Astronomy. -* [bcbio-nextgen](https://github.com/chapmanb/bcbio-nextgen) - Providing best-practice pipelines for fully automated high throughput sequencing analysis. -* [bccb](https://github.com/chapmanb/bcbb) - Collection of useful code related to biological analysis. -* [Biopython](http://biopython.org/wiki/Main_Page) - Biopython is a set of freely available tools for biological computation. -* [cclib](http://cclib.github.io/) - A library for parsing and interpreting the results of computational chemistry packages. -* [Colour](http://colour-science.org/) - Implementing a comprehensive number of colour theory transformations and algorithms. -* [Karate Club](https://github.com/benedekrozemberczki/karateclub) - Unsupervised machine learning toolbox for graph structured data. -* [NetworkX](https://networkx.github.io/) - A high-productivity software for complex networks. -* [NIPY](http://nipy.org) - A collection of neuroimaging toolkits. -* [NumPy](http://www.numpy.org/) - A fundamental package for scientific computing with Python. -* [ObsPy](https://github.com/obspy/obspy/wiki/) - A Python toolbox for seismology. -* [Open Babel](https://open-babel.readthedocs.io/) - A chemical toolbox designed to speak the many languages of chemical data. -* [PyDy](http://www.pydy.org/) - Short for Python Dynamics, used to assist with workflow in the modeling of dynamic motion. -* [PyMC](https://github.com/pymc-devs/pymc3) - Markov Chain Monte Carlo sampling toolkit. -* [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. -* [RDKit](http://www.rdkit.org/) - Cheminformatics and Machine Learning Software. -* [SciPy](https://www.scipy.org/) - A Python-based ecosystem of open-source software for mathematics, science, and engineering. -* [SimPy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework. -* [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. -* [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. -* [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. +- [astropy](http://www.astropy.org/) - A community Python library for Astronomy. +- [bcbio-nextgen](https://github.com/chapmanb/bcbio-nextgen) - Providing best-practice pipelines for fully automated high throughput sequencing analysis. +- [bccb](https://github.com/chapmanb/bcbb) - Collection of useful code related to biological analysis. +- [Biopython](http://biopython.org/wiki/Main_Page) - Biopython is a set of freely available tools for biological computation. +- [cclib](http://cclib.github.io/) - A library for parsing and interpreting the results of computational chemistry packages. +- [Colour](http://colour-science.org/) - Implementing a comprehensive number of colour theory transformations and algorithms. +- [Karate Club](https://github.com/benedekrozemberczki/karateclub) - Unsupervised machine learning toolbox for graph structured data. +- [NetworkX](https://networkx.github.io/) - A high-productivity software for complex networks. +- [NIPY](http://nipy.org) - A collection of neuroimaging toolkits. +- [NumPy](http://www.numpy.org/) - A fundamental package for scientific computing with Python. +- [ObsPy](https://github.com/obspy/obspy/wiki/) - A Python toolbox for seismology. +- [Open Babel](https://open-babel.readthedocs.io/) - A chemical toolbox designed to speak the many languages of chemical data. +- [PyDy](http://www.pydy.org/) - Short for Python Dynamics, used to assist with workflow in the modeling of dynamic motion. +- [PyMC](https://github.com/pymc-devs/pymc3) - Markov Chain Monte Carlo sampling toolkit. +- [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. +- [RDKit](http://www.rdkit.org/) - Cheminformatics and Machine Learning Software. +- [SciPy](https://www.scipy.org/) - A Python-based ecosystem of open-source software for mathematics, science, and engineering. +- [SimPy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework. +- [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. +- [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. +- [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. ## Search -*Libraries and software for indexing and performing search queries on data.* +_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-dsl-py](https://github.com/elastic/elasticsearch-dsl-py) - The official high-level Python client for Elasticsearch. -* [elasticsearch-py](https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html) - 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/). -* [whoosh](http://whoosh.readthedocs.io/en/latest/) - A fast, pure Python search engine library. +- [django-haystack](https://github.com/django-haystack/django-haystack) - Modular search for Django. +- [elasticsearch-dsl-py](https://github.com/elastic/elasticsearch-dsl-py) - The official high-level Python client for Elasticsearch. +- [elasticsearch-py](https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html) - 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/). +- [whoosh](http://whoosh.readthedocs.io/en/latest/) - A fast, pure Python search engine library. ## Serialization -*Libraries for serializing complex data types.* +_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. -* [pysimdjson](https://github.com/TkTech/pysimdjson) - A Python bindings for [simdjson](https://github.com/lemire/simdjson). -* [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) - A Python wrapper around [RapidJSON](https://github.com/Tencent/rapidjson). -* [toonify](https://github.com/ScrapeGraphAI/toonify) - A compact, human-readable serialization format that reduces LLM token usage by 30-60% compared to JSON. -* [ultrajson](https://github.com/esnme/ultrajson) - A fast JSON decoder and encoder written in C with Python bindings. +- [marshmallow](https://github.com/marshmallow-code/marshmallow) - A lightweight library for converting complex objects to and from simple Python datatypes. +- [pysimdjson](https://github.com/TkTech/pysimdjson) - A Python bindings for [simdjson](https://github.com/lemire/simdjson). +- [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) - A Python wrapper around [RapidJSON](https://github.com/Tencent/rapidjson). +- [toonify](https://github.com/ScrapeGraphAI/toonify) - A compact, human-readable serialization format that reduces LLM token usage by 30-60% compared to JSON. +- [ultrajson](https://github.com/esnme/ultrajson) - A fast JSON decoder and encoder written in C with Python bindings. ## Serverless Frameworks -*Frameworks for developing serverless Python code.* +_Frameworks for developing serverless Python code._ -* [python-lambda](https://github.com/nficano/python-lambda) - A toolkit for developing and deploying Python code in AWS Lambda. -* [Zappa](https://github.com/zappa/Zappa) - A tool for deploying WSGI applications on AWS Lambda and API Gateway. +- [python-lambda](https://github.com/nficano/python-lambda) - A toolkit for developing and deploying Python code in AWS Lambda. +- [Zappa](https://github.com/zappa/Zappa) - A tool for deploying WSGI applications on AWS Lambda and API Gateway. ## Shell -*Shells based on Python.* +_Shells based on Python._ -* [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered shell. Full-featured and cross-platform. +- [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered shell. Full-featured and cross-platform. ## Specific Formats Processing -*Libraries for parsing and manipulating specific text formats.* +_Libraries for parsing and manipulating specific text formats._ -* General - * [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. - * [unoconv](https://github.com/unoconv/unoconv) - Convert between any document format supported by LibreOffice/OpenOffice. - * [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. - * [xlwt](https://github.com/python-excel/xlwt) / [xlrd](https://github.com/python-excel/xlrd) - Writing and reading data and formatting information from Excel files. -* PDF - * [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. - * [PyPDF2](https://github.com/mstamy2/PyPDF2) - A library capable of splitting, merging and transforming PDF pages. - * [ReportLab](https://www.reportlab.com/opensource/) - Allowing Rapid creation of rich PDF documents. -* Markdown - * [Mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown. - * [Python-Markdown](https://github.com/waylan/Python-Markdown) - A Python implementation of John Gruber’s Markdown. -* YAML - * [PyYAML](http://pyyaml.org/) - YAML implementations for Python. -* CSV - * [csvkit](https://github.com/wireservice/csvkit) - Utilities for converting to and working with CSV. -* Archive - * [unp](https://github.com/mitsuhiko/unp) - A command line tool that can unpack archives easily. +- General + - [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. + - [unoconv](https://github.com/unoconv/unoconv) - Convert between any document format supported by LibreOffice/OpenOffice. + - [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. + - [xlwt](https://github.com/python-excel/xlwt) / [xlrd](https://github.com/python-excel/xlrd) - Writing and reading data and formatting information from Excel files. +- PDF + - [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. + - [PyPDF2](https://github.com/mstamy2/PyPDF2) - A library capable of splitting, merging and transforming PDF pages. + - [ReportLab](https://www.reportlab.com/opensource/) - Allowing Rapid creation of rich PDF documents. +- Markdown + - [Mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown. + - [Python-Markdown](https://github.com/waylan/Python-Markdown) - A Python implementation of John Gruber’s Markdown. +- YAML + - [PyYAML](http://pyyaml.org/) - YAML implementations for Python. +- CSV + - [csvkit](https://github.com/wireservice/csvkit) - Utilities for converting to and working with CSV. +- Archive + - [unp](https://github.com/mitsuhiko/unp) - A command line tool that can unpack archives easily. ## Static Site Generator -*Static site generator is a software that takes some text + templates as input and produces HTML files on the output.* +_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. -* [makesite](https://github.com/sunainapai/makesite) - Simple, lightweight, and magic-free static site/blog generator (< 130 lines). -* [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. -* [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. +- [lektor](https://github.com/lektor/lektor) - An easy to use static CMS and blog engine. +- [makesite](https://github.com/sunainapai/makesite) - Simple, lightweight, and magic-free static site/blog generator (< 130 lines). +- [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. +- [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. ## Task Queues -*Libraries for working with task queues.* +_Libraries for working with task queues._ -* [celery](https://docs.celeryproject.org/en/stable/) - An asynchronous task queue/job queue based on distributed message passing. -* [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. -* [mrq](https://github.com/pricingassistant/mrq) - A distributed worker task queue in Python using Redis & gevent. -* [rq](https://github.com/rq/rq) - Simple job queues for Python. +- [celery](https://docs.celeryproject.org/en/stable/) - An asynchronous task queue/job queue based on distributed message passing. +- [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. +- [mrq](https://github.com/pricingassistant/mrq) - A distributed worker task queue in Python using Redis & gevent. +- [rq](https://github.com/rq/rq) - Simple job queues for Python. ## Template Engine -*Libraries and tools for templating and lexing.* +_Libraries and tools for templating and lexing._ -* [Genshi](https://genshi.edgewall.org/) - Python templating toolkit for generation of web-aware output. -* [Jinja2](https://github.com/pallets/jinja) - A modern and designer friendly templating language. -* [Mako](http://www.makotemplates.org/) - Hyperfast and lightweight templating for the Python platform. +- [Genshi](https://genshi.edgewall.org/) - Python templating toolkit for generation of web-aware output. +- [Jinja2](https://github.com/pallets/jinja) - A modern and designer friendly templating language. +- [Mako](http://www.makotemplates.org/) - Hyperfast and lightweight templating for the Python platform. ## Testing -*Libraries for testing codebases and generating test data.* +_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. - * [nose2](https://github.com/nose-devs/nose2) - The successor to `nose`, based on `unittest2. - * [pytest](https://docs.pytest.org/en/latest/) - A mature full-featured Python testing tool. - * [Robot Framework](https://github.com/robotframework/robotframework) - A generic test automation framework. - * [unittest](https://docs.python.org/3/library/unittest.html) - (Python standard library) Unit testing framework. -* Test Runners - * [green](https://github.com/CleanCut/green) - A clean, colorful test runner. - * [mamba](http://nestorsalceda.github.io/mamba/) - The definitive testing tool for Python. Born under the banner of BDD. - * [tox](https://tox.readthedocs.io/en/latest/) - 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. - * [PyAutoGUI](https://github.com/asweigart/pyautogui) - PyAutoGUI is a cross-platform GUI automation Python module for human beings. - * [Schemathesis](https://github.com/kiwicom/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications. - * [Selenium](https://pypi.org/project/selenium/) - Python bindings for [Selenium](https://selenium.dev/) [WebDriver](https://selenium.dev/documentation/webdriver/). - * [splinter](https://github.com/cobrateam/splinter) - Open source tool for testing web applications. -* Mock - * [doublex](https://pypi.org/project/doublex/) - Powerful test doubles framework for Python. - * [freezegun](https://github.com/spulec/freezegun) - Travel through time by mocking the datetime module. - * [httmock](https://github.com/patrys/httmock) - A mocking library for requests for Python 2.6+ and 3.2+. - * [httpretty](https://github.com/gabrielfalcao/HTTPretty) - HTTP request mock tool for Python. - * [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. - * [VCR.py](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. - * [mixer](https://github.com/klen/mixer) - Another fixtures replacement. Supports Django, Flask, SQLAlchemy, Peewee and etc. - * [polyfactory](https://github.com/litestar-org/polyfactory) - mock data generation library with support to classes (continuation of `pydantic-factories`) -* Code Coverage - * [coverage](https://pypi.org/project/coverage/) - 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. +- Testing Frameworks + - [hypothesis](https://github.com/HypothesisWorks/hypothesis) - Hypothesis is an advanced Quickcheck style property based testing library. + - [nose2](https://github.com/nose-devs/nose2) - The successor to `nose`, based on `unittest2. + - [pytest](https://docs.pytest.org/en/latest/) - A mature full-featured Python testing tool. + - [Robot Framework](https://github.com/robotframework/robotframework) - A generic test automation framework. + - [unittest](https://docs.python.org/3/library/unittest.html) - (Python standard library) Unit testing framework. +- Test Runners + - [green](https://github.com/CleanCut/green) - A clean, colorful test runner. + - [mamba](http://nestorsalceda.github.io/mamba/) - The definitive testing tool for Python. Born under the banner of BDD. + - [tox](https://tox.readthedocs.io/en/latest/) - 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. + - [PyAutoGUI](https://github.com/asweigart/pyautogui) - PyAutoGUI is a cross-platform GUI automation Python module for human beings. + - [Schemathesis](https://github.com/kiwicom/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications. + - [Selenium](https://pypi.org/project/selenium/) - Python bindings for [Selenium](https://selenium.dev/) [WebDriver](https://selenium.dev/documentation/webdriver/). + - [splinter](https://github.com/cobrateam/splinter) - Open source tool for testing web applications. +- Mock + - [doublex](https://pypi.org/project/doublex/) - Powerful test doubles framework for Python. + - [freezegun](https://github.com/spulec/freezegun) - Travel through time by mocking the datetime module. + - [httmock](https://github.com/patrys/httmock) - A mocking library for requests for Python 2.6+ and 3.2+. + - [httpretty](https://github.com/gabrielfalcao/HTTPretty) - HTTP request mock tool for Python. + - [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. + - [VCR.py](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. + - [mixer](https://github.com/klen/mixer) - Another fixtures replacement. Supports Django, Flask, SQLAlchemy, Peewee and etc. + - [polyfactory](https://github.com/litestar-org/polyfactory) - mock data generation library with support to classes (continuation of `pydantic-factories`) +- Code Coverage + - [coverage](https://pypi.org/project/coverage/) - 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. ## Text Processing -*Libraries for parsing and manipulating plain texts.* +_Libraries for parsing and manipulating plain texts._ -* General - * [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/LuminosoInsight/python-ftfy) - Makes Unicode text less broken and more consistent automagically. - * [fuzzywuzzy](https://github.com/seatgeek/fuzzywuzzy) - Fuzzy String Matching. - * [Levenshtein](https://github.com/ztane/python-Levenshtein/) - Fast computation of Levenshtein distance and string similarity. - * [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 (拼音). - * [textdistance](https://github.com/orsinium/textdistance) - Compute distance between sequences with 30+ algorithms. - * [unidecode](https://pypi.org/project/Unidecode/) - ASCII transliterations of Unicode text. -* Slugify - * [awesome-slugify](https://github.com/dimka665/awesome-slugify) - A Python slugify library that can preserve unicode. - * [python-slugify](https://github.com/un33k/python-slugify) - A Python slugify library that translates unicode to ASCII. - * [unicode-slugify](https://github.com/mozilla/unicode-slugify) - A slugifier that generates unicode slugs with Django as a dependency. -* Unique identifiers - * [hashids](https://github.com/davidaurelio/hashids-python) - Implementation of [hashids](http://hashids.org) in Python. - * [shortuuid](https://github.com/skorokithakis/shortuuid) - A generator library for concise, unambiguous and URL-safe UUIDs. -* Parser - * [ply](https://github.com/dabeaz/ply) - Implementation of lex and yacc parsing tools for Python. - * [pygments](http://pygments.org/) - 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. +- General + - [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/LuminosoInsight/python-ftfy) - Makes Unicode text less broken and more consistent automagically. + - [fuzzywuzzy](https://github.com/seatgeek/fuzzywuzzy) - Fuzzy String Matching. + - [Levenshtein](https://github.com/ztane/python-Levenshtein/) - Fast computation of Levenshtein distance and string similarity. + - [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 (拼音). + - [textdistance](https://github.com/orsinium/textdistance) - Compute distance between sequences with 30+ algorithms. + - [unidecode](https://pypi.org/project/Unidecode/) - ASCII transliterations of Unicode text. +- Slugify + - [awesome-slugify](https://github.com/dimka665/awesome-slugify) - A Python slugify library that can preserve unicode. + - [python-slugify](https://github.com/un33k/python-slugify) - A Python slugify library that translates unicode to ASCII. + - [unicode-slugify](https://github.com/mozilla/unicode-slugify) - A slugifier that generates unicode slugs with Django as a dependency. +- Unique identifiers + - [hashids](https://github.com/davidaurelio/hashids-python) - Implementation of [hashids](http://hashids.org) in Python. + - [shortuuid](https://github.com/skorokithakis/shortuuid) - A generator library for concise, unambiguous and URL-safe UUIDs. +- Parser + - [ply](https://github.com/dabeaz/ply) - Implementation of lex and yacc parsing tools for Python. + - [pygments](http://pygments.org/) - 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. ## URL Manipulation -*Libraries for parsing URLs.* +_Libraries for parsing URLs._ -* [furl](https://github.com/gruns/furl) - A small Python library that makes parsing and manipulating URLs easy. -* [purl](https://github.com/codeinthehole/purl) - A simple, immutable URL class with a clean API for interrogation and manipulation. -* [pyshorteners](https://github.com/ellisonleao/pyshorteners) - A pure Python URL shortening lib. -* [webargs](https://github.com/marshmallow-code/webargs) - A friendly library for parsing HTTP request arguments with built-in support for popular web frameworks. +- [furl](https://github.com/gruns/furl) - A small Python library that makes parsing and manipulating URLs easy. +- [purl](https://github.com/codeinthehole/purl) - A simple, immutable URL class with a clean API for interrogation and manipulation. +- [pyshorteners](https://github.com/ellisonleao/pyshorteners) - A pure Python URL shortening lib. +- [webargs](https://github.com/marshmallow-code/webargs) - A friendly library for parsing HTTP request arguments with built-in support for popular web frameworks. ## Video -*Libraries for manipulating video and GIFs.* +_Libraries for manipulating video and GIFs._ -* [moviepy](https://zulko.github.io/moviepy/) - A module for script-based movie editing with many formats, including animated GIFs. -* [scikit-video](https://github.com/aizvorski/scikit-video) - Video processing routines for SciPy. -* [vidgear](https://github.com/abhiTronix/vidgear) - Most Powerful multi-threaded Video Processing framework. +- [moviepy](https://zulko.github.io/moviepy/) - A module for script-based movie editing with many formats, including animated GIFs. +- [scikit-video](https://github.com/aizvorski/scikit-video) - Video processing routines for SciPy. +- [vidgear](https://github.com/abhiTronix/vidgear) - Most Powerful multi-threaded Video Processing framework. ## Web Asset Management -*Tools for managing, compressing and minifying website assets.* +_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-pipeline](https://github.com/jazzband/django-pipeline) - An asset packaging library for Django. -* [django-storages](https://github.com/jschneier/django-storages) - A collection of custom storage back ends for Django. -* [fanstatic](http://www.fanstatic.org/en/latest/) - Packages, optimizes, and serves static file dependencies as Python packages. -* [flask-assets](https://github.com/miracle2k/flask-assets) - Helps you integrate webassets into your Flask app. -* [webassets](https://github.com/miracle2k/webassets) - Bundles, optimizes, and manages unique cache-busting URLs for static resources. +- [django-compressor](https://github.com/django-compressor/django-compressor) - Compresses linked and inline JavaScript or CSS into a single cached file. +- [django-pipeline](https://github.com/jazzband/django-pipeline) - An asset packaging library for Django. +- [django-storages](https://github.com/jschneier/django-storages) - A collection of custom storage back ends for Django. +- [fanstatic](http://www.fanstatic.org/en/latest/) - Packages, optimizes, and serves static file dependencies as Python packages. +- [flask-assets](https://github.com/miracle2k/flask-assets) - Helps you integrate webassets into your Flask app. +- [webassets](https://github.com/miracle2k/webassets) - Bundles, optimizes, and manages unique cache-busting URLs for static resources. ## Web Content Extracting -*Libraries for extracting web contents.* +_Libraries for extracting web contents._ -* [html2text](https://github.com/Alir3z4/html2text) - Convert HTML to Markdown-formatted text. -* [lassie](https://github.com/michaelhelmick/lassie) - Web Content Retrieval for Humans. -* [micawber](https://github.com/coleifer/micawber) - A small library for extracting rich content from URLs. -* [newspaper](https://github.com/codelucas/newspaper) - News extraction, article extraction and content curation in Python. -* [python-readability](https://github.com/buriy/python-readability) - Fast Python port of arc90's readability tool. -* [requests-html](https://github.com/psf/requests-html) - Pythonic HTML Parsing for Humans. -* [sumy](https://github.com/miso-belica/sumy) - A module for automatic summarization of text documents and HTML pages. -* [textract](https://github.com/deanmalmgren/textract) - Extract text from any document, Word, PowerPoint, PDFs, etc. -* [toapi](https://github.com/gaojiuli/toapi) - Every web site provides APIs. +- [html2text](https://github.com/Alir3z4/html2text) - Convert HTML to Markdown-formatted text. +- [lassie](https://github.com/michaelhelmick/lassie) - Web Content Retrieval for Humans. +- [micawber](https://github.com/coleifer/micawber) - A small library for extracting rich content from URLs. +- [newspaper](https://github.com/codelucas/newspaper) - News extraction, article extraction and content curation in Python. +- [python-readability](https://github.com/buriy/python-readability) - Fast Python port of arc90's readability tool. +- [requests-html](https://github.com/psf/requests-html) - Pythonic HTML Parsing for Humans. +- [sumy](https://github.com/miso-belica/sumy) - A module for automatic summarization of text documents and HTML pages. +- [textract](https://github.com/deanmalmgren/textract) - Extract text from any document, Word, PowerPoint, PDFs, etc. +- [toapi](https://github.com/gaojiuli/toapi) - Every web site provides APIs. ## Web Crawling -*Libraries to automate web scraping.* +_Libraries to automate web scraping._ -* [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. -* [grab](https://github.com/lorien/grab) - Site scraping framework. -* [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. +- [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. +- [grab](https://github.com/lorien/grab) - Site scraping framework. +- [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. ## Web Frameworks -*Traditional full stack web frameworks. Also see [RESTful API](https://github.com/vinta/awesome-python#restful-api).* +_Traditional full stack web frameworks. Also see [RESTful API](https://github.com/vinta/awesome-python#restful-api)._ -* Synchronous - * [django](https://github.com/django/django) - The most popular web framework in Python. - * [awesome-django](https://github.com/shahraizali/awesome-django) - * [awesome-django](https://github.com/wsvincent/awesome-django) - * [flask](https://github.com/pallets/flask) - A microframework for Python. - * [awesome-flask](https://github.com/humiaozuzu/awesome-flask) - * [pyramid](https://pylonsproject.org/) - 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. - * [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. - * [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) -* Asynchronous - * [microdot](https://github.com/miguelgrinberg/microdot) - The impossibly small web framework for Python and MicroPython. - * [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. +- Synchronous + - [django](https://github.com/django/django) - The most popular web framework in Python. + - [awesome-django](https://github.com/shahraizali/awesome-django) + - [awesome-django](https://github.com/wsvincent/awesome-django) + - [flask](https://github.com/pallets/flask) - A microframework for Python. + - [awesome-flask](https://github.com/humiaozuzu/awesome-flask) + - [pyramid](https://pylonsproject.org/) - 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. + - [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. + - [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) +- Asynchronous + - [microdot](https://github.com/miguelgrinberg/microdot) - The impossibly small web framework for Python and MicroPython. + - [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. ## WebSocket -*Libraries for working with 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. -* [websockets](https://github.com/aaugustin/websockets) - A library for building WebSocket servers and clients with a focus on correctness and simplicity. +- [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. +- [websockets](https://github.com/aaugustin/websockets) - A library for building WebSocket servers and clients with a focus on correctness and simplicity. ## WSGI Servers -*WSGI-compatible web servers.* +_WSGI-compatible web servers._ -* [gunicorn](https://github.com/benoitc/gunicorn) - Pre-forked, ported from Ruby's Unicorn project. -* [uwsgi](https://uwsgi-docs.readthedocs.io/en/latest/) - 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. -* [werkzeug](https://github.com/pallets/werkzeug) - A WSGI utility library for Python that powers Flask and can easily be embedded into your own projects. +- [gunicorn](https://github.com/benoitc/gunicorn) - Pre-forked, ported from Ruby's Unicorn project. +- [uwsgi](https://uwsgi-docs.readthedocs.io/en/latest/) - 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. +- [werkzeug](https://github.com/pallets/werkzeug) - A WSGI utility library for Python that powers Flask and can easily be embedded into your own projects. # Resources @@ -1163,25 +1164,24 @@ 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/) +- [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) - Hidden Figures of Python: Stories from the underrepresented group members of the Python community. -* [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/) +- [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 me [@VintaChen](https://twitter.com/VintaChen) on Twitter or open an issue on GitHub. - From 1d6327894789fd5f50391f25a39fe537839265ee Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:04:50 +0530 Subject: [PATCH 0084/1130] added pyside to gui --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed2e4bd2..de29e638 100644 --- a/README.md +++ b/README.md @@ -568,6 +568,7 @@ _Libraries for working with graphical user interface applications._ - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). - [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. +- [PySide](https://doc.qt.io/qtforpython/) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt but this is the official binding. - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. - [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. - [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. From b25765958460baa8c2bfc3ffaed003c5ad0ad1b3 Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:08:30 +0530 Subject: [PATCH 0085/1130] updated pyqt link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed2e4bd2..2a337447 100644 --- a/README.md +++ b/README.md @@ -567,7 +567,7 @@ _Libraries for working with graphical user interface applications._ - [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). -- [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. +- [PyQt](https://www.riverbankcomputing.com/static/Docs/PyQt6/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. - [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. - [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. From 87f11bcb724f6db6d846ba8cf6332a76011c147a Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:10:20 +0530 Subject: [PATCH 0086/1130] Update PySide description in README Clarified the description of PySide in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de29e638..d5b744bf 100644 --- a/README.md +++ b/README.md @@ -568,7 +568,7 @@ _Libraries for working with graphical user interface applications._ - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). - [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. -- [PySide](https://doc.qt.io/qtforpython/) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt but this is the official binding. +- [PySide](https://doc.qt.io/qtforpython/) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt but this is the official binding with different licensing. - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. - [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. - [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. From 259c26a1f193bcc6163c3acf9e547a96438e43dd Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:15:44 +0530 Subject: [PATCH 0087/1130] Add CustomTkinter to GUI libraries section Added CustomTkinter library to GUI libraries list. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed2e4bd2..a902444c 100644 --- a/README.md +++ b/README.md @@ -558,6 +558,7 @@ _Functional Programming with Python._ _Libraries for working with graphical user interface applications._ - [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. +- [CustomTkinter](https://customtkinter.tomschimansky.com/) - A modern and customizable python UI-library based on Tkinter. - [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework - [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. - [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. From 00a07420cd1ee1f0114af386b1a9f9cb93cd5aea Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:34:01 +0530 Subject: [PATCH 0088/1130] Add cx_freeze to distribution libraries list Added cx_freeze to the list of libraries for creating packaged executables. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed2e4bd2..9874d592 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,7 @@ _Frameworks and libraries for Distributed Computing._ _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). - [py2app](https://github.com/ronaldoussoren/py2app) - Freezes Python scripts (Mac OS X). - [py2exe](https://github.com/py2exe/py2exe) - Freezes Python scripts (Windows). From 7acd4d350c456aafd9b51c1ed7a16096d4acb82b Mon Sep 17 00:00:00 2001 From: Sivani-kn Date: Thu, 8 Jan 2026 16:12:21 +0530 Subject: [PATCH 0089/1130] Add Reflex --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ed2e4bd2..4227aaa6 100644 --- a/README.md +++ b/README.md @@ -1139,6 +1139,7 @@ _Traditional full stack web frameworks. Also see [RESTful API](https://github.co - [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) - Asynchronous - [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 . - [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. ## WebSocket From 40b6aab97946a5e3db5b811d0b955b2cf0e3f329 Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:22:49 +0530 Subject: [PATCH 0090/1130] Update CustomTkinter link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a902444c..33026d41 100644 --- a/README.md +++ b/README.md @@ -558,7 +558,7 @@ _Functional Programming with Python._ _Libraries for working with graphical user interface applications._ - [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. -- [CustomTkinter](https://customtkinter.tomschimansky.com/) - A modern and customizable python UI-library based on Tkinter. +- [CustomTkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter. - [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework - [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. - [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. From 3f0dda11777f8f6aa6fae1936157e695bb6536dd Mon Sep 17 00:00:00 2001 From: Soledad Galli Date: Thu, 8 Jan 2026 06:41:40 -0500 Subject: [PATCH 0091/1130] make changes as requested README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 89975d15..83560589 100644 --- a/README.md +++ b/README.md @@ -697,7 +697,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). *Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python).* * [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. -* [Feature-engine](https://feature-engine.trainindata.com/) - sklearn compatible API with the widest toolset for feature engineering and selection +* [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. * [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. * [NuPIC](https://github.com/numenta/nupic) - Numenta Platform for Intelligent Computing. From 3d5d1c8cb9a7d97c9812c05f6b1e187039d457c6 Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:15:11 +0530 Subject: [PATCH 0092/1130] Update PySide description for clarity Clarified the description of PySide in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5b744bf..5401afa5 100644 --- a/README.md +++ b/README.md @@ -568,7 +568,7 @@ _Libraries for working with graphical user interface applications._ - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). - [PyQt](https://doc.qt.io/qtforpython/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. -- [PySide](https://doc.qt.io/qtforpython/) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt but this is the official binding with different licensing. +- [PySide](https://doc.qt.io/qtforpython/) - 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. - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. - [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. - [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. From e858779e03254a15c8829db6a6dfae85ffaaece2 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Fri, 9 Jan 2026 14:32:51 +0800 Subject: [PATCH 0093/1130] fix: quote if condition to fix YAML parsing error The colon in 'DECISION: REJECT' was causing YAML parsing issues. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/claude-pr-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 1a5c4068..33a876bd 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -49,7 +49,7 @@ jobs: DECISION: REJECT or DECISION: PASS - name: Close PR if rejected - if: contains(steps.claude-review.outputs.response, 'DECISION: REJECT') + if: "contains(steps.claude-review.outputs.response, 'DECISION: REJECT')" env: GH_TOKEN: ${{ github.token }} run: | From 6f2fb4a584e970b9d4f960d74f698c782ade0a46 Mon Sep 17 00:00:00 2001 From: Sylvain Hellegouarch Date: Fri, 9 Jan 2026 15:59:09 +0100 Subject: [PATCH 0094/1130] Add Chaos Toolkit Signed-off-by: Sylvain Hellegouarch --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 83ee9a88..997b7aa5 100644 --- a/README.md +++ b/README.md @@ -452,6 +452,8 @@ _Software and libraries for DevOps._ - [psutil](https://github.com/giampaolo/psutil) - A cross-platform process and system utilities module. - Backup - [borg](https://github.com/borgbackup/borg) - A deduplicating archiver with compression and encryption. +- Chaos Engineering + - [chaostoolkit](https://github.com/chaostoolkit/chaostoolkit) - A Chaos Engineering toolkit & Orchestration for Developers. ## Distributed Computing From 5e8a25e57e9b3b47b4b5bca5951cfdb7a884ca19 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 11 Jan 2026 23:03:51 +0800 Subject: [PATCH 0095/1130] Reorganize CONTRIBUTING.md for better clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move "Automatic Rejection" section to top of document as first-line filter for reviewers. Consolidate duplicate rejection criteria from "Common Mistakes" and bottom "Automatic Rejection" sections into single comprehensive list. Reorder remaining sections in logical flow: rejections, quality requirements, acceptance criteria, format reference, review process. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- CONTRIBUTING.md | 71 +++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9feac7b..d5dd034b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,28 @@ # Contributing -## Quick Checklist +## Automatic Rejection -Before submitting a PR, verify: +PRs will be **closed** if: -- [ ] One link per Pull Request -- [ ] PR title format: `Add project-name` -- [ ] Entry format: `* [project-name](url) - A short description ending with a period.` -- [ ] Description is concise (one sentence) -- [ ] Placed in the appropriate category/subcategory -- [ ] No trailing whitespace -- [ ] Spelling and grammar checked +- 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 + +## 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 @@ -31,21 +43,13 @@ Your submission must meet **ONE** of the following criteria: ### 3. Hidden Gem -- Exceptional quality despite fewer stars (may have <500 stars) +- 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 -## 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" - ## Entry Format Reference ### Standard Entry @@ -82,26 +86,10 @@ All submissions must satisfy **ALL** of these: ## Adding a New Section -If adding a new category: - 1. Add section description in italics: `*Libraries for doing X.*` 2. Add the section title to the Table of Contents 3. Keep sections in alphabetical order -## PR Description Template - -Please include the following in your PR description: - -``` -## Why This Project Is Awesome - -[Explain which criterion it meets: Industry Standard / Rising Star / Hidden Gem] - -## How It Differs - -[If similar entries exist, explain what makes this one unique] -``` - ## Review Process PRs are reviewed by automated tools and maintainers: @@ -113,14 +101,3 @@ PRs are reviewed by automated tools and maintainers: 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: - -- 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 From a504dd569ff0d8ca2556a293c05d313696077359 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 11 Jan 2026 23:04:08 +0800 Subject: [PATCH 0096/1130] Clarify PR template checklist wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update description guideline from "no mention of Python" to "concise and short" for clarity. Adjust Rising Star formatting for consistency. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ab34f964..67b78347 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,14 +3,14 @@ - [ ] One link per Pull Request - [ ] PR title format: `Add project-name` - [ ] Entry format: `* [project-name](url) - Description ending with period.` -- [ ] Description is concise (no mention of "Python") +- [ ] 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** - 5,000+ stars in <2 years, significant adoption +- [ ] **Rising Star** - 5000+ stars in < 2 years, significant adoption - [ ] **Hidden Gem** - Exceptional quality, solves niche problems elegantly Explain: From a085ede8af706d9a57d58bd02637cde8cd56ede5 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Sun, 11 Jan 2026 20:02:56 +0400 Subject: [PATCH 0097/1130] Add orjson library to serialization section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81d45f1b..f92435bd 100644 --- a/README.md +++ b/README.md @@ -932,6 +932,7 @@ _Libraries and software for indexing and performing search queries on data._ _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. +- [orjson](https://github.com/ijl/orjson) - Fast, correct JSON library. - [pysimdjson](https://github.com/TkTech/pysimdjson) - A Python bindings for [simdjson](https://github.com/lemire/simdjson). - [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) - A Python wrapper around [RapidJSON](https://github.com/Tencent/rapidjson). - [toonify](https://github.com/ScrapeGraphAI/toonify) - A compact, human-readable serialization format that reduces LLM token usage by 30-60% compared to JSON. From deed9da58e4e3f498e5fd1bccd023fe9e36da63a Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Mon, 12 Jan 2026 11:50:31 +0530 Subject: [PATCH 0098/1130] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81d45f1b..09d50f52 100644 --- a/README.md +++ b/README.md @@ -639,6 +639,7 @@ _Libraries for working with HTTP._ _Libraries for programming with hardware._ +- [bleak](https://github.com/hbldh/bleak) - A cross platform Bluetooth Low Energy Client for Python using asyncio. - [keyboard](https://github.com/boppreh/keyboard) - Hook and simulate global keyboard events on Windows and Linux. - [mouse](https://github.com/boppreh/mouse) - Hook and simulate global mouse events on Windows and Linux. - [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices. From 448d795e483c7418f6c17d6ce4c839939bfaaa5c Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:01:16 +0530 Subject: [PATCH 0099/1130] Add Transformers framework to the README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81d45f1b..9f11075b 100644 --- a/README.md +++ b/README.md @@ -727,6 +727,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. - [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. - [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. +- [Transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks. - [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. ## Microsoft Windows From 636f0387c927fbb1403d7c37f17fcd6fc01176c2 Mon Sep 17 00:00:00 2001 From: Sakthi Murugan C <168261877+Sakth1@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:15:52 +0530 Subject: [PATCH 0100/1130] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81d45f1b..5288fc93 100644 --- a/README.md +++ b/README.md @@ -719,6 +719,7 @@ _Libraries for generating and working with logs._ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python)._ +- [diffusers](https://github.com/huggingface/diffusers) - A library that provides pretrained diffusion models for generating and editing images, audio, and video. - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. - [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. From 2b274538e7a8bb6465dc6c6c4c01e8b7fe9200df Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 12 Jan 2026 17:30:10 +0800 Subject: [PATCH 0101/1130] Improve PR template with project section and clarified checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dedicated Project section at top for contributors to specify project name and URL upfront. Clarify checklist item from "One link per Pull Request" to "One project per PR" for better consistency with PR title format guideline. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- .github/PULL_REQUEST_TEMPLATE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 67b78347..f88b5534 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,10 @@ +## Project + +[Project Name](url) + ## Checklist -- [ ] One link per Pull Request +- [ ] One project per PR - [ ] PR title format: `Add project-name` - [ ] Entry format: `* [project-name](url) - Description ending with period.` - [ ] Description is concise and short From 49a84fbcaf5f5eb875b60d99d105e17fa9d1fb13 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Mon, 12 Jan 2026 15:01:38 +0400 Subject: [PATCH 0102/1130] Add geopandas to geolocation section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..d69e597b 100644 --- a/README.md +++ b/README.md @@ -608,6 +608,7 @@ _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. ## HTML Manipulation From 8ed5de167e907d03afa46bc4da68029a6faf291c Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Mon, 12 Jan 2026 19:45:26 +0400 Subject: [PATCH 0103/1130] add shapely to Science section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..65c5e543 100644 --- a/README.md +++ b/README.md @@ -918,6 +918,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [SimPy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework. - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. +- [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane. - [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. ## Search From 4e635ec4a01cc431f9d02f8ff18efccd22e8a730 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Mon, 12 Jan 2026 19:56:55 +0400 Subject: [PATCH 0104/1130] add manim --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..e1800d5f 100644 --- a/README.md +++ b/README.md @@ -1096,6 +1096,7 @@ _Libraries for parsing URLs._ _Libraries for manipulating video and GIFs._ +- [manim](https://github.com/ManimCommunity/manim) - An animation engine for explanatory math videos. - [moviepy](https://zulko.github.io/moviepy/) - A module for script-based movie editing with many formats, including animated GIFs. - [scikit-video](https://github.com/aizvorski/scikit-video) - Video processing routines for SciPy. - [vidgear](https://github.com/abhiTronix/vidgear) - Most Powerful multi-threaded Video Processing framework. From 21046be191f9ba4e79f9c0f075107567af68af89 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Mon, 12 Jan 2026 20:19:31 +0400 Subject: [PATCH 0105/1130] add open bloomberg --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..15fa6c21 100644 --- a/README.md +++ b/README.md @@ -495,6 +495,7 @@ _Libraries for generating project documentation._ _Libraries for downloading._ - [akshare](https://github.com/jindaxiang/akshare) - A financial data interface library, built for human beings! +- [OpenBB](https://github.com/OpenBB-finance/OpenBB) - A financial data platform for analysts, quants and AI agents. - [s3cmd](https://github.com/s3tools/s3cmd) - A command line tool for managing Amazon S3 and CloudFront. - [youtube-dl](https://github.com/ytdl-org/youtube-dl/) - A command-line program to download videos from YouTube and other video sites. From bdb177d9d4f7d0a073800074d7aa5feb044fd1a3 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Mon, 12 Jan 2026 20:24:55 +0400 Subject: [PATCH 0106/1130] Add `RAGFlow` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..42567e20 100644 --- a/README.md +++ b/README.md @@ -727,6 +727,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. - [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. - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. +- [RAGFlow](https://github.com/infiniflow/ragflow) - An open-source RAG engine for document understanding and question answering with LLMs. - [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. - [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. - [Transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks. From f119bddfe75cd07d1f3c240fc3fc38fc02561e19 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Tue, 13 Jan 2026 13:28:51 +0400 Subject: [PATCH 0107/1130] move manim to Science --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1800d5f..40bac28a 100644 --- a/README.md +++ b/README.md @@ -905,6 +905,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [cclib](http://cclib.github.io/) - A library for parsing and interpreting the results of computational chemistry packages. - [Colour](http://colour-science.org/) - Implementing a comprehensive number of colour theory transformations and algorithms. - [Karate Club](https://github.com/benedekrozemberczki/karateclub) - Unsupervised machine learning toolbox for graph structured data. +- [manim](https://github.com/ManimCommunity/manim) - An animation engine for explanatory math videos. - [NetworkX](https://networkx.github.io/) - A high-productivity software for complex networks. - [NIPY](http://nipy.org) - A collection of neuroimaging toolkits. - [NumPy](http://www.numpy.org/) - A fundamental package for scientific computing with Python. @@ -1096,7 +1097,6 @@ _Libraries for parsing URLs._ _Libraries for manipulating video and GIFs._ -- [manim](https://github.com/ManimCommunity/manim) - An animation engine for explanatory math videos. - [moviepy](https://zulko.github.io/moviepy/) - A module for script-based movie editing with many formats, including animated GIFs. - [scikit-video](https://github.com/aizvorski/scikit-video) - Video processing routines for SciPy. - [vidgear](https://github.com/abhiTronix/vidgear) - Most Powerful multi-threaded Video Processing framework. From 8fda2cb6c6339a124f26d707a18799d13423c80c Mon Sep 17 00:00:00 2001 From: Jinyang Date: Tue, 13 Jan 2026 13:33:11 +0400 Subject: [PATCH 0108/1130] update entry location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65c5e543..eb66e9ae 100644 --- a/README.md +++ b/README.md @@ -915,10 +915,10 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. - [RDKit](http://www.rdkit.org/) - Cheminformatics and Machine Learning Software. - [SciPy](https://www.scipy.org/) - A Python-based ecosystem of open-source software for mathematics, science, and engineering. +- [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane. - [SimPy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework. - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. -- [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane. - [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. ## Search From cb7d3d51cbc42cad5e00681fe335528fb76fc118 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Tue, 13 Jan 2026 09:06:09 -0500 Subject: [PATCH 0109/1130] Add Django MongoDB Backend to NoSQL Databases list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..d1da47c3 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,7 @@ _Libraries for connecting and operating databases._ - [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. - NoSQL Databases - [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. + - [Django MongoDB Backend](https://github.com/mongodb/django-mongodb-backend) - Official MongoDB database backend for Django - [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. - [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. - [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. From 002c43590da63bc8287c8da0ffb5511fa3ca4537 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Tue, 13 Jan 2026 09:10:35 -0500 Subject: [PATCH 0110/1130] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d1da47c3..46ba7997 100644 --- a/README.md +++ b/README.md @@ -386,7 +386,7 @@ _Libraries for connecting and operating databases._ - [pymssql](https://github.com/pymssql/pymssql) - A simple database interface to Microsoft SQL Server. - NoSQL Databases - [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. - - [Django MongoDB Backend](https://github.com/mongodb/django-mongodb-backend) - Official MongoDB database backend for Django + - [Django MongoDB Backend](https://github.com/mongodb/django-mongodb-backend) - Official MongoDB database backend for Django. - [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. - [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. - [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. From 7007921f326069c046e38b81fe75eed63f8ef1e5 Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Tue, 13 Jan 2026 09:11:51 -0500 Subject: [PATCH 0111/1130] Add ODMantic to NoSQL Databases list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dab7e6d..81015fdb 100644 --- a/README.md +++ b/README.md @@ -795,6 +795,7 @@ _Libraries that implement Object-Relational Mapping or data mapping techniques._ - [SQLModel](https://sqlmodel.tiangolo.com/) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. - NoSQL Databases - [mongoengine](https://github.com/MongoEngine/mongoengine) - A Python Object-Document-Mapper for working with MongoDB. + - [ODMantic](https://github.com/art049/odmantic) - Sync and Async ODM for MongoDB built on top of Pydantic for model definition and validation. - [PynamoDB](https://github.com/pynamodb/PynamoDB) - A Pythonic interface for [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). ## Package Management From 70e7ab03201e374899705f205d7ddde8b85ecd3a Mon Sep 17 00:00:00 2001 From: Alex Bevilacqua Date: Wed, 14 Jan 2026 08:58:45 -0500 Subject: [PATCH 0112/1130] Add Beanie ODM to Python libraries list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6668aa6c..32603fe3 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,7 @@ _Libraries for connecting and operating databases._ - [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. - [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. + - [Beanie](https://github.com/BeanieODM/beanie) - An asynchronous Python object-document mapper (ODM) for MongoDB. ## Date and Time From a65fc1f7db7b026469f200e7914e740783021e78 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Wed, 14 Jan 2026 19:48:15 +0400 Subject: [PATCH 0113/1130] add yfinance --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6668aa6c..b3b87ed7 100644 --- a/README.md +++ b/README.md @@ -498,6 +498,7 @@ _Libraries for downloading._ - [akshare](https://github.com/jindaxiang/akshare) - A financial data interface library, built for human beings! - [OpenBB](https://github.com/OpenBB-finance/OpenBB) - A financial data platform for analysts, quants and AI agents. - [s3cmd](https://github.com/s3tools/s3cmd) - A command line tool for managing Amazon S3 and CloudFront. +- [yfinance](https://github.com/ranaroussi/yfinance) - Easy Pythonic way to download market and financial data from Yahoo Finance. - [youtube-dl](https://github.com/ytdl-org/youtube-dl/) - A command-line program to download videos from YouTube and other video sites. ## Editor Plugins and IDEs From 6fc1bbd9fca3abcf4c85ad0911372afd7888a7eb Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Wed, 14 Jan 2026 19:57:43 +0400 Subject: [PATCH 0114/1130] add ty --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6668aa6c..0680911b 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,7 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [yapf](https://github.com/google/yapf) - Yet another Python code formatter from Google. - 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. + - [ty](https://github.com/astral-sh/ty) - An extremely fast Python type checker and language server. - [pyre-check](https://github.com/facebook/pyre-check) - Performant type checking. - [typeshed](https://github.com/python/typeshed) - Collection of library stubs for Python, with static types. - Static Type Annotations Generators From 3f674ba2811734c406b6d0113883cde598fd9a83 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Wed, 14 Jan 2026 20:03:32 +0400 Subject: [PATCH 0115/1130] fix entry location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0680911b..0f85526e 100644 --- a/README.md +++ b/README.md @@ -246,8 +246,8 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [yapf](https://github.com/google/yapf) - Yet another Python code formatter from Google. - 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. - - [ty](https://github.com/astral-sh/ty) - An extremely fast Python type checker and language server. - [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. - Static Type Annotations Generators - [monkeytype](https://github.com/Instagram/MonkeyType) - A system for Python that generates static type annotations by collecting runtime types. From 97831e1078cd6d7a8c6045d18baf347a360dabd9 Mon Sep 17 00:00:00 2001 From: Padraic Slattery Date: Wed, 14 Jan 2026 17:13:12 +0100 Subject: [PATCH 0116/1130] docs: Update outdated GitHub Actions version --- .github/workflows/claude-pr-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 33a876bd..66a2f349 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 1 From e281def297d89aba82a717da34d565e2bb03933e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Fri, 16 Jan 2026 00:40:54 +0800 Subject: [PATCH 0117/1130] Fix formatting and structure in README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove PyPI entry from pip sub-items - Fix capitalization: Hatch → hatch - Move awesome-fasthtml link under fastHTML entry Co-Authored-By: Claude Opus 4.5 --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f6dcd21..990bbaa5 100644 --- a/README.md +++ b/README.md @@ -811,9 +811,8 @@ _Libraries for package and dependency management._ - [pip](https://pip.pypa.io/en/stable/) - The package installer for Python. - [pip-tools](https://github.com/jazzband/pip-tools) - A set of tools to keep your pinned Python dependencies fresh. - - [PyPI](https://pypi.org/) - [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. -- [Hatch](https://github.com/pypa/hatch) - Modern, extensible Python project management. +- [hatch](https://github.com/pypa/hatch) - Modern, extensible Python project management. - [poetry](https://github.com/sdispater/poetry) - Python dependency management and packaging made easy. - [uv](https://github.com/astral-sh/uv) - An extremely fast Python package and project manager, written in Rust. @@ -1157,8 +1156,8 @@ _Traditional full stack web frameworks. Also see [RESTful API](https://github.co - [pyramid](https://pylonsproject.org/) - 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. - - [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. - [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) + - [masonite](https://github.com/MasoniteFramework/masonite) - The modern and developer centric Python web framework. - Asynchronous - [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 . From 5abfdbaffa7d0b590ba9fc25c1d259e178f96d1f Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Fri, 16 Jan 2026 21:34:29 +0400 Subject: [PATCH 0118/1130] add pydantic ai --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 990bbaa5..df3ed004 100644 --- a/README.md +++ b/README.md @@ -733,6 +733,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. - [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. - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. +- [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - [RAGFlow](https://github.com/infiniflow/ragflow) - An open-source RAG engine for document understanding and question answering with LLMs. - [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. - [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. From d9bba73d00be18484eff3b10975818e6e91b3411 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Fri, 16 Jan 2026 22:49:03 +0400 Subject: [PATCH 0119/1130] remove public archive and unmaintained --- README.md | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/README.md b/README.md index 990bbaa5..ffcb6941 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,6 @@ _Python implementation of data structures, algorithms and design patterns. Also - [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 - - [pypattyrn](https://github.com/tylerlaberge/PyPattyrn) - A simple yet effective library for implementing common design patterns. - [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. @@ -388,9 +387,7 @@ _Libraries for connecting and operating databases._ - NoSQL Databases - [cassandra-driver](https://github.com/datastax/python-driver) - The Python Driver for Apache Cassandra. - [Django MongoDB Backend](https://github.com/mongodb/django-mongodb-backend) - Official MongoDB database backend for Django. - - [happybase](https://github.com/python-happybase/happybase) - A developer-friendly library for Apache HBase. - [kafka-python](https://github.com/dpkp/kafka-python) - The Python client for Apache Kafka. - - [motor](https://github.com/mongodb/motor) - The async Python driver for MongoDB. - [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. - [Beanie](https://github.com/BeanieODM/beanie) - An asynchronous Python object-document mapper (ODM) for MongoDB. @@ -470,7 +467,6 @@ _Frameworks and libraries for Distributed Computing._ - [PySpark](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/) Python API. - [Ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem. - Stream Processing - - [faust](https://github.com/robinhood/faust) - A stream processing library, porting the ideas from [Kafka Streams](https://kafka.apache.org/documentation/streams/) to Python. - [streamparse](https://github.com/Parsely/streamparse) - Run Python code against real-time streams of data via [Apache Storm](http://storm.apache.org/). ## Distribution @@ -525,7 +521,6 @@ _Libraries for sending and parsing email._ - Mail Servers - [modoboa](https://github.com/modoboa/modoboa) - A mail hosting and management platform including a modern Web UI. - - [salmon](https://github.com/moggers87/salmon) - A Python Mail Server. - Clients - [imbox](https://github.com/martinrusev/imbox) - Python IMAP for Humans. - [yagmail](https://github.com/kootenpv/yagmail) - Yet another Gmail/SMTP client. @@ -646,7 +641,6 @@ _Libraries for working with HTTP._ _Libraries for programming with hardware._ - [bleak](https://github.com/hbldh/bleak) - A cross platform Bluetooth Low Energy Client for Python using asyncio. -- [keyboard](https://github.com/boppreh/keyboard) - Hook and simulate global keyboard events on Windows and Linux. - [mouse](https://github.com/boppreh/mouse) - Hook and simulate global mouse events on Windows and Linux. - [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices. - [scapy](https://github.com/secdev/scapy) - A brilliant packet manipulation library. @@ -660,7 +654,6 @@ _Libraries for manipulating images._ - [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. -- [pywal](https://github.com/dylanaraps/pywal) - A tool that generates color schemes from images. - [quads](https://github.com/fogleman/Quads) - Computer art based on quadtrees. - [scikit-image](http://scikit-image.org/) - 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. @@ -695,7 +688,6 @@ _Interactive Python interpreters (REPL)._ _Libraries for working with i18n._ - [Babel](http://babel.pocoo.org/en/latest/) - An internationalization library for Python. -- [PyICU](https://github.com/ovalhub/pyicu) - A wrapper of International Components for Unicode C++ library ([ICU](http://site.icu-project.org/)). ## Job Scheduler @@ -754,7 +746,6 @@ _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. -- [magenta](https://github.com/magenta/magenta) - A tool to generate music and art using artificial intelligence. - [pluginbase](https://github.com/mitsuhiko/pluginbase) - A simple but flexible plugin system for Python. - [tryton](http://www.tryton.org/) - A general-purpose business framework. @@ -769,7 +760,6 @@ _Libraries for working with human languages._ - [pattern](https://github.com/clips/pattern) - A web mining module. - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. - [pytext](https://github.com/facebookresearch/pytext) - A natural language modeling framework based on PyTorch. - - [PyTorch-NLP](https://github.com/PetrochukM/PyTorch-NLP) - A toolkit enabling rapid deep learning NLP prototyping for research. - [spacy](https://spacy.io/) - 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 @@ -795,7 +785,6 @@ _Libraries that implement Object-Relational Mapping or data mapping techniques._ - [SQLAlchemy](https://www.sqlalchemy.org/) - 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. - - [orm](https://github.com/encode/orm) - An async ORM. - [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. - [pydal](https://github.com/web2py/pydal/) - A pure Python Database Abstraction Layer. @@ -854,7 +843,6 @@ _Libraries for building recommender systems._ - [annoy](https://github.com/spotify/annoy) - Approximate Nearest Neighbors in C++/Python optimized for memory usage. - [fastFM](https://github.com/ibayer/fastFM) - A library for Factorization Machines. - [implicit](https://github.com/benfred/implicit) - A fast Python implementation of collaborative filtering for implicit datasets. -- [libffm](https://github.com/guestwalk/libffm) - A library for Field-aware Factorization Machine (FFM). - [lightfm](https://github.com/lyst/lightfm) - A Python implementation of a number of popular recommendation algorithms. - [spotlight](https://github.com/maciejkula/spotlight) - Deep recommender models using PyTorch. - [Surprise](https://github.com/NicolasHug/Surprise) - A scikit for building and analyzing recommender systems. @@ -892,7 +880,6 @@ _Libraries for building RESTful APIs._ _Libraries for robotics._ - [PythonRobotics](https://github.com/AtsushiSakai/PythonRobotics) - This is a compilation of various robotics algorithms with visualizations. -- [rospy](http://wiki.ros.org/rospy) - This is a library for ROS (Robot Operating System). ## RPC Servers @@ -975,10 +962,8 @@ _Libraries for parsing and manipulating specific text formats._ - [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. - - [unoconv](https://github.com/unoconv/unoconv) - Convert between any document format supported by LibreOffice/OpenOffice. - [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. - - [xlwt](https://github.com/python-excel/xlwt) / [xlrd](https://github.com/python-excel/xlrd) - Writing and reading data and formatting information from Excel files. - PDF - [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. @@ -1069,7 +1054,6 @@ _Libraries for parsing and manipulating plain texts._ - [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/LuminosoInsight/python-ftfy) - Makes Unicode text less broken and more consistent automagically. - - [fuzzywuzzy](https://github.com/seatgeek/fuzzywuzzy) - Fuzzy String Matching. - [Levenshtein](https://github.com/ztane/python-Levenshtein/) - Fast computation of Levenshtein distance and string similarity. - [pangu.py](https://github.com/vinta/pangu.py) - Paranoid text spacing. - [pyfiglet](https://github.com/pwaller/pyfiglet) - An implementation of figlet written in Python. @@ -1084,7 +1068,6 @@ _Libraries for parsing and manipulating plain texts._ - [hashids](https://github.com/davidaurelio/hashids-python) - Implementation of [hashids](http://hashids.org) in Python. - [shortuuid](https://github.com/skorokithakis/shortuuid) - A generator library for concise, unambiguous and URL-safe UUIDs. - Parser - - [ply](https://github.com/dabeaz/ply) - Implementation of lex and yacc parsing tools for Python. - [pygments](http://pygments.org/) - 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. @@ -1098,7 +1081,6 @@ _Libraries for parsing URLs._ - [furl](https://github.com/gruns/furl) - A small Python library that makes parsing and manipulating URLs easy. - [purl](https://github.com/codeinthehole/purl) - A simple, immutable URL class with a clean API for interrogation and manipulation. -- [pyshorteners](https://github.com/ellisonleao/pyshorteners) - A pure Python URL shortening lib. - [webargs](https://github.com/marshmallow-code/webargs) - A friendly library for parsing HTTP request arguments with built-in support for popular web frameworks. ## Video From 4b8f4ca55242e3fea53cdec22adc6707fe6353a8 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Fri, 16 Jan 2026 23:05:32 +0400 Subject: [PATCH 0120/1130] remove pytext --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index ffcb6941..b623f559 100644 --- a/README.md +++ b/README.md @@ -759,7 +759,6 @@ _Libraries for working with human languages._ - [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. - [pattern](https://github.com/clips/pattern) - A web mining module. - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. - - [pytext](https://github.com/facebookresearch/pytext) - A natural language modeling framework based on PyTorch. - [spacy](https://spacy.io/) - 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 From 18bc6fa770c0bf8f920cebf8c3899b5997ca6dff Mon Sep 17 00:00:00 2001 From: Aditya Date: Sat, 17 Jan 2026 15:25:06 +0530 Subject: [PATCH 0121/1130] Add docling to Data Analysis --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index df3ed004..a2d7baa0 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,8 @@ _Libraries for data analysis._ - [pandas](http://pandas.pydata.org/) - 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. +- [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. + ## Data Validation From b9811358febeb07a4880e0a6228e475147c58804 Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Sat, 17 Jan 2026 18:57:17 +0400 Subject: [PATCH 0122/1130] Fix entry order --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2d7baa0..b6ac4f30 100644 --- a/README.md +++ b/README.md @@ -326,11 +326,11 @@ _Libraries for data analysis._ - [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. +- [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. - [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. - [pandas](http://pandas.pydata.org/) - 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. -- [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. ## Data Validation From 017cdec13e7290f73eaf30c673a10545f8afb12c Mon Sep 17 00:00:00 2001 From: Jinyang Wang Date: Sat, 17 Jan 2026 19:58:54 +0400 Subject: [PATCH 0123/1130] format --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b6ac4f30..55fe951f 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,6 @@ _Libraries for data analysis._ - [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. - ## Data Validation _Libraries for validating data. Used for forms in many cases._ From 704735ad902917e161c933369b7959e2ebd8a5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20D=C3=B6rfelt?= <33229141+marph91@users.noreply.github.com> Date: Sun, 18 Jan 2026 17:44:48 +0100 Subject: [PATCH 0124/1130] add jimmy --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a84594b8..403744c8 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ _Libraries for parsing and manipulating specific text formats._ - [PyPDF2](https://github.com/mstamy2/PyPDF2) - A library capable of splitting, merging and transforming PDF pages. - [ReportLab](https://www.reportlab.com/opensource/) - Allowing Rapid creation of rich PDF documents. - Markdown + - [Jimmy](https://github.com/marph91/jimmy) - Convert your notes to Markdown. - [Mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown. - [Python-Markdown](https://github.com/waylan/Python-Markdown) - A Python implementation of John Gruber’s Markdown. - YAML From f9a7c238a2dec2f0d4218d42e2c970844f6816aa Mon Sep 17 00:00:00 2001 From: Kiran K Kotari Date: Sun, 18 Jan 2026 18:42:07 -0500 Subject: [PATCH 0125/1130] feat: pyenv-win --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a84594b8..9f73027c 100644 --- a/README.md +++ b/README.md @@ -534,6 +534,7 @@ _Libraries for sending and parsing email._ _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. - [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. ## File Manipulation From 71efe98a7e5b015844e0ad3466f04b1c0aa0f982 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 19 Jan 2026 21:35:19 +0800 Subject: [PATCH 0126/1130] Merge changes: Add uv, improve descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add uv package manager to Environment Management - Improve descriptions for granian, Flet, NiceGUI, Cocos2d, JustHTML - Update cpython description formatting - Refine Shell section header 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9f73027c..da4dc8a7 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,7 @@ _Python implementation of data structures, algorithms and design patterns. Also _[ASGI](https://asgi.readthedocs.io/en/latest/)-compatible web servers._ - [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) - Granian is a Rust HTTP server for Python applications built on top of Hyper and Tokio,supporting WSGI/ASGI/RSGI. +- [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/encode/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. @@ -535,6 +535,7 @@ _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 package and project manager, written in Rust. - [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. ## File Manipulation @@ -567,11 +568,11 @@ _Libraries for working with graphical user interface applications._ - [CustomTkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter. - [DearPyGui](https://github.com/RaylockLLC/DearPyGui/) - A Simple GPU accelerated Python GUI framework - [enaml](https://github.com/nucleic/enaml) - Creating beautiful user-interfaces with Declarative Syntax like QML. -- [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. Run on Windows, macOS, Linux, Android, iOS, and the Web. +- [Flet](https://flet.dev) - Cross-platform GUI framework for building modern apps in pure Python. - [Flexx](https://github.com/zoofIO/flexx) - Flexx is a pure Python toolkit for creating GUI's, that uses web technology for its rendering. - [Gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. - [kivy](https://kivy.org/) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS. -- [NiceGui](https://github.com/zauberzeug/nicegui) - It is great for micro web apps, dashboards, robotics projects, smart home solutions and similar use cases. You can also use it in development, for example when tweaking/configuring a machine learning algorithm or tuning motor controllers. +- [NiceGUI](https://github.com/zauberzeug/nicegui) - An easy-to-use, Python-based UI framework, which shows up in your web browser. - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [PyGObject](https://pygobject.readthedocs.io/) - Python Bindings for GLib/GObject/GIO/GTK+ (GTK+3). - [PySide](https://doc.qt.io/qtforpython/) - 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. @@ -594,7 +595,7 @@ _Libraries for working with GraphQL._ _Awesome game development libraries._ - [Arcade](https://api.arcade.academy/en/latest/) - Arcade is a modern Python framework for crafting games with compelling graphics and sound. -- [Cocos2d](https://www.cocos.com/en/cocos2d-x) - cocos2d is a framework for building 2D games, demos, and other graphical/interactive applications. +- [Cocos2d](https://www.cocos.com/en/cocos2d-x) - A framework for building 2D games, demos, and other graphical/interactive applications. - [Harfang3D](http://www.harfang3d.com) - Python framework for 3D, VR and game development. - [Panda3D](https://www.panda3d.org/) - 3D game engine developed by Disney. - [Pygame](http://www.pygame.org/news.html) - Pygame is a set of Python modules designed for writing games. @@ -620,7 +621,7 @@ _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://pypi.org/project/cssutils/) - A CSS library for Python. - [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. -- [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. No C extensions to compile. No system dependencies to install. No complex API to learn. +- [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. - [lxml](http://lxml.de/) - 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. @@ -666,7 +667,7 @@ _Libraries for manipulating images._ _Implementations of Python._ - [clpython](https://github.com/metawilm/cl-python) - Implementation of the Python programming language written in Common Lisp. -- [cpython](https://github.com/python/cpython) - **Default, most widely used implementation of the Python programming language written in C.** +- [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. @@ -948,7 +949,7 @@ _Frameworks for developing serverless Python code._ ## Shell -_Shells based on Python._ +_Shells built with Python._ - [xonsh](https://github.com/xonsh/xonsh/) - A Python-powered shell. Full-featured and cross-platform. @@ -1190,4 +1191,4 @@ Your contributions are always welcome! Please take a look at the [contribution g --- -If you have any question about this opinionated list, do not hesitate to contact me [@VintaChen](https://twitter.com/VintaChen) on Twitter or open an issue on GitHub. +If you have any question about this opinionated list, do not hesitate to contact [@VintaChen](https://twitter.com/VintaChen) on Twitter. From 0cc831720bf3c5dfacef110061ef7eeba9f9b0c4 Mon Sep 17 00:00:00 2001 From: leftkats Date: Mon, 19 Jan 2026 00:14:28 +0200 Subject: [PATCH 0127/1130] feat: rasa --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc27958c..c3ac97f5 100644 --- a/README.md +++ b/README.md @@ -763,6 +763,7 @@ _Libraries for working with human languages._ - [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. - [pattern](https://github.com/clips/pattern) - A web mining module. - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. + - [rasa](https://github.com/RasaHQ/rasa) - An open-source machine learning framework for automated text and voice-based conversations. - [spacy](https://spacy.io/) - 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 From f620d92fd93802f8957d3c9c575baa92f2813630 Mon Sep 17 00:00:00 2001 From: leftkats Date: Mon, 19 Jan 2026 18:40:56 +0200 Subject: [PATCH 0128/1130] fix: move rase to ML section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3ac97f5..dc812b47 100644 --- a/README.md +++ b/README.md @@ -730,6 +730,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. - [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - [RAGFlow](https://github.com/infiniflow/ragflow) - An open-source RAG engine for document understanding and question answering with LLMs. +- [rasa](https://github.com/RasaHQ/rasa) - An open-source machine learning framework for automated text and voice-based conversations. - [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. - [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. - [Transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks. @@ -763,7 +764,6 @@ _Libraries for working with human languages._ - [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. - [pattern](https://github.com/clips/pattern) - A web mining module. - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. - - [rasa](https://github.com/RasaHQ/rasa) - An open-source machine learning framework for automated text and voice-based conversations. - [spacy](https://spacy.io/) - 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 From 5115f903b731ac9ac1776cfc221846153233b62a Mon Sep 17 00:00:00 2001 From: bfly Date: Fri, 23 Jan 2026 23:03:31 +0800 Subject: [PATCH 0129/1130] Add ccb to Command-line Tools / Productivity Tools --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc812b47..a9aed06e 100644 --- a/README.md +++ b/README.md @@ -276,6 +276,7 @@ _Libraries for building command-line applications._ _Useful CLI-based tools for productivity._ - Productivity Tools + - [ccb](https://github.com/bfly123/claude_code_bridge) - A CLI tool to orchestrate multiple LLMs (Claude, Gemini, etc.) in Tmux panes with cross-agent interaction. - [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. From e513a3607674e34397b5f59d1dd1a17dffefb46a Mon Sep 17 00:00:00 2001 From: Dwight Gunning Date: Fri, 23 Jan 2026 22:12:25 -0500 Subject: [PATCH 0130/1130] Add edgartools to Data Analysis --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a9aed06e..3e19fbe9 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,7 @@ _Libraries for data analysis._ - [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. - [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. +- [edgartools](https://github.com/dgunning/edgartools) - Library for getting structured data from SEC EDGAR filings and XBRL financial statements. - [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. - [pandas](http://pandas.pydata.org/) - 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. From bf8e9df398ac52f8aff779338ff35b0578c71f9c Mon Sep 17 00:00:00 2001 From: Dwight Gunning Date: Sat, 24 Jan 2026 12:27:59 -0500 Subject: [PATCH 0131/1130] Moved edgartools to Downloader section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e19fbe9..7c56515a 100644 --- a/README.md +++ b/README.md @@ -327,7 +327,6 @@ _Libraries for data analysis._ - [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. - [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. -- [edgartools](https://github.com/dgunning/edgartools) - Library for getting structured data from SEC EDGAR filings and XBRL financial statements. - [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. - [pandas](http://pandas.pydata.org/) - 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. @@ -497,6 +496,7 @@ _Libraries for generating project documentation._ _Libraries for downloading._ - [akshare](https://github.com/jindaxiang/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. - [s3cmd](https://github.com/s3tools/s3cmd) - A command line tool for managing Amazon S3 and CloudFront. - [yfinance](https://github.com/ranaroussi/yfinance) - Easy Pythonic way to download market and financial data from Yahoo Finance. From b94196ce1cb15c11ad55388278d6590f5c7cd6d9 Mon Sep 17 00:00:00 2001 From: vinDelphini Date: Sun, 25 Jan 2026 06:29:02 +0530 Subject: [PATCH 0132/1130] Add LangChain to Machine Learning --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c56515a..b133ca10 100644 --- a/README.md +++ b/README.md @@ -727,6 +727,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. - [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. +- [LangChain](https://github.com/langchain-ai/langchain) - Building applications with LLMs through composability. - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. - [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. - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. From 54704cff08c6ef2cf0936865d5d14205036d3ff7 Mon Sep 17 00:00:00 2001 From: vinDelphini Date: Sun, 25 Jan 2026 06:29:12 +0530 Subject: [PATCH 0133/1130] Add LlamaIndex to Machine Learning --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c56515a..93a6b438 100644 --- a/README.md +++ b/README.md @@ -727,6 +727,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. - [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. +- [LlamaIndex](https://github.com/run-llama/llama_index) - A data framework for your LLM application. - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. - [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. - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. From 7398873cfff830a3e7858aad3ac6774cb40c75f9 Mon Sep 17 00:00:00 2001 From: vinDelphini Date: Sun, 25 Jan 2026 06:29:24 +0530 Subject: [PATCH 0134/1130] Add Litestar to Web Frameworks --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7c56515a..9710e030 100644 --- a/README.md +++ b/README.md @@ -1148,6 +1148,7 @@ _Traditional full stack web frameworks. Also see [RESTful API](https://github.co - [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 . - [tornado](https://github.com/tornadoweb/tornado) - A web framework and asynchronous networking library. From cb4d47dbff0be62bed4b0e29bacbbed763854f07 Mon Sep 17 00:00:00 2001 From: GOPINATH S Date: Tue, 27 Jan 2026 19:12:33 +0530 Subject: [PATCH 0135/1130] Remove duplicate awesome-django entry --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index abe6b67b..4fd0248a 100644 --- a/README.md +++ b/README.md @@ -1141,7 +1141,6 @@ _Traditional full stack web frameworks. Also see [RESTful API](https://github.co - Synchronous - [django](https://github.com/django/django) - The most popular web framework in Python. - [awesome-django](https://github.com/shahraizali/awesome-django) - - [awesome-django](https://github.com/wsvincent/awesome-django) - [flask](https://github.com/pallets/flask) - A microframework for Python. - [awesome-flask](https://github.com/humiaozuzu/awesome-flask) - [pyramid](https://pylonsproject.org/) - A small, fast, down-to-earth, open source Python web framework. From 4441a5f487416281b4775ff275fd29f9edb264b2 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 28 Jan 2026 00:31:44 +0530 Subject: [PATCH 0136/1130] add-textual --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index abe6b67b..d2037261 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,7 @@ _Libraries for building command-line applications._ - [bashplotlib](https://github.com/glamp/bashplotlib) - Making basic plots in the terminal. - [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) - Framework for building terminal user interfaces in Python. - [tqdm](https://github.com/tqdm/tqdm) - Fast, extensible progress bar for loops and CLI. ## Command-line Tools From b7aaa07a81fa99cb02177e2d5e6fa676188d4c72 Mon Sep 17 00:00:00 2001 From: Aditya Date: Wed, 28 Jan 2026 12:48:26 +0530 Subject: [PATCH 0137/1130] add-plotly --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4fd0248a..0337b412 100644 --- a/README.md +++ b/README.md @@ -354,6 +354,7 @@ _Libraries for visualizing data. Also see [awesome-javascript](https://github.co - [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. - [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. - [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. - [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](http://www.graphviz.org/). From fd2eeee215f6d8c942d402edf3f9e839e606e2f3 Mon Sep 17 00:00:00 2001 From: Asad Munir Date: Thu, 29 Jan 2026 19:28:13 +0500 Subject: [PATCH 0138/1130] docs: enhance ML library descriptions with additional context --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0337b412..027f2fa9 100644 --- a/README.md +++ b/README.md @@ -736,10 +736,10 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - [RAGFlow](https://github.com/infiniflow/ragflow) - An open-source RAG engine for document understanding and question answering with LLMs. - [rasa](https://github.com/RasaHQ/rasa) - An open-source machine learning framework for automated text and voice-based conversations. -- [scikit-learn](http://scikit-learn.org/) - The most popular Python library for Machine Learning. -- [Spark ML](http://spark.apache.org/docs/latest/ml-guide.html) - [Apache Spark](http://spark.apache.org/)'s scalable Machine Learning library. +- [scikit-learn](http://scikit-learn.org/) - 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. - [Transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks. -- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. +- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library for efficient ML. ## Microsoft Windows From 34216a299c33b8f5b9dc7a4106ae57cb9038342a Mon Sep 17 00:00:00 2001 From: Jinyang Date: Fri, 30 Jan 2026 15:32:18 +0400 Subject: [PATCH 0139/1130] Update description for 'textual' package Clarified the description of the 'textual' package in the README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2037261..7ed485d8 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ _Libraries for building command-line applications._ - [bashplotlib](https://github.com/glamp/bashplotlib) - Making basic plots in the terminal. - [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) - Framework for building terminal user interfaces in Python. + - [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 From ca16ac5814199868453543cb530dd2fccab5918f Mon Sep 17 00:00:00 2001 From: Jinyang Date: Fri, 30 Jan 2026 15:33:38 +0400 Subject: [PATCH 0140/1130] Fix punctuation in textual library description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ed485d8..d2c20d6f 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ _Libraries for building command-line applications._ - [bashplotlib](https://github.com/glamp/bashplotlib) - Making basic plots in the terminal. - [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 + - [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 From 1fd050e33e6744821d35420526ddb8a9439ea382 Mon Sep 17 00:00:00 2001 From: Camila Maia Date: Fri, 30 Jan 2026 13:53:14 -0300 Subject: [PATCH 0141/1130] Add ScanAPI to Testing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index add28643..aab23075 100644 --- a/README.md +++ b/README.md @@ -1027,6 +1027,7 @@ _Libraries for testing codebases and generating test data._ - [nose2](https://github.com/nose-devs/nose2) - The successor to `nose`, based on `unittest2. - [pytest](https://docs.pytest.org/en/latest/) - A mature full-featured Python testing tool. - [Robot Framework](https://github.com/robotframework/robotframework) - A generic test automation framework. + - [ScanAPI](https://pypi.org/project/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 - [green](https://github.com/CleanCut/green) - A clean, colorful test runner. From b05419f590e721c261c3f62499e052711fa82c1c Mon Sep 17 00:00:00 2001 From: Asad Munir Date: Sat, 31 Jan 2026 13:59:27 +0500 Subject: [PATCH 0142/1130] fix: remove redundant description from xgboost entry --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 027f2fa9..5d1a77c8 100644 --- a/README.md +++ b/README.md @@ -739,7 +739,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [scikit-learn](http://scikit-learn.org/) - 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. - [Transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pretrained transformer models for NLP, vision, and audio tasks. -- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library for efficient ML. +- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. ## Microsoft Windows From 1c707c9a7dcedfdfc29b728766ca5ba8fa00cef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9A=D0=BE=D0=B7=D1=8B?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Sun, 1 Feb 2026 00:05:03 +0300 Subject: [PATCH 0143/1130] Add python-cqrs --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index dd473c2b..9cb22c6c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Computer Vision](#computer-vision) - [Configuration Files](#configuration-files) - [Cryptography](#cryptography) + - [CQRS & Event Sourcing](#cqrs) - [Data Analysis](#data-analysis) - [Data Validation](#data-validation) - [Data Visualization](#data-visualization) @@ -320,6 +321,12 @@ _Libraries for storing and parsing configuration options._ - [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. +## CQRS & Event Sourcing + +_Event-Driven Architecture frameworks for CQRS pattern and event-driven microservices._ + +- [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS, Transaction Outbox, Saga orchestration, FastAPI/FastStream integration. + ## Data Analysis _Libraries for data analysis._ From b4504573591da6d9ef6c9e2e8efefdffdbff1eb9 Mon Sep 17 00:00:00 2001 From: njg7194 Date: Sun, 1 Feb 2026 13:24:14 +0900 Subject: [PATCH 0144/1130] Add vllm to Deep Learning section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dd473c2b..aa8685fb 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,7 @@ _Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learni - [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. +- [vllm](https://github.com/vllm-project/vllm) - A high-throughput and memory-efficient inference and serving engine for LLMs. ## DevOps Tools From 712bc9b8c55c7279489bc43aa9456c996502cdd2 Mon Sep 17 00:00:00 2001 From: njg7194 Date: Sun, 1 Feb 2026 13:24:48 +0900 Subject: [PATCH 0145/1130] Add markitdown to File Manipulation section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dd473c2b..b467fb9e 100644 --- a/README.md +++ b/README.md @@ -546,6 +546,7 @@ _Libraries for Python version and virtual environment management._ _Libraries for file manipulation._ +- [markitdown](https://github.com/microsoft/markitdown) - Python tool for converting files and office documents to Markdown. - [mimetypes](https://docs.python.org/3/library/mimetypes.html) - (Python standard library) Map filenames to MIME types. - [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). - [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) An cross-platform, object-oriented path library. From 087349932302fad4457dab29a7a396172fb5c014 Mon Sep 17 00:00:00 2001 From: njg7194 Date: Sun, 1 Feb 2026 13:25:21 +0900 Subject: [PATCH 0146/1130] Add browser-use to Web Crawling section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dd473c2b..ab3e878f 100644 --- a/README.md +++ b/README.md @@ -1132,6 +1132,7 @@ _Libraries for extracting web contents._ _Libraries to automate web scraping._ +- [browser-use](https://github.com/browser-use/browser-use) - Make websites accessible for AI agents with easy browser automation. - [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. - [grab](https://github.com/lorien/grab) - Site scraping framework. - [mechanicalsoup](https://github.com/MechanicalSoup/MechanicalSoup) - A Python library for automating interaction with websites. From df021e87d5667265ae79591dd3b81974953cb930 Mon Sep 17 00:00:00 2001 From: NinjaOfNeurons Date: Sat, 31 Jan 2026 23:42:21 -0500 Subject: [PATCH 0147/1130] Add functools to Functional Programming section --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dd473c2b..75b87d51 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,8 @@ _Functional Programming with Python._ - [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. +- [functools](https://docs.python.org/3/library/functools.html) - (Python standard library) Higher-order functions and operations on callable objects. + ## GUI Development From 5f2655e5172da3eef88dbcce7d21d187b09c20a5 Mon Sep 17 00:00:00 2001 From: NinjaOfNeurons Date: Sat, 31 Jan 2026 23:44:52 -0500 Subject: [PATCH 0148/1130] removing extra space --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 75b87d51..a5a63a5c 100644 --- a/README.md +++ b/README.md @@ -565,7 +565,6 @@ _Functional Programming with Python._ - [toolz](https://github.com/pytoolz/toolz) - A collection of functional utilities for iterators, functions, and dictionaries. - [functools](https://docs.python.org/3/library/functools.html) - (Python standard library) Higher-order functions and operations on callable objects. - ## GUI Development _Libraries for working with graphical user interface applications._ From 82455f3d1b4d5b2653a354b596b451213f03d386 Mon Sep 17 00:00:00 2001 From: NinjaOfNeurons Date: Sun, 1 Feb 2026 04:52:05 -0500 Subject: [PATCH 0149/1130] Fix alphabetical order in Computer Vision section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5a63a5c..3027bede 100644 --- a/README.md +++ b/README.md @@ -559,11 +559,11 @@ _Functional Programming with Python._ - [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming. - [cytoolz](https://github.com/pytoolz/cytoolz/) - Cython implementation of `Toolz`: High performance functional utilities. +- [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. -- [functools](https://docs.python.org/3/library/functools.html) - (Python standard library) Higher-order functions and operations on callable objects. ## GUI Development From 7c2f38e92434e004e3e5adc86519d4a7054c84eb Mon Sep 17 00:00:00 2001 From: NinjaOfNeurons Date: Sun, 1 Feb 2026 05:48:38 -0500 Subject: [PATCH 0150/1130] adding arparse a command-line library --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dbd596f0..e764261b 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- _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. - [cliff](https://github.com/openstack/cliff) - A framework for creating command-line programs with multi-level commands. From d4622e20f54342b4be7738d9d50c42b226fa64b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9A=D0=BE=D0=B7=D1=8B?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Sun, 1 Feb 2026 20:10:01 +0300 Subject: [PATCH 0151/1130] Fix example position --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 9cb22c6c..599d6ae3 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ _Python implementation of data structures, algorithms and design patterns. Also - Design Patterns - [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. + - [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS/CQS, Transaction Outbox, Saga orchestration, FastAPI/FastStream integration. ## ASGI Servers @@ -321,12 +322,6 @@ _Libraries for storing and parsing configuration options._ - [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. -## CQRS & Event Sourcing - -_Event-Driven Architecture frameworks for CQRS pattern and event-driven microservices._ - -- [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS, Transaction Outbox, Saga orchestration, FastAPI/FastStream integration. - ## Data Analysis _Libraries for data analysis._ From bd732cac7b52781529adf9ad2fa7146cea183497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9A=D0=BE=D0=B7=D1=8B?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Sun, 1 Feb 2026 20:11:13 +0300 Subject: [PATCH 0152/1130] Remove new category --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 599d6ae3..489f29d8 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Computer Vision](#computer-vision) - [Configuration Files](#configuration-files) - [Cryptography](#cryptography) - - [CQRS & Event Sourcing](#cqrs) - [Data Analysis](#data-analysis) - [Data Validation](#data-validation) - [Data Visualization](#data-visualization) From 4c871950246852d3df4835c4f4b7e5684b49417e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D0=B4=D0=B8=D0=BC=20=D0=9A=D0=BE=D0=B7=D1=8B?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Sun, 1 Feb 2026 21:31:07 +0300 Subject: [PATCH 0153/1130] Update description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 489f29d8..c01a49e7 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ _Python implementation of data structures, algorithms and design patterns. Also - Design Patterns - [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. - - [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS/CQS, Transaction Outbox, Saga orchestration, FastAPI/FastStream integration. + - [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS/CQS, Transaction Outbox, Saga orchestration. ## ASGI Servers From dd844d18184f57c0bfe899cb3c90272364550830 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Mon, 2 Feb 2026 10:39:09 +0400 Subject: [PATCH 0154/1130] fix entry order --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c01a49e7..be25d480 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,9 @@ _Python implementation of data structures, algorithms and design patterns. Also - [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/vadikko2/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. - - [python-cqrs](https://github.com/vadikko2/python-cqrs) - Event-Driven Architecture Framework with CQRS/CQS, Transaction Outbox, Saga orchestration. ## ASGI Servers From 8e30a425151948853c4ff6eaf8b315d68af9c70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frangel=20Ra=C3=BAl=20Crespo=20Barrera?= Date: Mon, 2 Feb 2026 02:00:22 -0500 Subject: [PATCH 0155/1130] Add sherlock to Penetration Testing. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 63e26c5b..3c2cc86e 100644 --- a/README.md +++ b/README.md @@ -834,6 +834,7 @@ _Frameworks and tools for penetration testing._ - [fsociety](https://github.com/Manisso/fsociety) - A Penetration testing framework. - [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. ## Permissions From 89f481bd3f0267e82f163100f2014ac40286d7c2 Mon Sep 17 00:00:00 2001 From: Hemasree1738 Date: Sat, 7 Feb 2026 13:10:10 +0530 Subject: [PATCH 0156/1130] fixed typing error from bccb to bcbb --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c2cc86e..ac36ba58 100644 --- a/README.md +++ b/README.md @@ -910,7 +910,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [astropy](http://www.astropy.org/) - A community Python library for Astronomy. - [bcbio-nextgen](https://github.com/chapmanb/bcbio-nextgen) - Providing best-practice pipelines for fully automated high throughput sequencing analysis. -- [bccb](https://github.com/chapmanb/bcbb) - Collection of useful code related to biological analysis. +- [bcbb](https://github.com/chapmanb/bcbb) - Collection of useful code related to biological analysis. - [Biopython](http://biopython.org/wiki/Main_Page) - Biopython is a set of freely available tools for biological computation. - [cclib](http://cclib.github.io/) - A library for parsing and interpreting the results of computational chemistry packages. - [Colour](http://colour-science.org/) - Implementing a comprehensive number of colour theory transformations and algorithms. From 69b2f535864ca4cefb5681f9fe1094b5c426a45e Mon Sep 17 00:00:00 2001 From: Na'aman Hirschfeld Date: Sun, 8 Feb 2026 15:31:03 +0100 Subject: [PATCH 0157/1130] Add kreuzberg to Text Processing --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac36ba58..1b4d0eb9 100644 --- a/README.md +++ b/README.md @@ -971,6 +971,7 @@ _Shells built with Python._ _Libraries for parsing and manipulating specific text formats._ - General + - [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. - [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 From 1c2d9e1714e590dbcde66fbf509e5332378be1a7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Tue, 10 Feb 2026 12:00:50 +0800 Subject: [PATCH 0158/1130] docs(review): add niche/thin-wrapper rejection criterion Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md index 76d0a527..a026ce92 100644 --- a/.claude/commands/review-pending-prs.md +++ b/.claude/commands/review-pending-prs.md @@ -42,6 +42,7 @@ Check these rules first - if any fail, recommend rejection: - 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 From 42db7e655994c1311cf09f10c730c35707365817 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Tue, 10 Feb 2026 12:04:01 +0800 Subject: [PATCH 0159/1130] ci: disable automatic PR trigger for Claude review workflow Switch from pull_request event to workflow_dispatch only, commenting out the pull_request trigger. This allows manual invocation while preventing automatic runs on every PR. Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 66a2f349..a8a08ce2 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -1,10 +1,11 @@ name: Claude PR Review on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened, labeled] - paths: - - "README.md" + workflow_dispatch: + # pull_request: + # types: [opened, synchronize, ready_for_review, reopened, labeled] + # paths: + # - "README.md" jobs: claude-pr-review: From 573db541620250143057a6d37ce6d20807ca4fd1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Tue, 10 Feb 2026 14:05:56 +0800 Subject: [PATCH 0160/1130] docs: remove Awesome badge from heading Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac36ba58..29e2fbd3 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-# Awesome Python [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) +# Awesome Python An opinionated list of awesome Python frameworks, libraries, software and resources. From 942555d8e34ee2025461c77c19918e086e383b71 Mon Sep 17 00:00:00 2001 From: Vaibhav Gupta <23f2002998@ds.study.iitm.ac.in> Date: Wed, 11 Feb 2026 02:23:53 +0530 Subject: [PATCH 0161/1130] Add gTTS project entry --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81ad4598..c7acc423 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,7 @@ _Libraries for manipulating audio and its metadata._ - [audioFlux](https://github.com/libAudioFlux/audioFlux) - A library for audio and music analysis, feature extraction. - [audioread](https://github.com/beetbox/audioread) - Cross-library (GStreamer + Core Audio + MAD + FFmpeg) audio decoding. - [dejavu](https://github.com/worldveil/dejavu) - Audio fingerprinting and recognition. + - [gTTS](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. - [kapre](https://github.com/keunwoochoi/kapre) - Keras Audio Preprocessors. - [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. From 42b8558a31d4b9c309b5645c85459e2610c529b9 Mon Sep 17 00:00:00 2001 From: Milan Rother <105657697+milanofthe@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:08:26 +0100 Subject: [PATCH 0162/1130] Add PathSim to the list of Science libraries --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81ad4598..7c4e8a66 100644 --- a/README.md +++ b/README.md @@ -931,6 +931,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. - [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. +- [PathSim](https://github.com/pathsim/pathsim) - A block-based system modeling and simulation framework with a browser-based visual editor. ## Search From 061a7b476d41ff2a7943229e6c51acea05df4c62 Mon Sep 17 00:00:00 2001 From: Milan Rother <105657697+milanofthe@users.noreply.github.com> Date: Sat, 14 Feb 2026 12:50:38 +0100 Subject: [PATCH 0163/1130] move PathSim to correct alphabetical location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f813bb5..f89e853f 100644 --- a/README.md +++ b/README.md @@ -922,6 +922,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [NumPy](http://www.numpy.org/) - A fundamental package for scientific computing with Python. - [ObsPy](https://github.com/obspy/obspy/wiki/) - A Python toolbox for seismology. - [Open Babel](https://open-babel.readthedocs.io/) - A chemical toolbox designed to speak the many languages of chemical data. +- [PathSim](https://github.com/pathsim/pathsim) - A block-based system modeling and simulation framework with a browser-based visual editor. - [PyDy](http://www.pydy.org/) - Short for Python Dynamics, used to assist with workflow in the modeling of dynamic motion. - [PyMC](https://github.com/pymc-devs/pymc3) - Markov Chain Monte Carlo sampling toolkit. - [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. @@ -932,7 +933,6 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. - [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. -- [PathSim](https://github.com/pathsim/pathsim) - A block-based system modeling and simulation framework with a browser-based visual editor. ## Search From 2a7a2dd2afda49938885594b4195db42ac4ca231 Mon Sep 17 00:00:00 2001 From: Daksh Date: Mon, 16 Feb 2026 19:37:27 +0000 Subject: [PATCH 0164/1130] added quantum computing section and move qutip --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index f89e853f..5122649e 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). - [Penetration testing](#penetration-testing) - [Permissions](#permissions) - [Processes](#processes) + - [Quantum Computing](#quantum-computing) - [Recommender Systems](#recommender-systems) - [Refactoring](#refactoring) - [RESTful API](#restful-api) @@ -853,6 +854,15 @@ _Libraries for starting and communicating with OS processes._ - [sarge](https://sarge.readthedocs.io/en/latest/) - Yet another wrapper for subprocess. - [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. +## Quantum Computing + +_Libraries for quantum computing._ + +- [Qiskit](https://github.com/Qiskit/qiskit) — An IBM-backed quantum SDK for building, simulating, and running circuits on real quantum hardware. +- [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. +- [ProjectQ](https://github.com/ProjectQ-Framework/ProjectQ) — An open-source quantum compiler and simulation framework for low-level circuit experimentation. + ## Recommender Systems _Libraries for building recommender systems._ From bd643f0e5f11e97d255fea175ff0b1ebaee4458c Mon Sep 17 00:00:00 2001 From: Daksh Date: Mon, 16 Feb 2026 19:43:04 +0000 Subject: [PATCH 0165/1130] moved qutip to quantum computing section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5122649e..e9e14e8a 100644 --- a/README.md +++ b/README.md @@ -862,6 +862,7 @@ _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. - [ProjectQ](https://github.com/ProjectQ-Framework/ProjectQ) — An open-source quantum compiler and simulation framework for low-level circuit experimentation. +- [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. ## Recommender Systems @@ -935,7 +936,6 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [PathSim](https://github.com/pathsim/pathsim) - A block-based system modeling and simulation framework with a browser-based visual editor. - [PyDy](http://www.pydy.org/) - Short for Python Dynamics, used to assist with workflow in the modeling of dynamic motion. - [PyMC](https://github.com/pymc-devs/pymc3) - Markov Chain Monte Carlo sampling toolkit. -- [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. - [RDKit](http://www.rdkit.org/) - Cheminformatics and Machine Learning Software. - [SciPy](https://www.scipy.org/) - A Python-based ecosystem of open-source software for mathematics, science, and engineering. - [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane. From 2332d846c51e6a1c4a89fa48536d82cadd96b94c Mon Sep 17 00:00:00 2001 From: Daksh Date: Mon, 16 Feb 2026 19:50:20 +0000 Subject: [PATCH 0166/1130] following one project per PR rule --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index e9e14e8a..160db9c6 100644 --- a/README.md +++ b/README.md @@ -859,9 +859,6 @@ _Libraries for starting and communicating with OS processes._ _Libraries for quantum computing._ - [Qiskit](https://github.com/Qiskit/qiskit) — An IBM-backed quantum SDK for building, simulating, and running circuits on real quantum hardware. -- [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. -- [ProjectQ](https://github.com/ProjectQ-Framework/ProjectQ) — An open-source quantum compiler and simulation framework for low-level circuit experimentation. - [QuTiP](http://qutip.org/) - Quantum Toolbox in Python. ## Recommender Systems From 80a7409afbbbf1043e2adc9f5004b50ea0838931 Mon Sep 17 00:00:00 2001 From: Yury Fedoseev Date: Mon, 16 Feb 2026 13:40:57 -0800 Subject: [PATCH 0167/1130] Add pdf_oxide to PDF section Fast PDF library for text extraction, image extraction, and markdown conversion. Rust-powered via PyO3 with Python 3.8-3.14 support. MIT/Apache-2.0 licensed. https://github.com/yfedoseev/pdf_oxide https://pypi.org/project/pdf-oxide/ --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f89e853f..99248a99 100644 --- a/README.md +++ b/README.md @@ -984,6 +984,7 @@ _Libraries for parsing and manipulating specific text formats._ - [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. - [PyPDF2](https://github.com/mstamy2/PyPDF2) - A library capable of splitting, merging and transforming PDF pages. From ae59607ad030ae74911373f3ccf10c532f0c90da Mon Sep 17 00:00:00 2001 From: Daksh Date: Tue, 17 Feb 2026 19:27:23 +0000 Subject: [PATCH 0168/1130] added Pennylane library to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 160db9c6..27c5e782 100644 --- a/README.md +++ b/README.md @@ -858,6 +858,7 @@ _Libraries for starting and communicating with OS processes._ _Libraries for quantum computing._ +- [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](http://qutip.org/) - Quantum Toolbox in Python. From 062545c83bf29437537c014c29dcbb89bb689f9b Mon Sep 17 00:00:00 2001 From: Daksh Date: Wed, 18 Feb 2026 12:56:25 +0000 Subject: [PATCH 0169/1130] add cirq library to quantum computing section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27c5e782..b90e3d0c 100644 --- a/README.md +++ b/README.md @@ -858,6 +858,7 @@ _Libraries for starting and communicating with OS processes._ _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](http://qutip.org/) - Quantum Toolbox in Python. From c0703f2d7d6ec4af203ec42cd0781fe358e5b2e5 Mon Sep 17 00:00:00 2001 From: CipherGaurav Date: Fri, 20 Feb 2026 20:12:25 +0530 Subject: [PATCH 0170/1130] Fix typo in bidict description in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b90e3d0c..ead4e68b 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ _Compile software from source code._ _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.. +- [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. - [dataclasses](https://docs.python.org/3/library/dataclasses.html) - (Python standard library) Data classes. - [dotteddict](https://github.com/carlosescri/DottedDict) - A library that provides a method of accessing lists and dicts with a dotted path notation. From 067160a7879973062d4f78bbaa79eabf439a2b22 Mon Sep 17 00:00:00 2001 From: CarlosAlbertoFurtado Date: Mon, 23 Feb 2026 17:44:55 -0300 Subject: [PATCH 0171/1130] Fix insecure HTTP links to HTTPS --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b90e3d0c..2172f2a8 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ _Libraries for data analysis._ - [desbordante](https://github.com/desbordante/desbordante-core/) - An open source data profiler for complex pattern discovery. - [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. - [optimus](https://github.com/hi-primus/optimus) - Agile Data Science Workflows made easy with PySpark. -- [pandas](http://pandas.pydata.org/) - A library providing high-performance, easy-to-use data structures and data analysis tools. +- [pandas](https://pandas.pydata.org/) - 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. @@ -591,7 +591,7 @@ _Libraries for working with graphical user interface applications._ - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. - [Tkinter](https://wiki.python.org/moin/TkInter) - Tkinter is Python's de-facto standard GUI package. - [Toga](https://github.com/pybee/toga) - A Python native, OS native GUI toolkit. -- [urwid](http://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. +- [urwid](https://urwid.org/) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. - [wxPython](https://wxpython.org/) - A blending of the wxWidgets C++ class library with the Python. ## GraphQL @@ -609,7 +609,7 @@ _Awesome game development libraries._ - [Cocos2d](https://www.cocos.com/en/cocos2d-x) - A framework for building 2D games, demos, and other graphical/interactive applications. - [Harfang3D](http://www.harfang3d.com) - Python framework for 3D, VR and game development. - [Panda3D](https://www.panda3d.org/) - 3D game engine developed by Disney. -- [Pygame](http://www.pygame.org/news.html) - Pygame is a set of Python modules designed for writing games. +- [Pygame](https://www.pygame.org/news.html) - Pygame is a set of Python modules designed for writing games. - [PyOgre](http://www.ogre3d.org/tikiwiki/PyOgre) - Python bindings for the Ogre 3D render engine, can be used for games, simulations, anything 3D. - [PyOpenGL](http://pyopengl.sourceforge.net/) - Python ctypes bindings for OpenGL and it's related APIs. - [PySDL2](https://pysdl2.readthedocs.io) - A ctypes based wrapper for the SDL2 library. @@ -633,11 +633,11 @@ _Libraries for working with HTML and XML._ - [cssutils](https://pypi.org/project/cssutils/) - A CSS library for Python. - [html5lib](https://github.com/html5lib/html5lib-python) - A standards-compliant library for parsing and serializing HTML documents and fragments. - [JustHTML](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. -- [lxml](http://lxml.de/) - A very fast, easy-to-use and versatile library for handling HTML and XML. +- [lxml](https://lxml.de/) - 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. - [untangle](https://github.com/stchris/untangle) - Converts XML documents to Python objects for easy access. -- [WeasyPrint](http://weasyprint.org) - A visual rendering engine for HTML and CSS that can export to PDF. +- [WeasyPrint](https://weasyprint.org) - A visual rendering engine for HTML and CSS that can export to PDF. - [xmldataset](https://xmldataset.readthedocs.io/en/latest/) - Simple XML Parsing. - [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON. @@ -664,12 +664,12 @@ _Libraries for programming with hardware._ _Libraries for manipulating images._ - [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](http://www.pythonware.com/products/pil/) fork. -- [pymatting](http://github.com/pymatting/pymatting) - A library for alpha matting. +- [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. - [quads](https://github.com/fogleman/Quads) - Computer art based on quadtrees. -- [scikit-image](http://scikit-image.org/) - A Python library for (scientific) image processing. +- [scikit-image](https://scikit-image.org/) - 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. @@ -701,7 +701,7 @@ _Interactive Python interpreters (REPL)._ _Libraries for working with i18n._ -- [Babel](http://babel.pocoo.org/en/latest/) - An internationalization library for Python. +- [Babel](https://babel.pocoo.org/en/latest/) - An internationalization library for Python. ## Job Scheduler @@ -774,7 +774,7 @@ _Libraries for working with human languages._ - General - [gensim](https://github.com/RaRe-Technologies/gensim) - Topic Modeling for Humans. - [langid.py](https://github.com/saffsd/langid.py) - Stand-alone language identification system. - - [nltk](http://www.nltk.org/) - A leading platform for building Python programs to work with human language data. + - [nltk](https://www.nltk.org/) - A leading platform for building Python programs to work with human language data. - [pattern](https://github.com/clips/pattern) - A web mining module. - [polyglot](https://github.com/aboSamoor/polyglot) - Natural language pipeline supporting hundreds of languages. - [spacy](https://spacy.io/) - A library for industrial-strength natural language processing in Python and Cython. @@ -919,7 +919,7 @@ _RPC-compatible servers._ _Libraries for scientific computing. Also see [Python-for-Scientists](https://github.com/TomNicholas/Python-for-Scientists)._ -- [astropy](http://www.astropy.org/) - A community Python library for Astronomy. +- [astropy](https://www.astropy.org/) - A community Python library for Astronomy. - [bcbio-nextgen](https://github.com/chapmanb/bcbio-nextgen) - Providing best-practice pipelines for fully automated high throughput sequencing analysis. - [bcbb](https://github.com/chapmanb/bcbb) - Collection of useful code related to biological analysis. - [Biopython](http://biopython.org/wiki/Main_Page) - Biopython is a set of freely available tools for biological computation. From 2e07687dc824375498a50584dd42bf1cdde86fa4 Mon Sep 17 00:00:00 2001 From: Vitali Avagyan Date: Sun, 1 Mar 2026 22:00:26 +0000 Subject: [PATCH 0172/1130] feat: add code-graph-rag to Code Analysis section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d833521..ad1c735c 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,7 @@ _Libraries for caching data._ _Tools of static analysis, linters and code quality checkers. Also see [awesome-static-analysis](https://github.com/mre/awesome-static-analysis)._ - Code Analysis + - [code-graph-rag](https://github.com/vitali87/code-graph-rag) - Builds knowledge graphs from multi-language codebases using Tree-sitter and Memgraph, enabling natural language querying of code structure. - [code2flow](https://github.com/scottrogowski/code2flow) - Turn your Python and JavaScript code into DOT flowcharts. - [prospector](https://github.com/PyCQA/prospector) - A tool to analyze Python code. - [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. From 5888b93f7e576be7084c10605c31a937f7c57e82 Mon Sep 17 00:00:00 2001 From: rohan436 Date: Tue, 3 Mar 2026 16:56:40 +0800 Subject: [PATCH 0173/1130] docs: fix grammar in pathlib description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27400d4d..2791b4db 100644 --- a/README.md +++ b/README.md @@ -555,7 +555,7 @@ _Libraries for file manipulation._ - [markitdown](https://github.com/microsoft/markitdown) - Python tool for converting files and office documents to Markdown. - [mimetypes](https://docs.python.org/3/library/mimetypes.html) - (Python standard library) Map filenames to MIME types. - [path.py](https://github.com/jaraco/path.py) - A module wrapper for [os.path](https://docs.python.org/3/library/os.path.html). -- [pathlib](https://docs.python.org/3/library/pathlib.html) - (Python standard library) An cross-platform, object-oriented path library. +- [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. From 68544e289246cf19a41835a880657d70b84e25d1 Mon Sep 17 00:00:00 2001 From: CarlosAlbertoFurtado Date: Tue, 3 Mar 2026 07:43:47 -0300 Subject: [PATCH 0174/1130] Add Instructor to Machine Learning section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27400d4d..1b047c2b 100644 --- a/README.md +++ b/README.md @@ -737,6 +737,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. - [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. +- [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. - [LlamaIndex](https://github.com/run-llama/llama_index) - A data framework for your LLM application. - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. From 852202a1a4a1c8e497df7f77956ba24272f74ef6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 5 Mar 2026 01:16:30 +0800 Subject: [PATCH 0175/1130] Remove Claude PR review GitHub Actions workflow Co-Authored-By: Claude --- .github/workflows/claude-pr-review.yml | 59 -------------------------- 1 file changed, 59 deletions(-) delete mode 100644 .github/workflows/claude-pr-review.yml diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml deleted file mode 100644 index a8a08ce2..00000000 --- a/.github/workflows/claude-pr-review.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Claude PR Review - -on: - workflow_dispatch: - # pull_request: - # types: [opened, synchronize, ready_for_review, reopened, labeled] - # paths: - # - "README.md" - -jobs: - claude-pr-review: - if: | - github.event.label.name == 'claude review' || - github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' || - github.event.pull_request.author_association == 'FIRST_TIMER' || - github.event.pull_request.author_association == 'NONE' - - runs-on: ubuntu-latest - - permissions: - contents: read # Cannot merge PR - pull-requests: write - issues: read - actions: read # Required for Claude to read CI results on PRs - id-token: write # Required for Claude GitHub app to function - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@v1 - with: - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - plugin_marketplaces: "https://github.com/anthropics/claude-code.git" - plugins: "code-review@claude-code-plugins" - prompt: | - Review this PR against the acceptance criteria in CONTRIBUTING.md. - Check for automatic rejection reasons (abandoned, duplicates, spam). - - IMPORTANT: Only read README.md and CONTRIBUTING.md. Do not read or access any other files. - - /code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }} - - After your review, output your final decision on the LAST line in this exact format: - DECISION: REJECT or DECISION: PASS - - - name: Close PR if rejected - if: "contains(steps.claude-review.outputs.response, 'DECISION: REJECT')" - env: - GH_TOKEN: ${{ github.token }} - run: | - gh pr close ${{ github.event.pull_request.number }} \ - --repo ${{ github.repository }} \ - --comment "This PR has been automatically closed based on the review. Please address the feedback and re-open the PR." From d71e38771c73c6f5171c1bc3ef4eb37274ea922a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Thu, 5 Mar 2026 01:16:33 +0800 Subject: [PATCH 0176/1130] Remove Warp sponsorship banner from README Co-Authored-By: Claude --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 6ac873d1..00b4d2ef 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,3 @@ - -
- # Awesome Python An opinionated list of awesome Python frameworks, libraries, software and resources. From 61d69eeff890abd770cbfb533877ef62531050ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AE=B6=E5=90=8D?= Date: Sun, 8 Mar 2026 21:12:05 +0800 Subject: [PATCH 0177/1130] Add Agno to Machine Learning section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 00b4d2ef..f93941d0 100644 --- a/README.md +++ b/README.md @@ -724,6 +724,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [diffusers](https://github.com/huggingface/diffusers) - A library that provides pretrained diffusion models for generating and editing images, audio, and video. - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. +- [Agno](https://github.com/agno-agi/agno) - Open-source Python library for building AI agents and agentic systems. - [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. - [Instructor](https://github.com/567-labs/instructor) - A library for extracting structured data from LLMs, powered by Pydantic. From 9101ad17bbd2a390b75b27a28ec7973eddb1eaea Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Tue, 10 Mar 2026 23:13:39 +0000 Subject: [PATCH 0178/1130] Fix missing closing backtick in nose2 description The description 'based on unittest2.' was missing the closing backtick, causing incorrect Markdown rendering. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 00b4d2ef..0d876101 100644 --- a/README.md +++ b/README.md @@ -1034,7 +1034,7 @@ _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. - - [nose2](https://github.com/nose-devs/nose2) - The successor to `nose`, based on `unittest2. + - [nose2](https://github.com/nose-devs/nose2) - The successor to `nose`, based on `unittest2`. - [pytest](https://docs.pytest.org/en/latest/) - A mature full-featured Python testing tool. - [Robot Framework](https://github.com/robotframework/robotframework) - A generic test automation framework. - [ScanAPI](https://pypi.org/project/scanapi/) - Automated Testing and Documentation for your REST API. From c07522717f5c7362f4534a31623a1fec5236e115 Mon Sep 17 00:00:00 2001 From: Muhammad Zain ul abidin Date: Fri, 13 Mar 2026 11:00:23 +0500 Subject: [PATCH 0179/1130] Add crawl4ai to web scraping libraries list Crawl4AI correct url --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0d876101..1316cb99 100644 --- a/README.md +++ b/README.md @@ -1144,6 +1144,7 @@ _Libraries to automate web scraping._ - [browser-use](https://github.com/browser-use/browser-use) - Make websites accessible for AI agents with easy browser automation. - [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. +- [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. - [grab](https://github.com/lorien/grab) - Site scraping framework. - [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. From bdeb5b90e841e9c6fce2e68c69594a92a84fe54a Mon Sep 17 00:00:00 2001 From: Muhammad Zain ul abidin Date: Fri, 13 Mar 2026 11:03:59 +0500 Subject: [PATCH 0180/1130] Fix duplicate entry for feedparser in README Fixed Alphabetic Order --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1316cb99..77eda819 100644 --- a/README.md +++ b/README.md @@ -1143,8 +1143,8 @@ _Libraries for extracting web contents._ _Libraries to automate web scraping._ - [browser-use](https://github.com/browser-use/browser-use) - Make websites accessible for AI agents with easy browser automation. -- [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. - [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. +- [feedparser](https://github.com/kurtmckee/feedparser) - Universal feed parser. - [grab](https://github.com/lorien/grab) - Site scraping framework. - [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. From 9eb21b268e8c4558dd134964f124c1d0529e1e14 Mon Sep 17 00:00:00 2001 From: AshithaT Date: Sun, 15 Mar 2026 22:58:19 +0530 Subject: [PATCH 0181/1130] Add pgmpy library to Machine Learning section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 77eda819..36086db4 100644 --- a/README.md +++ b/README.md @@ -731,6 +731,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [LlamaIndex](https://github.com/run-llama/llama_index) - A data framework for your LLM application. - [Metrics](https://github.com/benhamner/Metrics) - Machine learning evaluation metrics. - [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. - [PraisonAI](https://github.com/MervinPraison/PraisonAI) - Production-ready Multi-AI Agents framework with self-reflection, 100+ LLM support, MCP integration, and agentic workflows. - [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - [RAGFlow](https://github.com/infiniflow/ragflow) - An open-source RAG engine for document understanding and question answering with LLMs. From 742c99aa3c5517079366f67f38c1f79ecbe1c659 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Tue, 17 Mar 2026 07:37:53 +0400 Subject: [PATCH 0182/1130] fix entry order --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f93941d0..d5f9fc54 100644 --- a/README.md +++ b/README.md @@ -722,9 +722,9 @@ _Libraries for generating and working with logs._ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://github.com/josephmisiti/awesome-machine-learning#python)._ +- [Agno](https://github.com/agno-agi/agno) - Open-source Python library for building AI agents and agentic systems. - [diffusers](https://github.com/huggingface/diffusers) - A library that provides pretrained diffusion models for generating and editing images, audio, and video. - [gym](https://github.com/openai/gym) - A toolkit for developing and comparing reinforcement learning algorithms. -- [Agno](https://github.com/agno-agi/agno) - Open-source Python library for building AI agents and agentic systems. - [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. - [Instructor](https://github.com/567-labs/instructor) - A library for extracting structured data from LLMs, powered by Pydantic. From a8eac91658ad902de4ecc51e767a2c9e62c04c2c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Tue, 17 Mar 2026 19:20:36 +0800 Subject: [PATCH 0183/1130] add sponsorship --- README.md | 168 ++++++++++++++++++++++++------------------------- SPONSORSHIP.md | 36 +++++++++++ 2 files changed, 120 insertions(+), 84 deletions(-) create mode 100644 SPONSORSHIP.md diff --git a/README.md b/README.md index 4170d110..340e28e7 100644 --- a/README.md +++ b/README.md @@ -2,94 +2,94 @@ An opinionated list of awesome Python frameworks, libraries, software and resources. -Inspired by [awesome-php](https://github.com/ziadoz/awesome-php). +> The **#10 most-starred repo on GitHub**. Put your product where Python developers discover tools. [Become a sponsor](SPONSORSHIP.md). -- [Awesome Python](#awesome-python) - - [Admin Panels](#admin-panels) - - [Algorithms and Design Patterns](#algorithms-and-design-patterns) - - [ASGI Servers](#asgi-servers) - - [Asynchronous Programming](#asynchronous-programming) - - [Audio](#audio) - - [Authentication](#authentication) - - [Build Tools](#build-tools) - - [Built-in Classes Enhancement](#built-in-classes-enhancement) - - [Caching](#caching) - - [CMS](#cms) - - [Code Analysis](#code-analysis) - - [Command-line Interface Development](#command-line-interface-development) - - [Command-line Tools](#command-line-tools) - - [Computer Vision](#computer-vision) - - [Configuration Files](#configuration-files) - - [Cryptography](#cryptography) - - [Data Analysis](#data-analysis) - - [Data Validation](#data-validation) - - [Data Visualization](#data-visualization) - - [Database Drivers](#database-drivers) - - [Database](#database) - - [Date and Time](#date-and-time) - - [Debugging Tools](#debugging-tools) - - [Deep Learning](#deep-learning) - - [DevOps Tools](#devops-tools) - - [Distributed Computing](#distributed-computing) - - [Distribution](#distribution) - - [Documentation](#documentation) - - [Downloader](#downloader) - - [Editor Plugins and IDEs](#editor-plugins-and-ides) - - [Email](#email) - - [Environment Management](#environment-management) - - [File Manipulation](#file-manipulation) - - [Functional Programming](#functional-programming) - - [Game Development](#game-development) - - [Geolocation](#geolocation) - - [GUI Development](#gui-development) - - [Hardware](#hardware) - - [HTML Manipulation](#html-manipulation) - - [HTTP Clients](#http-clients) - - [Image Processing](#image-processing) - - [Implementations](#implementations) - - [Interactive Interpreter](#interactive-interpreter) - - [Internationalization](#internationalization) - - [Job Scheduler](#job-scheduler) - - [Logging](#logging) - - [Machine Learning](#machine-learning) - - [Miscellaneous](#miscellaneous) - - [Natural Language Processing](#natural-language-processing) - - [Network Virtualization](#network-virtualization) - - [ORM](#orm) - - [Package Management](#package-management) - - [Package Repositories](#package-repositories) - - [Penetration testing](#penetration-testing) - - [Permissions](#permissions) - - [Processes](#processes) - - [Quantum Computing](#quantum-computing) - - [Recommender Systems](#recommender-systems) - - [Refactoring](#refactoring) - - [RESTful API](#restful-api) - - [Robotics](#robotics) - - [RPC Servers](#rpc-servers) - - [Science](#science) - - [Search](#search) - - [Serialization](#serialization) - - [Serverless Frameworks](#serverless-frameworks) - - [Shell](#shell) - - [Specific Formats Processing](#specific-formats-processing) - - [Static Site Generator](#static-site-generator) - - [Task Queues](#task-queues) - - [Template Engine](#template-engine) - - [Testing](#testing) - - [Text Processing](#text-processing) - - [URL Manipulation](#url-manipulation) - - [Video](#video) - - [Web Asset Management](#web-asset-management) - - [Web Content Extracting](#web-content-extracting) - - [Web Crawling](#web-crawling) - - [Web Frameworks](#web-frameworks) - - [WebSocket](#websocket) - - [WSGI Servers](#wsgi-servers) +# Categories + +- [Admin Panels](#admin-panels) +- [Algorithms and Design Patterns](#algorithms-and-design-patterns) +- [ASGI Servers](#asgi-servers) +- [Asynchronous Programming](#asynchronous-programming) +- [Audio](#audio) +- [Authentication](#authentication) +- [Build Tools](#build-tools) +- [Built-in Classes Enhancement](#built-in-classes-enhancement) +- [Caching](#caching) +- [CMS](#cms) +- [Code Analysis](#code-analysis) +- [Command-line Interface Development](#command-line-interface-development) +- [Command-line Tools](#command-line-tools) +- [Computer Vision](#computer-vision) +- [Configuration Files](#configuration-files) +- [Cryptography](#cryptography) +- [Data Analysis](#data-analysis) +- [Data Validation](#data-validation) +- [Data Visualization](#data-visualization) +- [Database Drivers](#database-drivers) +- [Database](#database) +- [Date and Time](#date-and-time) +- [Debugging Tools](#debugging-tools) +- [Deep Learning](#deep-learning) +- [DevOps Tools](#devops-tools) +- [Distributed Computing](#distributed-computing) +- [Distribution](#distribution) +- [Documentation](#documentation) +- [Downloader](#downloader) +- [Editor Plugins and IDEs](#editor-plugins-and-ides) +- [Email](#email) +- [Environment Management](#environment-management) +- [File Manipulation](#file-manipulation) +- [Functional Programming](#functional-programming) +- [Game Development](#game-development) +- [Geolocation](#geolocation) +- [GUI Development](#gui-development) +- [Hardware](#hardware) +- [HTML Manipulation](#html-manipulation) +- [HTTP Clients](#http-clients) +- [Image Processing](#image-processing) +- [Implementations](#implementations) +- [Interactive Interpreter](#interactive-interpreter) +- [Internationalization](#internationalization) +- [Job Scheduler](#job-scheduler) +- [Logging](#logging) +- [Machine Learning](#machine-learning) +- [Miscellaneous](#miscellaneous) +- [Natural Language Processing](#natural-language-processing) +- [Network Virtualization](#network-virtualization) +- [ORM](#orm) +- [Package Management](#package-management) +- [Package Repositories](#package-repositories) +- [Penetration testing](#penetration-testing) +- [Permissions](#permissions) +- [Processes](#processes) +- [Quantum Computing](#quantum-computing) +- [Recommender Systems](#recommender-systems) +- [Refactoring](#refactoring) +- [RESTful API](#restful-api) +- [Robotics](#robotics) +- [RPC Servers](#rpc-servers) +- [Science](#science) +- [Search](#search) +- [Serialization](#serialization) +- [Serverless Frameworks](#serverless-frameworks) +- [Shell](#shell) +- [Specific Formats Processing](#specific-formats-processing) +- [Static Site Generator](#static-site-generator) +- [Task Queues](#task-queues) +- [Template Engine](#template-engine) +- [Testing](#testing) +- [Text Processing](#text-processing) +- [URL Manipulation](#url-manipulation) +- [Video](#video) +- [Web Asset Management](#web-asset-management) +- [Web Content Extracting](#web-content-extracting) +- [Web Crawling](#web-crawling) +- [Web Frameworks](#web-frameworks) +- [WebSocket](#websocket) +- [WSGI Servers](#wsgi-servers) - [Resources](#resources) - [Newsletters](#newsletters) - [Podcasts](#podcasts) -- [Contributing](#contributing) --- diff --git a/SPONSORSHIP.md b/SPONSORSHIP.md new file mode 100644 index 00000000..3f9e8a68 --- /dev/null +++ b/SPONSORSHIP.md @@ -0,0 +1,36 @@ +# 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 | ![Stars](https://img.shields.io/github/stars/vinta/awesome-python?style=for-the-badge) | +| Forks | ![Forks](https://img.shields.io/github/forks/vinta/awesome-python?style=for-the-badge) | +| Watchers | ![Watchers](https://img.shields.io/github/watchers/vinta/awesome-python?style=for-the-badge) | +| Contributors | ![Contributors](https://img.shields.io/github/contributors/vinta/awesome-python?style=for-the-badge) | + +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. From bd73b1faf099628acdd6b7b436df9ea205220935 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Wed, 18 Mar 2026 07:57:37 +0400 Subject: [PATCH 0184/1130] remove zipline No longer maintained and out-dated. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 340e28e7..9f34e225 100644 --- a/README.md +++ b/README.md @@ -934,7 +934,6 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [SimPy](https://gitlab.com/team-simpy/simpy) - A process-based discrete-event simulation framework. - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - [SymPy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. -- [Zipline](https://github.com/quantopian/zipline) - A Pythonic algorithmic trading library. ## Search From 4af84dac8e2fc4a53071588909444ae8c9089f0f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:48:45 +0800 Subject: [PATCH 0185/1130] remove mkdocs site infrastructure Replace the MkDocs-based build (mkdocs.yml, requirements.txt, docs/CNAME, docs/css/extra.css) with a custom website builder as part of the site relaunch. Co-Authored-By: Claude --- docs/CNAME | 1 - docs/css/extra.css | 9 --------- mkdocs.yml | 26 -------------------------- requirements.txt | 2 -- 4 files changed, 38 deletions(-) delete mode 100644 docs/CNAME delete mode 100644 docs/css/extra.css delete mode 100644 mkdocs.yml delete mode 100644 requirements.txt diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 0f6ced66..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -awesome-python.com \ No newline at end of file diff --git a/docs/css/extra.css b/docs/css/extra.css deleted file mode 100644 index 1230d796..00000000 --- a/docs/css/extra.css +++ /dev/null @@ -1,9 +0,0 @@ -@media (min-width: 960px) { - html { - scroll-behavior: smooth; - } - - .md-content__inner > ul:nth-child(5) { - display: none; - } -} diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 67a0373a..00000000 --- a/mkdocs.yml +++ /dev/null @@ -1,26 +0,0 @@ -site_name: Awesome Python -site_url: https://awesome-python.com -site_description: A curated list of awesome Python frameworks, libraries and software -site_author: Vinta Chen -repo_name: vinta/awesome-python -repo_url: https://github.com/vinta/awesome-python -theme: - name: material - palette: - primary: red - accent: pink -extra: - social: - - type: github - link: https://github.com/vinta - - type: twitter - link: https://twitter.com/vinta - - type: linkedin - link: https://www.linkedin.com/in/vinta -google_analytics: - - UA-510626-7 - - auto -extra_css: - - css/extra.css -nav: - - "Life is short, you need Python.": "index.md" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 89d64c30..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -mkdocs==1.0.4 -mkdocs-material==4.0.2 From 177183d9bde4b4f0a51f0d97e9f95588204a619d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:48:49 +0800 Subject: [PATCH 0186/1130] add custom website build system Replaces MkDocs with a bespoke Python site generator using Jinja2 templates and Markdown. Adds uv for dependency management, GitHub Actions workflow for deployment, and Makefile targets for local development (fetch_stars, build, preview, deploy). Co-Authored-By: Claude --- .github/workflows/deploy-website.yml | 48 + .gitignore | 14 +- Makefile | 18 +- pyproject.toml | 23 + uv.lock | 258 +++ website/build.py | 502 +++++ website/data/github_stars.json | 2627 ++++++++++++++++++++++ website/fetch_github_stars.py | 192 ++ website/static/main.js | 154 ++ website/static/style.css | 459 ++++ website/templates/base.html | 67 + website/templates/index.html | 146 ++ website/tests/test_build.py | 642 ++++++ website/tests/test_fetch_github_stars.py | 161 ++ 14 files changed, 5298 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/deploy-website.yml create mode 100644 pyproject.toml create mode 100644 uv.lock create mode 100644 website/build.py create mode 100644 website/data/github_stars.json create mode 100644 website/fetch_github_stars.py create mode 100644 website/static/main.js create mode 100644 website/static/style.css create mode 100644 website/templates/base.html create mode 100644 website/templates/index.html create mode 100644 website/tests/test_build.py create mode 100644 website/tests/test_fetch_github_stars.py diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml new file mode 100644 index 00000000..28e254ea --- /dev/null +++ b/.github/workflows/deploy-website.yml @@ -0,0 +1,48 @@ +name: Deploy Website + +on: + push: + branches: + - master + +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@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --no-dev + + - name: Build site + run: uv run python website/build.py + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: website/output/ + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 096c327d..083917b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,15 @@ +# macOS .DS_Store +# python +.venv/ *.py[co] -docs/index.md -site/ +# website +website/output/ -# PyCharm IDE -.idea +# claude code +.claude/skills/ +.superpowers/ +.gstack/ +skills-lock.json diff --git a/Makefile b/Makefile index eda7a8ff..5d6d7581 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,14 @@ site_install: - pip install -r requirements.txt + uv sync --no-dev -site_link: - ln -sf $(CURDIR)/README.md $(CURDIR)/docs/index.md +fetch_stars: + uv run python website/fetch_github_stars.py -site_preview: site_link - mkdocs serve +site_build: + uv run python website/build.py -site_build: site_link - mkdocs build +site_preview: site_build + python -m http.server -d website/output/ 8000 -site_deploy: site_link - mkdocs gh-deploy --clean +site_deploy: site_build + @echo "Deploy via GitHub Actions (push to master)" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..d564cde9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "awesome-python" +version = "0.1.0" +description = "An opinionated list of awesome Python frameworks, libraries, software and resources." +requires-python = ">=3.13" +dependencies = [ + "httpx==0.28.1", + "jinja2==3.1.6", + "markdown==3.10.2", +] + +[dependency-groups] +dev = [ + "pytest==9.0.2", + "ruff==0.15.6", +] + +[tool.pytest.ini_options] +testpaths = ["website/tests"] + +[tool.ruff] +target-version = "py313" +line-length = 100 diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..1f7b17c7 --- /dev/null +++ b/uv.lock @@ -0,0 +1,258 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "awesome-python" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "httpx" }, + { name = "jinja2" }, + { name = "markdown" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = "==0.28.1" }, + { name = "jinja2", specifier = "==3.1.6" }, + { name = "markdown", specifier = "==3.10.2" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = "==9.0.2" }, + { name = "ruff", specifier = "==0.15.6" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +] diff --git a/website/build.py b/website/build.py new file mode 100644 index 00000000..b8340eb5 --- /dev/null +++ b/website/build.py @@ -0,0 +1,502 @@ +#!/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 + +import markdown +from jinja2 import Environment, FileSystemLoader + +# Thematic grouping of categories. Each category name must match exactly +# as it appears in README.md (the ## heading text). +SECTION_GROUPS: list[tuple[str, list[str]]] = [ + ("Web & API", [ + "Web Frameworks", "RESTful API", "GraphQL", "WebSocket", + "ASGI Servers", "WSGI Servers", "HTTP Clients", "Template Engine", + "Web Asset Management", "Web Content Extracting", "Web Crawling", + ]), + ("Data & ML", [ + "Data Analysis", "Data Validation", "Data Visualization", + "Machine Learning", "Deep Learning", "Computer Vision", + "Natural Language Processing", "Recommender Systems", "Science", + "Quantum Computing", + ]), + ("DevOps & Infrastructure", [ + "DevOps Tools", "Distributed Computing", "Task Queues", + "Job Scheduler", "Serverless Frameworks", "Logging", "Processes", + "Shell", "Network Virtualization", "RPC Servers", + ]), + ("Database & Storage", [ + "Database", "Database Drivers", "ORM", "Caching", "Search", + "Serialization", + ]), + ("Development Tools", [ + "Testing", "Debugging Tools", "Code Analysis", "Build Tools", + "Refactoring", "Documentation", "Editor Plugins and IDEs", + "Interactive Interpreter", + ]), + ("CLI & GUI", [ + "Command-line Interface Development", "Command-line Tools", + "GUI Development", + ]), + ("Content & Media", [ + "Audio", "Video", "Image Processing", "HTML Manipulation", + "Text Processing", "Specific Formats Processing", + "File Manipulation", "Downloader", + ]), + ("System & Runtime", [ + "Asynchronous Programming", "Environment Management", + "Package Management", "Package Repositories", "Distribution", + "Implementations", "Built-in Classes Enhancement", + "Functional Programming", "Configuration Files", + ]), + ("Security & Auth", [ + "Authentication", "Cryptography", "Penetration Testing", + "Permissions", + ]), + ("Specialized", [ + "CMS", "Admin Panels", "Email", "Game Development", "Geolocation", + "Hardware", "Internationalization", "Date and Time", + "URL Manipulation", "Robotics", "Microsoft Windows", "Miscellaneous", + "Algorithms and Design Patterns", "Static Site Generator", + ]), + ("Resources", []), # Filled dynamically from parsed resources +] + + +def slugify(name: str) -> str: + """Convert a category name to a URL-friendly slug.""" + slug = name.lower() + slug = re.sub(r"[^a-z0-9\s-]", "", slug) + slug = re.sub(r"[\s]+", "-", slug.strip()) + slug = re.sub(r"-+", "-", slug) + return slug + + +def count_entries(content: str) -> int: + """Count library entries (lines starting with * [ or - [) in a content block.""" + return sum(1 for line in content.split("\n") if re.match(r"\s*[-*]\s+\[", line)) + + +def extract_preview(content: str, *, max_names: int = 4) -> str: + """Extract first N main library names from markdown content for preview text. + + Only includes top-level or single-indent entries (indent <= 3 spaces), + skipping subcategory labels (items without links) and deep sub-entries. + """ + names = [] + for m in re.finditer(r"^(\s*)[-*]\s+\[([^\]]+)\]", content, re.MULTILINE): + indent_len = len(m.group(1)) + if indent_len > 3: + continue + names.append(m.group(2)) + if len(names) >= max_names: + break + return ", ".join(names) + + +def render_content_html(content: str) -> str: + """Render category markdown content to HTML with subcategory detection. + + Lines that are list items without links (e.g., "- Synchronous") are + treated as subcategory headers and rendered as bold dividers. + + Indent levels in the README: + - 0 spaces: top-level entry or subcategory label + - 2 spaces: entry under a subcategory (still a main entry) + - 4+ spaces: sub-entry (e.g., awesome-django under django) + """ + lines = content.split("\n") + out: list[str] = [] + + for line in lines: + stripped = line.strip() + indent_len = len(line) - len(line.lstrip()) + + # Detect subcategory labels: list items without links + m = re.match(r"^[-*]\s+(.+)$", stripped) + if m and "[" not in stripped: + label = m.group(1) + out.append(f'
{label}
') + continue + + # Entry with link and description: * [name](url) - Description. + m = re.match( + r"^\s*[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*[-\u2013\u2014]\s*(.+)$", + line, + ) + if m: + name, url, desc = m.groups() + if indent_len > 3: + out.append( + f'
' + f'{name}' + f"
" + ) + else: + out.append( + f'
' + f'{name}' + f'—{desc}' + f"
" + ) + continue + + # Link-only entry (no description): * [name](url) + m = re.match(r"^\s*[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*$", line) + if m: + name, url = m.groups() + if indent_len > 3: + out.append( + f'
' + f'{name}' + f"
" + ) + else: + out.append( + f'
' + f'{name}' + f"
" + ) + continue + + return "\n".join(out) + + +def parse_readme(text: str) -> tuple[list[dict], list[dict]]: + """Parse README.md text into categories and resources. + + Returns: + (categories, resources) where each is a list of dicts with keys: + name, slug, description, content + """ + lines = text.split("\n") + + separator_idx = None + for i, line in enumerate(lines): + if line.strip() == "---" and i > 0: + separator_idx = i + break + + if separator_idx is None: + return [], [] + + resources_idx = None + contributing_idx = None + for i, line in enumerate(lines): + if line.strip() == "# Resources": + resources_idx = i + elif line.strip() == "# Contributing": + contributing_idx = i + + cat_end = resources_idx if resources_idx is not None else len(lines) + category_lines = lines[separator_idx + 1 : cat_end] + + resource_lines = [] + if resources_idx is not None: + res_end = contributing_idx if contributing_idx is not None else len(lines) + resource_lines = lines[resources_idx:res_end] + + categories = _extract_sections(category_lines, level=2) + resources = _extract_sections(resource_lines, level=2) + + return categories, resources + + +def _extract_sections(lines: list[str], *, level: int) -> list[dict]: + """Extract ## sections from a block of lines.""" + prefix = "#" * level + " " + sections = [] + current_name = None + current_lines: list[str] = [] + + for line in lines: + if line.startswith(prefix) and not line.startswith(prefix + "#"): + if current_name is not None: + sections.append(_build_section(current_name, current_lines)) + current_name = line[len(prefix) :].strip() + current_lines = [] + elif current_name is not None: + current_lines.append(line) + + if current_name is not None: + sections.append(_build_section(current_name, current_lines)) + + return sections + + +def _build_section(name: str, lines: list[str]) -> dict: + """Build a section dict from a name and its content lines.""" + while lines and not lines[0].strip(): + lines = lines[1:] + while lines and not lines[-1].strip(): + lines = lines[:-1] + + description = "" + content_lines = lines + if lines: + m = re.match(r"^_(.+)_$", lines[0].strip()) + if m: + description = m.group(1) + content_lines = lines[1:] + while content_lines and not content_lines[0].strip(): + content_lines = content_lines[1:] + + content = "\n".join(content_lines).strip() + + return { + "name": name, + "slug": slugify(name), + "description": description, + "content": content, + } + + +def render_markdown(text: str) -> str: + """Render markdown text to HTML.""" + md = markdown.Markdown(extensions=["extra"]) + return md.convert(text) + + +def strip_markdown_links(text: str) -> str: + """Replace [text](url) with just text for plain-text contexts.""" + return re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text) + + +def render_inline_markdown(text: str) -> str: + """Render inline markdown (links, bold, italic) to HTML.""" + from markupsafe import Markup + + html = markdown.markdown(text) + # Strip wrapping

...

since this is inline content + html = re.sub(r"^

(.*)

$", r"\1", html.strip()) + # Add target/rel to links for external navigation + html = html.replace(" list[dict]: + """Organize categories and resources into thematic section groups.""" + cat_by_name = {c["name"]: c for c in categories} + groups = [] + + for group_name, cat_names in SECTION_GROUPS: + if group_name == "Resources": + # Resources group uses parsed resources directly + group_cats = list(resources) + else: + group_cats = [cat_by_name[n] for n in cat_names if n in cat_by_name] + + if group_cats: + groups.append({ + "name": group_name, + "slug": slugify(group_name), + "categories": group_cats, + }) + + # Any categories not in a group go into "Other" + grouped_names = set() + for _, cat_names in SECTION_GROUPS: + grouped_names.update(cat_names) + ungrouped = [c for c in categories if c["name"] not in grouped_names] + if ungrouped: + groups.append({ + "name": "Other", + "slug": "other", + "categories": ungrouped, + }) + + return groups + + +class Entry(TypedDict): + name: str + url: str + description: str + category: str + group: str + stars: int | None + owner: str | None + pushed_at: str | None + + +class StarData(TypedDict): + stars: int + owner: str + pushed_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], + resources: list[dict], + groups: list[dict], +) -> list[dict]: + """Flatten categories into individual library entries for table display.""" + cat_to_group: dict[str, str] = {} + for group in groups: + for cat in group["categories"]: + cat_to_group[cat["name"]] = group["name"] + + entries: list[dict] = [] + for cat in categories: + group_name = cat_to_group.get(cat["name"], "Other") + last_entry_indent = -1 + for line in cat["content"].split("\n"): + indent_len = len(line) - len(line.lstrip()) + + # Link-only sub-item deeper than parent → "also see" + m_sub = re.match(r"\s*[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*$", line) + if m_sub and indent_len > last_entry_indent >= 0 and entries: + entries[-1]["also_see"].append({ + "name": m_sub.group(1), + "url": m_sub.group(2), + }) + continue + + if indent_len > 3: + continue + m = re.match( + r"\s*[-*]\s+\[([^\]]+)\]\(([^)]+)\)\s*(?:[-\u2013\u2014]\s*(.+))?$", + line, + ) + if m: + last_entry_indent = indent_len + entries.append({ + "name": m.group(1), + "url": m.group(2), + "description": render_inline_markdown(m.group(3)) if m.group(3) else "", + "category": cat["name"], + "group": group_name, + "stars": None, + "owner": None, + "pushed_at": None, + "also_see": [], + }) + 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") + + # Extract subtitle from the first non-empty, non-heading line + subtitle = "" + for line in readme_text.split("\n"): + stripped = line.strip() + if stripped and not stripped.startswith("#"): + subtitle = stripped + break + + categories, resources = parse_readme(readme_text) + + # Enrich with entry counts, rendered HTML, previews, and clean descriptions + for cat in categories + resources: + cat["entry_count"] = count_entries(cat["content"]) + cat["content_html"] = render_content_html(cat["content"]) + cat["preview"] = extract_preview(cat["content"]) + cat["description"] = strip_markdown_links(cat["description"]) + + total_entries = sum(c["entry_count"] for c in categories) + + # Organize into groups + groups = group_categories(categories, resources) + + # Flatten entries for table view + entries = extract_entries(categories, resources, groups) + + # Load and merge GitHub star data + 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: + entry["stars"] = stars_data[repo_key]["stars"] + entry["owner"] = stars_data[repo_key]["owner"] + entry["pushed_at"] = stars_data[repo_key].get("pushed_at", "") + + # Sort by stars descending + entries = sort_entries(entries) + + # Set up Jinja2 + env = Environment( + loader=FileSystemLoader(website / "templates"), + autoescape=True, + ) + + # Output directory + site_dir = website / "output" + if site_dir.exists(): + shutil.rmtree(site_dir) + site_dir.mkdir(parents=True) + + # Generate single index.html + 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", + ) + + # Copy static assets + static_src = website / "static" + static_dst = site_dir / "static" + if static_src.exists(): + shutil.copytree(static_src, static_dst) + + # Write CNAME + (site_dir / "CNAME").write_text("awesome-python.com\n", encoding="utf-8") + + print(f"Built single page with {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)) diff --git a/website/data/github_stars.json b/website/data/github_stars.json new file mode 100644 index 00000000..1476651f --- /dev/null +++ b/website/data/github_stars.json @@ -0,0 +1,2627 @@ +{ + "0rpc/zerorpc-python": { + "stars": 3237, + "owner": "0rpc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "567-labs/instructor": { + "stars": 12554, + "owner": "567-labs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Alir3z4/html2text": { + "stars": 2135, + "owner": "Alir3z4", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "AnswerDotAI/fasthtml": { + "stars": 6883, + "owner": "AnswerDotAI", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "AtsushiSakai/PythonRobotics": { + "stars": 28887, + "owner": "AtsushiSakai", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "BeanieODM/beanie": { + "stars": 2661, + "owner": "BeanieODM", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Bogdanp/dramatiq": { + "stars": 5172, + "owner": "Bogdanp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ChristosChristofidis/awesome-deep-learning": { + "stars": 27712, + "owner": "ChristosChristofidis", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "CleanCut/green": { + "stars": 806, + "owner": "CleanCut", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Cornices/cornice": { + "stars": 390, + "owner": "Cornices", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "DLR-RM/stable-baselines3": { + "stars": 12908, + "owner": "DLR-RM", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Delgan/loguru": { + "stars": 23690, + "owner": "Delgan", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "DiffSK/configobj": { + "stars": 337, + "owner": "DiffSK", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "DmytroLitvinov/awesome-flake8-extensions": { + "stars": 1276, + "owner": "DmytroLitvinov", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "EmilStenstrom/justhtml": { + "stars": 1116, + "owner": "EmilStenstrom", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "FactoryBoy/factory_boy": { + "stars": 3781, + "owner": "FactoryBoy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "HBNetwork/python-decouple": { + "stars": 3017, + "owner": "HBNetwork", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "HypothesisWorks/hypothesis": { + "stars": 8498, + "owner": "HypothesisWorks", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Instagram/MonkeyType": { + "stars": 4996, + "owner": "Instagram", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "IronLanguages/ironpython3": { + "stars": 2735, + "owner": "IronLanguages", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "JaidedAI/EasyOCR": { + "stars": 29096, + "owner": "JaidedAI", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Kozea/pygal": { + "stars": 2752, + "owner": "Kozea", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Lightning-AI/pytorch-lightning": { + "stars": 30934, + "owner": "Lightning-AI", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "LuminosoInsight/python-ftfy": { + "stars": 4015, + "owner": "rspeer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "MagicStack/uvloop": { + "stars": 11687, + "owner": "MagicStack", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ManimCommunity/manim": { + "stars": 37253, + "owner": "ManimCommunity", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Manisso/fsociety": { + "stars": 11925, + "owner": "Manisso", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Maratyszcza/PeachPy": { + "stars": 2048, + "owner": "Maratyszcza", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "MasoniteFramework/masonite": { + "stars": 2365, + "owner": "MasoniteFramework", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "MechanicalSoup/MechanicalSoup": { + "stars": 4850, + "owner": "MechanicalSoup", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "MervinPraison/PraisonAI": { + "stars": 5677, + "owner": "MervinPraison", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Microsoft/PTVS": { + "stars": 2567, + "owner": "microsoft", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "MongoEngine/mongoengine": { + "stars": 4349, + "owner": "MongoEngine", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "NicolasHug/Surprise": { + "stars": 6772, + "owner": "NicolasHug", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Nuitka/Nuitka": { + "stars": 14642, + "owner": "Nuitka", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "OpenBB-finance/OpenBB": { + "stars": 63238, + "owner": "OpenBB-finance", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Parisson/TimeSide": { + "stars": 394, + "owner": "Parisson", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Parsely/streamparse": { + "stars": 1504, + "owner": "pystorm", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PennyLaneAI/pennylane": { + "stars": 3111, + "owner": "PennyLaneAI", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PrefectHQ/prefect": { + "stars": 21889, + "owner": "PrefectHQ", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PyCQA/flake8": { + "stars": 3770, + "owner": "PyCQA", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PyCQA/prospector": { + "stars": 2074, + "owner": "prospector-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PyMySQL/PyMySQL": { + "stars": 7838, + "owner": "PyMySQL", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "PyMySQL/mysqlclient": { + "stars": 2525, + "owner": "PyMySQL", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Pylons/colander": { + "stars": 464, + "owner": "Pylons", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Pylons/waitress": { + "stars": 1572, + "owner": "Pylons", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Qiskit/qiskit": { + "stars": 7137, + "owner": "Qiskit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "RaRe-Technologies/gensim": { + "stars": 16375, + "owner": "piskvorky", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "RasaHQ/rasa": { + "stars": 21086, + "owner": "RasaHQ", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "RaylockLLC/DearPyGui": { + "stars": 15279, + "owner": "hoffstadt", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "SCons/scons": { + "stars": 2357, + "owner": "SCons", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "SciTools/cartopy": { + "stars": 1589, + "owner": "SciTools", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ScrapeGraphAI/toonify": { + "stars": 323, + "owner": "ScrapeGraphAI", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "SmileyChris/django-countries": { + "stars": 1521, + "owner": "SmileyChris", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Suor/django-cacheops": { + "stars": 2263, + "owner": "Suor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Suor/funcy": { + "stars": 3501, + "owner": "Suor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Supervisor/supervisor": { + "stars": 9007, + "owner": "Supervisor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Tencent/rapidjson": { + "stars": 15007, + "owner": "Tencent", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Textualize/rich": { + "stars": 55801, + "owner": "Textualize", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Textualize/textual": { + "stars": 34878, + "owner": "Textualize", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "TheAlgorithms/Python": { + "stars": 218785, + "owner": "TheAlgorithms", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "TkTech/pysimdjson": { + "stars": 761, + "owner": "TkTech", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "TomNicholas/Python-for-Scientists": { + "stars": 357, + "owner": "TomNicholas", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "Valloric/YouCompleteMe": { + "stars": 26276, + "owner": "ycm-core", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "WhyNotHugo/python-barcode": { + "stars": 649, + "owner": "WhyNotHugo", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ZoomerAnalytics/xlwings": { + "stars": 6, + "owner": "ZoomerAnalytics", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "aaugustin/websockets": { + "stars": 5643, + "owner": "python-websockets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "abhiTronix/vidgear": { + "stars": 3684, + "owner": "abhiTronix", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "aboSamoor/polyglot": { + "stars": 2368, + "owner": "aboSamoor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "agno-agi/agno": { + "stars": 38754, + "owner": "agno-agi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ahupp/python-magic": { + "stars": 2896, + "owner": "ahupp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "aizvorski/scikit-video": { + "stars": 152, + "owner": "aizvorski", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ajenti/ajenti": { + "stars": 7908, + "owner": "ajenti", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "alecthomas/voluptuous": { + "stars": 1847, + "owner": "alecthomas", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "altair-viz/altair": { + "stars": 10301, + "owner": "vega", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "amitt001/delegator.py": { + "stars": 1746, + "owner": "amitt001", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "amoffat/sh": { + "stars": 7241, + "owner": "amoffat", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "amosgyamfi/awesome-fasthtml": { + "stars": 79, + "owner": "amosgyamfi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "andialbrecht/sqlparse": { + "stars": 3999, + "owner": "andialbrecht", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ansible/ansible": { + "stars": 68310, + "owner": "ansible", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "apache/spark": { + "stars": 42992, + "owner": "apache", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "arrow-py/arrow": { + "stars": 9035, + "owner": "arrow-py", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "art049/odmantic": { + "stars": 1168, + "owner": "art049", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "astral-sh/ruff": { + "stars": 46329, + "owner": "astral-sh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "astral-sh/ty": { + "stars": 17739, + "owner": "astral-sh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "astral-sh/uv": { + "stars": 81192, + "owner": "astral-sh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "asweigart/pyautogui": { + "stars": 12363, + "owner": "asweigart", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "aws/aws-sdk-pandas": { + "stars": 4106, + "owner": "aws", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bbangert/beaker": { + "stars": 545, + "owner": "bbangert", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "beetbox/audioread": { + "stars": 536, + "owner": "beetbox", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "beetbox/beets": { + "stars": 14856, + "owner": "beetbox", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "benedekrozemberczki/karateclub": { + "stars": 2276, + "owner": "benedekrozemberczki", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "benfred/implicit": { + "stars": 3773, + "owner": "benfred", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "benfred/py-spy": { + "stars": 15031, + "owner": "benfred", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "benhamner/Metrics": { + "stars": 1654, + "owner": "benhamner", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "benoitc/gunicorn": { + "stars": 10481, + "owner": "benoitc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bfly123/claude_code_bridge": { + "stars": 1642, + "owner": "bfly123", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bloomberg/bqplot": { + "stars": 3684, + "owner": "bqplot", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bokeh/bokeh": { + "stars": 20365, + "owner": "bokeh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "boppreh/mouse": { + "stars": 961, + "owner": "boppreh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "borgbackup/borg": { + "stars": 13081, + "owner": "borgbackup", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "boto/boto3": { + "stars": 9736, + "owner": "boto", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bpython/bpython": { + "stars": 2771, + "owner": "bpython", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "browser-use/browser-use": { + "stars": 81099, + "owner": "browser-use", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "bterwijn/memory_graph": { + "stars": 771, + "owner": "bterwijn", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "buildout/buildout": { + "stars": 613, + "owner": "buildout", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "buriy/python-readability": { + "stars": 2894, + "owner": "buriy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "canonical/cloud-init": { + "stars": 3627, + "owner": "canonical", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "carlosescri/DottedDict": { + "stars": 222, + "owner": "carlosescri", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "cdgriffith/Box": { + "stars": 2822, + "owner": "cdgriffith", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "chaostoolkit/chaostoolkit": { + "stars": 2001, + "owner": "chaostoolkit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "chapmanb/bcbb": { + "stars": 645, + "owner": "chapmanb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "chapmanb/bcbio-nextgen": { + "stars": 1027, + "owner": "bcbio", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "chardet/chardet": { + "stars": 2486, + "owner": "chardet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "chriskiehl/Gooey": { + "stars": 22025, + "owner": "chriskiehl", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "clips/pattern": { + "stars": 8856, + "owner": "clips", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "cobrateam/splinter": { + "stars": 2767, + "owner": "cobrateam", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "codeinthehole/purl": { + "stars": 303, + "owner": "codeinthehole", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "codelucas/newspaper": { + "stars": 15009, + "owner": "codelucas", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "coleifer/huey": { + "stars": 5940, + "owner": "coleifer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "coleifer/micawber": { + "stars": 674, + "owner": "coleifer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "coleifer/peewee": { + "stars": 11952, + "owner": "coleifer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "conda/conda": { + "stars": 7342, + "owner": "conda", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "cookiecutter/cookiecutter": { + "stars": 24744, + "owner": "cookiecutter", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "copier-org/copier": { + "stars": 3214, + "owner": "copier-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "crossbario/autobahn-python": { + "stars": 2534, + "owner": "crossbario", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "cython/cython": { + "stars": 10654, + "owner": "cython", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dahlia/awesome-sqlalchemy": { + "stars": 3031, + "owner": "dahlia", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dashingsoft/pyarmor": { + "stars": 4989, + "owner": "dashingsoft", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dask/dask": { + "stars": 13768, + "owner": "dask", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "datafolklabs/cement": { + "stars": 1341, + "owner": "datafolklabs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "datastax/python-driver": { + "stars": 1426, + "owner": "apache", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dateutil/dateutil": { + "stars": 2604, + "owner": "dateutil", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "davidaurelio/hashids-python": { + "stars": 1423, + "owner": "davidaurelio", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "daviddrysdale/python-phonenumbers": { + "stars": 3720, + "owner": "daviddrysdale", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "davidhalter/jedi": { + "stars": 6123, + "owner": "davidhalter", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "davidhalter/jedi-vim": { + "stars": 5319, + "owner": "davidhalter", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dbader/schedule": { + "stars": 12246, + "owner": "dbader", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dbcli/litecli": { + "stars": 3214, + "owner": "dbcli", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dbcli/mycli": { + "stars": 11886, + "owner": "dbcli", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dbcli/pgcli": { + "stars": 13073, + "owner": "dbcli", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "deanmalmgren/textract": { + "stars": 4482, + "owner": "deanmalmgren", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "derek73/python-nameparser": { + "stars": 702, + "owner": "derek73", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "desbordante/desbordante-core": { + "stars": 469, + "owner": "Desbordante", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "devpi/devpi": { + "stars": 1146, + "owner": "devpi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "devsnd/tinytag": { + "stars": 805, + "owner": "tinytag", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dfunckt/django-rules": { + "stars": 1970, + "owner": "dfunckt", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dgunning/edgartools": { + "stars": 1859, + "owner": "dgunning", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dhamaniasad/awesome-postgres": { + "stars": 11767, + "owner": "dhamaniasad", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dimka665/awesome-slugify": { + "stars": 491, + "owner": "voronind", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-cache-machine/django-cache-machine": { + "stars": 885, + "owner": "django-cache-machine", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-compressor/django-compressor": { + "stars": 2871, + "owner": "django-compressor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-guardian/django-guardian": { + "stars": 3893, + "owner": "django-guardian", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-haystack/django-haystack": { + "stars": 3800, + "owner": "django-haystack", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-haystack/pysolr": { + "stars": 697, + "owner": "django-haystack", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django-tastypie/django-tastypie": { + "stars": 3955, + "owner": "django-tastypie", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django/channels": { + "stars": 6336, + "owner": "django", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django/daphne": { + "stars": 2651, + "owner": "django", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "django/django": { + "stars": 87081, + "owner": "django", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dmlc/xgboost": { + "stars": 28138, + "owner": "dmlc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "docling-project/docling": { + "stars": 55968, + "owner": "docling-project", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dpkp/kafka-python": { + "stars": 5887, + "owner": "dpkp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dry-python/returns": { + "stars": 4238, + "owner": "dry-python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "dynaconf/dynaconf": { + "stars": 4272, + "owner": "dynaconf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "elapouya/python-docx-template": { + "stars": 2588, + "owner": "elapouya", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "elastic/elasticsearch-dsl-py": { + "stars": 3883, + "owner": "elastic", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "eliben/pyelftools": { + "stars": 2217, + "owner": "eliben", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "emcconville/wand": { + "stars": 1479, + "owner": "emcconville", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "emmett-framework/granian": { + "stars": 5173, + "owner": "emmett-framework", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "encode/django-rest-framework": { + "stars": 29928, + "owner": "encode", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "encode/httpx": { + "stars": 15163, + "owner": "encode", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "encode/uvicorn": { + "stars": 10496, + "owner": "Kludex", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "erikrose/more-itertools": { + "stars": 4043, + "owner": "more-itertools", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "esnme/ultrajson": { + "stars": 4474, + "owner": "ultrajson", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "evhub/coconut": { + "stars": 4313, + "owner": "evhub", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "fabric/fabric": { + "stars": 15406, + "owner": "fabric", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "facebook/PathPicker": { + "stars": 5232, + "owner": "facebook", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "facebook/pyre-check": { + "stars": 7153, + "owner": "facebook", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "facebookresearch/hydra": { + "stars": 10258, + "owner": "facebookresearch", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "faif/python-patterns": { + "stars": 42795, + "owner": "faif", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "falconry/falcon": { + "stars": 9805, + "owner": "falconry", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "feature-engine/feature_engine": { + "stars": 2214, + "owner": "feature-engine", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "feincms/feincms": { + "stars": 1077, + "owner": "feincms", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "fengsp/plan": { + "stars": 1182, + "owner": "fengsp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "fighting41love/funNLP": { + "stars": 79457, + "owner": "fighting41love", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "flask-admin/flask-admin": { + "stars": 6057, + "owner": "pallets-eco", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "flask-api/flask-api": { + "stars": 1468, + "owner": "flask-api", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "flask-restful/flask-restful": { + "stars": 6924, + "owner": "flask-restful", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "fogleman/Quads": { + "stars": 1223, + "owner": "fogleman", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "fxsjy/jieba": { + "stars": 34802, + "owner": "fxsjy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gabrielfalcao/HTTPretty": { + "stars": 2209, + "owner": "gabrielfalcao", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gaojiuli/toapi": { + "stars": 3555, + "owner": "elliotgao2", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gawel/pyquery": { + "stars": 2379, + "owner": "gawel", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "geopandas/geopandas": { + "stars": 5067, + "owner": "geopandas", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "geopy/geopy": { + "stars": 4783, + "owner": "geopy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "getnikola/nikola": { + "stars": 2722, + "owner": "getnikola", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "getpelican/pelican": { + "stars": 13249, + "owner": "getpelican", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "getsentry/responses": { + "stars": 4330, + "owner": "getsentry", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "getsentry/sentry-python": { + "stars": 2156, + "owner": "getsentry", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gevent/gevent": { + "stars": 6443, + "owner": "gevent", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "giampaolo/psutil": { + "stars": 11108, + "owner": "giampaolo", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "glamp/bashplotlib": { + "stars": 1917, + "owner": "glamp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gleitz/howdoi": { + "stars": 10831, + "owner": "gleitz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "google/jax": { + "stars": 35125, + "owner": "jax-ml", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "google/python-fire": { + "stars": 28155, + "owner": "google", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "google/pytype": { + "stars": 5029, + "owner": "google", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "google/yapf": { + "stars": 13991, + "owner": "google", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gorakhargosh/watchdog": { + "stars": 7283, + "owner": "gorakhargosh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gotcha/ipdb": { + "stars": 1968, + "owner": "gotcha", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "grantjenks/python-diskcache": { + "stars": 2846, + "owner": "grantjenks", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "grantjenks/python-sortedcontainers": { + "stars": 3935, + "owner": "grantjenks", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "graphql-python/graphene": { + "stars": 8252, + "owner": "graphql-python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gruns/furl": { + "stars": 2797, + "owner": "gruns", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "gruns/icecream": { + "stars": 10032, + "owner": "gruns", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "h2oai/h2o-3": { + "stars": 7511, + "owner": "h2oai", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "has2k1/plotnine": { + "stars": 4521, + "owner": "has2k1", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "hbldh/bleak": { + "stars": 2351, + "owner": "hbldh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "hi-primus/optimus": { + "stars": 1539, + "owner": "hi-primus", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "html5lib/html5lib-python": { + "stars": 1218, + "owner": "html5lib", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "httpie/cli": { + "stars": 37725, + "owner": "httpie", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "hugapi/hug": { + "stars": 6905, + "owner": "hugapi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "huggingface/diffusers": { + "stars": 33076, + "owner": "huggingface", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "huggingface/transformers": { + "stars": 157984, + "owner": "huggingface", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "humiaozuzu/awesome-flask": { + "stars": 12696, + "owner": "humiaozuzu", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ibayer/fastFM": { + "stars": 1090, + "owner": "ibayer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ijl/orjson": { + "stars": 7961, + "owner": "ijl", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "indico/indico": { + "stars": 2031, + "owner": "indico", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "inducer/pudb": { + "stars": 3218, + "owner": "inducer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "infiniflow/ragflow": { + "stars": 75265, + "owner": "infiniflow", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ionelmc/python-hunter": { + "stars": 866, + "owner": "ionelmc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ionelmc/python-manhole": { + "stars": 400, + "owner": "ionelmc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "isnowfy/snownlp": { + "stars": 6614, + "owner": "isnowfy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jab/bidict": { + "stars": 1578, + "owner": "jab", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jaraco/path.py": { + "stars": 1124, + "owner": "jaraco", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/django-debug-toolbar": { + "stars": 8351, + "owner": "django-commons", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/django-oauth-toolkit": { + "stars": 3310, + "owner": "django-oauth", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/django-pipeline": { + "stars": 1543, + "owner": "jazzband", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/geojson": { + "stars": 984, + "owner": "jazzband", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/pip-tools": { + "stars": 7993, + "owner": "jazzband", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jazzband/tablib": { + "stars": 4751, + "owner": "jazzband", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jeffknupp/sandman2": { + "stars": 2044, + "owner": "jeffknupp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jek/blinker": { + "stars": 2034, + "owner": "pallets-eco", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jendrikseipp/vulture": { + "stars": 4379, + "owner": "jendrikseipp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jet-admin/jet-bridge": { + "stars": 1794, + "owner": "jet-admin", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jfkirk/tensorrec": { + "stars": 1302, + "owner": "jfkirk", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jiaaro/pydub": { + "stars": 9743, + "owner": "jiaaro", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jindaxiang/akshare": { + "stars": 17394, + "owner": "akfamily", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jmcnamara/XlsxWriter": { + "stars": 3920, + "owner": "jmcnamara", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "joke2k/faker": { + "stars": 19220, + "owner": "joke2k", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jonathanslenders/ptpython": { + "stars": 5410, + "owner": "prompt-toolkit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jonathanslenders/python-prompt-toolkit": { + "stars": 10329, + "owner": "prompt-toolkit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jorgenschaefer/elpy": { + "stars": 1940, + "owner": "jorgenschaefer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jpadilla/pyjwt": { + "stars": 5622, + "owner": "jpadilla", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "jschneier/django-storages": { + "stars": 2939, + "owner": "jschneier", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "keleshev/schema": { + "stars": 2944, + "owner": "keleshev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "keon/algorithms": { + "stars": 25390, + "owner": "keon", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "keras-team/keras": { + "stars": 63928, + "owner": "keras-team", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "keunwoochoi/kapre": { + "stars": 946, + "owner": "keunwoochoi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kevin1024/vcrpy": { + "stars": 2951, + "owner": "kevin1024", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kiwicom/schemathesis": { + "stars": 3114, + "owner": "schemathesis", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "klen/mixer": { + "stars": 954, + "owner": "klen", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "knipknap/SpiffWorkflow": { + "stars": 1864, + "owner": "sartography", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kootenpv/yagmail": { + "stars": 2725, + "owner": "kootenpv", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kornia/kornia": { + "stars": 11119, + "owner": "kornia", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kreuzberg-dev/kreuzberg": { + "stars": 6736, + "owner": "kreuzberg-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "kurtmckee/feedparser": { + "stars": 2327, + "owner": "kurtmckee", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "laixintao/iredis": { + "stars": 2728, + "owner": "laixintao", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lancopku/pkuseg-python": { + "stars": 6702, + "owner": "lancopku", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "langchain-ai/langchain": { + "stars": 129943, + "owner": "langchain-ai", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lektor/lektor": { + "stars": 3926, + "owner": "lektor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lemire/simdjson": { + "stars": 23448, + "owner": "simdjson", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lepture/authlib": { + "stars": 5244, + "owner": "authlib", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lepture/mistune": { + "stars": 2998, + "owner": "lepture", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lericson/pylibmc": { + "stars": 493, + "owner": "lericson", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "libAudioFlux/audioFlux": { + "stars": 3281, + "owner": "libAudioFlux", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "librosa/librosa": { + "stars": 8263, + "owner": "librosa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "libvips/pyvips": { + "stars": 789, + "owner": "libvips", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lincolnloop/python-qrcode": { + "stars": 4864, + "owner": "lincolnloop", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "linkedin/shiv": { + "stars": 1918, + "owner": "linkedin", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "litestar-org/litestar": { + "stars": 8099, + "owner": "litestar-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "litestar-org/polyfactory": { + "stars": 1428, + "owner": "litestar-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lk-geimfari/mimesis": { + "stars": 4799, + "owner": "lk-geimfari", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "locustio/locust": { + "stars": 27608, + "owner": "locustio", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lorien/grab": { + "stars": 2458, + "owner": "lorien", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "lyst/lightfm": { + "stars": 5066, + "owner": "lyst", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "maciejkula/spotlight": { + "stars": 3042, + "owner": "maciejkula", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "madmaze/pytesseract": { + "stars": 6321, + "owner": "madmaze", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mahmoud/boltons": { + "stars": 6856, + "owner": "mahmoud", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mailgun/flanker": { + "stars": 1650, + "owner": "mailgun", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marcelotduarte/cx_Freeze": { + "stars": 1532, + "owner": "marcelotduarte", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marimo-team/marimo": { + "stars": 19725, + "owner": "marimo-team", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "markusschanta/awesome-jupyter": { + "stars": 4569, + "owner": "markusschanta", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marph91/jimmy": { + "stars": 400, + "owner": "marph91", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marrow/mailer": { + "stars": 293, + "owner": "marrow", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marshmallow-code/marshmallow": { + "stars": 7228, + "owner": "marshmallow-code", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "marshmallow-code/webargs": { + "stars": 1405, + "owner": "marshmallow-code", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "martinblech/xmltodict": { + "stars": 5726, + "owner": "martinblech", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "martinrusev/imbox": { + "stars": 1211, + "owner": "martinrusev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "matplotlib/matplotlib": { + "stars": 22585, + "owner": "matplotlib", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "metawilm/cl-python": { + "stars": 394, + "owner": "metawilm", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mhammond/pywin32": { + "stars": 5531, + "owner": "mhammond", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mher/flower": { + "stars": 7130, + "owner": "mher", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "michaelhelmick/lassie": { + "stars": 630, + "owner": "michaelhelmick", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "micropython/micropython": { + "stars": 21553, + "owner": "micropython", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "microsoft/markitdown": { + "stars": 90875, + "owner": "microsoft", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "miguelgrinberg/microdot": { + "stars": 2093, + "owner": "miguelgrinberg", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mindflayer/python-mocket": { + "stars": 309, + "owner": "mindflayer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mindsdb/mindsdb": { + "stars": 38775, + "owner": "mindsdb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mingrammer/diagrams": { + "stars": 42078, + "owner": "mingrammer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mininet/mininet": { + "stars": 5788, + "owner": "mininet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "miracle2k/flask-assets": { + "stars": 459, + "owner": "miracle2k", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "miracle2k/webassets": { + "stars": 935, + "owner": "miracle2k", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "miso-belica/sumy": { + "stars": 3664, + "owner": "miso-belica", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mitmproxy/pdoc": { + "stars": 2474, + "owner": "mitmproxy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mitsuhiko/pluginbase": { + "stars": 1141, + "owner": "mitsuhiko", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mitsuhiko/unp": { + "stars": 455, + "owner": "mitsuhiko", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mkdocs/mkdocs": { + "stars": 21860, + "owner": "mkdocs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "modoboa/modoboa": { + "stars": 3468, + "owner": "modoboa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mongodb/django-mongodb-backend": { + "stars": 218, + "owner": "mongodb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mongodb/mongo-python-driver": { + "stars": 4338, + "owner": "mongodb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "moses-palmer/pynput": { + "stars": 2125, + "owner": "moses-palmer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mozilla/unicode-slugify": { + "stars": 328, + "owner": "mozilla", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mozillazg/python-pinyin": { + "stars": 5271, + "owner": "mozillazg", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mpdavis/python-jose": { + "stars": 1743, + "owner": "mpdavis", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mpi4py/mpi4py": { + "stars": 902, + "owner": "mpi4py", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mre/awesome-static-analysis": { + "stars": 14439, + "owner": "analysis-tools-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "msiemens/tinydb": { + "stars": 7487, + "owner": "msiemens", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mstamy2/PyPDF2": { + "stars": 9878, + "owner": "py-pdf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mwaskom/seaborn": { + "stars": 13770, + "owner": "mwaskom", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "mymarilyn/clickhouse-driver": { + "stars": 1293, + "owner": "mymarilyn", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "napalm-automation/napalm": { + "stars": 2438, + "owner": "napalm-automation", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nficano/python-lambda": { + "stars": 1521, + "owner": "nficano", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nicfit/eyeD3": { + "stars": 631, + "owner": "nicfit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nose-devs/nose2": { + "stars": 822, + "owner": "nose-devs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "noxrepo/pox": { + "stars": 652, + "owner": "noxrepo", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nucleic/enaml": { + "stars": 1574, + "owner": "nucleic", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "numba/numba": { + "stars": 10935, + "owner": "numba", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nvbn/thefuck": { + "stars": 95714, + "owner": "nvbn", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "nvdv/vprof": { + "stars": 3982, + "owner": "nvdv", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "oauthlib/oauthlib": { + "stars": 2958, + "owner": "oauthlib", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "offerrall/FuncToWeb": { + "stars": 389, + "owner": "offerrall", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "openai/gym": { + "stars": 37100, + "owner": "openai", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "openembedded/bitbake": { + "stars": 509, + "owner": "openembedded", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "openstack/cliff": { + "stars": 260, + "owner": "openstack", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "orsinium/textdistance": { + "stars": 3524, + "owner": "life4", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets-eco/flask-debugtoolbar": { + "stars": 980, + "owner": "pallets-eco", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/click": { + "stars": 17367, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/flask": { + "stars": 71376, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/itsdangerous": { + "stars": 3102, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/jinja": { + "stars": 11513, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/markupsafe": { + "stars": 685, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pallets/werkzeug": { + "stars": 6849, + "owner": "pallets", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "paramiko/paramiko": { + "stars": 9712, + "owner": "paramiko", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pathsim/pathsim": { + "stars": 334, + "owner": "pathsim", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pathwaycom/pathway": { + "stars": 60281, + "owner": "pathwaycom", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "patrys/httmock": { + "stars": 472, + "owner": "patrys", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "patx/pickledb": { + "stars": 1069, + "owner": "patx", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pdfminer/pdfminer.six": { + "stars": 6933, + "owner": "pdfminer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pennersr/django-allauth": { + "stars": 10307, + "owner": "pennersr", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "peterbrittain/asciimatics": { + "stars": 4271, + "owner": "peterbrittain", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pgjones/hypercorn": { + "stars": 1536, + "owner": "pgjones", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pgmpy/pgmpy": { + "stars": 3213, + "owner": "pgmpy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pikepdf/pikepdf": { + "stars": 2667, + "owner": "pikepdf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "planetopendata/awesome-sqlite": { + "stars": 388, + "owner": "planetopendata", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "platformio/platformio-core": { + "stars": 8931, + "owner": "platformio", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "plotly/plotly.py": { + "stars": 18354, + "owner": "plotly", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pndurette/gTTS": { + "stars": 2595, + "owner": "pndurette", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pola-rs/polars": { + "stars": 37775, + "owner": "pola-rs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ponyorm/pony": { + "stars": 3826, + "owner": "ponyorm", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "prabhupant/python-ds": { + "stars": 3074, + "owner": "prabhupant", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pricingassistant/mrq": { + "stars": 896, + "owner": "pricingassistant", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "prompt-toolkit/python-prompt-toolkit": { + "stars": 10329, + "owner": "prompt-toolkit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "psf/black": { + "stars": 41430, + "owner": "psf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "psf/requests": { + "stars": 53881, + "owner": "psf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "psf/requests-html": { + "stars": 13869, + "owner": "psf", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "psycopg/psycopg": { + "stars": 2322, + "owner": "psycopg", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pudo/dataset": { + "stars": 4853, + "owner": "pudo", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pwaller/pyfiglet": { + "stars": 1545, + "owner": "pwaller", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "py2exe/py2exe": { + "stars": 995, + "owner": "py2exe", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pybee/toga": { + "stars": 5323, + "owner": "beeware", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pybuilder/pybuilder": { + "stars": 1956, + "owner": "pybuilder", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyca/cryptography": { + "stars": 7515, + "owner": "pyca", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyca/pynacl": { + "stars": 1185, + "owner": "pyca", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pydantic/pydantic": { + "stars": 27237, + "owner": "pydantic", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pydantic/pydantic-ai": { + "stars": 15531, + "owner": "pydantic", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyenv-win/pyenv-win": { + "stars": 7064, + "owner": "pyenv-win", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyenv/pyenv": { + "stars": 44440, + "owner": "pyenv", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyeve/cerberus": { + "stars": 3270, + "owner": "pyeve", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyeve/eve": { + "stars": 6746, + "owner": "pyeve", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyexcel/pyexcel": { + "stars": 1284, + "owner": "pyexcel", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyglet/pyglet": { + "stars": 2172, + "owner": "pyglet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pygraphviz/pygraphviz": { + "stars": 834, + "owner": "pygraphviz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyinfra-dev/pyinfra": { + "stars": 4869, + "owner": "pyinfra-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyinstaller/pyinstaller": { + "stars": 12924, + "owner": "pyinstaller", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyinvoke/invoke": { + "stars": 4722, + "owner": "pyinvoke", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pylint-dev/pylint": { + "stars": 5661, + "owner": "pylint-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pymatting/pymatting": { + "stars": 1891, + "owner": "pymatting", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pymc-devs/pymc3": { + "stars": 9527, + "owner": "pymc-devs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pymssql/pymssql": { + "stars": 880, + "owner": "pymssql", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pynamodb/PynamoDB": { + "stars": 2647, + "owner": "pynamodb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pypa/bandersnatch": { + "stars": 528, + "owner": "pypa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pypa/hatch": { + "stars": 7145, + "owner": "pypa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pypa/virtualenv": { + "stars": 5017, + "owner": "pypa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pypa/warehouse": { + "stars": 3978, + "owner": "pypi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyparsing/pyparsing": { + "stars": 2465, + "owner": "pyparsing", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyqtgraph/pyqtgraph": { + "stars": 4311, + "owner": "pyqtgraph", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pyston/pyston": { + "stars": 2507, + "owner": "pyston", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-attrs/attrs": { + "stars": 5746, + "owner": "python-attrs", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-greenlet/greenlet": { + "stars": 1814, + "owner": "python-greenlet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-jsonschema/jsonschema": { + "stars": 4935, + "owner": "python-jsonschema", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-mode/python-mode": { + "stars": 5478, + "owner": "python-mode", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-openxml/python-docx": { + "stars": 5486, + "owner": "python-openxml", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-pillow/Pillow": { + "stars": 13437, + "owner": "python-pillow", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-rapidjson/python-rapidjson": { + "stars": 532, + "owner": "python-rapidjson", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-rope/rope": { + "stars": 2183, + "owner": "python-rope", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python-trio/trio": { + "stars": 7204, + "owner": "python-trio", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python/cpython": { + "stars": 72015, + "owner": "python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python/mypy": { + "stars": 20302, + "owner": "python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "python/typeshed": { + "stars": 5021, + "owner": "python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pythonnet/pythonnet": { + "stars": 5418, + "owner": "pythonnet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pytoolz/cytoolz": { + "stars": 1103, + "owner": "pytoolz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pytoolz/toolz": { + "stars": 5125, + "owner": "pytoolz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pytorch/pytorch": { + "stars": 98355, + "owner": "pytorch", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "pytransitions/transitions": { + "stars": 6459, + "owner": "pytransitions", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "quantopian/zipline": { + "stars": 19514, + "owner": "quantopian", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "quantumlib/Cirq": { + "stars": 4890, + "owner": "quantumlib", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "quodlibet/mutagen": { + "stars": 1867, + "owner": "quodlibet", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "r0x0r/pywebview": { + "stars": 5803, + "owner": "r0x0r", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ranaroussi/yfinance": { + "stars": 22172, + "owner": "ranaroussi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ray-project/ray": { + "stars": 41786, + "owner": "ray-project", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "redis/redis-py": { + "stars": 13505, + "owner": "redis", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "reflex-dev/reflex": { + "stars": 28234, + "owner": "reflex-dev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "robotframework/robotframework": { + "stars": 11478, + "owner": "robotframework", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ronaldoussoren/py2app": { + "stars": 421, + "owner": "ronaldoussoren", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "rq/rq": { + "stars": 10605, + "owner": "rq", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "rsalmei/alive-progress": { + "stars": 6256, + "owner": "rsalmei", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "run-llama/llama_index": { + "stars": 47740, + "owner": "run-llama", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "s3tools/s3cmd": { + "stars": 4869, + "owner": "s3tools", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "saffsd/langid.py": { + "stars": 2455, + "owner": "saffsd", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "saltstack/salt": { + "stars": 15281, + "owner": "saltstack", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "samuelcolvin/watchfiles": { + "stars": 2444, + "owner": "samuelcolvin", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sanic-org/sanic": { + "stars": 18639, + "owner": "sanic-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "scanny/python-pptx": { + "stars": 3233, + "owner": "scanny", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "schematics/schematics": { + "stars": 2591, + "owner": "schematics", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "scottrogowski/code2flow": { + "stars": 4545, + "owner": "scottrogowski", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "scrapy/scrapy": { + "stars": 60855, + "owner": "scrapy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sdispater/pendulum": { + "stars": 6628, + "owner": "python-pendulum", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sdispater/poetry": { + "stars": 34316, + "owner": "python-poetry", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sebastien/cuisine": { + "stars": 1270, + "owner": "sebastien", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "secdev/scapy": { + "stars": 12108, + "owner": "secdev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sehmaschine/django-grappelli": { + "stars": 3927, + "owner": "sehmaschine", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "selwin/python-user-agents": { + "stars": 1520, + "owner": "selwin", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sergree/matchering": { + "stars": 2446, + "owner": "sergree", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "shahraizali/awesome-django": { + "stars": 1901, + "owner": "shahraizali", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "shapely/shapely": { + "stars": 4395, + "owner": "shapely", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sherlock-project/sherlock": { + "stars": 73803, + "owner": "sherlock-project", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "simonw/datasette": { + "stars": 10835, + "owner": "simonw", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "simonw/sqlite-utils": { + "stars": 2019, + "owner": "simonw", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sirfz/tesserocr": { + "stars": 2160, + "owner": "sirfz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "skorokithakis/shortuuid": { + "stars": 2178, + "owner": "skorokithakis", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sloria/doitlive": { + "stars": 3566, + "owner": "sloria", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sphinx-doc/sphinx": { + "stars": 7721, + "owner": "sphinx-doc", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "spotify/annoy": { + "stars": 14181, + "owner": "spotify", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "spotify/luigi": { + "stars": 18694, + "owner": "spotify", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "spulec/freezegun": { + "stars": 4498, + "owner": "spulec", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "spyder-ide/spyder": { + "stars": 9163, + "owner": "spyder-ide", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sqlalchemy/dogpile.cache": { + "stars": 291, + "owner": "sqlalchemy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sqlmapproject/sqlmap": { + "stars": 36853, + "owner": "sqlmapproject", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "stanfordnlp/stanza": { + "stars": 7739, + "owner": "stanfordnlp", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "statsmodels/statsmodels": { + "stars": 11298, + "owner": "statsmodels", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "stchris/untangle": { + "stars": 631, + "owner": "stchris", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "strawberry-graphql/strawberry-django": { + "stars": 488, + "owner": "strawberry-graphql", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "streamlit/streamlit": { + "stars": 43924, + "owner": "streamlit", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sunainapai/makesite": { + "stars": 1872, + "owner": "sunainapai", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "sympy/sympy": { + "stars": 14495, + "owner": "sympy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tartley/colorama": { + "stars": 3770, + "owner": "tartley", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tayllan/awesome-algorithms": { + "stars": 24835, + "owner": "tayllan", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tensorflow/tensorflow": { + "stars": 194201, + "owner": "tensorflow", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "thauber/django-schedule": { + "stars": 850, + "owner": "thauber", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "thumbor/thumbor": { + "stars": 10465, + "owner": "thumbor", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tiangolo/fastapi": { + "stars": 96302, + "owner": "fastapi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tiangolo/typer": { + "stars": 19041, + "owner": "fastapi", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "timofurrer/awesome-asyncio": { + "stars": 5030, + "owner": "timofurrer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "timofurrer/try": { + "stars": 751, + "owner": "timofurrer", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "timothycrosley/isort": { + "stars": 6916, + "owner": "PyCQA", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tmux-python/tmuxp": { + "stars": 4451, + "owner": "tmux-python", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tmux/tmux": { + "stars": 43165, + "owner": "tmux", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tomerfiliba/rpyc": { + "stars": 1693, + "owner": "tomerfiliba-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tomschimansky/customtkinter": { + "stars": 13223, + "owner": "TomSchimansky", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tornadoweb/tornado": { + "stars": 22406, + "owner": "tornadoweb", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tqdm/tqdm": { + "stars": 31040, + "owner": "tqdm", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "trustedsec/social-engineer-toolkit": { + "stars": 14671, + "owner": "trustedsec", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "twisted/treq": { + "stars": 606, + "owner": "twisted", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "twisted/twisted": { + "stars": 5951, + "owner": "twisted", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "tyiannak/pyAudioAnalysis": { + "stars": 6234, + "owner": "tyiannak", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "typeddjango/awesome-python-typing": { + "stars": 1950, + "owner": "typeddjango", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ultraplot/UltraPlot": { + "stars": 279, + "owner": "Ultraplot", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "un33k/python-slugify": { + "stars": 1599, + "owner": "un33k", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "unclecode/crawl4ai": { + "stars": 62122, + "owner": "unclecode", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "unfoldadmin/django-unfold": { + "stars": 3369, + "owner": "unfoldadmin", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "uralbash/awesome-pyramid": { + "stars": 570, + "owner": "uralbash", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "urllib3/urllib3": { + "stars": 4012, + "owner": "urllib3", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "vadikko2/python-cqrs": { + "stars": 44, + "owner": "pypatterns", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "vinta/pangu.py": { + "stars": 276, + "owner": "vinta", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "vispy/vispy": { + "stars": 3558, + "owner": "vispy", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "vitali87/code-graph-rag": { + "stars": 2131, + "owner": "vitali87", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "vllm-project/vllm": { + "stars": 73457, + "owner": "vllm-project", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "wagtail/wagtail": { + "stars": 20240, + "owner": "wagtail", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "waylan/Python-Markdown": { + "stars": 4186, + "owner": "Python-Markdown", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "web2py/pydal": { + "stars": 531, + "owner": "web2py", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "wireservice/csvkit": { + "stars": 6360, + "owner": "wireservice", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "wooey/wooey": { + "stars": 2218, + "owner": "wooey", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "worldveil/dejavu": { + "stars": 6736, + "owner": "worldveil", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "xonsh/xonsh": { + "stars": 9252, + "owner": "xonsh", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "yfedoseev/pdf_oxide": { + "stars": 431, + "owner": "yfedoseev", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "yoloseem/awesome-sphinxdoc": { + "stars": 973, + "owner": "ygzgxyz", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ytdl-org/youtube-dl": { + "stars": 139894, + "owner": "ytdl-org", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "zappa/Zappa": { + "stars": 3676, + "owner": "zappa", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "zauberzeug/nicegui": { + "stars": 15518, + "owner": "zauberzeug", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "zoofIO/flexx": { + "stars": 3343, + "owner": "flexxui", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "zopefoundation/ZODB": { + "stars": 752, + "owner": "zopefoundation", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "ztane/python-Levenshtein": { + "stars": 1278, + "owner": "ztane", + "fetched_at": "2026-03-17T20:08:08.973003+00:00" + }, + "josephmisiti/awesome-machine-learning": { + "stars": 72017, + "owner": "josephmisiti", + "fetched_at": "2026-03-17T20:12:03.066921+00:00" + }, + "sorrycc/awesome-javascript": { + "stars": 34931, + "owner": "sorrycc", + "fetched_at": "2026-03-17T20:12:03.066921+00:00" + }, + "vinta/awesome-python": { + "stars": 287640, + "owner": "vinta", + "fetched_at": "2026-03-17T20:12:03.066921+00:00" + } +} diff --git a/website/fetch_github_stars.py b/website/fetch_github_stars.py new file mode 100644 index 00000000..4f9b50a3 --- /dev/null +++ b/website/fetch_github_stars.py @@ -0,0 +1,192 @@ +#!/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 + +CACHE_MAX_AGE_DAYS = 7 +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 = 100 + + +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 load_cache() -> dict: + """Load the star cache from disk. Returns empty dict if missing or corrupt.""" + if CACHE_FILE.exists(): + try: + return json.loads(CACHE_FILE.read_text(encoding="utf-8")) + except json.JSONDecodeError: + print(f"Warning: corrupt cache at {CACHE_FILE}, starting fresh.", file=sys.stderr) + return {} + return {} + + +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 pushedAt owner {{ login }} }}" + ) + 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 + result[repo] = { + "stars": node.get("stargazerCount", 0), + "owner": node.get("owner", {}).get("login", ""), + "pushed_at": node.get("pushedAt", ""), + } + 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_cache() + 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_days = (now - fetched).days + if age_days < CACHE_MAX_AGE_DAYS: + 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"], + "pushed_at": results[repo]["pushed_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() diff --git a/website/static/main.js b/website/static/main.js new file mode 100644 index 00000000..16e18311 --- /dev/null +++ b/website/static/main.js @@ -0,0 +1,154 @@ +// State +var activeFilter = null; // { type: "cat"|"group", value: "..." } +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 countEl = document.querySelector('.count'); +var rows = document.querySelectorAll('.table tbody tr.row'); +var tags = document.querySelectorAll('.tag'); +var tbody = document.querySelector('.table tbody'); + +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) { + show = row.dataset[activeFilter.type] === activeFilter.value; + } + + // 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); + } + + row.hidden = !show; + + if (show) { + visibleCount++; + row.querySelector('.col-num').textContent = String(visibleCount); + } + }); + + if (noResults) noResults.hidden = visibleCount > 0; + if (countEl) countEl.textContent = visibleCount; + + // 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; + } + } +} + +// 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(); + }); +} + +// 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(); + } + }); +} diff --git a/website/static/style.css b/website/static/style.css new file mode 100644 index 00000000..c31b6875 --- /dev/null +++ b/website/static/style.css @@ -0,0 +1,459 @@ +/* === 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); +} + +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; +} + +a { color: var(--accent); text-decoration: none; } +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; + justify-content: space-between; + align-items: flex-start; +} + +.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; +} + +.hero-submit:hover { + border-color: var(--accent); + color: var(--accent); + text-decoration: none; +} + +.hero h1 { + font-family: var(--font-display); + font-size: clamp(2rem, 5vw, 3rem); + font-weight: 400; + letter-spacing: -0.01em; + line-height: 1.1; + color: var(--accent); + margin-bottom: 0.75rem; +} + +.hero-sub { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: 0.5rem; +} + +.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); +} + +.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.15rem 0.5rem; + font-family: inherit; + font-size: var(--text-xs); + color: var(--text-muted); + cursor: pointer; +} + +.filter-clear:hover { + border-color: var(--text-muted); + color: var(--text); +} + +.stats { + font-size: var(--text-sm); + color: var(--text-muted); + font-variant-numeric: tabular-nums; +} + +.stats strong { color: var(--text-secondary); } + +/* === Table === */ +.table-wrap { + width: 100%; + padding: 0; + overflow-x: auto; +} + +.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; +} + +.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: right; +} + +.col-name { + width: 35%; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; +} + +.col-name > a { + font-weight: 500; + color: var(--accent); + text-decoration: none; +} + +.col-name > a:hover { text-decoration: underline; color: var(--accent-hover); } + +/* === Stars Column === */ +.col-stars { + width: 5rem; + font-variant-numeric: tabular-nums; + white-space: nowrap; + color: var(--text-secondary); +} + +/* === 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; } + +/* === 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); +} + +.expand-content { + font-size: var(--text-sm); + color: var(--text-secondary); + line-height: 1.6; +} + +.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, .col-group { + width: 13%; + white-space: nowrap; +} + +/* === Tags === */ +.tag { + background: var(--accent-light); + border: none; + font-family: inherit; + font-size: var(--text-xs); + color: oklch(45% 0.06 240); + cursor: pointer; + padding: 0.15rem 0.35rem; + border-radius: 3px; + white-space: nowrap; +} + +.tag:hover { + background: var(--accent-light); + color: var(--accent); +} + +.tag.active { + background: var(--highlight); + color: var(--highlight-text); + font-weight: 600; +} + +/* === 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: space-between; +} + +.footer a { color: var(--text-muted); text-decoration: none; } +.footer a:hover { color: var(--accent); } + +.footer-links { + display: flex; + gap: 1rem; +} + +/* === Responsive === */ +@media (max-width: 900px) { + .col-group { display: none; } +} + +@media (max-width: 640px) { + .hero { padding: 2rem 1.25rem 1rem; } + .controls { padding: 0 1.25rem 0.75rem; } + + .table thead th:first-child, + .table tbody td:first-child { padding-left: 1.25rem; } + + .table thead th:last-child, + .table tbody td:last-child { padding-right: 1.25rem; } + + .col-cat { display: none; } + .col-name { white-space: normal; } + .footer { padding: 1.25rem; flex-direction: column; gap: 0.5rem; } +} + +/* === 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 { + transition-duration: 0.01ms !important; + } +} diff --git a/website/templates/base.html b/website/templates/base.html new file mode 100644 index 00000000..4a3bc249 --- /dev/null +++ b/website/templates/base.html @@ -0,0 +1,67 @@ + + + + + + {% block title %}Awesome Python{% endblock %} + + + + + + + + + + + + + + + +
{% block content %}{% endblock %}
+ + + + + + + diff --git a/website/templates/index.html b/website/templates/index.html new file mode 100644 index 00000000..ea2c4821 --- /dev/null +++ b/website/templates/index.html @@ -0,0 +1,146 @@ +{% extends "base.html" %} +{% block content %} +
+
+
+

Awesome Python

+

+ {{ subtitle }}
Curated by + @vinta + since 2014. +

+ awesome-python on GitHub → +
+ Submit a Project +
+
+ +
+
+ + + + + +
+ +
+ +
+ + + + + + + + + + + + + {% for entry in entries %} + + + + + + + + + + + + + {% endfor %} + +
#Project NameGitHub StarsCategoryGroup
+
+ {% if entry.description %} +
{{ entry.description | safe }}
+ {% endif %} {% if entry.also_see %} +
+ Also see: {% for see in entry.also_see %}{{ see.name }}{% if not loop.last %}, {% endif %}{% endfor %} +
+ {% endif %} +
+ {% if entry.owner %}{{ entry.owner }}/{% endif %}{{ entry.url | replace("https://", "") }}{% if entry.pushed_at %}·Last pushed {{ entry.pushed_at[:10] }}{% endif %} +
+
+
+
+ + +{% endblock %} diff --git a/website/tests/test_build.py b/website/tests/test_build.py new file mode 100644 index 00000000..e551f954 --- /dev/null +++ b/website/tests/test_build.py @@ -0,0 +1,642 @@ +"""Tests for the build module.""" + +import json +import os +import shutil +import sys +import textwrap +from pathlib import Path + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from build import ( + build, + count_entries, + extract_github_repo, + extract_preview, + group_categories, + load_stars, + parse_readme, + render_content_html, + slugify, + sort_entries, +) + +# --------------------------------------------------------------------------- +# 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" + + +# --------------------------------------------------------------------------- +# count_entries +# --------------------------------------------------------------------------- + + +class TestCountEntries: + def test_counts_dash_entries(self): + assert count_entries("- [a](url) - Desc.\n- [b](url) - Desc.") == 2 + + def test_counts_star_entries(self): + assert count_entries("* [a](url) - Desc.") == 1 + + def test_ignores_non_entries(self): + assert count_entries("Some text\n- [a](url) - Desc.\nMore text") == 1 + + def test_counts_indented_entries(self): + assert count_entries(" - [a](url) - Desc.") == 1 + + def test_empty_content(self): + assert count_entries("") == 0 + + +# --------------------------------------------------------------------------- +# extract_preview +# --------------------------------------------------------------------------- + + +class TestExtractPreview: + def test_basic(self): + content = "* [alpha](url) - A.\n* [beta](url) - B.\n* [gamma](url) - C." + assert extract_preview(content) == "alpha, beta, gamma" + + def test_max_four(self): + content = "\n".join(f"* [lib{i}](url) - Desc." for i in range(10)) + assert extract_preview(content) == "lib0, lib1, lib2, lib3" + + def test_empty(self): + assert extract_preview("") == "" + + def test_skips_subcategory_labels(self): + content = "* Synchronous\n* [django](url) - Framework.\n* [flask](url) - Micro." + assert extract_preview(content) == "django, flask" + + +# --------------------------------------------------------------------------- +# render_content_html +# --------------------------------------------------------------------------- + + +class TestRenderContentHtml: + def test_basic_entry(self): + content = "* [django](https://example.com) - A web framework." + html = render_content_html(content) + assert 'href="https://example.com"' in html + assert "django" in html + assert "A web framework." in html + assert 'class="entry"' in html + + def test_subcategory_label(self): + content = "* Synchronous\n* [django](https://x.com) - Framework." + html = render_content_html(content) + assert 'class="subcat"' in html + assert "Synchronous" in html + + def test_sub_entry(self): + content = "* [django](https://x.com) - Framework.\n * [awesome-django](https://y.com)" + html = render_content_html(content) + assert 'class="entry-sub"' in html + assert "awesome-django" in html + + def test_link_only_entry(self): + content = "* [tool](https://x.com)" + html = render_content_html(content) + assert 'href="https://x.com"' in html + assert "tool" in html + + +# --------------------------------------------------------------------------- +# parse_readme +# --------------------------------------------------------------------------- + +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! +""") + + +class TestParseReadme: + def test_category_count(self): + cats, resources = parse_readme(MINIMAL_README) + assert len(cats) == 2 + + def test_resource_count(self): + cats, resources = parse_readme(MINIMAL_README) + assert len(resources) == 2 + + def test_category_names(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["name"] == "Alpha" + assert cats[1]["name"] == "Beta" + + def test_category_slugs(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["slug"] == "alpha" + assert cats[1]["slug"] == "beta" + + def test_category_description(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["description"] == "Libraries for alpha stuff." + assert cats[1]["description"] == "Tools for beta." + + def test_category_content_has_entries(self): + cats, _ = parse_readme(MINIMAL_README) + assert "lib-a" in cats[0]["content"] + assert "lib-b" in cats[0]["content"] + + def test_resources_names(self): + _, resources = parse_readme(MINIMAL_README) + assert resources[0]["name"] == "Newsletters" + assert resources[1]["name"] == "Podcasts" + + def test_resources_content(self): + _, resources = parse_readme(MINIMAL_README) + assert "News One" in resources[0]["content"] + assert "Pod One" in resources[1]["content"] + + def test_contributing_skipped(self): + cats, resources = parse_readme(MINIMAL_README) + all_names = [c["name"] for c in cats] + [r["name"] for r in resources] + assert "Contributing" not in all_names + + def test_no_separator(self): + cats, resources = parse_readme("# Just a heading\n\nSome text.\n") + assert cats == [] + 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. + """) + cats, resources = parse_readme(readme) + assert cats[0]["description"] == "" + assert "item" in cats[0]["content"] + + +# --------------------------------------------------------------------------- +# parse_readme on real README +# --------------------------------------------------------------------------- + + +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.cats, self.resources = parse_readme(self.readme_text) + + def test_at_least_83_categories(self): + assert len(self.cats) >= 83 + + 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_admin_panels(self): + assert self.cats[0]["name"] == "Admin Panels" + assert self.cats[0]["slug"] == "admin-panels" + + def test_last_category_is_wsgi_servers(self): + assert self.cats[-1]["name"] == "WSGI Servers" + assert self.cats[-1]["slug"] == "wsgi-servers" + + def test_restful_api_slug(self): + slugs = [c["slug"] for c in self.cats] + assert "restful-api" in slugs + + def test_descriptions_extracted(self): + admin = self.cats[0] + assert admin["description"] == "Libraries for administrative interfaces." + + +# --------------------------------------------------------------------------- +# group_categories +# --------------------------------------------------------------------------- + + +class TestGroupCategories: + def test_groups_known_categories(self): + cats = [ + {"name": "Web Frameworks", "slug": "web-frameworks"}, + {"name": "Testing", "slug": "testing"}, + ] + groups = group_categories(cats, []) + group_names = [g["name"] for g in groups] + assert "Web & API" in group_names + assert "Development Tools" in group_names + + def test_ungrouped_go_to_other(self): + cats = [{"name": "Unknown Category", "slug": "unknown-category"}] + groups = group_categories(cats, []) + group_names = [g["name"] for g in groups] + assert "Other" in group_names + + def test_resources_grouped(self): + resources = [{"name": "Newsletters", "slug": "newsletters"}] + groups = group_categories([], resources) + group_names = [g["name"] for g in groups] + assert "Resources" in group_names + + +# --------------------------------------------------------------------------- +# render_markdown (kept for compatibility) +# --------------------------------------------------------------------------- + + +class TestRenderMarkdown: + def test_renders_link_list(self): + from build import render_markdown + + html = render_markdown("- [lib](https://example.com) - Does stuff.") + assert "
  • " in html + assert 'lib' in html + + def test_renders_plain_text(self): + from build import render_markdown + + html = render_markdown("Hello world") + assert "

    Hello world

    " in html + + +# --------------------------------------------------------------------------- +# 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( + "{% block title %}{% endblock %}" + "" + "{% block content %}{% endblock %}", + encoding="utf-8", + ) + (tpl_dir / "index.html").write_text( + '{% extends "base.html" %}{% block content %}' + "{% for group in groups %}" + '
    ' + "

    {{ group.name }}

    " + "{% for cat in group.categories %}" + '
    ' + "{{ cat.name }}" + "{{ cat.preview }}" + "{{ cat.entry_count }}" + '' + "
    " + "{% endfor %}" + "
    " + "{% endfor %}" + "{% endblock %}", + encoding="utf-8", + ) + + def test_build_creates_single_page(self, tmp_path): + readme = textwrap.dedent("""\ + # Awesome Python + + Intro. + + --- + + ## 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_creates_cname(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + --- + + ## Only + + - [x](https://x.com) - X. + + # Contributing + + Done. + """) + self._make_repo(tmp_path, readme) + build(str(tmp_path)) + + cname = tmp_path / "website" / "output" / "CNAME" + assert cname.exists() + assert "awesome-python.com" in cname.read_text() + + 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 + + --- + + ## Alpha + + - [a](https://x.com) - A. + + ## 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 + + 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"] diff --git a/website/tests/test_fetch_github_stars.py b/website/tests/test_fetch_github_stars.py new file mode 100644 index 00000000..2465899f --- /dev/null +++ b/website/tests/test_fetch_github_stars.py @@ -0,0 +1,161 @@ +"""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, + load_cache, + 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 TestLoadCache: + def test_returns_empty_when_missing(self, tmp_path, monkeypatch): + monkeypatch.setattr("fetch_github_stars.CACHE_FILE", tmp_path / "nonexistent.json") + result = load_cache() + assert result == {} + + def test_loads_valid_cache(self, tmp_path, monkeypatch): + cache_file = tmp_path / "stars.json" + cache_file.write_text('{"a/b": {"stars": 1}}', encoding="utf-8") + monkeypatch.setattr("fetch_github_stars.CACHE_FILE", cache_file) + result = load_cache() + assert result == {"a/b": {"stars": 1}} + + def test_returns_empty_on_corrupt_json(self, tmp_path, monkeypatch): + cache_file = tmp_path / "stars.json" + cache_file.write_text("not json", encoding="utf-8") + monkeypatch.setattr("fetch_github_stars.CACHE_FILE", cache_file) + result = load_cache() + assert result == {} + + +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 From cd7b8f6bb0692c46377b8c88d68975b186cff999 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:48:53 +0800 Subject: [PATCH 0187/1130] update README description and remove Resources section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 'tools' to the description tagline - Remove the Resources TOC entry (Newsletters, Podcasts) and corresponding section — content is no longer relevant to the relaunched site - Remove uv from the Environment Management section (it's now a dev dependency managed by pyproject.toml, not a curated list entry) Co-Authored-By: Claude --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 9f34e225..d31fdfee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Awesome Python -An opinionated list of awesome Python frameworks, libraries, software and resources. +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). @@ -87,9 +87,6 @@ An opinionated list of awesome Python frameworks, libraries, software and resour - [Web Frameworks](#web-frameworks) - [WebSocket](#websocket) - [WSGI Servers](#wsgi-servers) -- [Resources](#resources) - - [Newsletters](#newsletters) - - [Podcasts](#podcasts) --- @@ -534,7 +531,6 @@ _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 package and project manager, written in Rust. - [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. ## File Manipulation From 2fe0f5c2bd36652734425673e9d698e2b925cab4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:57:26 +0800 Subject: [PATCH 0188/1130] ci: bump actions/checkout to v6 and upload-pages-artifact to v4 Co-Authored-By: Claude --- .github/workflows/deploy-website.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 28e254ea..66e7021e 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -18,7 +18,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v7 @@ -32,7 +32,7 @@ jobs: run: uv run python website/build.py - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: website/output/ From 87a16f47ead7ed523afc166eb3e73199b07c03a3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:57:32 +0800 Subject: [PATCH 0189/1130] build: load .env in Makefile and rename fetch_stars to site_fetch_stats Adds -include .env with export so environment variables (e.g. GitHub token) are available to uv commands without manual export. Renames the target to match the site_ prefix convention used by the other targets. Co-Authored-By: Claude --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5d6d7581..8cb7005e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ +-include .env +export + site_install: uv sync --no-dev -fetch_stars: +site_fetch_stats: uv run python website/fetch_github_stars.py site_build: From 7eb9b11a67c523bbfb1227d0f50a2d3744a752d5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 13:57:36 +0800 Subject: [PATCH 0190/1130] data: remove zipline entry from github_stars.json Follows the removal of zipline from README.md (see bd73b1f). Co-Authored-By: Claude --- website/data/github_stars.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/website/data/github_stars.json b/website/data/github_stars.json index 1476651f..c71287fd 100644 --- a/website/data/github_stars.json +++ b/website/data/github_stars.json @@ -2119,11 +2119,6 @@ "owner": "pytransitions", "fetched_at": "2026-03-17T20:08:08.973003+00:00" }, - "quantopian/zipline": { - "stars": 19514, - "owner": "quantopian", - "fetched_at": "2026-03-17T20:08:08.973003+00:00" - }, "quantumlib/Cirq": { "stars": 4890, "owner": "quantumlib", From c5caa5a5e15e95652a70431c8eb7c2da19287a21 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 14:03:50 +0800 Subject: [PATCH 0191/1130] ci: hardcode deployment URL to https://awesome-python.com The deploy-pages action outputs http:// despite HTTPS being enforced. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/deploy-website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 66e7021e..e9fda8f6 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest environment: name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + url: https://awesome-python.com/ steps: - name: Deploy to GitHub Pages id: deployment From 5fa7c7d1a670387dc04b6408a06e7b1a6dbbbf42 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 17:20:23 +0800 Subject: [PATCH 0192/1130] feat(website): add markdown-it-py README parser and inline renderer tests Introduce readme_parser.py which parses README.md into structured section data using the markdown-it-py AST. Includes TypedDicts for ParsedEntry/ParsedSection, slugify(), render_inline_html(), and render_inline_text(). Add test_readme_parser.py covering HTML escaping, link rendering, emphasis, strong, and code_inline for both renderers. Co-Authored-By: Claude --- website/readme_parser.py | 93 +++++++++++++++++++++++++++++ website/tests/test_readme_parser.py | 69 +++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 website/readme_parser.py create mode 100644 website/tests/test_readme_parser.py diff --git a/website/readme_parser.py b/website/readme_parser.py new file mode 100644 index 00000000..a98e0e0c --- /dev/null +++ b/website/readme_parser.py @@ -0,0 +1,93 @@ +"""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.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 + content: str # raw markdown (backward compat) + entries: list[ParsedEntry] + entry_count: int + preview: str + content_html: str # rendered HTML, properly escaped + + +# --- 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'{inner}' + ) + case "em": + parts.append(f"{render_inline_html(child.children)}") + case "strong": + parts.append(f"{render_inline_html(child.children)}") + case "code_inline": + parts.append(f"{escape(child.content)}") + 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) diff --git a/website/tests/test_readme_parser.py b/website/tests/test_readme_parser.py new file mode 100644 index 00000000..974143e5 --- /dev/null +++ b/website/tests/test_readme_parser.py @@ -0,0 +1,69 @@ +"""Tests for the readme_parser module.""" + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) +from readme_parser import 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 & 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" in html + + def test_emphasis(self): + children = _parse_inline("*italic* text") + assert "italic" in render_inline_html(children) + + def test_strong(self): + children = _parse_inline("**bold** text") + assert "bold" in render_inline_html(children) + + def test_code_inline(self): + children = _parse_inline("`some code`") + assert "some 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" 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" From 1c67c9f0e68718cd23f885d11a9168d8d0d53980 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 17:21:49 +0800 Subject: [PATCH 0193/1130] feat: replace regex README parser with markdown-it-py AST parser Introduce parse_readme() which uses MarkdownIt to build a full AST instead of line-by-line regex matching. The function splits the document at the thematic break, groups nodes by h2 heading, extracts category descriptions from leading italic paragraphs, and separates the Categories, Resources, and Contributing sections cleanly. Add markdown-it-py==4.0.0 (+ mdurl) as a runtime dependency to support the new parser. Tests cover section counts, names, slugs, descriptions, content presence, boundary conditions (no separator, no description), and mixed description markup. Co-Authored-By: Claude --- pyproject.toml | 1 + uv.lock | 23 ++++ website/readme_parser.py | 167 ++++++++++++++++++++++++++++ website/tests/test_readme_parser.py | 135 +++++++++++++++++++++- 4 files changed, 325 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d564cde9..3f03420a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ dependencies = [ "httpx==0.28.1", "jinja2==3.1.6", "markdown==3.10.2", + "markdown-it-py==4.0.0", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 1f7b17c7..51bd6822 100644 --- a/uv.lock +++ b/uv.lock @@ -22,6 +22,7 @@ dependencies = [ { name = "httpx" }, { name = "jinja2" }, { name = "markdown" }, + { name = "markdown-it-py" }, ] [package.dev-dependencies] @@ -35,6 +36,7 @@ requires-dist = [ { name = "httpx", specifier = "==0.28.1" }, { name = "jinja2", specifier = "==3.1.6" }, { name = "markdown", specifier = "==3.10.2" }, + { name = "markdown-it-py", specifier = "==4.0.0" }, ] [package.metadata.requires-dev] @@ -137,6 +139,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -189,6 +203,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "packaging" version = "26.0" diff --git a/website/readme_parser.py b/website/readme_parser.py index a98e0e0c..62afd94c 100644 --- a/website/readme_parser.py +++ b/website/readme_parser.py @@ -5,6 +5,7 @@ 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 @@ -91,3 +92,169 @@ def render_inline_text(children: list[SyntaxTreeNode]) -> str: 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 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 "" + + +def _has_description(nodes: list[SyntaxTreeNode]) -> bool: + """Check if the first node is a description paragraph (_italic text_).""" + if not nodes: + return False + first = nodes[0] + if first.type != "paragraph": + return False + for child in first.children: + if child.type == "inline" and len(child.children) == 1: + if child.children[0].type == "em": + return True + return False + + +def _nodes_to_raw_markdown(nodes: list[SyntaxTreeNode], source_lines: list[str]) -> str: + """Extract raw markdown text for AST nodes using source line mappings.""" + if not nodes: + return "" + start_line = None + end_line = None + for node in nodes: + node_map = node.map + if node_map is not None: + if start_line is None or node_map[0] < start_line: + start_line = node_map[0] + if end_line is None or node_map[1] > end_line: + end_line = node_map[1] + if start_line is None: + return "" + return "\n".join(source_lines[start_line:end_line]).strip() + + +# --- Stubs for Tasks 3 & 4 (replace in later tasks) ------------------------- + + +def _parse_section_entries(content_nodes: list[SyntaxTreeNode]) -> list[ParsedEntry]: + return [] + + +def _render_section_html(content_nodes: list[SyntaxTreeNode]) -> str: + return "" + + +# --- Section splitting ------------------------------------------------------- + + +def _group_by_h2( + nodes: list[SyntaxTreeNode], + source_lines: list[str], +) -> 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 + desc = _extract_description(current_body) + content_nodes = current_body[1:] if _has_description(current_body) else current_body + content = _nodes_to_raw_markdown(content_nodes, source_lines) + 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) + + sections.append(ParsedSection( + name=current_name, + slug=slugify(current_name), + description=desc, + content=content, + entries=entries, + entry_count=entry_count, + preview=preview, + content_html=content_html, + )) + 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 parse_readme(text: str) -> tuple[list[ParsedSection], list[ParsedSection]]: + """Parse README.md text into categories and resources. + + Returns (categories, resources) where each is a list of ParsedSection dicts. + """ + md = MarkdownIt("commonmark") + tokens = md.parse(text) + root = SyntaxTreeNode(tokens) + source_lines = text.split("\n") + children = root.children + + # Find thematic break (---) + hr_idx = None + for i, node in enumerate(children): + if node.type == "hr": + hr_idx = i + break + if hr_idx is None: + return [], [] + + # Find # Resources and # Contributing boundaries + resources_idx = None + contributing_idx = None + for i, node in enumerate(children): + if 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 + + # 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] + + categories = _group_by_h2(cat_nodes, source_lines) + resources = _group_by_h2(res_nodes, source_lines) + + return categories, resources diff --git a/website/tests/test_readme_parser.py b/website/tests/test_readme_parser.py index 974143e5..3f32e844 100644 --- a/website/tests/test_readme_parser.py +++ b/website/tests/test_readme_parser.py @@ -2,9 +2,10 @@ import os import sys +import textwrap sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from readme_parser import render_inline_html, render_inline_text +from readme_parser import parse_readme, render_inline_html, render_inline_text from markdown_it import MarkdownIt from markdown_it.tree import SyntaxTreeNode @@ -67,3 +68,135 @@ class TestRenderInlineText: 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! +""") + + +class TestParseReadmeSections: + def test_category_count(self): + cats, resources = parse_readme(MINIMAL_README) + assert len(cats) == 2 + + def test_resource_count(self): + cats, resources = parse_readme(MINIMAL_README) + assert len(resources) == 2 + + def test_category_names(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["name"] == "Alpha" + assert cats[1]["name"] == "Beta" + + def test_category_slugs(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["slug"] == "alpha" + assert cats[1]["slug"] == "beta" + + def test_category_description(self): + cats, _ = parse_readme(MINIMAL_README) + assert cats[0]["description"] == "Libraries for alpha stuff." + assert cats[1]["description"] == "Tools for beta." + + def test_category_content_has_entries(self): + cats, _ = parse_readme(MINIMAL_README) + assert "lib-a" in cats[0]["content"] + assert "lib-b" in cats[0]["content"] + + def test_resource_names(self): + _, resources = parse_readme(MINIMAL_README) + assert resources[0]["name"] == "Newsletters" + assert resources[1]["name"] == "Podcasts" + + def test_resource_content(self): + _, resources = parse_readme(MINIMAL_README) + assert "News One" in resources[0]["content"] + assert "Pod One" in resources[1]["content"] + + def test_contributing_skipped(self): + cats, resources = parse_readme(MINIMAL_README) + all_names = [c["name"] for c in cats] + [r["name"] for r in resources] + assert "Contributing" not in all_names + + def test_no_separator(self): + cats, resources = parse_readme("# Just a heading\n\nSome text.\n") + assert cats == [] + 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. + """) + cats, resources = parse_readme(readme) + assert cats[0]["description"] == "" + assert "item" in cats[0]["content"] + + 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. + """) + cats, _ = parse_readme(readme) + assert cats[0]["description"] == "Algorithms. Also see awesome-algos." From 3d015bc63026635087701358237d9ddb60fb67d7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 18 Mar 2026 17:23:11 +0800 Subject: [PATCH 0194/1130] feat(parser): implement entry extraction from bullet list AST nodes Replace _parse_section_entries stub with full implementation that walks bullet_list AST nodes to extract ParsedEntry records, including support for subcategory labels (text-only list items) and also_see nested links. Add _parse_list_entries, helper finders (_find_inline, _find_first_link, _find_child), and _extract_description_html with separator stripping. Extend test suite with TestParseSectionEntries covering flat entries, link-only entries, subcategorized entries, also_see, entry_count, preview first-four, and XSS escaping in description HTML. Co-Authored-By: Claude --- website/readme_parser.py | 114 +++++++++++++++++++++++++++- website/tests/test_readme_parser.py | 105 ++++++++++++++++++++++++- 2 files changed, 216 insertions(+), 3 deletions(-) diff --git a/website/readme_parser.py b/website/readme_parser.py index 62afd94c..71a36742 100644 --- a/website/readme_parser.py +++ b/website/readme_parser.py @@ -155,11 +155,121 @@ def _nodes_to_raw_markdown(nodes: list[SyntaxTreeNode], source_lines: list[str]) return "\n".join(source_lines[start_line:end_line]).strip() -# --- Stubs for Tasks 3 & 4 (replace in later tasks) ------------------------- +# --- Entry extraction -------------------------------------------------------- + +_DESC_SEP_RE = re.compile(r"^\s*[-\u2013\u2014]\s*") + + +def _find_inline(node: SyntaxTreeNode) -> SyntaxTreeNode | None: + """Find the inline node in a list_item's paragraph.""" + for child in node.children: + if child.type == "paragraph": + for sub in child.children: + if sub.type == "inline": + return sub + return None + + +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 _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 _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: + # Subcategory label — recurse into nested bullet_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]: - return [] + """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 (stub for Task 4) -------------------------------- def _render_section_html(content_nodes: list[SyntaxTreeNode]) -> str: diff --git a/website/tests/test_readme_parser.py b/website/tests/test_readme_parser.py index 3f32e844..f0f53e92 100644 --- a/website/tests/test_readme_parser.py +++ b/website/tests/test_readme_parser.py @@ -5,7 +5,7 @@ import sys import textwrap sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) -from readme_parser import parse_readme, render_inline_html, render_inline_text +from readme_parser import _parse_section_entries, parse_readme, render_inline_html, render_inline_text from markdown_it import MarkdownIt from markdown_it.tree import SyntaxTreeNode @@ -200,3 +200,106 @@ class TestParseReadmeSections: """) cats, _ = parse_readme(readme) assert cats[0]["description"] == "Algorithms. Also see awesome-algos." + + +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_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. + """) + cats, _ = parse_readme(readme) + # 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. + """) + cats, _ = parse_readme(readme) + assert cats[0]["preview"] == "alpha, beta, gamma, delta" + + def test_description_html_escapes_xss(self): + nodes = _content_nodes('- [lib](https://x.com) - A lib.\n') + entries = _parse_section_entries(nodes) + assert "\n") + html = _render_section_html(nodes) + assert "\n") html = _render_section_html(nodes) assert " diff --git a/website/templates/index.html b/website/templates/index.html index 88a5337a..3231c1a4 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -51,21 +51,21 @@
    {{ "{:,}".format(entries | length) }}
    -
    unique projects indexed
    +
    projects
    {{ total_categories }}
    -
    categories to search
    +
    categories
    {{ groups | length }}
    -
    editorial groupings
    +
    topic groups
    - Scroll into the index + Jump to the list @@ -73,11 +73,11 @@
    -

    One searchable surface for the ecosystem

    +

    Search every project in one place

    - Use / to focus search, tap a tag to filter, and open a row for - descriptions, related projects, and source details. + Press / to search. Tap a tag to filter. Click any row for + details.

    @@ -127,7 +127,7 @@ Project Name GitHub Stars Last Commit - Category + Tags - {% endfor %} {% if entry.source_type == 'Built-in' %} + {% endfor %} + + {% if entry.source_type == 'Built-in' %} → From 5a8c565a882eda8f956df09ad35a3f54ddef374b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:24:24 +0800 Subject: [PATCH 0325/1130] style(css): add background color to final-cta section Co-Authored-By: Claude --- website/static/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/website/static/style.css b/website/static/style.css index 6f80e1a0..1db22f55 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -835,6 +835,7 @@ th[data-sort].sort-asc::after { .final-cta { padding-block: clamp(3rem, 7vw, 5.5rem); + background: oklch(94% 0.025 72); display: grid; gap: 1rem; } From 0308fd1b3ca71ac1ed35fc593a9c68ba662e369e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:26:36 +0800 Subject: [PATCH 0326/1130] feat: show category label on mobile in project name column On narrow screens the category column is hidden. This adds a .mobile-cat span inside the name cell that renders the first category below the project name, giving mobile users the context they were missing. Co-Authored-By: Claude --- website/static/style.css | 16 ++++++++++++++++ website/templates/index.html | 1 + 2 files changed, 17 insertions(+) diff --git a/website/static/style.css b/website/static/style.css index 1db22f55..16a16c1d 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -611,6 +611,10 @@ kbd { white-space: nowrap; } +.mobile-cat { + display: none; +} + .col-name > a { color: var(--ink); font-size: clamp(1rem, 1.5vw, 1.08rem); @@ -1066,6 +1070,18 @@ th[data-sort].sort-asc::after { display: none; } + .col-name { + white-space: normal; + } + + .mobile-cat { + display: block; + margin-top: 0.25rem; + font-size: var(--text-tag); + font-weight: 600; + color: var(--ink-muted); + } + .col-stars { width: 5.4rem; } diff --git a/website/templates/index.html b/website/templates/index.html index 8181079d..88960135 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -151,6 +151,7 @@ {{ entry.name }} + {{ entry.categories[0] }} {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% From 97f18d295f1832fdb265b53e848650319e48c718 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:28:25 +0800 Subject: [PATCH 0327/1130] feat: add clear action to no-results message When a search or filter yields no results, the message now includes an inline button that resets both the search input and the active filter. Improves discoverability and reduces dead-end frustration. Co-Authored-By: Claude --- website/static/main.js | 10 ++++++++++ website/static/style.css | 20 ++++++++++++++++++++ website/templates/index.html | 5 ++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/website/static/main.js b/website/static/main.js index 97b56e2f..223100ea 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -270,6 +270,16 @@ if (filterClear) { }); } +// No-results clear +var noResultsClear = document.querySelector('.no-results-clear'); +if (noResultsClear) { + noResultsClear.addEventListener('click', function () { + if (searchInput) searchInput.value = ''; + activeFilter = null; + applyFilters(); + }); +} + // Column sorting document.querySelectorAll('th[data-sort]').forEach(function (th) { th.addEventListener('click', function () { diff --git a/website/static/style.css b/website/static/style.css index 16a16c1d..da525151 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -837,6 +837,26 @@ th[data-sort].sort-asc::after { font-size: var(--text-lg); } +.no-results-hint { + margin-top: 0.5rem; + font-size: var(--text-sm); +} + +.no-results-clear { + background: none; + border: none; + color: var(--accent-deep); + font: inherit; + cursor: pointer; + text-decoration: underline; + text-decoration-color: oklch(58% 0.16 45 / 0.4); + text-underline-offset: 0.2em; +} + +.no-results-clear:hover { + color: var(--accent); +} + .final-cta { padding-block: clamp(3rem, 7vw, 5.5rem); background: oklch(94% 0.025 72); diff --git a/website/templates/index.html b/website/templates/index.html index 88960135..f3183f30 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -237,7 +237,10 @@ - +
    From d3070b735e0d507c43f0cd79fd3d58b2492aa887 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:30:04 +0800 Subject: [PATCH 0328/1130] feat: add build date to footer Displays the UTC date the site was last built in the footer so visitors can see how fresh the data is. Co-Authored-By: Claude --- website/build.py | 2 ++ website/static/style.css | 11 +++++++++++ website/templates/base.html | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/website/build.py b/website/build.py index 5ab9c9ef..af10ebb2 100644 --- a/website/build.py +++ b/website/build.py @@ -4,6 +4,7 @@ import json import re import shutil +from datetime import datetime, timezone from pathlib import Path from typing import TypedDict @@ -191,6 +192,7 @@ def build(repo_root: str) -> None: entries=entries, total_entries=total_entries, total_categories=len(categories), + build_date=datetime.now(timezone.utc).strftime("%B %d, %Y"), ), encoding="utf-8", ) diff --git a/website/static/style.css b/website/static/style.css index da525151..5133dab7 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -908,12 +908,23 @@ th[data-sort].sort-asc::after { text-underline-offset: 0.2em; } +.footer-left { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + .footer-brand { font-weight: 700; letter-spacing: 0.03em; color: oklch(82% 0.02 75); } +.footer-date { + font-size: 0.7rem; + color: oklch(50% 0.02 55); +} + .footer-links { display: block; text-align: right; diff --git a/website/templates/base.html b/website/templates/base.html index 9a17e0cc..cf48cfd5 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -44,7 +44,10 @@
    {% block content %}{% endblock %}
    - Awesome Python +
    From 38412182e7da1fb2e36e651e1b2c022e92ce2081 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:32:35 +0800 Subject: [PATCH 0331/1130] style: increase footer vertical padding from 2rem to 3rem Co-Authored-By: Claude --- website/static/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 5133dab7..78a2c73d 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -888,7 +888,7 @@ th[data-sort].sort-asc::after { .footer { margin-top: auto; background: oklch(16% 0.025 35); - padding: 2rem var(--shell-pad); + padding: 3rem var(--shell-pad); display: flex; align-items: center; justify-content: space-between; From 53d280ddcf4e8e646f1f1f421c5ee396ddede351 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:37:59 +0800 Subject: [PATCH 0332/1130] fix(css): scope final-cta grid to inner section-shell wrapper Move display:grid and gap from .final-cta to .final-cta > .section-shell so the grid context is applied to the correct container element, not the outer section. Wrap final-cta content in index.html with a section-shell div accordingly. Also fix .no-results bottom padding that was missing. Co-Authored-By: Claude --- website/static/style.css | 5 ++++- website/templates/index.html | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 78a2c73d..98fa665b 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -831,7 +831,7 @@ th[data-sort].sort-asc::after { } .no-results { - padding: 2.4rem var(--shell-pad) 0; + padding: 2.4rem var(--shell-pad); text-align: center; color: var(--ink-muted); font-size: var(--text-lg); @@ -860,6 +860,9 @@ th[data-sort].sort-asc::after { .final-cta { padding-block: clamp(3rem, 7vw, 5.5rem); background: oklch(94% 0.025 72); +} + +.final-cta > .section-shell { display: grid; gap: 1rem; } diff --git a/website/templates/index.html b/website/templates/index.html index d365de95..6abd906a 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -243,7 +243,8 @@
    -
    +
    +

    Know a project that belongs here?

    Tell us what it does and why it stands out.

    @@ -263,5 +264,6 @@ >Star the repository
    +
    {% endblock %} From b12d80f67e7a43707ff77227c1a0f7f56f689158 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:42:35 +0800 Subject: [PATCH 0333/1130] fix(readme): correct playwright entry name to playwright-python The project name in the entry was 'playwright' but the GitHub repository is 'playwright-python', which is also how the Python package is referred to. Updated the display name to match. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed5b19c3..d49b1a71 100644 --- a/README.md +++ b/README.md @@ -599,7 +599,7 @@ _Libraries for testing codebases and generating test data._ - [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. + - [playwright-python](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/). From 5fc022d59589b6dd0fc9e37d833af6fb8538aa25 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:45:18 +0800 Subject: [PATCH 0334/1130] refactor(build): remove resources from build pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resources are no longer passed through parse_readme, group_categories, or the index template — they are replaced with empty lists and the unused variable is prefixed with an underscore. Co-Authored-By: Claude --- website/build.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/build.py b/website/build.py index af10ebb2..29cbfc09 100644 --- a/website/build.py +++ b/website/build.py @@ -152,11 +152,11 @@ def build(repo_root: str) -> None: subtitle = stripped break - parsed_groups, resources = parse_readme(readme_text) + 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) + groups = group_categories(parsed_groups, []) entries = extract_entries(categories, groups) stars_data = load_stars(website / "data" / "github_stars.json") @@ -186,7 +186,7 @@ def build(repo_root: str) -> None: (site_dir / "index.html").write_text( tpl_index.render( categories=categories, - resources=resources, + resources=[], groups=groups, subtitle=subtitle, entries=entries, @@ -204,7 +204,7 @@ def build(repo_root: str) -> None: (site_dir / "llms.txt").write_text(readme_text, encoding="utf-8") - print(f"Built single page with {len(parsed_groups)} groups, {len(categories)} categories + {len(resources)} resources") + print(f"Built single page with {len(parsed_groups)} groups, {len(categories)} categories") print(f"Total entries: {total_entries}") print(f"Output: {site_dir}") From df2191fc053b3d746365208563cd3cbe9ade21b5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 15:58:42 +0800 Subject: [PATCH 0335/1130] refactor(build): remove unused group_categories wrapper group_categories only ever appended a Resources group when the resources list was non-empty. All call sites passed an empty list, making it a no-op indirection. Inline parsed_groups directly and remove the dead code along with its tests. Co-Authored-By: Claude --- website/build.py | 29 ++++------------------------- website/tests/test_build.py | 35 ----------------------------------- 2 files changed, 4 insertions(+), 60 deletions(-) diff --git a/website/build.py b/website/build.py index 29cbfc09..2a6116a8 100644 --- a/website/build.py +++ b/website/build.py @@ -9,26 +9,7 @@ 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 +from readme_parser import parse_readme class StarData(TypedDict): @@ -152,12 +133,11 @@ def build(repo_root: str) -> None: subtitle = stripped break - parsed_groups, _resources = parse_readme(readme_text) + parsed_groups, _ = 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, []) - entries = extract_entries(categories, groups) + entries = extract_entries(categories, parsed_groups) stars_data = load_stars(website / "data" / "github_stars.json") for entry in entries: @@ -186,8 +166,7 @@ def build(repo_root: str) -> None: (site_dir / "index.html").write_text( tpl_index.render( categories=categories, - resources=[], - groups=groups, + groups=parsed_groups, subtitle=subtitle, entries=entries, total_entries=total_entries, diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 6302c3d3..0e7eb487 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -8,7 +8,6 @@ from pathlib import Path from build import ( build, extract_github_repo, - group_categories, load_stars, sort_entries, ) @@ -42,40 +41,6 @@ class TestSlugify: 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) # --------------------------------------------------------------------------- From cd3c8ad0768575e03ae39f2652c97a2d26402ee6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:03:47 +0800 Subject: [PATCH 0336/1130] refactor(css): consolidate --text-label and --text-tag into --text-xs Both variables mapped to near-identical small-text sizes already covered by --text-xs. Remove the redundant variables and migrate all call sites, including two remaining hardcoded rem values, to the shared token. Co-Authored-By: Claude --- website/static/style.css | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 98fa665b..fbccd2d2 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -35,8 +35,6 @@ --text-sm: 0.95rem; --text-base: 1rem; --text-lg: 1.125rem; - --text-label: 0.78rem; - --text-tag: 0.76rem; } html { @@ -183,7 +181,7 @@ kbd { } .hero-brand-mini { - font-size: var(--text-label); + font-size: var(--text-xs); font-weight: 800; letter-spacing: 0.04em; color: var(--hero-muted); @@ -250,7 +248,7 @@ kbd { .hero-kicker, .section-label { margin-bottom: 0.9rem; - font-size: var(--text-label); + font-size: var(--text-xs); font-weight: 800; letter-spacing: 0.04em; } @@ -678,7 +676,7 @@ th[data-sort].sort-asc::after { border-radius: 999px; background: var(--bg-paper-strong); color: var(--ink-soft); - font-size: 0.72rem; + font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.02em; } @@ -774,7 +772,7 @@ th[data-sort].sort-asc::after { background: var(--accent-soft); color: var(--accent-deep); padding: 0.34rem 0.68rem; - font-size: var(--text-tag); + font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.02em; cursor: pointer; @@ -810,7 +808,7 @@ th[data-sort].sort-asc::after { border: 0; background: none; color: var(--accent-deep); - font-size: 0.72rem; + font-size: var(--text-xs); font-weight: 800; letter-spacing: 0.03em; cursor: pointer; @@ -924,7 +922,7 @@ th[data-sort].sort-asc::after { } .footer-date { - font-size: 0.7rem; + font-size: var(--text-xs); color: oklch(50% 0.02 55); } @@ -1111,7 +1109,7 @@ th[data-sort].sort-asc::after { .mobile-cat { display: block; margin-top: 0.25rem; - font-size: var(--text-tag); + font-size: var(--text-xs); font-weight: 600; color: var(--ink-muted); } From 4bb9c1240b64a15b5a96769f57553491809d4cc6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:10:14 +0800 Subject: [PATCH 0337/1130] fix(website): accessibility and defensive layout improvements - Add aria-sort attributes to table header on sort state changes - Replace .table-wrap:focus outline:none with focus-visible outline - Move noscript message above the fold into main, before content - Upgrade hero-topbar div to nav with aria-label for landmark semantics - Remove role=button from tr elements (invalid ARIA on native elements) - Fix back-to-top button label and text (was labelled 'Back to search') - Switch font-size from 16px to 100% to respect user browser preferences - Add overflow-wrap and word-break to .col-name and description cells Co-Authored-By: Claude --- website/static/main.js | 3 +++ website/static/style.css | 11 ++++++++--- website/templates/base.html | 15 +++++++++------ website/templates/index.html | 13 ++++++------- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/website/static/main.js b/website/static/main.js index c1d8d019..83d5be04 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -212,6 +212,9 @@ function updateSortIndicators() { th.classList.remove('sort-asc', 'sort-desc'); if (activeSort && th.dataset.sort === activeSort.col) { th.classList.add('sort-' + activeSort.order); + th.setAttribute('aria-sort', activeSort.order === 'asc' ? 'ascending' : 'descending'); + } else { + th.removeAttribute('aria-sort'); } }); } diff --git a/website/static/style.css b/website/static/style.css index fbccd2d2..26349eb3 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -38,7 +38,7 @@ } html { - font-size: 16px; + font-size: 100%; scroll-behavior: smooth; } @@ -523,8 +523,9 @@ kbd { scroll-margin-top: 1rem; } -.table-wrap:focus { - outline: none; +.table-wrap:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -2px; } .table { @@ -617,6 +618,8 @@ kbd { color: var(--ink); font-size: clamp(1rem, 1.5vw, 1.08rem); font-weight: 700; + overflow-wrap: break-word; + word-break: break-word; } .col-name > a:hover { @@ -734,6 +737,8 @@ th[data-sort].sort-asc::after { color: var(--ink-soft); line-height: 1.7; text-wrap: pretty; + overflow-wrap: break-word; + word-break: break-word; animation: expand-in 220ms cubic-bezier(0.22, 1, 0.36, 1); } diff --git a/website/templates/base.html b/website/templates/base.html index cf48cfd5..317594d0 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -41,7 +41,15 @@ -
    {% block content %}{% endblock %}
    + {% block header %}{% endblock %} +
    + + {% block content %}{% endblock %} +
    - diff --git a/website/templates/index.html b/website/templates/index.html index 6abd906a..7168ba88 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -1,10 +1,10 @@ -{% extends "base.html" %} {% block content %} +{% extends "base.html" %} {% block header %}
    -
    +
    +
    @@ -68,7 +68,7 @@ Jump to the list
    - +{% endblock %} {% block content %}
    @@ -129,8 +129,8 @@ Last Commit Tags - @@ -139,7 +139,6 @@ {% for entry in entries %} Date: Sun, 22 Mar 2026 16:12:10 +0800 Subject: [PATCH 0338/1130] perf(fonts): trim Google Fonts to weights in use Narrowed the Cormorant Garamond request to 600 only and Manrope to 400/600/700/800, removing the unused 500 and 700 variants. Added font-weight: 600 to .final-cta h2 so the heading explicitly uses the retained weight. Co-Authored-By: Claude --- website/static/style.css | 1 + website/templates/base.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 26349eb3..efc2afb2 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -400,6 +400,7 @@ kbd { .final-cta h2 { font-family: var(--font-display); font-size: clamp(2.2rem, 4vw, 3.3rem); + font-weight: 600; line-height: 0.94; letter-spacing: -0.03em; } diff --git a/website/templates/base.html b/website/templates/base.html index 317594d0..5bca928d 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -21,7 +21,7 @@ From 80a5596b1169d946ba76290df5e8afa63a66d077 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:13:08 +0800 Subject: [PATCH 0339/1130] perf(hero): pause animations when hero scrolls out of view Uses IntersectionObserver to toggle an .offscreen class on the hero element, which sets animation-play-state: paused on the sheen, noise, and scroll-cue animations. Avoids unnecessary GPU work while the hero is not visible. Co-Authored-By: Claude --- website/static/main.js | 10 ++++++++++ website/static/style.css | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/website/static/main.js b/website/static/main.js index 83d5be04..da58fe22 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -41,6 +41,16 @@ function initRevealSections() { initRevealSections(); +// Pause hero animations when scrolled out of view +(function () { + var hero = document.querySelector('.hero'); + if (!hero || !('IntersectionObserver' in window)) return; + var observer = new IntersectionObserver(function (entries) { + hero.classList.toggle('offscreen', !entries[0].isIntersecting); + }); + observer.observe(hero); +})(); + // Relative time formatting function relativeTime(isoStr) { var date = new Date(isoStr); diff --git a/website/static/style.css b/website/static/style.css index efc2afb2..17cd5fc6 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -152,6 +152,12 @@ kbd { animation: sheen-drift 18s linear infinite; } +.hero.offscreen .hero-sheen, +.hero.offscreen .hero-noise, +.hero.offscreen .hero-scrollcue::after { + animation-play-state: paused; +} + .hero-noise { opacity: 0.1; background-image: From 50e27b992fd05d4994f2661b956437d6c60ca424 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:14:11 +0800 Subject: [PATCH 0340/1130] perf(css): add CSS containment to results section and detail panel Apply contain: layout style to .results-section and contain: layout style paint to the detail panel element to reduce browser layout recalculation scope during search interactions. Co-Authored-By: Claude --- website/static/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/static/style.css b/website/static/style.css index 17cd5fc6..0e477515 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -413,6 +413,7 @@ kbd { .results-section { padding-block: clamp(2.5rem, 6vw, 4.5rem) 0; + contain: layout style; } .results-intro { @@ -746,6 +747,7 @@ th[data-sort].sort-asc::after { text-wrap: pretty; overflow-wrap: break-word; word-break: break-word; + contain: layout style paint; animation: expand-in 220ms cubic-bezier(0.22, 1, 0.36, 1); } From 302ae14c2d8d58749c6d3c91091757f6840c0003 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:14:56 +0800 Subject: [PATCH 0341/1130] refactor(css): remove backdrop-filter blur from table header Drops the blur(14px) backdrop-filter on the sticky table header and raises the background opacity from 0.92 to 0.97 so the header remains clearly readable without the compositing overhead. Co-Authored-By: Claude --- website/static/style.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 0e477515..078d3344 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -571,8 +571,7 @@ kbd { letter-spacing: 0.03em; white-space: nowrap; border-bottom: 1px solid var(--line); - background: oklch(98.2% 0.012 80 / 0.92); - backdrop-filter: blur(14px); + background: oklch(98.2% 0.012 80 / 0.97); } .table tbody td { From 80a50511958318272d5728dfdbb0ae6c1cbad4a6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:16:36 +0800 Subject: [PATCH 0342/1130] fix(css): increase expand meta/also-see font size to --text-sm --text-xs was too small for secondary metadata rows; bump to --text-sm for better readability. Co-Authored-By: Claude --- website/static/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 078d3344..d9f5e463 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -769,7 +769,7 @@ th[data-sort].sort-asc::after { .expand-also-see, .expand-meta { margin-top: 0.45rem; - font-size: var(--text-xs); + font-size: var(--text-sm); color: var(--ink-muted); } From 6648961d7beff7038d90a1802474c764f5445886 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:19:38 +0800 Subject: [PATCH 0343/1130] fix(css): hide col-num and expand-row first-child at col-cat breakpoint Co-Authored-By: Claude --- website/static/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index d9f5e463..ba5d011e 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1111,7 +1111,9 @@ th[data-sort].sort-asc::after { padding-right: 0.8rem; } - .col-cat { + .col-num, + .col-cat, + .expand-row td:first-child { display: none; } From 86aa6232609626ac98906ce0308768e4fd2e8197 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:20:14 +0800 Subject: [PATCH 0344/1130] fix(css): increase tag padding on mobile breakpoint Co-Authored-By: Claude --- website/static/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/static/style.css b/website/static/style.css index ba5d011e..afe6bd1d 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1043,6 +1043,10 @@ th[data-sort].sort-asc::after { .tag-group { display: none; } + + .tag { + padding: 0.5rem 0.85rem; + } } @media (max-width: 680px) { From f2b635da19b90f0ca51650b891d62f2179b5ff81 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:20:52 +0800 Subject: [PATCH 0345/1130] fix(css): truncate long links in expand-meta and add mobile padding to expand-row .expand-meta links can overflow their container on narrow viewports. Apply ellipsis truncation to keep the row tidy. .expand-row td[colspan] gains symmetric inline padding on mobile to match the surrounding table spacing. Co-Authored-By: Claude --- website/static/style.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/website/static/style.css b/website/static/style.css index afe6bd1d..206cc036 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -773,6 +773,15 @@ th[data-sort].sort-asc::after { color: var(--ink-muted); } +.expand-meta a { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: bottom; +} + .expand-sep { margin-inline: 0.25rem; color: var(--line-strong); @@ -1133,6 +1142,11 @@ th[data-sort].sort-asc::after { color: var(--ink-muted); } + .expand-row td[colspan] { + padding-left: 0.8rem; + padding-right: 0.8rem; + } + .col-stars { width: 5.4rem; } From 58c0fd9e452a16e83536889bc1483f73a7a1955d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:23:41 +0800 Subject: [PATCH 0346/1130] fix(css): extend focus-visible outline to no-results-clear and footer links Co-Authored-By: Claude --- website/static/style.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 206cc036..a1d8b38d 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -352,7 +352,9 @@ kbd { .search:focus-visible, .filter-clear:focus-visible, .tag:focus-visible, -.back-to-top:focus-visible { +.back-to-top:focus-visible, +.no-results-clear:focus-visible, +.footer a:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } From 895da326f65b8f1415cbf4c22e0136479176852b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:24:54 +0800 Subject: [PATCH 0347/1130] refactor(css): remove unused --bg-hover and --hero-shadow tokens Neither variable was referenced anywhere in the stylesheet. Removing dead tokens keeps the design token surface minimal. Co-Authored-By: Claude --- website/static/style.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index a1d8b38d..275e181c 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -15,7 +15,6 @@ --bg-page: oklch(96.8% 0.018 80); --bg-paper: oklch(98.6% 0.01 80); --bg-paper-strong: oklch(95.7% 0.016 76); - --bg-hover: oklch(93.8% 0.026 72); --ink: oklch(22% 0.02 55); --ink-soft: oklch(38% 0.018 55); --ink-muted: oklch(52% 0.02 55); @@ -26,7 +25,6 @@ --accent-soft: oklch(92% 0.045 55); --highlight: oklch(87% 0.08 78); --hero-ink: oklch(15% 0.02 40); - --hero-shadow: oklch(8% 0.02 35 / 0.5); --hero-text: oklch(97% 0.012 85); --hero-muted: oklch(88% 0.02 82); --hero-line: oklch(100% 0 0 / 0.16); From 7fa0a425dc59aa3683ee37d30e852a652efa095e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:25:04 +0800 Subject: [PATCH 0348/1130] fix(css): remove outline:none suppression from .row:focus-visible td MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suppressing the outline on a focus-visible rule is self-defeating — it opts into the intent-based focus ring selector but then hides it. The row already receives a visible inset box-shadow on focus, so the outline:none was redundant and harmful to keyboard accessibility. Co-Authored-By: Claude --- website/static/style.css | 1 - 1 file changed, 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 275e181c..c28e3a7b 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -597,7 +597,6 @@ kbd { } .row:focus-visible td { - outline: none; background: oklch(95.7% 0.026 68); box-shadow: inset 3px 0 0 var(--accent); } From 3954a3e7423b8685e09feaf8151b0eb3478e9f71 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:26:27 +0800 Subject: [PATCH 0349/1130] refactor(css): extract footer color values into CSS custom properties Hardcoded oklch() values in footer rules are replaced with named tokens (--footer-bg, --footer-text, --footer-link, --footer-link-hover, --footer-muted, --footer-sep) declared in :root. No visual change. Co-Authored-By: Claude --- website/static/style.css | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index c28e3a7b..7ecd5724 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -33,6 +33,13 @@ --text-sm: 0.95rem; --text-base: 1rem; --text-lg: 1.125rem; + + --footer-bg: oklch(16% 0.025 35); + --footer-text: oklch(72% 0.02 75); + --footer-link: oklch(82% 0.02 75); + --footer-link-hover: oklch(95% 0.01 80); + --footer-muted: oklch(50% 0.02 55); + --footer-sep: oklch(55% 0.02 55); } html { @@ -909,22 +916,22 @@ th[data-sort].sort-asc::after { .footer { margin-top: auto; - background: oklch(16% 0.025 35); + background: var(--footer-bg); padding: 3rem var(--shell-pad); display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-size: var(--text-xs); - color: oklch(72% 0.02 75); + color: var(--footer-text); } .footer a { - color: oklch(82% 0.02 75); + color: var(--footer-link); } .footer a:hover { - color: oklch(95% 0.01 80); + color: var(--footer-link-hover); text-decoration: underline; text-decoration-color: oklch(95% 0.01 80 / 0.4); text-underline-offset: 0.2em; @@ -939,12 +946,12 @@ th[data-sort].sort-asc::after { .footer-brand { font-weight: 700; letter-spacing: 0.03em; - color: oklch(82% 0.02 75); + color: var(--footer-link); } .footer-date { font-size: var(--text-xs); - color: oklch(50% 0.02 55); + color: var(--footer-muted); } .footer-links { @@ -953,7 +960,7 @@ th[data-sort].sort-asc::after { } .footer-sep { - color: oklch(55% 0.02 55); + color: var(--footer-sep); } .noscript-msg { From 944787071535624b41c7d657ca3cbca1f11c1d1f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:26:33 +0800 Subject: [PATCH 0350/1130] feat(html): add theme-color meta tag for browser UI chrome Sets the mobile browser toolbar color to match the dark footer/page background (#1c1410), preventing a jarring white chrome flash on load. Co-Authored-By: Claude --- website/templates/base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/website/templates/base.html b/website/templates/base.html index 5bca928d..5e7cea95 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -17,6 +17,7 @@ /> + From 321df7b78c07bdde862ebb24cd0dde2d31c7d333 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:28:44 +0800 Subject: [PATCH 0351/1130] refactor(hero): remove metrics block from hero section The projects/categories/topic groups stats added visual clutter to the hero without contributing to the core purpose of the section. Co-Authored-By: Claude --- website/static/style.css | 40 ------------------------------------ website/templates/index.html | 15 -------------- 2 files changed, 55 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 7ecd5724..c77aa9e7 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -306,11 +306,6 @@ kbd { margin-top: 1.75rem; } -.hero-actions, -.hero-metrics { - width: 100%; -} - .hero-action { display: inline-flex; align-items: center; @@ -364,33 +359,6 @@ kbd { outline-offset: 3px; } -.hero-metrics { - margin-top: clamp(1.8rem, 4vw, 2.8rem); - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 1.5rem; - max-width: none; -} - -.hero-metrics div { - padding-top: 0.9rem; - border-top: 1px solid var(--hero-line); -} - -.hero-metrics dt { - font-size: clamp(1.6rem, 3.2vw, 2.4rem); - font-weight: 800; - line-height: 1; - color: var(--hero-text); -} - -.hero-metrics dd { - margin-top: 0.3rem; - color: var(--hero-muted); - font-size: var(--text-xs); - letter-spacing: 0.02em; -} - .hero-scrollcue { align-self: flex-start; display: inline-flex; @@ -1094,14 +1062,6 @@ th[data-sort].sort-asc::after { font-size: clamp(3.6rem, 18vw, 5.2rem); } - .hero-metrics { - gap: 1rem; - } - - .hero-metrics div { - min-width: 0; - } - .search { min-height: 3.5rem; border-radius: 1.25rem; diff --git a/website/templates/index.html b/website/templates/index.html index 7168ba88..226c789d 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -47,21 +47,6 @@ >View on GitHub
    - -
    -
    -
    {{ "{:,}".format(entries | length) }}
    -
    projects
    -
    -
    -
    {{ total_categories }}
    -
    categories
    -
    -
    -
    {{ groups | length }}
    -
    topic groups
    -
    -
    From 014ba9e39407d1104d14ffe29a645fef8defe02d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:29:47 +0800 Subject: [PATCH 0352/1130] refactor(hero): remove redundant scroll cue The "Jump to the list" anchor duplicated the "Browse the List" button. Removes the element, its CSS rules, the scroll-line keyframe animation, and cleans up the offscreen pause and focus-visible selector lists. Co-Authored-By: Claude Opus 4.6 (1M context) --- website/static/style.css | 36 ++---------------------------------- website/templates/index.html | 2 -- 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index c77aa9e7..73d147c4 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -158,8 +158,7 @@ kbd { } .hero.offscreen .hero-sheen, -.hero.offscreen .hero-noise, -.hero.offscreen .hero-scrollcue::after { +.hero.offscreen .hero-noise { animation-play-state: paused; } @@ -198,8 +197,7 @@ kbd { color: var(--hero-muted); } -.hero-topbar-link:hover, -.hero-scrollcue:hover { +.hero-topbar-link:hover { color: var(--hero-text); } @@ -348,7 +346,6 @@ kbd { .hero-action:focus-visible, .hero-topbar-link:focus-visible, -.hero-scrollcue:focus-visible, .search:focus-visible, .filter-clear:focus-visible, .tag:focus-visible, @@ -359,24 +356,6 @@ kbd { outline-offset: 3px; } -.hero-scrollcue { - align-self: flex-start; - display: inline-flex; - align-items: center; - gap: 0.65rem; - color: var(--hero-muted); - font-size: var(--text-xs); - letter-spacing: 0.04em; -} - -.hero-scrollcue::after { - content: ""; - width: 3rem; - height: 1px; - background: var(--hero-line); - animation: scroll-line 1.6s ease-in-out infinite; -} - .results-intro h2, .final-cta h2 { font-family: var(--font-display); @@ -984,17 +963,6 @@ th[data-sort].sort-asc::after { } } -@keyframes scroll-line { - 0%, 100% { - transform: scaleX(0.4); - transform-origin: left; - } - 50% { - transform: scaleX(1); - transform-origin: left; - } -} - @keyframes sheen-drift { from { transform: translateX(-30%); diff --git a/website/templates/index.html b/website/templates/index.html index 226c789d..cb8c276f 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -49,8 +49,6 @@ - - Jump to the list {% endblock %} {% block content %} From a12fef4e54eb9f2c01a60f71e690eadd9fde9814 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:30:14 +0800 Subject: [PATCH 0353/1130] refactor(results): remove redundant "Library index" section label The heading "Search every project in one place" already communicates the section's purpose. The label above it was visual noise. Co-Authored-By: Claude Opus 4.6 (1M context) --- website/templates/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/website/templates/index.html b/website/templates/index.html index cb8c276f..602df0bd 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -55,7 +55,6 @@
    -

    Search every project in one place

    From f11468b2629f3e398b1e04aab2477dcdb0773cc1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:36:55 +0800 Subject: [PATCH 0354/1130] remove(readme): remove python-cqrs from Design Patterns Co-Authored-By: Claude --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d49b1a71..061b899e 100644 --- a/README.md +++ b/README.md @@ -543,7 +543,6 @@ _Python implementation of data structures, algorithms and design patterns. Also - [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. From ef51d9a7aad0e19a7fee39aa1a07c5d047f55e4d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:38:48 +0800 Subject: [PATCH 0355/1130] refactor(html-xml): replace cssutils with tinycss2 cssutils is unmaintained; tinycss2 is the actively maintained low-level CSS parser and generator for Python. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 061b899e..c4e24b50 100644 --- a/README.md +++ b/README.md @@ -832,7 +832,7 @@ _Libraries for parsing and manipulating plain texts._ _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. +- [tinycss2](https://github.com/Kozea/tinycss2) - A low-level CSS parser and generator written in 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. From 8fbe0e03947d8be53ee857aea669864de302312b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:41:36 +0800 Subject: [PATCH 0356/1130] fix(readme): sort tinycss2 alphabetically in HTML/XML section Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4e24b50..231f65de 100644 --- a/README.md +++ b/README.md @@ -832,11 +832,11 @@ _Libraries for parsing and manipulating plain texts._ _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. -- [tinycss2](https://github.com/Kozea/tinycss2) - A low-level CSS parser and generator written in 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. +- [tinycss2](https://github.com/Kozea/tinycss2) - A low-level CSS parser and generator written in Python. - [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON. ## File Format Processing From 1b5a0c2a3c151748e741095fcd1ba885f4110333 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:41:42 +0800 Subject: [PATCH 0357/1130] docs(claude): expand project structure and entry format docs Documents the website/ build pipeline, Makefile targets, pyproject.toml tooling, and tightens the entry format example and key rules. Co-Authored-By: Claude --- CLAUDE.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 81d13417..15b939a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,31 +2,36 @@ ## 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. +A curated list of awesome Python frameworks, libraries, software and resources. Published at [awesome-python.com](https://awesome-python.com/). ## PR Review Guidelines -**For all PR review tasks, refer to [CONTRIBUTING.md](CONTRIBUTING.md)** which contains: +**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for acceptance criteria, quality requirements, rejection rules, and entry format. -- Acceptance criteria (Industry Standard, Rising Star, Hidden Gem) -- Quality requirements -- Automatic rejection criteria -- Entry format reference -- PR description template +## Structure -## Architecture & Structure +- **README.md**: Source of truth. Hierarchical categories with alphabetically ordered entries. +- **CONTRIBUTING.md**: Submission guidelines and review criteria. +- **website/**: Static site generator that builds awesome-python.com from README.md. + - `build.py`: Parses README.md and renders HTML via Jinja2 templates. + - `fetch_github_stars.py`: Fetches star counts into `website/data/`. + - `readme_parser.py`: Markdown-to-structured-data parser. + - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. + - `tests/`: Pytest tests for the build pipeline. +- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make fetch_github_stars`. +- **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. -The repository follows a single-file architecture: +## Entry Format -- **README.md**: All content in hierarchical structure (categories, subcategories, entries) -- **CONTRIBUTING.md**: Submission guidelines and review criteria -- **sort.py**: Script to enforce alphabetical ordering +```markdown +- [pypi-name](https://github.com/owner/repo) - Description ending with period. +``` -Entry format: `* [project-name](url) - Concise description ending with period.` +Use PyPI package name as display name. Use GitHub URLs when available. -## Key Considerations +## Key Rules -- 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 +- Alphabetical ordering within categories is mandatory. +- Quality over quantity. Only "awesome" projects. +- One project per PR. +- README.md is the single source of content truth. From 7d1007d3739cd6268451f309ace51b6f045313c8 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 16:44:18 +0800 Subject: [PATCH 0358/1130] docs(claude): clarify repo description and entry naming rule Reword the overview from 'curated list' to 'opinionated list' to better reflect editorial intent. Rename 'pypi-name' placeholder to 'project-name' and add a rule for projects not published on PyPI. Co-Authored-By: Claude --- CLAUDE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 15b939a8..7210cc41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Repository Overview -A curated list of awesome Python frameworks, libraries, software and resources. Published at [awesome-python.com](https://awesome-python.com/). +An opinionated list of Python frameworks, libraries, tools, and resources. Published at [awesome-python.com](https://awesome-python.com/). ## PR Review Guidelines @@ -24,10 +24,10 @@ A curated list of awesome Python frameworks, libraries, software and resources. ## Entry Format ```markdown -- [pypi-name](https://github.com/owner/repo) - Description ending with period. +- [project-name](https://github.com/owner/repo) - Description ending with period. ``` -Use PyPI package name as display name. Use GitHub URLs when available. +Use PyPI package name as display name. If not on PyPI, use the GitHub repo name. Use GitHub URLs when available. ## Key Rules From a92b1a6e867fd4088b521086c2cff1851c8cae6f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 23:42:18 +0800 Subject: [PATCH 0359/1130] fix(css): fix hero topbar layout on small screens Split hero-topbar and footer selectors to apply distinct responsive styles. The topbar now uses a horizontal row layout with nowrap so the nav link does not stack vertically, while hero-topbar-actions and hero-topbar-link get auto widths to avoid stretching full-width. Co-Authored-By: Claude --- website/static/style.css | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 73d147c4..7e0f8473 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1005,19 +1005,34 @@ th[data-sort].sort-asc::after { min-height: auto; } - .hero-topbar, + .hero-topbar { + align-items: center; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + gap: 0.75rem; + } + .footer { align-items: flex-start; flex-direction: column; } - .hero-topbar-actions, .hero-actions, .final-cta-actions { width: 100%; } - .hero-topbar-link, + .hero-topbar-actions { + width: auto; + flex: 0 0 auto; + } + + .hero-topbar-link { + width: auto; + white-space: nowrap; + } + .hero-action { width: 100%; } From dbff2522c879813a202d87ba31cd08cdff88bcc9 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 23:52:31 +0800 Subject: [PATCH 0360/1130] fix(css): hide last column in expand-row on mobile Co-Authored-By: Claude --- website/static/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 7e0f8473..38eeab29 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1075,7 +1075,8 @@ th[data-sort].sort-asc::after { .col-num, .col-cat, - .expand-row td:first-child { + .expand-row td:first-child, + .expand-row td:last-child { display: none; } From 88031d78a5997b896e321feac08d5766b816a052 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 22 Mar 2026 23:58:54 +0800 Subject: [PATCH 0361/1130] fix(css): center footer on mobile Consolidate two conflicting mobile footer rules into one, setting flex-direction to column, align-items to center, and text-align to center so the footer is symmetrically centered on narrow viewports. Co-Authored-By: Claude --- website/static/style.css | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 38eeab29..724c133a 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1014,8 +1014,9 @@ th[data-sort].sort-asc::after { } .footer { - align-items: flex-start; flex-direction: column; + align-items: center; + text-align: center; } .hero-actions, @@ -1037,10 +1038,6 @@ th[data-sort].sort-asc::after { width: 100%; } - .footer { - align-items: flex-end; - } - .hero h1 { font-size: clamp(3.6rem, 18vw, 5.2rem); } From 3395b2e4284609c0c0ded72839fe3dd3f30add13 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 00:09:06 +0800 Subject: [PATCH 0362/1130] fix(css): enable table horizontal scroll at 768px breakpoint Moves .table-wrap overflow-x rule from the 680px breakpoint to 768px so the table becomes scrollable before it gets too narrow to read. Co-Authored-By: Claude --- website/static/style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 724c133a..5c3d796f 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -998,6 +998,10 @@ th[data-sort].sort-asc::after { .tag { padding: 0.5rem 0.85rem; } + + .table-wrap { + overflow-x: auto; + } } @media (max-width: 680px) { @@ -1047,10 +1051,6 @@ th[data-sort].sort-asc::after { border-radius: 1.25rem; } - .table-wrap { - overflow-x: auto; - } - .table thead th { position: static; } From 25d3f307cc3ddbe61f5660a194e94ba5a801d509 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 00:25:35 +0800 Subject: [PATCH 0363/1130] docs(readme): reword sponsorship tagline Replace 'where Python developers discover tools' with 'in front of Python developers' for cleaner, more direct phrasing. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 231f65de..e396e42d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An opinionated list of Python frameworks, libraries, tools, and resources. -> The **#10 most-starred repo on GitHub**. Put your product where Python developers discover tools. [Become a sponsor](SPONSORSHIP.md). +> The **#10 most-starred repo on GitHub**. Put your product in front of Python developers. [Become a sponsor](SPONSORSHIP.md). # Categories From 394803d2be09af2cd89897e9d012cfac83d4f66b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 00:58:31 +0800 Subject: [PATCH 0364/1130] docs(readme): add agent skills entries Add Python-focused descriptions for Trail of Bits, Sentry Skills, and Django AI Plugins in the AI and Agents section. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e396e42d..b9e622a6 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,10 @@ An opinionated list of Python frameworks, libraries, tools, and resources. _Libraries for building AI applications, LLM integrations, and autonomous agents._ +- Agent Skills + - [django-ai-plugins](https://github.com/vintasoftware/django-ai-plugins) - Django backend agent skills for Django, DRF, Celery, and Django-specific code review. + - [sentry-skills](https://github.com/getsentry/skills) - Python-focused engineering skills for code review, debugging, and backend workflows. + - [trailofbits](https://github.com/trailofbits/skills) - Python-friendly security skills for auditing, testing, and safer backend development. - 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. From 1c249d4b5fe59bd563120bbdc41606ca3426d2a2 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:04:13 +0800 Subject: [PATCH 0365/1130] docs(readme): rename Testing Frameworks subcategory to Frameworks Shorter label that reads more naturally in the context of the Testing section, which already scopes it to testing. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9e622a6..356099a3 100644 --- a/README.md +++ b/README.md @@ -591,7 +591,7 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- _Libraries for testing codebases and generating test data._ -- Testing Frameworks +- 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. From f2b4a7bc83ef4a61a9e8bbd574dd302ac4926356 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:04:20 +0800 Subject: [PATCH 0366/1130] feat(website): surface subcategory labels as filterable tags Entries nested under a plain-text subcategory heading (e.g. "Frameworks" inside Testing) now carry a subcategory field populated by the parser. The build pipeline collects these into a subcategories list on each merged entry, and the template renders them as tag-subcat buttons that plug into the existing data-cats filter mechanism. A dedicated .tag-subcat style distinguishes them visually from category tags, and both are hidden on mobile alongside .tag-group. Co-Authored-By: Claude --- website/build.py | 4 ++++ website/readme_parser.py | 11 +++++++++-- website/static/style.css | 9 ++++++++- website/templates/index.html | 10 +++++++++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/website/build.py b/website/build.py index 2a6116a8..821c6814 100644 --- a/website/build.py +++ b/website/build.py @@ -102,6 +102,9 @@ def extract_entries( existing["categories"].append(cat["name"]) if group_name not in existing["groups"]: existing["groups"].append(group_name) + subcat = entry["subcategory"] + if subcat and subcat not in existing["subcategories"]: + existing["subcategories"].append(subcat) else: merged = { "name": entry["name"], @@ -109,6 +112,7 @@ def extract_entries( "description": entry["description"], "categories": [cat["name"]], "groups": [group_name], + "subcategories": [entry["subcategory"]] if entry["subcategory"] else [], "stars": None, "owner": None, "last_commit_at": None, diff --git a/website/readme_parser.py b/website/readme_parser.py index c0ecfc60..1068a339 100644 --- a/website/readme_parser.py +++ b/website/readme_parser.py @@ -20,6 +20,7 @@ class ParsedEntry(TypedDict): url: str description: str # inline HTML, properly escaped also_see: list[AlsoSee] + subcategory: str # sub-category label, empty if none class ParsedSection(TypedDict): @@ -178,7 +179,11 @@ def _extract_description_html(inline: SyntaxTreeNode, first_link: SyntaxTreeNode return _DESC_SEP_RE.sub("", html) -def _parse_list_entries(bullet_list: SyntaxTreeNode) -> list[ParsedEntry]: +def _parse_list_entries( + bullet_list: SyntaxTreeNode, + *, + subcategory: str = "", +) -> list[ParsedEntry]: """Extract entries from a bullet_list AST node. Handles three patterns: @@ -200,9 +205,10 @@ def _parse_list_entries(bullet_list: SyntaxTreeNode) -> list[ParsedEntry]: 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 + label = render_inline_text(inline.children) nested = _find_child(list_item, "bullet_list") if nested: - entries.extend(_parse_list_entries(nested)) + entries.extend(_parse_list_entries(nested, subcategory=label)) continue # Entry with a link @@ -231,6 +237,7 @@ def _parse_list_entries(bullet_list: SyntaxTreeNode) -> list[ParsedEntry]: url=url, description=desc_html, also_see=also_see, + subcategory=subcategory, )) return entries diff --git a/website/static/style.css b/website/static/style.css index 5c3d796f..97d6b864 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -779,6 +779,12 @@ th[data-sort].sort-asc::after { color: var(--hero-ink); } +.tag-subcat { + background: var(--bg-paper-strong); + color: var(--ink-soft); + font-weight: 600; +} + .back-to-top { border: 0; background: none; @@ -991,7 +997,8 @@ th[data-sort].sort-asc::after { display: none; } - .tag-group { + .tag-group, + .tag-subcat { display: none; } diff --git a/website/templates/index.html b/website/templates/index.html index 602df0bd..0689d260 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -121,7 +121,7 @@ {% for entry in entries %} {{ cat }} + {% endfor %} {% for subcat in entry.subcategories %} + {% endfor %} - {% endfor %} {% for subcat in entry.subcategories %} + {% for subcat in entry.subcategories %} + {% endfor %} {% for cat in entry.categories %} + {% endfor %} {% endfor %} {% for cat in entry.categories %} From 31fa9a4c3844f2fc0f816939d99c91ee40ca6ae6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:32:55 +0800 Subject: [PATCH 0375/1130] fix(css): reduce tag badge size and spacing --- website/static/style.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 5c3d796f..daf018f6 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -746,8 +746,8 @@ th[data-sort].sort-asc::after { border-radius: 999px; background: var(--accent-soft); color: var(--accent-deep); - padding: 0.34rem 0.68rem; - font-size: var(--text-xs); + padding: 0.14rem 0.48rem; + font-size: 0.6rem; font-weight: 700; letter-spacing: 0.02em; cursor: pointer; @@ -759,13 +759,13 @@ th[data-sort].sort-asc::after { } .tag + .tag { - margin-left: 0.4rem; + margin-left: 0.2rem; } .tag::after { content: ""; position: absolute; - inset: -0.45rem -0.2rem; + inset: -0.35rem -0.15rem; } .tag:hover { @@ -996,7 +996,7 @@ th[data-sort].sort-asc::after { } .tag { - padding: 0.5rem 0.85rem; + padding: 0.32rem 0.6rem; } .table-wrap { From 028c642a8e87ab3421caa7be1d0165d002729034 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:34:33 +0800 Subject: [PATCH 0376/1130] refactor(js): replace var with let/const and use double quotes Modernize variable declarations and string literals in main.js for consistency and to signal immutability intent. Pure style change with no behavioral differences. Co-Authored-By: Claude --- website/static/main.js | 331 ++++++++++++++++++++++------------------- 1 file changed, 181 insertions(+), 150 deletions(-) diff --git a/website/static/main.js b/website/static/main.js index da58fe22..f1150b0c 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -1,40 +1,43 @@ // 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'); +let activeFilter = null; // { type: "cat"|"group", value: "..." } +let activeSort = { col: "stars", order: "desc" }; +const searchInput = document.querySelector(".search"); +const filterBar = document.querySelector(".filter-bar"); +const filterValue = document.querySelector(".filter-value"); +const filterClear = document.querySelector(".filter-clear"); +const noResults = document.querySelector(".no-results"); +const rows = document.querySelectorAll(".table tbody tr.row"); +const tags = document.querySelectorAll(".tag"); +const tbody = document.querySelector(".table tbody"); function initRevealSections() { - var sections = document.querySelectorAll('[data-reveal]'); + const sections = document.querySelectorAll("[data-reveal]"); if (!sections.length) return; - if (!('IntersectionObserver' in window)) { + if (!("IntersectionObserver" in window)) { sections.forEach(function (section) { - section.classList.add('is-visible'); + section.classList.add("is-visible"); }); return; } - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - entry.target.classList.add('is-visible'); - observer.unobserve(entry.target); - }); - }, { - threshold: 0.12, - rootMargin: '0px 0px -8% 0px', - }); + const observer = new IntersectionObserver( + function (entries) { + entries.forEach(function (entry) { + if (!entry.isIntersecting) return; + entry.target.classList.add("is-visible"); + observer.unobserve(entry.target); + }); + }, + { + threshold: 0.12, + rootMargin: "0px 0px -8% 0px", + }, + ); sections.forEach(function (section, index) { - section.classList.add('will-reveal'); - section.style.transitionDelay = Math.min(index * 70, 180) + 'ms'; + section.classList.add("will-reveal"); + section.style.transitionDelay = Math.min(index * 70, 180) + "ms"; observer.observe(section); }); } @@ -43,34 +46,36 @@ initRevealSections(); // Pause hero animations when scrolled out of view (function () { - var hero = document.querySelector('.hero'); - if (!hero || !('IntersectionObserver' in window)) return; - var observer = new IntersectionObserver(function (entries) { - hero.classList.toggle('offscreen', !entries[0].isIntersecting); + const hero = document.querySelector(".hero"); + if (!hero || !("IntersectionObserver" in window)) return; + const observer = new IntersectionObserver(function (entries) { + hero.classList.toggle("offscreen", !entries[0].isIntersecting); }); observer.observe(hero); })(); // 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'; + const date = new Date(isoStr); + const now = new Date(); + const diffMs = now - date; + const diffHours = Math.floor(diffMs / 3600000); + const 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"; + const diffMonths = Math.floor(diffDays / 30); + if (diffMonths < 12) + return diffMonths === 1 ? "1 month ago" : diffMonths + " months ago"; + const 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'); +document.querySelectorAll(".col-commit[data-commit]").forEach(function (td) { + const time = td.querySelector("time"); if (time) time.textContent = relativeTime(td.dataset.commit); }); @@ -81,36 +86,37 @@ rows.forEach(function (row, i) { }); function collapseAll() { - var openRows = document.querySelectorAll('.table tbody tr.row.open'); + const openRows = document.querySelectorAll(".table tbody tr.row.open"); openRows.forEach(function (row) { - row.classList.remove('open'); - row.setAttribute('aria-expanded', 'false'); + row.classList.remove("open"); + row.setAttribute("aria-expanded", "false"); }); } function applyFilters() { - var query = searchInput ? searchInput.value.toLowerCase().trim() : ''; - var visibleCount = 0; + const query = searchInput ? searchInput.value.toLowerCase().trim() : ""; + let visibleCount = 0; // Collapse all expanded rows on filter/search change collapseAll(); rows.forEach(function (row) { - var show = true; + let 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; + const 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(); + let text = row.textContent.toLowerCase(); + const next = row.nextElementSibling; + if (next && next.classList.contains("expand-row")) { + text += " " + next.textContent.toLowerCase(); } row._searchText = text; } @@ -121,7 +127,7 @@ function applyFilters() { if (show) { visibleCount++; - var numCell = row.cells[0]; + const numCell = row.cells[0]; if (numCell.textContent !== String(visibleCount)) { numCell.textContent = String(visibleCount); } @@ -132,19 +138,20 @@ function applyFilters() { // 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); + const isActive = + activeFilter && + tag.dataset.type === activeFilter.type && + tag.dataset.value === activeFilter.value; + tag.classList.toggle("active", isActive); }); // Filter bar if (filterBar) { if (activeFilter) { - filterBar.classList.add('visible'); + filterBar.classList.add("visible"); if (filterValue) filterValue.textContent = activeFilter.value; } else { - filterBar.classList.remove('visible'); + filterBar.classList.remove("visible"); } } @@ -152,40 +159,46 @@ function applyFilters() { } function updateURL() { - var params = new URLSearchParams(); - var query = searchInput ? searchInput.value.trim() : ''; - if (query) params.set('q', query); + const params = new URLSearchParams(); + const query = searchInput ? searchInput.value.trim() : ""; + if (query) params.set("q", query); if (activeFilter) { - params.set(activeFilter.type === 'cat' ? 'category' : 'group', activeFilter.value); + 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); + 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); + const 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 === "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); + if (col === "stars") { + const text = row + .querySelector(".col-stars") + .textContent.trim() + .replace(/,/g, ""); + const num = parseInt(text, 10); return isNaN(num) ? -1 : num; } - if (col === 'commit-time') { - var attr = row.querySelector('.col-commit').getAttribute('data-commit'); + if (col === "commit-time") { + const 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); - var col = activeSort.col; - var order = activeSort.order; + const arr = Array.prototype.slice.call(rows); + const col = activeSort.col; + const order = activeSort.order; // Cache sort values once to avoid DOM queries per comparison arr.forEach(function (row) { @@ -193,22 +206,22 @@ function sortRows() { }); arr.sort(function (a, b) { - var aVal = a._sortVal; - var bVal = b._sortVal; - if (col === 'name') { - var cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0; + const aVal = a._sortVal; + const bVal = b._sortVal; + if (col === "name") { + const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0; if (cmp === 0) return a._origIndex - b._origIndex; - return order === 'desc' ? -cmp : cmp; + return 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; + const cmp = aVal - bVal; if (cmp === 0) return a._origIndex - b._origIndex; - return order === 'desc' ? -cmp : cmp; + return order === "desc" ? -cmp : cmp; }); - var frag = document.createDocumentFragment(); + const frag = document.createDocumentFragment(); arr.forEach(function (row) { frag.appendChild(row); frag.appendChild(row._expandRow); @@ -218,40 +231,43 @@ function sortRows() { } function updateSortIndicators() { - document.querySelectorAll('th[data-sort]').forEach(function (th) { - th.classList.remove('sort-asc', 'sort-desc'); + 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); - th.setAttribute('aria-sort', activeSort.order === 'asc' ? 'ascending' : 'descending'); + th.classList.add("sort-" + activeSort.order); + th.setAttribute( + "aria-sort", + activeSort.order === "asc" ? "ascending" : "descending", + ); } else { - th.removeAttribute('aria-sort'); + th.removeAttribute("aria-sort"); } }); } // Expand/collapse: event delegation on tbody if (tbody) { - tbody.addEventListener('click', function (e) { + 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; + if (e.target.closest("a") || e.target.closest(".tag")) return; - var row = e.target.closest('tr.row'); + const row = e.target.closest("tr.row"); if (!row) return; - var isOpen = row.classList.contains('open'); + const isOpen = row.classList.contains("open"); if (isOpen) { - row.classList.remove('open'); - row.setAttribute('aria-expanded', 'false'); + row.classList.remove("open"); + row.setAttribute("aria-expanded", "false"); } else { - row.classList.add('open'); - row.setAttribute('aria-expanded', 'true'); + 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'); + tbody.addEventListener("keydown", function (e) { + if (e.key !== "Enter" && e.key !== " ") return; + const row = e.target.closest("tr.row"); if (!row) return; e.preventDefault(); row.click(); @@ -260,13 +276,17 @@ if (tbody) { // Tag click: filter by category or group tags.forEach(function (tag) { - tag.addEventListener('click', function (e) { + tag.addEventListener("click", function (e) { e.preventDefault(); - var type = tag.dataset.type; - var value = tag.dataset.value; + const type = tag.dataset.type; + const value = tag.dataset.value; // Toggle: click same filter again to clear - if (activeFilter && activeFilter.type === type && activeFilter.value === value) { + if ( + activeFilter && + activeFilter.type === type && + activeFilter.value === value + ) { activeFilter = null; } else { activeFilter = { type: type, value: value }; @@ -277,31 +297,32 @@ tags.forEach(function (tag) { // Clear filter if (filterClear) { - filterClear.addEventListener('click', function () { + filterClear.addEventListener("click", function () { activeFilter = null; applyFilters(); }); } // No-results clear -var noResultsClear = document.querySelector('.no-results-clear'); +const noResultsClear = document.querySelector(".no-results-clear"); if (noResultsClear) { - noResultsClear.addEventListener('click', function () { - if (searchInput) searchInput.value = ''; + noResultsClear.addEventListener("click", function () { + if (searchInput) searchInput.value = ""; 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'; +document.querySelectorAll("th[data-sort]").forEach(function (th) { + th.addEventListener("click", function () { + const col = th.dataset.sort; + const defaultOrder = col === "name" ? "asc" : "desc"; + const 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' }; + if (activeSort.order === defaultOrder) + activeSort = { col: col, order: altOrder }; + else activeSort = { col: "stars", order: "desc" }; } else { activeSort = { col: col, order: defaultOrder }; } @@ -312,20 +333,27 @@ document.querySelectorAll('th[data-sort]').forEach(function (th) { // Search input if (searchInput) { - var searchTimer; - searchInput.addEventListener('input', function () { + let 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) { + 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 = ''; + if (e.key === "Escape" && document.activeElement === searchInput) { + searchInput.value = ""; activeFilter = null; applyFilters(); searchInput.blur(); @@ -334,24 +362,24 @@ if (searchInput) { } // Back to top -var backToTop = document.querySelector('.back-to-top'); -var resultsSection = document.querySelector('#library-index'); -var tableWrap = document.querySelector('.table-wrap'); -var stickyHeaderCell = backToTop ? backToTop.closest('th') : null; +const backToTop = document.querySelector(".back-to-top"); +const resultsSection = document.querySelector("#library-index"); +const tableWrap = document.querySelector(".table-wrap"); +const stickyHeaderCell = backToTop ? backToTop.closest("th") : null; function updateBackToTopVisibility() { if (!backToTop || !tableWrap || !stickyHeaderCell) return; - var tableRect = tableWrap.getBoundingClientRect(); - var headRect = stickyHeaderCell.getBoundingClientRect(); - var hasPassedHeader = tableRect.top <= 0 && headRect.bottom > 0; + const tableRect = tableWrap.getBoundingClientRect(); + const headRect = stickyHeaderCell.getBoundingClientRect(); + const hasPassedHeader = tableRect.top <= 0 && headRect.bottom > 0; - backToTop.classList.toggle('visible', hasPassedHeader); + backToTop.classList.toggle("visible", hasPassedHeader); } if (backToTop) { - var scrollTicking = false; - window.addEventListener('scroll', function () { + let scrollTicking = false; + window.addEventListener("scroll", function () { if (!scrollTicking) { requestAnimationFrame(function () { updateBackToTopVisibility(); @@ -361,12 +389,12 @@ if (backToTop) { } }); - window.addEventListener('resize', updateBackToTopVisibility); + window.addEventListener("resize", updateBackToTopVisibility); - backToTop.addEventListener('click', function () { - var target = searchInput || resultsSection; + backToTop.addEventListener("click", function () { + const target = searchInput || resultsSection; if (!target) return; - target.scrollIntoView({ behavior: 'smooth', block: 'center' }); + target.scrollIntoView({ behavior: "smooth", block: "center" }); if (searchInput) searchInput.focus(); }); @@ -375,16 +403,19 @@ if (backToTop) { // 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'); + const params = new URLSearchParams(location.search); + const q = params.get("q"); + const cat = params.get("category"); + const group = params.get("group"); + const sort = params.get("sort"); + const 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')) { + 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) { From c5dd3060efddabb17770f8f4c09ce48a002416d9 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:43:12 +0800 Subject: [PATCH 0377/1130] chore: add __pycache__ to .gitignore and remove sys.path hack in tests Co-Authored-By: Claude --- .gitignore | 1 + website/tests/test_fetch_github_stars.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8c0ca9e8..ca26a6e8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # python .venv/ +__pycache__/ *.py[co] # website diff --git a/website/tests/test_fetch_github_stars.py b/website/tests/test_fetch_github_stars.py index 6c9eb383..ecdccf73 100644 --- a/website/tests/test_fetch_github_stars.py +++ b/website/tests/test_fetch_github_stars.py @@ -1,10 +1,7 @@ """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, From 25a3f4d9037a796b8106e137286f6ba88b8d3cd6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 23 Mar 2026 01:43:19 +0800 Subject: [PATCH 0378/1130] refactor(parser): remove resources parsing, preview, and content_html fields parse_readme now returns list[ParsedGroup] instead of a tuple. The resources section (Newsletters, Podcasts), preview string, and content_html rendering are no longer produced by the parser or consumed by the build. Removes _render_section_html, _group_by_h2, and the associated dead code and tests. Co-Authored-By: Claude --- website/build.py | 3 +- website/readme_parser.py | 132 +++----------------------- website/tests/test_build.py | 16 +--- website/tests/test_readme_parser.py | 139 ++++------------------------ 4 files changed, 37 insertions(+), 253 deletions(-) diff --git a/website/build.py b/website/build.py index 6ff46df0..f2057472 100644 --- a/website/build.py +++ b/website/build.py @@ -139,7 +139,7 @@ def build(repo_root: str) -> None: subtitle = stripped break - parsed_groups, _ = parse_readme(readme_text) + parsed_groups = 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) @@ -172,7 +172,6 @@ def build(repo_root: str) -> None: (site_dir / "index.html").write_text( tpl_index.render( categories=categories, - groups=parsed_groups, subtitle=subtitle, entries=entries, total_entries=total_entries, diff --git a/website/readme_parser.py b/website/readme_parser.py index 91b0faf2..4f36ed77 100644 --- a/website/readme_parser.py +++ b/website/readme_parser.py @@ -29,8 +29,6 @@ class ParsedSection(TypedDict): 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): @@ -258,69 +256,6 @@ def _parse_section_entries(content_nodes: list[SyntaxTreeNode]) -> list[ParsedEn 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'

    {label}
    ') - 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'
    {name}
    ') - else: - desc = _extract_description_html(inline, first_link) - if desc: - out.append( - f'
    {name}' - f'—{desc}
    ' - ) - else: - out.append(f'
    {name}
    ') - - # 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 ------------------------------------------------------- @@ -330,45 +265,15 @@ def _build_section(name: str, body: list[SyntaxTreeNode]) -> ParsedSection: 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. @@ -445,43 +350,30 @@ def _parse_grouped_sections( return groups -def parse_readme(text: str) -> tuple[list[ParsedGroup], list[ParsedSection]]: - """Parse README.md text into grouped categories and resources. +def parse_readme(text: str) -> list[ParsedGroup]: + """Parse README.md text into grouped categories. - Returns (groups, resources) where groups is a list of ParsedGroup dicts - containing nested categories, and resources is a flat list of ParsedSection. + Returns a list of ParsedGroup dicts containing nested categories. + Content between the thematic break (---) and # Resources or # Contributing + is parsed as categories grouped by bold markers (**Group Name**). """ md = MarkdownIt("commonmark") tokens = md.parse(text) root = SyntaxTreeNode(tokens) children = root.children - # Find thematic break (---), # Resources, and # Contributing in one pass + # Find thematic break (---) and section boundaries in one pass hr_idx = None - resources_idx = None - contributing_idx = None + cat_end_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 cat_end_idx is None and text_content in ("Resources", "Contributing"): + cat_end_idx = i if hr_idx is None: - return [], [] + 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 + cat_nodes = children[hr_idx + 1 : cat_end_idx or len(children)] + return _parse_grouped_sections(cat_nodes) diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 0e7eb487..c9d29f45 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -59,19 +59,13 @@ class TestBuild: ) (tpl_dir / "index.html").write_text( '{% extends "base.html" %}{% block content %}' - "{% for group in groups %}" - '
    ' - "

    {{ group.name }}

    " - "{% for cat in group.categories %}" - '
    ' - "{{ cat.name }}" - "{{ cat.preview }}" - "{{ cat.entry_count }}" - '' + "{% for entry in entries %}" + '
    ' + "{{ entry.name }}" + "{{ entry.categories | join(', ') }}" + "{{ entry.groups | join(', ') }}" "
    " "{% endfor %}" - "
    " - "{% endfor %}" "{% endblock %}", encoding="utf-8", ) diff --git a/website/tests/test_readme_parser.py b/website/tests/test_readme_parser.py index d365c45c..cea5cbbf 100644 --- a/website/tests/test_readme_parser.py +++ b/website/tests/test_readme_parser.py @@ -7,7 +7,6 @@ import pytest from readme_parser import ( _parse_section_entries, - _render_section_html, parse_readme, render_inline_html, render_inline_text, @@ -159,50 +158,39 @@ GROUPED_README = textwrap.dedent("""\ class TestParseReadmeSections: def test_ungrouped_categories_go_to_other(self): - groups, resources = parse_readme(MINIMAL_README) + groups = 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) + 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) + 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) + 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) + groups = 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") + groups = parse_readme("# Just a heading\n\nSome text.\n") assert groups == [] - assert resources == [] def test_no_description(self): readme = textwrap.dedent("""\ @@ -224,7 +212,7 @@ class TestParseReadmeSections: Done. """) - groups, resources = parse_readme(readme) + groups = parse_readme(readme) cats = groups[0]["categories"] assert cats[0]["description"] == "" assert cats[0]["entries"][0]["name"] == "item" @@ -245,42 +233,37 @@ class TestParseReadmeSections: Done. """) - groups, _ = parse_readme(readme) + 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) + groups = parse_readme(GROUPED_README) assert len(groups) == 2 def test_group_names(self): - groups, _ = parse_readme(GROUPED_README) + 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) + 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) + 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) + 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 @@ -299,7 +282,7 @@ class TestParseGroupedReadme: Done. """) - groups, _ = parse_readme(readme) + groups = parse_readme(readme) assert len(groups) == 1 assert groups[0]["name"] == "HasCats" @@ -319,7 +302,7 @@ class TestParseGroupedReadme: Done. """) - groups, _ = parse_readme(readme) + 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 @@ -345,7 +328,7 @@ class TestParseGroupedReadme: Done. """) - groups, _ = parse_readme(readme) + groups = parse_readme(readme) assert len(groups) == 2 assert groups[0]["name"] == "Other" assert groups[0]["categories"][0]["name"] == "Orphan" @@ -438,33 +421,11 @@ class TestParseSectionEntries: Done. """) - groups, _ = parse_readme(readme) + 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 lib.\n') entries = _parse_section_entries(nodes) @@ -472,58 +433,13 @@ class TestParseSectionEntries: 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('- [](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("- \n") - html = _render_section_html(nodes) - assert "
    diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 0ebeb585..9ea2b617 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -615,6 +615,46 @@ class TestBuild: parent = (site / "categories" / "web-frameworks" / "index.html").read_text(encoding="utf-8") assert "category-breadcrumb" not in parent + def test_index_embeds_filter_urls_json(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + --- + + **AI & ML** + + ## Deep Learning + + - [dl1](https://example.com/dl1) - DL. + + ## Machine Learning + + - Classical + + - [ml1](https://example.com/ml1) - ML. + + # Contributing + + Done. + """) + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + build(tmp_path) + + site = tmp_path / "website" / "output" + index_html = (site / "index.html").read_text(encoding="utf-8") + + marker = '", start) + data = json.loads(index_html[start:end]) + + assert data["Deep Learning"] == "/categories/deep-learning/" + assert data["Machine Learning"] == "/categories/machine-learning/" + assert data["AI & ML"] == "/categories/ai-ml/" + assert data["Machine Learning > Classical"] == "/categories/machine-learning/classical/" + def test_build_creates_group_pages(self, tmp_path): readme = textwrap.dedent("""\ # T From 704332271b3ea4f6744cd29adce0b31367128aef Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 00:40:52 +0800 Subject: [PATCH 0546/1130] fix(website): escape in embedded filter URLs JSON `| safe` bypasses Jinja autoescape. If a category name ever contained "", the literal substring would close the script block early, leaking JSON content into the DOM and creating an XSS vector. Replace " --- website/build.py | 12 +++++++----- website/tests/test_build.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/website/build.py b/website/build.py index f8d79d4a..3e94a2b2 100644 --- a/website/build.py +++ b/website/build.py @@ -92,6 +92,10 @@ def category_public_url(category: ParsedSection) -> str: return f"{SITE_URL}categories/{category['slug']}/" +def group_path(group_slug: str) -> str: + return f"/categories/{group_slug}/" + + def group_public_url(group_slug: str) -> str: return f"{SITE_URL}categories/{group_slug}/" @@ -315,11 +319,9 @@ def build(repo_root: Path) -> None: entries = sort_entries(entries) category_urls = {cat["name"]: category_path(cat) for cat in categories} - filter_urls: dict[str, str] = {} - for cat in categories: - filter_urls[cat["name"]] = category_path(cat) + filter_urls: dict[str, str] = dict(category_urls) for group in parsed_groups: - filter_urls[group["name"]] = f"/categories/{group['slug']}/" + filter_urls[group["name"]] = group_path(group["slug"]) for entry in entries: for sub in entry.get("subcategories", []): filter_urls[sub["value"]] = sub["url"] @@ -348,7 +350,7 @@ def build(repo_root: Path) -> None: build_date=build_date.strftime("%B %d, %Y"), sponsors=sponsors, category_urls=category_urls, - filter_urls_json=json.dumps(filter_urls, sort_keys=True), + filter_urls_json=json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace(" Classical"] == "/categories/machine-learning/classical/" + def test_filter_urls_json_escapes_closing_script_tag(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + --- + + ## Sneaky + + - [a](https://example.com) - A. + + # Contributing + + Done. + """) + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + build(tmp_path) + + site = tmp_path / "website" / "output" + index_html = (site / "index.html").read_text(encoding="utf-8") + + marker = '", start) + block = index_html[start:end] + assert "" not in block + data = json.loads(block) + assert any("Sneaky" in key for key in data) + def test_build_creates_group_pages(self, tmp_path): readme = textwrap.dedent("""\ # T From 04a04a136b4143b4c7753e9edd8422433c8c2267 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 00:44:43 +0800 Subject: [PATCH 0547/1130] feat(website): add data-url to tag buttons for client-side routing Co-Authored-By: Claude Opus 4.7 (1M context) --- website/build.py | 1 + website/templates/category.html | 10 +++++++--- website/templates/index.html | 9 +++++++-- website/tests/test_build.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/website/build.py b/website/build.py index 3e94a2b2..7c87f986 100644 --- a/website/build.py +++ b/website/build.py @@ -332,6 +332,7 @@ def build(repo_root: Path) -> None: trim_blocks=True, lstrip_blocks=True, ) + env.filters["slugify"] = slugify site_dir = website / "output" if site_dir.exists(): diff --git a/website/templates/category.html b/website/templates/category.html index 8983c77e..c17cebc5 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -117,15 +117,19 @@ {% for subcat in entry.subcategories %} - {% endfor %} - {% if entry.groups %} - {% endif %} diff --git a/website/templates/index.html b/website/templates/index.html index b3f9c6e2..2f909644 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -227,7 +227,7 @@ {% for subcat in entry.subcategories %} - {% endfor %} {% for cat in entry.categories %} @@ -235,10 +235,15 @@ class="tag" href="{{ category_urls[cat] }}" data-value="{{ cat }}" + data-url="{{ category_urls[cat] }}" >{{ cat }} {% endfor %} - {% if entry.source_type == 'Built-in' %} diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 85c7ea21..b482c1a6 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -724,6 +724,37 @@ class TestBuild: assert "wf1" in web_dev assert "dl1" not in web_dev + def test_tag_buttons_have_data_url(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + --- + + **AI & ML** + + ## Deep Learning + + - Vision + + - [v1](https://example.com/v1) - Vision lib. + + # Contributing + + Done. + """) + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + build(tmp_path) + + site = tmp_path / "website" / "output" + index_html = (site / "index.html").read_text(encoding="utf-8") + + assert 'data-value="Deep Learning"' in index_html + assert 'data-url="/categories/deep-learning/"' in index_html + assert 'data-value="AI & ML"' in index_html or 'data-value="AI & ML"' in index_html + assert 'data-url="/categories/ai-ml/"' in index_html + assert 'data-url="/categories/deep-learning/vision/"' in index_html + # --------------------------------------------------------------------------- # extract_github_repo From b0136ac2669f2872aa3d83aef9e649b478cef9fa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 00:49:41 +0800 Subject: [PATCH 0548/1130] feat(website): switch index filter URLs from querystring to path Tag clicks on / pushState a category/group/subcategory path; on static pages they fully navigate. Search and sort stay in querystring. Built-in source tag has no data-url and stays as an in-page filter. The isIndexDocument flag is captured at load time so toggling on the index keeps working after pushState changes location.pathname. Co-Authored-By: Claude Opus 4.7 (1M context) --- website/static/main.js | 62 +++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/website/static/main.js b/website/static/main.js index f875f8b1..b8d35337 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -167,19 +167,39 @@ function applyFilters() { updateURL(); } -function updateURL() { +const filterUrlsScript = document.getElementById("filter-urls"); +const filterToUrl = filterUrlsScript + ? JSON.parse(filterUrlsScript.textContent) + : {}; +const urlToFilter = {}; +Object.keys(filterToUrl).forEach(function (k) { + urlToFilter[filterToUrl[k]] = k; +}); + +const isIndexDocument = + location.pathname === "/" || location.pathname === "/index.html"; + +function isIndexPage() { + return isIndexDocument; +} + +function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; if (query) params.set("q", query); - if (activeFilter) { - params.set("filter", activeFilter); - } if (activeSort.col !== "stars" || activeSort.order !== "desc") { params.set("sort", activeSort.col); params.set("order", activeSort.order); } const qs = params.toString(); - history.replaceState(null, "", qs ? "?" + qs : location.pathname); + return qs ? "?" + qs : ""; +} + +function updateURL() { + if (!isIndexPage()) return; + const path = + activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/"; + history.replaceState(null, "", path + buildQueryString()); } function getSortValue(row, col) { @@ -288,8 +308,21 @@ tags.forEach(function (tag) { tag.addEventListener("click", function (e) { e.preventDefault(); const value = tag.dataset.value; - activeFilter = activeFilter === value ? null : value; - applyFilters(); + const url = tag.dataset.url; + if (isIndexPage()) { + activeFilter = activeFilter === value ? null : value; + if (activeFilter && url) { + history.pushState(null, "", url + buildQueryString()); + } else { + history.pushState(null, "", "/" + buildQueryString()); + } + applyFilters(); + } else if (url) { + window.location.href = url; + } else { + activeFilter = activeFilter === value ? null : value; + applyFilters(); + } }); }); @@ -396,19 +429,28 @@ if (backToTop) { (function () { const params = new URLSearchParams(location.search); const q = params.get("q"); - const filter = params.get("filter"); const sort = params.get("sort"); const order = params.get("order"); if (q && searchInput) searchInput.value = q; - if (filter) activeFilter = filter; if ( (sort === "name" || sort === "stars" || sort === "commit-time") && (order === "desc" || order === "asc") ) { activeSort = { col: sort, order: order }; } - if (q || filter || sort) { + if (isIndexPage()) { + const matched = urlToFilter[location.pathname]; + if (matched) activeFilter = matched; + } + if (q || activeFilter || sort) { sortRows(); } updateSortIndicators(); })(); + +window.addEventListener("popstate", function () { + if (!isIndexPage()) return; + const matched = urlToFilter[location.pathname]; + activeFilter = matched || null; + applyFilters(); +}); From 8e00055c8cf8efe118af1f05154377a0f4a6a677 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 07:51:39 +0800 Subject: [PATCH 0549/1130] fix(website): remove duplicate tag on group/category pages The category template rendered a tag for `category.name` plus a tag for `entry.groups[0]`, which duplicated the group name on group pages where those values are identical (e.g. /categories/python-language/ showing "Python Language" twice). It also never rendered `entry.categories`, so group pages omitted each project's actual category. Mirror the index template's tag rendering on category, group, and subcategory pages, and mark whichever tag matches the current page URL as active. Pass `category_urls` and `current_path` to each render call so the template can match by URL. --- website/build.py | 6 ++++++ website/templates/category.html | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/website/build.py b/website/build.py index 7c87f986..4b65d2bc 100644 --- a/website/build.py +++ b/website/build.py @@ -369,6 +369,8 @@ def build(repo_root: Path) -> None: entries=category_entries, total_categories=len(categories), page_kind="category", + category_urls=category_urls, + current_path=category_path(category), ), encoding="utf-8", ) @@ -390,6 +392,8 @@ def build(repo_root: Path) -> None: entries=group_entries, total_categories=len(categories), page_kind="group", + category_urls=category_urls, + current_path=group_path(group["slug"]), ), encoding="utf-8", ) @@ -425,6 +429,8 @@ def build(repo_root: Path) -> None: total_categories=len(categories), page_kind="subcategory", parent_category=category, + category_urls=category_urls, + current_path=subcategory_path(category["slug"], sub["slug"]), ), encoding="utf-8", ) diff --git a/website/templates/category.html b/website/templates/category.html index c17cebc5..b3af6507 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -117,18 +117,25 @@ {% for subcat in entry.subcategories %} - {% endfor %} - + {% for cat in entry.categories %} + {{ cat }} + {% endfor %} {% if entry.groups %} + {% set group_url = "/categories/" ~ (entry.groups[0] | slugify) ~ "/" %} From 8e72e8af8f74189d8e53ad690a180f1859470a5f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 07:56:54 +0800 Subject: [PATCH 0550/1130] feat(website): strip #library-index from URL after All projects click The "All projects" link in the category-page topbar pointed to /#library-index so the browser would scroll to the library section on arrival. The hash stayed in the URL, which looked like an internal anchor state rather than a clean homepage URL. On homepage load, if the hash is #library-index, scroll to the section explicitly and use history.replaceState to drop the hash from the URL. The scrollIntoView call covers the case where the script runs before the browser's native anchor scroll, since replaceState removes the hash the browser would have used. --- website/static/main.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/website/static/main.js b/website/static/main.js index b8d35337..0cb0e1f6 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -59,6 +59,19 @@ document.querySelectorAll("[data-scroll-to]").forEach(function (link) { }); }); +// Land at #library-index without leaving the hash in the URL +if (window.location.hash === "#library-index") { + const target = document.getElementById("library-index"); + if (target) { + target.scrollIntoView(); + } + history.replaceState( + null, + "", + window.location.pathname + window.location.search, + ); +} + // Pause hero animations when scrolled out of view (function () { const hero = document.querySelector(".hero"); From 1139402838fb1949bf960de9dddd0db3141f0b94 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 08:01:37 +0800 Subject: [PATCH 0551/1130] fix(website): stop category-page heading from wrapping The results-intro grid (1fr + 28rem note column) squeezed the heading on category pages with long names, e.g. "Python Projects in Environment Management" wrapped onto two lines. Scope a single-column override to .category-results so the heading takes the full row and the note drops below right-aligned. Index page layout is untouched since its heading is short. --- website/static/style.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/website/static/style.css b/website/static/style.css index f0a1bc8f..27b4d34e 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -502,6 +502,15 @@ kbd { padding-top: clamp(2.5rem, 5vw, 3.75rem); } +.category-results .results-intro { + grid-template-columns: 1fr; + gap: 0.6rem; +} + +.category-results .results-intro .results-note { + justify-self: end; +} + .category-table .col-name { width: min(42rem, 48vw); white-space: normal; From 03db91bcd04279f899540f5f34aa3829d164412e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 08:02:39 +0800 Subject: [PATCH 0552/1130] style(website): left-align results note on category pages Switch justify-self from end to start so the "Sorted by GitHub stars..." note sits flush left under the heading instead of right-aligned. --- website/static/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index 27b4d34e..852b01a4 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -508,7 +508,7 @@ kbd { } .category-results .results-intro .results-note { - justify-self: end; + justify-self: start; } .category-table .col-name { From 033694204c1c812209a5d9c5d36c871cd6c4ed84 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 08:17:17 +0800 Subject: [PATCH 0553/1130] feat(website): move descriptions into expand row on category pages Removes inline .category-row-desc from the name cell and renders entry.description inside .expand-content instead, matching the index page pattern. Drops the now-unused CSS rules for .category-row-desc and the overridden .category-table .expand-content padding. Co-Authored-By: Claude --- website/static/style.css | 35 --------------------------------- website/templates/category.html | 7 +++---- 2 files changed, 3 insertions(+), 39 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 852b01a4..6876dc2b 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -511,41 +511,6 @@ kbd { justify-self: start; } -.category-table .col-name { - width: min(42rem, 48vw); - white-space: normal; -} - -.category-table .col-name > a { - display: inline-block; -} - -.category-row-desc { - display: block; - max-width: 68ch; - margin-top: 0.32rem; - color: var(--ink-soft); - font-size: var(--text-sm); - font-weight: 500; - line-height: 1.55; - text-wrap: pretty; -} - -.category-row-desc a { - color: var(--accent-deep); - text-decoration: underline; - text-decoration-color: var(--accent-underline); - text-underline-offset: 0.18em; -} - -.category-row-desc a:hover { - color: var(--accent); -} - -.category-table .expand-content { - padding-block: 0.25rem 0.15rem; -} - .sponsor-band { padding-block: clamp(2.5rem, 5.5vw, 4rem); background: diff --git a/website/templates/category.html b/website/templates/category.html index b3af6507..a49dde40 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -86,9 +86,6 @@ {{ entry.name }} - {% if entry.description %} - {{ entry.description | safe }} - {% endif %} {% if entry.subcategories %}{{ entry.subcategories[0].name }}{% else %}{{ category.name }}{% endif %}
    - {% if entry.also_see %} + {% if entry.description %} +
    {{ entry.description | safe }}
    + {% endif %} {% if entry.also_see %}
    Also see: {% for see in entry.also_see %} Date: Sun, 3 May 2026 08:26:37 +0800 Subject: [PATCH 0554/1130] feat(website): mirror index layout on category pages Add search input, filter chips, no-results block, and back-to-top button to category/group/subcategory pages. Pass filter_urls_json to all page types so tag-chip navigation works site-wide. Fix JS so filter-clear and no-results-clear redirect to / on non-index pages instead of trying to filter a non-existent local table. Remove the now-redundant .category-results CSS overrides. Co-Authored-By: Claude --- website/build.py | 7 +++- website/static/main.js | 14 +++++-- website/static/style.css | 15 +------ website/templates/category.html | 72 ++++++++++++++++++++++++++++----- website/tests/test_build.py | 2 +- 5 files changed, 80 insertions(+), 30 deletions(-) diff --git a/website/build.py b/website/build.py index 4b65d2bc..8fed7860 100644 --- a/website/build.py +++ b/website/build.py @@ -339,6 +339,8 @@ def build(repo_root: Path) -> None: shutil.rmtree(site_dir) site_dir.mkdir(parents=True) + filter_urls_json = json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace(" None: build_date=build_date.strftime("%B %d, %Y"), sponsors=sponsors, category_urls=category_urls, - filter_urls_json=json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace(" None: page_kind="category", category_urls=category_urls, current_path=category_path(category), + filter_urls_json=filter_urls_json, ), encoding="utf-8", ) @@ -394,6 +397,7 @@ def build(repo_root: Path) -> None: page_kind="group", category_urls=category_urls, current_path=group_path(group["slug"]), + filter_urls_json=filter_urls_json, ), encoding="utf-8", ) @@ -431,6 +435,7 @@ def build(repo_root: Path) -> None: parent_category=category, category_urls=category_urls, current_path=subcategory_path(category["slug"], sub["slug"]), + filter_urls_json=filter_urls_json, ), encoding="utf-8", ) diff --git a/website/static/main.js b/website/static/main.js index 0cb0e1f6..4da6f669 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -341,6 +341,10 @@ tags.forEach(function (tag) { if (filterClear) { filterClear.addEventListener("click", function () { + if (!isIndexPage()) { + window.location.href = "/"; + return; + } activeFilter = null; applyFilters(); }); @@ -349,6 +353,10 @@ if (filterClear) { const noResultsClear = document.querySelector(".no-results-clear"); if (noResultsClear) { noResultsClear.addEventListener("click", function () { + if (!isIndexPage()) { + window.location.href = "/"; + return; + } if (searchInput) searchInput.value = ""; activeFilter = null; applyFilters(); @@ -451,10 +459,8 @@ if (backToTop) { ) { activeSort = { col: sort, order: order }; } - if (isIndexPage()) { - const matched = urlToFilter[location.pathname]; - if (matched) activeFilter = matched; - } + const matched = urlToFilter[location.pathname]; + if (matched) activeFilter = matched; if (q || activeFilter || sort) { sortRows(); } diff --git a/website/static/style.css b/website/static/style.css index 6876dc2b..77b58442 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -430,7 +430,7 @@ kbd { .tag:focus-visible, .back-to-top:focus-visible, .no-results-clear:focus-visible, -.category-table a:focus-visible, +.table a:focus-visible, .footer a:focus-visible, .sort-btn:focus-visible { outline: 2px solid var(--accent); @@ -498,19 +498,6 @@ kbd { text-decoration-color: oklch(100% 0 0 / 0.7); } -.category-results { - padding-top: clamp(2.5rem, 5vw, 3.75rem); -} - -.category-results .results-intro { - grid-template-columns: 1fr; - gap: 0.6rem; -} - -.category-results .results-intro .results-note { - justify-self: start; -} - .sponsor-band { padding-block: clamp(2.5rem, 5.5vw, 4rem); background: diff --git a/website/templates/category.html b/website/templates/category.html index a49dde40..96ced7f5 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -38,24 +38,58 @@ {% endblock %} {% block content %} -
    + +
    -

    Python Projects in {{ category.name }}

    +

    Search every project in one place

    - Sorted by GitHub stars when available. Click any row for details. + Press / to search. Tap a tag to filter. Click any row for + details.

    -

    {{ category.name }} results

    +
    +

    Search and filter

    +
    + + + + + +
    +
    + Filtering for + +
    +
    + +

    Results

    - +
    @@ -69,7 +103,11 @@ - + @@ -79,7 +117,7 @@ data-tags="{{ entry.categories | join('||') }}{% if entry.subcategories %}||{{ entry.subcategories | map(attribute='value') | join('||') }}{% endif %}||{{ entry.groups | join('||') }}{% if entry.source_type == 'Built-in' %}||Built-in{% endif %}" tabindex="0" aria-expanded="false" - aria-controls="category-expand-{{ loop.index }}" + aria-controls="expand-{{ loop.index }}" > - +
    Row number TagsDetails + +
    {{ loop.index }} @@ -145,7 +183,7 @@ →
    @@ -188,6 +226,14 @@
    + +
    @@ -203,7 +249,13 @@ rel="noopener" >Submit a project - Browse all + Star the repository
    diff --git a/website/tests/test_build.py b/website/tests/test_build.py index b482c1a6..57657a66 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -302,7 +302,7 @@ class TestBuild: assert 'href="https://example.com/w1"' in category_html assert "A widget." in category_html assert 'href="https://github.com/owner/w2"' in category_html - assert '' in category_html + assert '
    ' in category_html assert "42" in category_html assert "2026-01-01T00:00:00+00:00" in category_html From 70a8255289eb5ffc03621dc2e9b32769463dd624 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 08:35:55 +0800 Subject: [PATCH 0555/1130] feat(website): add /categories/built-in/ page for Built-in tag filter Register Built-in as a navigable filter path alongside regular category and group slugs, emit the page during build, add it to the sitemap, and wire the Built-in tag buttons in index.html and category.html to navigate there via data-url. Co-Authored-By: Claude --- website/build.py | 35 ++++++++++++++++++++++++++++++++- website/templates/category.html | 6 +++++- website/templates/index.html | 6 +++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/website/build.py b/website/build.py index 8fed7860..5a753a65 100644 --- a/website/build.py +++ b/website/build.py @@ -20,6 +20,11 @@ SITE_URL = "https://awesome-python.com/" SITEMAP_URL = f"{SITE_URL}sitemap.xml" SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9" +BUILTIN_FILTER = "Built-in" +BUILTIN_SLUG = "built-in" +BUILTIN_PATH = f"/categories/{BUILTIN_SLUG}/" +BUILTIN_PUBLIC_URL = f"{SITE_URL}categories/{BUILTIN_SLUG}/" + SOURCE_TYPE_DOMAINS = { "docs.python.org": "Built-in", "gitlab.com": "GitLab", @@ -287,7 +292,7 @@ def build(repo_root: Path) -> None: categories = [cat for g in parsed_groups for cat in g["categories"]] cat_slugs = [cat["slug"] for cat in categories] group_slugs = [g["slug"] for g in parsed_groups] - all_top_level_slugs = cat_slugs + group_slugs + all_top_level_slugs = cat_slugs + group_slugs + [BUILTIN_SLUG] duplicates = {s for s in all_top_level_slugs if all_top_level_slugs.count(s) > 1} if duplicates: raise ValueError( @@ -325,6 +330,9 @@ def build(repo_root: Path) -> None: for entry in entries: for sub in entry.get("subcategories", []): filter_urls[sub["value"]] = sub["url"] + builtin_entries = [e for e in entries if e.get("source_type") == BUILTIN_FILTER] + if builtin_entries: + filter_urls[BUILTIN_FILTER] = BUILTIN_PATH env = Environment( loader=FileSystemLoader(website / "templates"), @@ -402,6 +410,29 @@ def build(repo_root: Path) -> None: encoding="utf-8", ) + if builtin_entries: + page_dir = categories_dir / BUILTIN_SLUG + page_dir.mkdir(parents=True, exist_ok=True) + synthetic = { + "name": BUILTIN_FILTER, + "slug": BUILTIN_SLUG, + "description": "", + "description_html": "", + } + (page_dir / "index.html").write_text( + tpl_category.render( + category=synthetic, + category_url=BUILTIN_PUBLIC_URL, + entries=builtin_entries, + total_categories=len(categories), + page_kind="built-in", + category_urls=category_urls, + current_path=BUILTIN_PATH, + filter_urls_json=filter_urls_json, + ), + encoding="utf-8", + ) + seen_subcats: set[tuple[str, str]] = set() for category in categories: cat_url_prefix = f"/categories/{category['slug']}/" @@ -455,6 +486,8 @@ def build(repo_root: Path) -> None: sitemap_urls = [(SITE_URL, sitemap_date)] sitemap_urls.extend((category_public_url(c), sitemap_date) for c in categories) sitemap_urls.extend((group_public_url(g["slug"]), sitemap_date) for g in parsed_groups) + if builtin_entries: + sitemap_urls.append((BUILTIN_PUBLIC_URL, sitemap_date)) for cat_slug, sub_slug in sorted(seen_subcats): sitemap_urls.append((subcategory_public_url(cat_slug, sub_slug), sitemap_date)) write_sitemap_xml(site_dir / "sitemap.xml", sitemap_urls) diff --git a/website/templates/category.html b/website/templates/category.html index 96ced7f5..bb97ae61 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -176,7 +176,11 @@ {% endif %} {% if entry.source_type == 'Built-in' %} - {% endif %} diff --git a/website/templates/index.html b/website/templates/index.html index 2f909644..7e79bdba 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -247,7 +247,11 @@ {{ entry.groups[0] }} {% if entry.source_type == 'Built-in' %} - {% endif %} From b82a254a09668ade6a62644af9a42eaf44503ceb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 09:03:25 +0800 Subject: [PATCH 0556/1130] fix(website): clear filter lands at /#library-index on category pages Co-Authored-By: Claude --- website/static/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/static/main.js b/website/static/main.js index 4da6f669..e4854543 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -342,7 +342,7 @@ tags.forEach(function (tag) { if (filterClear) { filterClear.addEventListener("click", function () { if (!isIndexPage()) { - window.location.href = "/"; + window.location.href = "/#library-index"; return; } activeFilter = null; From 64781112d8ce6b1ff634b0acbbf44b72db59dfef Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 09:08:37 +0800 Subject: [PATCH 0557/1130] feat(website): add Browse by category nav to group page hero Co-Authored-By: Claude --- website/build.py | 1 + website/templates/category.html | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/website/build.py b/website/build.py index 5a753a65..a00bc103 100644 --- a/website/build.py +++ b/website/build.py @@ -406,6 +406,7 @@ def build(repo_root: Path) -> None: category_urls=category_urls, current_path=group_path(group["slug"]), filter_urls_json=filter_urls_json, + group_categories=group["categories"], ), encoding="utf-8", ) diff --git a/website/templates/category.html b/website/templates/category.html index bb97ae61..ad147fdb 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -34,6 +34,23 @@

    {{ category.description_html | safe }}

    {% endif %} + + {% if group_categories %} + + {% endif %} {% endblock %} From c68b985d7c0bc110c0eade90fc9bd346ac3fd96a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 3 May 2026 09:35:39 +0800 Subject: [PATCH 0558/1130] feat(website): add /sponsorship/ landing page Adds a dedicated sponsorship page at /sponsorship/ built from the Jinja2 template, with hero stats, tier cards, and CSS. Updates the index.html sponsor sidebar link to point to /sponsorship/ instead of the GitHub SPONSORSHIP.md. Adds the URL to the sitemap and test fixtures. Also renames .impeccable.md to DESIGN.md. Co-Authored-By: Claude --- .impeccable.md => DESIGN.md | 0 SPONSORSHIP.md | 2 +- website/build.py | 21 ++ website/static/style.css | 309 ++++++++++++++++++++++++++++- website/templates/index.html | 9 +- website/templates/sponsorship.html | 252 +++++++++++++++++++++++ website/tests/test_build.py | 7 + 7 files changed, 590 insertions(+), 10 deletions(-) rename .impeccable.md => DESIGN.md (100%) create mode 100644 website/templates/sponsorship.html diff --git a/.impeccable.md b/DESIGN.md similarity index 100% rename from .impeccable.md rename to DESIGN.md diff --git a/SPONSORSHIP.md b/SPONSORSHIP.md index b833748a..debd6f91 100644 --- a/SPONSORSHIP.md +++ b/SPONSORSHIP.md @@ -37,7 +37,7 @@ Your sponsorship puts your product in front of developers at the exact moment th ## Get Started -Email [vinta.chen@gmail.com](mailto:vinta.chen@gmail.com?subject=awesome-python%20Sponsorship) with: +Email [vinta.chen@gmail.com](mailto:vinta.chen@gmail.com?subject=Awesome%20Python%20Sponsorship) with: - **Tier:** Headline Sponsor ($500/mo) or Featured Sponsor ($150/mo) - **Content:** Product name, URL, logo, and description (Headline tier) or `[Name](URL) - Description.` entry (Featured tier) diff --git a/website/build.py b/website/build.py index a00bc103..35fdaa4e 100644 --- a/website/build.py +++ b/website/build.py @@ -25,6 +25,9 @@ BUILTIN_SLUG = "built-in" BUILTIN_PATH = f"/categories/{BUILTIN_SLUG}/" BUILTIN_PUBLIC_URL = f"{SITE_URL}categories/{BUILTIN_SLUG}/" +SPONSORSHIP_PATH = "/sponsorship/" +SPONSORSHIP_PUBLIC_URL = f"{SITE_URL}sponsorship/" + SOURCE_TYPE_DOMAINS = { "docs.python.org": "Built-in", "gitlab.com": "GitLab", @@ -434,6 +437,23 @@ def build(repo_root: Path) -> None: encoding="utf-8", ) + sponsorship_dir = site_dir / "sponsorship" + sponsorship_dir.mkdir(parents=True, exist_ok=True) + tpl_sponsorship = env.get_template("sponsorship.html") + hero_stats: list[str] = [] + if repo_stars: + hero_stats.append(f"{repo_stars}+ stars on GitHub") + hero_stats.append(f"{total_entries}+ curated projects") + hero_stats.append(f"Updated {build_date.strftime('%B %d, %Y')}") + (sponsorship_dir / "index.html").write_text( + tpl_sponsorship.render( + total_entries=total_entries, + total_categories=len(categories), + hero_stats=hero_stats, + ), + encoding="utf-8", + ) + seen_subcats: set[tuple[str, str]] = set() for category in categories: cat_url_prefix = f"/categories/{category['slug']}/" @@ -491,6 +511,7 @@ def build(repo_root: Path) -> None: sitemap_urls.append((BUILTIN_PUBLIC_URL, sitemap_date)) for cat_slug, sub_slug in sorted(seen_subcats): sitemap_urls.append((subcategory_public_url(cat_slug, sub_slug), sitemap_date)) + sitemap_urls.append((SPONSORSHIP_PUBLIC_URL, sitemap_date)) write_sitemap_xml(site_dir / "sitemap.xml", sitemap_urls) (site_dir / "index.md").write_text(markdown_index, encoding="utf-8") (site_dir / "llms.txt").write_text(llms_txt, encoding="utf-8") diff --git a/website/static/style.css b/website/static/style.css index 77b58442..4d02f72c 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1082,6 +1082,298 @@ th[data-sort].sort-asc::after { color: var(--accent); } +.sponsorship-hero .category-hero-shell { + padding-bottom: clamp(3.25rem, 6vw, 5rem); + gap: clamp(2rem, 5vw, 3.5rem); +} + +.sponsorship-hero-copy h1 { + font-size: clamp(3.4rem, 8.5vw, 6.5rem); +} + +.sponsorship-proof { + margin-top: 1.6rem; +} + +.sponsorship-proof .proof-sep { + color: oklch(100% 0 0 / 0.32); + margin-inline: 0.15rem; +} + +.sponsorship-hero .hero-actions { + margin-top: 1.9rem; +} + +.sponsorship-section { + padding-block: clamp(2.75rem, 5.5vw, 4.25rem); + border-bottom: 1px solid var(--line); +} + +.sponsorship-section:first-of-type { + padding-top: clamp(3.25rem, 6vw, 4.75rem); +} + +.sponsorship-section:last-of-type { + border-bottom: 0; + padding-bottom: clamp(3.5rem, 7vw, 5.5rem); +} + +.sponsorship-getstarted { + background: var(--cta-bg); + border-top: 1px solid var(--line); +} + +.sponsorship-shell { + display: grid; + grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); + gap: clamp(1.75rem, 5vw, 4rem); + align-items: start; +} + +.sponsorship-meta { + display: flex; + flex-direction: column; + gap: 0.85rem; + position: sticky; + top: 1.5rem; +} + +.sponsorship-meta .section-label { + margin-bottom: 0; + font-size: var(--text-lg); +} + +.sponsorship-meta-note { + color: var(--ink-muted); + font-size: var(--text-sm); + line-height: 1.55; +} + +.sponsorship-body { + display: flex; + flex-direction: column; + gap: 1.6rem; + font-size: var(--text-lg); + color: var(--ink-soft); + line-height: 1.7; +} + +.sponsorship-body p { + text-wrap: pretty; +} + +.sponsorship-body code { + font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace; + font-size: 0.92em; + padding: 0.08rem 0.4rem; + border-radius: 0.4rem; + background: var(--bg-paper-strong); + color: var(--ink); +} + +.sponsorship-body a:not(.hero-action):not(.tier-cta) { + color: var(--accent-deep); + text-decoration: underline; + text-decoration-color: var(--accent-underline); + text-underline-offset: 0.2em; + transition: color 180ms ease; +} + +.sponsorship-body a:not(.hero-action):not(.tier-cta):hover { + color: var(--accent); +} + +.sponsorship-lede { + font-family: var(--font-display); + font-size: clamp(1.55rem, 2.6vw, 2rem); + line-height: 1.25; + color: var(--ink); + letter-spacing: -0.01em; + text-wrap: pretty; +} + +.sponsorship-facts { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 1.4rem; + border-top: 1px solid var(--line); + padding-top: 1.6rem; +} + +.sponsorship-facts > div { + display: grid; + grid-template-columns: minmax(0, 12rem) minmax(0, 1fr); + gap: clamp(1rem, 3vw, 2rem); + align-items: baseline; +} + +.sponsorship-facts dt { + font-size: var(--text-xs); + font-weight: 800; + letter-spacing: 0.05em; + color: var(--ink); +} + +.sponsorship-facts dd { + color: var(--ink-soft); + font-size: var(--text-base); + line-height: 1.65; +} + +.tier-list { + list-style: none; + padding: 0; + margin: 0; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: clamp(1.5rem, 3vw, 2.75rem); +} + +.tier { + display: flex; + flex-direction: column; + gap: 1rem; + padding-block: 1.65rem; + border-top: 1px solid var(--line-strong); +} + +.tier-eyebrow { + font-size: var(--text-xs); + font-weight: 800; + letter-spacing: 0.05em; + color: var(--ink); +} + +.tier-price { + display: flex; + align-items: baseline; + gap: 0.55rem; + margin-bottom: 0.25rem; +} + +.tier-amount { + font-family: var(--font-display); + font-size: clamp(3rem, 5.5vw, 4.5rem); + font-weight: 600; + line-height: 0.9; + letter-spacing: -0.025em; + color: var(--ink); +} + +.tier-cadence { + color: var(--ink-muted); + font-size: var(--text-base); + font-weight: 600; + letter-spacing: 0.01em; +} + +.tier-summary { + font-size: var(--text-lg); + color: var(--ink); + line-height: 1.5; + text-wrap: pretty; +} + +.tier-includes { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.6rem; + border-top: 1px solid var(--line); + padding-top: 1.1rem; +} + +.tier-includes li { + position: relative; + padding-left: 1.4rem; + color: var(--ink-soft); + font-size: var(--text-base); + line-height: 1.6; +} + +.tier-includes li::before { + content: ""; + position: absolute; + left: 0; + top: 0.65rem; + width: 0.55rem; + height: 1px; + background: var(--line-strong); +} + +.tier-cta { + align-self: start; + margin-top: 0.75rem; + color: var(--accent-deep); + font-size: var(--text-sm); + font-weight: 700; + letter-spacing: 0.01em; + text-decoration: underline; + text-decoration-color: var(--accent-underline); + text-underline-offset: 0.22em; + transition: color 180ms ease, text-decoration-color 180ms ease; +} + +.tier-cta:hover { + color: var(--accent); + text-decoration-color: var(--accent); +} + +.past-sponsors { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 0.85rem; +} + +.past-sponsors li { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.65rem; + padding-block: 0.4rem; +} + +.past-sponsors a { + font-family: var(--font-display); + font-size: clamp(1.6rem, 2.8vw, 2.1rem); + font-weight: 600; + line-height: 1; + letter-spacing: -0.02em; + color: var(--ink); + transition: color 180ms ease; +} + +.past-sponsors a:hover { + color: var(--accent-deep); +} + +.past-sponsor-desc { + color: var(--ink-muted); + font-size: var(--text-base); +} + +.sponsorship-cta-row { + display: flex; + flex-wrap: wrap; + gap: 0.85rem; + margin-top: 0.5rem; +} + +.sponsorship-cta-row .hero-action-primary { + color: var(--hero-text); + background: linear-gradient(135deg, var(--accent), var(--accent-deep)); +} + +.sponsorship-fineprint { + font-size: var(--text-base); + color: var(--ink-muted); +} + .final-cta { padding-block: clamp(3rem, 7vw, 5.5rem); background: var(--cta-bg); @@ -1227,10 +1519,25 @@ th[data-sort].sort-asc::after { .hero-grid, .results-intro, - .sponsor-shell { + .sponsor-shell, + .sponsorship-shell { grid-template-columns: 1fr; } + .sponsorship-meta { + position: static; + } + + .tier-list { + grid-template-columns: 1fr; + gap: 0; + } + + .sponsorship-facts > div { + grid-template-columns: 1fr; + gap: 0.35rem; + } + .hero-category-nav { grid-template-columns: 1fr; gap: 0.95rem; diff --git a/website/templates/index.html b/website/templates/index.html index 7e79bdba..36408415 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -85,14 +85,7 @@ From 3167cff71f173d7f53d502956e6740cdc950c582 Mon Sep 17 00:00:00 2001 From: chernistry Date: Sat, 9 May 2026 23:14:45 +0300 Subject: [PATCH 0597/1130] Add bernstein to AI and Agents > Orchestration --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bd173633..6519d263 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Orchestration - [ag2](https://github.com/ag2ai/ag2) - An open-source AgentOS for multi-agent orchestration and building agentic AI systems. - [autogen](https://github.com/microsoft/autogen) - A programming framework for building agentic AI applications. + - [bernstein](https://github.com/sipyourdrink-ltd/bernstein) - A deterministic Python orchestrator for CLI coding agents (Claude Code, Codex, Gemini CLI, and 40+ more) with parallel git worktrees and an HMAC-signed audit chain. - [bub](https://github.com/bubbuild/bub) - A lightweight, hook-first Python framework for channel-native agents that live alongside people. - [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. From b9df44ecc444cc15e5c431ae6417241c1e7aab9f Mon Sep 17 00:00:00 2001 From: "Leon.C" <160379708+zichen0116@users.noreply.github.com> Date: Mon, 11 May 2026 11:12:02 +0800 Subject: [PATCH 0598/1130] docs: add CODE_OF_CONDUCT.md Add Contributor Covenant Code of Conduct to establish community guidelines and ensure a welcoming environment for all contributors. --- CODE_OF_CONDUCT.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f13d736d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,40 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery +- Trolling, insulting or derogatory comments +- Public or private harassment +- Publishing others' private information without permission +- Other conduct which could reasonably be considered inappropriate + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainers. All complaints will be reviewed and +investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), +version 2.0. From 04abb895d7142558fc3c12f807af12ec843ea75d Mon Sep 17 00:00:00 2001 From: Aman Gupta <98139823+dat-a-man@users.noreply.github.com> Date: Mon, 11 May 2026 07:57:29 +0000 Subject: [PATCH 0599/1130] Updated --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 6519d263..4df4f541 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour **Data & Science** - [Data Analysis](#data-analysis) +- [Data Ingestion / ETL](#data-ingestion--etl) - [Data Validation](#data-validation) - [Data Visualization](#data-visualization) - [Geolocation](#geolocation) @@ -484,6 +485,12 @@ _Libraries for data analysis._ - [lumibot](https://github.com/Lumiwealth/lumibot) - Algorithmic trading framework for backtesting and live deployment across stocks, options, crypto, futures, and forex. - [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 Ingestion / ETL + +_Libraries for extracting data from external sources and loading it into databases, warehouses, and lakehouses._ + +- [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. ### Data Validation From c506774ac7beb562e2a8ea1dddf88d752730bb10 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Mon, 11 May 2026 13:40:03 +0400 Subject: [PATCH 0600/1130] Refactor data libraries section in README.md for clarity and organization --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4df4f541..2f637ec8 100644 --- a/README.md +++ b/README.md @@ -469,28 +469,29 @@ _Libraries for serializing complex data types._ _Libraries for data analysis._ +- [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. +- [data-profiling](https://github.com/Data-Centric-AI-Community/data-profiling) - Generate detailed data profiling reports for pandas DataFrames. +- [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. + + +### Data Ingestion / ETL + +_Libraries for data extraction, transformation, and loading pipelines across multiple sources and destinations._ + - 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. - - [data-profiling](https://github.com/Data-Centric-AI-Community/data-profiling) - Generate detailed data profiling reports for pandas DataFrames. - - [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. + - [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. - 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. - [lumibot](https://github.com/Lumiwealth/lumibot) - Algorithmic trading framework for backtesting and live deployment across stocks, options, crypto, futures, and forex. - [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 Ingestion / ETL - -_Libraries for extracting data from external sources and loading it into databases, warehouses, and lakehouses._ - -- [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. ### Data Validation From e503d16eb26a1253ad677c9a8f09c6842beb40d3 Mon Sep 17 00:00:00 2001 From: Ankita Ghosh Date: Mon, 11 May 2026 15:30:27 +0530 Subject: [PATCH 0601/1130] fix: remove duplicate ruff entry from Code Linters section (#3119) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4df4f541..1d019d02 100644 --- a/README.md +++ b/README.md @@ -612,7 +612,6 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [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. From 5885075bf7ef21b0c2bb3fe677e050b2f30a84b6 Mon Sep 17 00:00:00 2001 From: Mohd Aasim Ansari Date: Sun, 17 May 2026 07:31:18 +0000 Subject: [PATCH 0602/1130] Add ultralytics to Computer Vision section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81eff0ed..f65b3ab1 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ _Libraries for Computer Vision._ - [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). +- [ultralytics](https://github.com/ultralytics/ultralytics) - Ultralytics YOLO for object detection, segmentation, pose estimation, and classification with state-of-the-art accuracy and speed. ### Recommender Systems From 420619b114cbb3ebd7cc958020455d61bb6e3d5c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 17 May 2026 17:13:32 +0800 Subject: [PATCH 0603/1130] add links to the website --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 81eff0ed..c9708a59 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Awesome Python +# [Awesome Python](https://awesome-python.com/) -An opinionated guide to the best Python frameworks, libraries, tools, and resources. +An opinionated guide to the best Python frameworks, libraries, tools, and resources. Use the [website](https://awesome-python.com/) to search and filter projects more easily. ## **Sponsors** @@ -479,7 +479,6 @@ _Libraries for data analysis._ - [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. - ### Data Ingestion / ETL _Libraries for data extraction, transformation, and loading pipelines across multiple sources and destinations._ From 3e66360e400e5baae2bc3d3017a28e1de2f85fd4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 17 May 2026 17:15:31 +0800 Subject: [PATCH 0604/1130] refine wording --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c9708a59..52d0c73e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # [Awesome Python](https://awesome-python.com/) -An opinionated guide to the best Python frameworks, libraries, tools, and resources. Use the [website](https://awesome-python.com/) to search and filter projects more easily. +An opinionated guide to the best Python frameworks, libraries, tools, and resources. + +**Visit the [website](https://awesome-python.com/) to search and filter projects more easily.** ## **Sponsors** From a9b13e4c4a64546a730f40b093a74854d60ebc77 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Mon, 18 May 2026 13:53:45 +0800 Subject: [PATCH 0605/1130] update .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0d9f410b..f081c833 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# secrets +.env* + # macOS .DS_Store @@ -10,7 +13,7 @@ __pycache__/ website/output/ website/data/ -# planning docs +# docs docs/ # agents From a089ae28cc12418e9044539fe77abab848179ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raahul=20Dutta=20-=20=E0=A6=B0=E0=A6=BE=E0=A6=B9=E0=A7=81?= =?UTF-8?q?=E0=A6=B2=20=F0=9F=96=96?= Date: Tue, 19 May 2026 06:31:32 +0200 Subject: [PATCH 0606/1130] Merge pull request #3137 from raahulrahl/add-bindu Add bindu to AI and Agents > Orchestration --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3920b7ec..c7b25af2 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - [ag2](https://github.com/ag2ai/ag2) - An open-source AgentOS for multi-agent orchestration and building agentic AI systems. - [autogen](https://github.com/microsoft/autogen) - A programming framework for building agentic AI applications. - [bernstein](https://github.com/sipyourdrink-ltd/bernstein) - A deterministic Python orchestrator for CLI coding agents (Claude Code, Codex, Gemini CLI, and 40+ more) with parallel git worktrees and an HMAC-signed audit chain. + - [bindu](https://github.com/getbindu/Bindu) - A framework that wraps any agent handler with DID-based cryptographic identity, A2A JSON-RPC over HTTP, OAuth2 auth, x402 (USDC) payments, and a built-in operator inbox. - [bub](https://github.com/bubbuild/bub) - A lightweight, hook-first Python framework for channel-native agents that live alongside people. - [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. From e403a8f0648343fc2208319cf6a0431c7cf247dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 09:58:37 +0400 Subject: [PATCH 0607/1130] build(deps): bump idna from 3.11 to 3.15 (#3147) Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.15. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md) - [Commits](https://github.com/kjd/idna/compare/v3.11...v3.15) --- updated-dependencies: - dependency-name: idna dependency-version: '3.15' dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 635a3db0..6ac5047c 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.13" [options] -exclude-newer = "2026-04-30T04:38:39.45925Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3D" [[package]] @@ -126,11 +126,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.15" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, ] [[package]] From a10ec14611354a1490cc007fc0f3bd2f1c01f9e1 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Sat, 23 May 2026 20:20:33 +0400 Subject: [PATCH 0608/1130] Add graphify (#3154) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c7b25af2..e5b52eb1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Agent Skills - [django-ai-plugins](https://github.com/vintasoftware/django-ai-plugins) - Django backend agent skills for Django, DRF, Celery, and Django-specific code review. + - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, R scripts, shell scripts, docs, papers, images, or videos into a queryable knowledge graph. - [sentry-skills](https://github.com/getsentry/skills) - Python-focused engineering skills for code review, debugging, and backend workflows. - [trailofbits-skills](https://github.com/trailofbits/skills) - Python-friendly security skills for auditing, testing, and safer backend development. Also [skills-curated](https://github.com/trailofbits/skills-curated). - Orchestration From 9036e5ede2e73f4836a35cacbe75036496678178 Mon Sep 17 00:00:00 2001 From: Dewan Shakil Date: Sun, 24 May 2026 04:25:55 +0530 Subject: [PATCH 0609/1130] Add KittenTTS --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e5b52eb1..28aaf94f 100644 --- a/README.md +++ b/README.md @@ -964,6 +964,7 @@ _Libraries for manipulating audio, video, and their metadata._ - Audio - [birdnet](https://github.com/kahst/BirdNET-Analyzer) - Deep learning framework for acoustic species detection; identifies bird species from audio recordings using TensorFlow. - [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. + - [KittenTTS](https://github.com/KittenML/KittenTTS) - Lightweight ONNX text-to-speech library with small CPU-friendly models. - [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. From fe6379f34d6a6a5039c05c436231b9b0c9b3b87e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 24 May 2026 15:25:01 +0800 Subject: [PATCH 0610/1130] clean up --- README.md | 2 -- SPONSORSHIP.md | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e5b52eb1..3361439b 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour ## **Sponsors** -- **[pyr](https://pyrun.dev)**: Zero-config Python project manager. Bootstraps its own runtime, app-convention, and working imports - out the box. - > The **#10 most-starred repo on GitHub**. Put your product in front of Python developers. [Become a sponsor](SPONSORSHIP.md). ## Categories diff --git a/SPONSORSHIP.md b/SPONSORSHIP.md index 4900a104..67f7cbc5 100644 --- a/SPONSORSHIP.md +++ b/SPONSORSHIP.md @@ -34,6 +34,7 @@ Your sponsorship puts your product in front of developers at the exact moment th ## Previously Sponsored By - [Warp](https://www.warp.dev/) - The terminal for modern developers. +- [pyr](https://pyrun.dev) - Zero-config Python project manager. ## Get Started From 9e20e5153ab5b028691e112dc1d95ee91fe8d0ec Mon Sep 17 00:00:00 2001 From: "xiaoyunchong.xyc" Date: Mon, 25 May 2026 00:47:50 +0800 Subject: [PATCH 0611/1130] Add FunASR to Speech section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3361439b..f1ad70b7 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - [vllm](https://github.com/vllm-project/vllm) - A high-throughput and memory-efficient inference and serving engine for LLMs. - Speech - [openai-whisper](https://github.com/openai/whisper) - A general-purpose automatic speech recognition model trained on 680k hours of multilingual and multitask supervised data. + - [funasr](https://github.com/modelscope/FunASR) - Industrial-grade speech recognition toolkit with 170x realtime speed, 50+ languages, speaker diarization, and emotion detection. - [vibevoice](https://github.com/microsoft/VibeVoice) - A family of open-source voice AI models from Microsoft for text-to-speech and long-form speech recognition. - [voxcpm](https://github.com/OpenBMB/VoxCPM) - A tokenizer-free text-to-speech foundation model for multilingual speech generation and voice cloning. From 79e0d8dc29000779cf4976448a59313b392a1e05 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Wed, 27 May 2026 15:41:37 +0800 Subject: [PATCH 0612/1130] docs: add repository agent guidance --- AGENTS.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..29c863bc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ +# AGENTS.md + +## Repository Overview + +An opinionated list of Python frameworks, libraries, tools, and resources. Published at [awesome-python.com](https://awesome-python.com/). + +## Entry Guidelines + +**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for acceptance criteria, quality requirements, rejection rules, and entry format. Apply these rules whenever adding or removing an entry, whether reviewing a PR or committing directly. + +## Structure + +- **README.md**: Source of truth for catalog entries and README sponsor placements. Hierarchical categories with alphabetically ordered entries. +- **CONTRIBUTING.md**: Submission guidelines and review criteria. +- **SPONSORSHIP.md**: Sponsor tiers, placement rules, and the editorial-independence policy. `website/templates/sponsorship.html` separately defines which sponsorship content appears on the published website page. +- **website/**: Static site generator that builds awesome-python.com from README.md. + - `build.py`: Parses README.md and renders HTML via Jinja2 templates. + - `fetch_github_stars.py`: Fetches star counts into `website/data/`. + - `readme_parser.py`: Markdown-to-structured-data parser. + - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. + - `tests/`: Pytest tests for the build pipeline. +- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. +- **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. + +## Key Rules + +- Alphabetical ordering within categories is mandatory. +- Quality over quantity. Only "awesome" projects. +- One project per PR. +- One entry per commit when adding or deleting entries. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. +- README.md is the source of truth for catalog entries and README sponsor placements; treat `SPONSORSHIP.md` and `website/templates/sponsorship.html` as separate sponsorship content surfaces. From 96da94e633cafafb210a31aca87474ae7996c1a2 Mon Sep 17 00:00:00 2001 From: Shawn Chen Date: Thu, 28 May 2026 17:59:03 +1200 Subject: [PATCH 0613/1130] Add chdb to Database --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f83de0ba..dfc7676a 100644 --- a/README.md +++ b/README.md @@ -435,6 +435,7 @@ _Libraries for connecting and operating databases._ _Databases implemented in Python._ +- [chdb](https://github.com/chdb-io/chdb) - In-process OLAP SQL engine with the full ClickHouse dialect, zero-copy pandas/Arrow interop, and federation to remote ClickHouse clusters via `remoteSecure()`. - [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. From d51eff7eafb48755e8a9b75f8b281f519f93e5fc Mon Sep 17 00:00:00 2001 From: Samxss Date: Sat, 30 May 2026 10:33:04 -0500 Subject: [PATCH 0614/1130] docs: add scikit-lego to Machine Learning section (#3168) Added scikit-lego library to the Machine Learning category keeping strict alphabetical order. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6a0634c5..59a4c19b 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [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. +- * [scikit-lego](https://github.com/koaning/scikit-lego) - A collection of lego bricks for scikit-learn pipelines. - [spark.ml](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/)'s scalable [Machine Learning library](https://spark.apache.org/docs/latest/ml-guide.html) for distributed computing. - [TabGAN](https://github.com/Diyago/Tabular-data-generation) - Synthetic tabular data generation using GANs, Diffusion Models, and LLMs. - [timesfm](https://github.com/google-research/timesfm) - A pretrained foundation model from Google Research for time-series forecasting. From f0bfc4cfa28c80f8fe4e42ae565fddb64429ca63 Mon Sep 17 00:00:00 2001 From: Renato <33936751+N4S4@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:23:10 +0100 Subject: [PATCH 0615/1130] Add synology-api entry to README (#3174) Add an entry for synology-api to the README's list of Python libraries. The link points to N4S4/synology-api and notes it provides a Python wrapper for Synology NAS APIs (Surveillance Station, File Station, Download Station, Docker, and 50+ other endpoints). --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59a4c19b..ac39ccfe 100644 --- a/README.md +++ b/README.md @@ -1124,6 +1124,7 @@ _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. +- [synology-api](https://github.com/N4S4/synology-api) - Python wrapper for Synology NAS APIs: Surveillance Station, File Station, Download Station, Docker, and 50+ other endpoints. ### Microsoft Windows From d85e849fa18e9a3c2d53a12302a678fe4b092e22 Mon Sep 17 00:00:00 2001 From: Tianyu Hu <1002770724@qq.com> Date: Thu, 4 Jun 2026 12:22:42 +0800 Subject: [PATCH 0616/1130] Update canonical GitHub project links (#3177) --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ac39ccfe..b20ceaf3 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [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. +- [mindsdb](https://github.com/mindsdb/minds-platform) - 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. - * [scikit-lego](https://github.com/koaning/scikit-lego) - A collection of lego bricks for scikit-learn pipelines. @@ -477,7 +477,7 @@ _Libraries for data analysis._ - [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. -- [data-profiling](https://github.com/Data-Centric-AI-Community/data-profiling) - Generate detailed data profiling reports for pandas DataFrames. +- [data-profiling](https://github.com/Data-Centric-AI-Community/fg-data-profiling) - Generate detailed data profiling reports for pandas DataFrames. - [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. @@ -562,7 +562,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [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 - - [mesa](https://github.com/projectmesa/mesa) - An agent-based modeling framework for building, analyzing, and visualizing complex system simulations. + - [mesa](https://github.com/mesa/mesa) - An agent-based modeling framework for building, analyzing, and visualizing complex system simulations. - [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. @@ -925,7 +925,7 @@ _Libraries for parsing and manipulating specific text formats._ - [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. + - [markdown](https://github.com/Python-Markdown/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 @@ -963,7 +963,7 @@ _Libraries for manipulating images._ _Libraries for manipulating audio, video, and their metadata._ - Audio - - [birdnet](https://github.com/kahst/BirdNET-Analyzer) - Deep learning framework for acoustic species detection; identifies bird species from audio recordings using TensorFlow. + - [birdnet](https://github.com/birdnet-team/BirdNET-Analyzer) - Deep learning framework for acoustic species detection; identifies bird species from audio recordings using TensorFlow. - [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. - [KittenTTS](https://github.com/KittenML/KittenTTS) - Lightweight ONNX text-to-speech library with small CPU-friendly models. - [librosa](https://github.com/librosa/librosa) - Python library for audio and music analysis. @@ -975,7 +975,7 @@ _Libraries for manipulating audio, video, and their metadata._ - 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. + - [tinytag](https://github.com/tinytag/tinytag) - A library for reading music meta data of MP3, OGG, FLAC and Wave files. ### Game Development @@ -1016,7 +1016,7 @@ _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`. +- [more-itertools](https://github.com/more-itertools/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. @@ -1071,7 +1071,7 @@ _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). +- [warehouse](https://github.com/pypi/warehouse) - Next generation Python Package Repository (PyPI). ### Distribution @@ -1138,7 +1138,7 @@ _Python programming on Microsoft Windows._ _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. +- [blinker](https://github.com/pallets-eco/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. From 5fe1dbd2043180d685a8c91fad71ae5fb409db4a Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Thu, 4 Jun 2026 06:25:05 +0200 Subject: [PATCH 0617/1130] Add uuid-utils (#3178) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b20ceaf3..dc20826d 100644 --- a/README.md +++ b/README.md @@ -1008,6 +1008,7 @@ _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. +- [uuid-utils](https://github.com/aminalaee/uuid-utils) - A fast, Rust-backed drop-in replacement for Python's built-in `uuid` module, supporting RFC 9562 (UUIDv6, UUIDv7, and UUIDv8). ### Functional Programming From b7e67813045ae62bc772c03f5ef9155373322044 Mon Sep 17 00:00:00 2001 From: Jinyang Date: Thu, 4 Jun 2026 08:29:45 +0400 Subject: [PATCH 0618/1130] add nuwa-skill --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc20826d..a5ecae82 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Agent Skills - [django-ai-plugins](https://github.com/vintasoftware/django-ai-plugins) - Django backend agent skills for Django, DRF, Celery, and Django-specific code review. - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, R scripts, shell scripts, docs, papers, images, or videos into a queryable knowledge graph. + - [nuwa-skill](https://github.com/alchaincyf/nuwa-skill/blob/main/README_EN.md) - Nuwa distills the thinking of anyone — let Musk, Naval, Munger, and Feynman work for you. - [sentry-skills](https://github.com/getsentry/skills) - Python-focused engineering skills for code review, debugging, and backend workflows. - [trailofbits-skills](https://github.com/trailofbits/skills) - Python-friendly security skills for auditing, testing, and safer backend development. Also [skills-curated](https://github.com/trailofbits/skills-curated). - Orchestration From 6dd7b2a65947d3c0aa87e909dd6633c96d2a8fc1 Mon Sep 17 00:00:00 2001 From: Beslan Birzhev Date: Thu, 4 Jun 2026 07:35:12 +0300 Subject: [PATCH 0619/1130] Add kubesdk (#3166) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc20826d..84ea401f 100644 --- a/README.md +++ b/README.md @@ -735,6 +735,7 @@ _Software and libraries for DevOps._ - 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. + - [kubesdk](https://github.com/puzl-cloud/kubesdk) - A modern async-first Kubernetes client and API model generator. - [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks. ### Distributed Computing From 4ae14cb62e03c3b1a374c51e5ebec7012ed44fd2 Mon Sep 17 00:00:00 2001 From: fauzan171 Date: Sun, 7 Jun 2026 00:05:49 +0700 Subject: [PATCH 0620/1130] fix: correct authlib description in Authentication section The previous description 'JavaScript Object Signing and Encryption draft implementation' was inaccurate. Authlib is a comprehensive Python library for building OAuth 1.0, OAuth 2.0, and OpenID Connect clients and servers. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d198f3e..c8cc8272 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ _Tools for managing, compressing and minifying website assets._ _Libraries for implementing authentication schemes._ - OAuth - - [authlib](https://github.com/authlib/authlib) - JavaScript Object Signing and Encryption draft implementation. + - [authlib](https://github.com/authlib/authlib) - A comprehensive library for building OAuth 1.0, OAuth 2.0, and OpenID Connect clients and servers. - [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. From 3e8404c95f1e61b51d6e996f7c8b11b15b38564a Mon Sep 17 00:00:00 2001 From: fauzan171 Date: Sun, 7 Jun 2026 00:07:29 +0700 Subject: [PATCH 0621/1130] fix: update outdated version references in descriptions - sanic: Remove 'Python 3.6+' version constraint (now requires Python >=3.10 per PyPI metadata) - django-guardian: Remove 'Django 1.2+' version constraint (outdated reference, now requires Python >=3.10 per PyPI metadata) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d198f3e..403b87bc 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ _Libraries for building RESTful and GraphQL APIs._ - [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. + - [sanic](https://github.com/sanic-org/sanic) - A Python 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. @@ -326,7 +326,7 @@ _Libraries for implementing authentication schemes._ - 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-guardian](https://github.com/django-guardian/django-guardian) - Implementation of per-object permissions for Django. - [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 From 647d723ecf02150042f5a20de93eba3d7ce7db0e Mon Sep 17 00:00:00 2001 From: fauzan171 Date: Sun, 7 Jun 2026 00:08:39 +0700 Subject: [PATCH 0622/1130] fix: update chardet description to remove outdated Python 2 reference Python 2 reached end-of-life in January 2020. The 'Python 2/3 compatible' qualifier is no longer relevant and misleading. Updated to match the official repository description. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d198f3e..5663170a 100644 --- a/README.md +++ b/README.md @@ -869,7 +869,7 @@ _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. + - [chardet](https://github.com/chardet/chardet) - Python 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. From bcfba785769e0c5cf26b53f93cc52ba08e7b9f77 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 7 Jun 2026 02:39:06 +0800 Subject: [PATCH 0623/1130] add Indie Dev sponsorship tier --- SPONSORSHIP.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/SPONSORSHIP.md b/SPONSORSHIP.md index 67f7cbc5..5eade931 100644 --- a/SPONSORSHIP.md +++ b/SPONSORSHIP.md @@ -26,11 +26,19 @@ Your sponsorship puts your product in front of developers at the exact moment th - Large logo and one-line description (max 120 characters) pinned at the very top of the README, above all project entries - Logo link in the sponsor section of [awesome-python.com](https://awesome-python.com/) -### Featured Sponsor - $150/month +### Featured Sponsor - $200/month - Text entry (`[Name](URL) - Description.`, max 120 characters) pinned at the top of the README, directly below Headline sponsors - Text link in the sponsor section of [awesome-python.com](https://awesome-python.com/) +### Indie Dev Sponsor - $99/month or $249/quarter + +- Lower-cost tier for indie developers, solo founders, and one-person companies only +- Text entry (`[Name](URL) - Description.`, max 120 characters) in the README sponsor section, directly below Featured sponsors +- Text link in the sponsor section of [awesome-python.com](https://awesome-python.com/) + +Indie Dev Sponsor is available only to founder-led products (one-person company). Funded startups, agencies, recruiters, larger companies, and products with enterprise sales teams should use Featured or Headline Sponsor. + ## Previously Sponsored By - [Warp](https://www.warp.dev/) - The terminal for modern developers. @@ -40,8 +48,8 @@ Your sponsorship puts your product in front of developers at the exact moment th Email [sponsorship@awesome-python.com](mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship) with: -- **Tier:** Headline Sponsor ($500/mo) or Featured Sponsor ($150/mo) -- **Content:** Product name, URL, logo, and description (Headline tier) or `[Name](URL) - Description.` entry (Featured tier) +- **Tier:** Headline Sponsor ($500/mo), Featured Sponsor ($200/mo), or Indie Dev Sponsor ($99/mo or $249/quarter) +- **Content:** Product name, URL, logo, and description (Headline tier) or `[Name](URL) - Description.` entry (Featured or Indie Dev tier) - **Duration:** 1, 3, 6 months, or longer - **Payment method:** US bank transfer (ACH/wire) or PayPal @@ -49,6 +57,6 @@ One upfront payment per term. Setup takes less than 24 hours. ## Editorial Independence -Sponsorship is logo/link placement in the README header. It does not influence which projects are listed. Listings are curated on merit through the normal [contribution process](CONTRIBUTING.md). +Sponsorship is logo or link placement in the README header. It does not influence which projects are listed. Listings are curated on merit through the normal [contribution process](CONTRIBUTING.md). We reserve the right to request changes to sponsor text, logos, or links that are misleading, off-topic, or incompatible with the README formatting. From ee08cd7d8696a414f7ec352837ea01a435ea0540 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 7 Jun 2026 02:49:13 +0800 Subject: [PATCH 0624/1130] update sponsorship webpage --- SPONSORSHIP.md | 12 +++---- website/static/style.css | 2 +- website/templates/sponsorship.html | 53 ++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/SPONSORSHIP.md b/SPONSORSHIP.md index 5eade931..e61b9955 100644 --- a/SPONSORSHIP.md +++ b/SPONSORSHIP.md @@ -37,12 +37,7 @@ Your sponsorship puts your product in front of developers at the exact moment th - Text entry (`[Name](URL) - Description.`, max 120 characters) in the README sponsor section, directly below Featured sponsors - Text link in the sponsor section of [awesome-python.com](https://awesome-python.com/) -Indie Dev Sponsor is available only to founder-led products (one-person company). Funded startups, agencies, recruiters, larger companies, and products with enterprise sales teams should use Featured or Headline Sponsor. - -## Previously Sponsored By - -- [Warp](https://www.warp.dev/) - The terminal for modern developers. -- [pyr](https://pyrun.dev) - Zero-config Python project manager. +Indie Dev Sponsor is available only to founder-led (one-person company) products. Funded startups, larger companies, and products with enterprise sales teams should use Featured or Headline Sponsor. ## Get Started @@ -55,6 +50,11 @@ Email [sponsorship@awesome-python.com](mailto:sponsorship@awesome-python.com?sub One upfront payment per term. Setup takes less than 24 hours. +## Previously Sponsored By + +- [Warp](https://www.warp.dev/) - The terminal for modern developers. +- [pyr](https://pyrun.dev) - Zero-config Python project manager. + ## Editorial Independence Sponsorship is logo or link placement in the README header. It does not influence which projects are listed. Listings are curated on merit through the normal [contribution process](CONTRIBUTING.md). diff --git a/website/static/style.css b/website/static/style.css index bbdac357..bac5b584 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -1278,7 +1278,7 @@ th[data-sort].sort-asc::after { padding: 0; margin: 0; display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1.5rem, 3vw, 2.75rem); } diff --git a/website/templates/sponsorship.html b/website/templates/sponsorship.html index f8807cfb..7111e450 100644 --- a/website/templates/sponsorship.html +++ b/website/templates/sponsorship.html @@ -71,8 +71,8 @@
    Who visits
    - Mid to senior Python developers arriving with a specific question: - a maintained ORM, a fast HTTP client, a task queue worth running in + Mid to senior Python developers arriving with a specific question: a + maintained ORM, a fast HTTP client, a task queue worth running in production.
    @@ -123,7 +123,7 @@
  • Logo link in the sponsor section of awesome-python.com.
  • Email about Headline tier @@ -131,7 +131,7 @@
  • Featured Sponsor

    - $150 + $200 / month

    @@ -146,11 +146,40 @@

  • Text link in the sponsor section of awesome-python.com.
  • Email about Featured tier +
  • +

    Indie Dev Sponsor

    +

    + $99 + / month or $249 / quarter +

    +

    + Lower-cost text link for eligible indie developers and solo + founders. +

    +
      +
    • + Text entry ([Name](URL) - Description., max 120 + characters) in the README sponsor section, directly below Featured + sponsors. +
    • +
    • Text link in the sponsor section of awesome-python.com.
    • +
    • + Available only to founder-led, one-person-company products. Funded + startups, agencies, recruiters, larger companies, and products + with enterprise sales teams should use Featured or Headline. +
    • +
    + Email about Indie Dev tier +
  • @@ -171,6 +200,12 @@ >The terminal for modern developers. +
  • + pyr + Zero-config Python project manager. +
  • @@ -193,13 +228,17 @@
    Tier
    -
    Headline Sponsor ($500/mo) or Featured Sponsor ($150/mo).
    +
    + Headline Sponsor ($500/mo), Featured Sponsor ($200/mo), or Indie Dev + Sponsor ($99/mo or $249/quarter). +
    Content
    Product name, URL, logo, and description (Headline tier), or - [Name](URL) - Description. entry (Featured tier). + [Name](URL) - Description. entry (Featured or Indie Dev + tier).
    From 9f156de2b499a6daa8e0f5dee47520749409946f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 7 Jun 2026 02:58:34 +0800 Subject: [PATCH 0625/1130] make descriptions shorter --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1a41027f..1a293885 100644 --- a/README.md +++ b/README.md @@ -134,10 +134,10 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Agent Skills - [django-ai-plugins](https://github.com/vintasoftware/django-ai-plugins) - Django backend agent skills for Django, DRF, Celery, and Django-specific code review. - - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, R scripts, shell scripts, docs, papers, images, or videos into a queryable knowledge graph. + - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. - [nuwa-skill](https://github.com/alchaincyf/nuwa-skill/blob/main/README_EN.md) - Nuwa distills the thinking of anyone — let Musk, Naval, Munger, and Feynman work for you. - [sentry-skills](https://github.com/getsentry/skills) - Python-focused engineering skills for code review, debugging, and backend workflows. - - [trailofbits-skills](https://github.com/trailofbits/skills) - Python-friendly security skills for auditing, testing, and safer backend development. Also [skills-curated](https://github.com/trailofbits/skills-curated). + - [trailofbits-skills](https://github.com/trailofbits/skills) - Python-friendly security skills for auditing, testing, and safer backend development. - Orchestration - [ag2](https://github.com/ag2ai/ag2) - An open-source AgentOS for multi-agent orchestration and building agentic AI systems. - [autogen](https://github.com/microsoft/autogen) - A programming framework for building agentic AI applications. @@ -192,7 +192,7 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git - [mindsdb](https://github.com/mindsdb/minds-platform) - 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. -- * [scikit-lego](https://github.com/koaning/scikit-lego) - A collection of lego bricks for scikit-learn pipelines. +- - [scikit-lego](https://github.com/koaning/scikit-lego) - A collection of lego bricks for scikit-learn pipelines. - [spark.ml](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/)'s scalable [Machine Learning library](https://spark.apache.org/docs/latest/ml-guide.html) for distributed computing. - [TabGAN](https://github.com/Diyago/Tabular-data-generation) - Synthetic tabular data generation using GANs, Diffusion Models, and LLMs. - [timesfm](https://github.com/google-research/timesfm) - A pretrained foundation model from Google Research for time-series forecasting. From 32ae78fd9606b1b74d1e8f7098897014f66e38c9 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 7 Jun 2026 03:08:43 +0800 Subject: [PATCH 0626/1130] fix: improve website SEO metadata --- website/build.py | 92 +++++++++++++++++++++++++++--- website/templates/category.html | 2 +- website/templates/sponsorship.html | 5 +- website/tests/test_build.py | 78 ++++++++++++++++++++++++- 4 files changed, 167 insertions(+), 10 deletions(-) diff --git a/website/build.py b/website/build.py index bcfc64d1..347c4fb0 100644 --- a/website/build.py +++ b/website/build.py @@ -28,6 +28,7 @@ BUILTIN_PUBLIC_URL = f"{SITE_URL}categories/{BUILTIN_SLUG}/" SPONSORSHIP_PATH = "/sponsorship/" SPONSORSHIP_PUBLIC_URL = f"{SITE_URL}sponsorship/" +SPONSORSHIP_DESCRIPTION = "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use." SOURCE_TYPE_DOMAINS = { "docs.python.org": "Built-in", @@ -128,6 +129,8 @@ def _website_node() -> dict: "@id": WEBSITE_ID, "name": "Awesome Python", "url": SITE_URL, + "inLanguage": "en", + "sameAs": "https://github.com/vinta/awesome-python", } @@ -164,21 +167,59 @@ def build_homepage_json_ld(entries: Sequence[TemplateEntry], total_categories: i "url": SITE_URL, "description": description, "isPartOf": ISPARTOF_WEBSITE, + "inLanguage": "en", "mainEntity": _item_list_payload(entries), }, ], } -def category_meta_description(name: str, entry_count: int, description: str) -> str: - count_sentence = f"Explore {entry_count} curated Python projects in {name}." +def category_meta_title(name: str, parent_name: str | None = None) -> str: + if parent_name: + title = f"{name} for {parent_name} - Awesome Python" + if len(title) <= 60: + return title + title = f"{parent_name}: {name} - Awesome Python" + if len(title) <= 60: + return title + return f"{name} - Awesome Python" + title = f"{name} Python Libraries - Awesome Python" + if len(title) <= 60: + return title + return f"{name} - Awesome Python" + + +def category_meta_description(name: str, entry_count: int, description: str, parent_name: str | None = None) -> str: + target = f"{name} for {parent_name}" if parent_name else name + count_sentence = f"Explore {entry_count} curated Python projects in {target}." if description: lead = description if description.endswith((".", "!", "?")) else f"{description}." return f"{lead} {count_sentence}" return f"{count_sentence} Part of the Awesome Python catalog." -def build_category_json_ld(name: str, url: str, description: str, entries: Sequence[TemplateEntry]) -> dict: +def build_breadcrumb_json_ld(items: Sequence[tuple[str, str]]) -> dict: + return { + "@type": "BreadcrumbList", + "itemListElement": [ + { + "@type": "ListItem", + "position": i, + "name": name, + "item": url, + } + for i, (name, url) in enumerate(items, start=1) + ], + } + + +def build_category_json_ld( + name: str, + url: str, + description: str, + entries: Sequence[TemplateEntry], + breadcrumbs: Sequence[tuple[str, str]], +) -> dict: return { "@context": "https://schema.org", "@graph": [ @@ -186,12 +227,38 @@ def build_category_json_ld(name: str, url: str, description: str, entries: Seque { "@type": "CollectionPage", "@id": url, - "name": f"{name} Python Libraries", + "name": name, "url": url, "description": description, "isPartOf": ISPARTOF_WEBSITE, + "inLanguage": "en", "mainEntity": _item_list_payload(entries), }, + build_breadcrumb_json_ld(breadcrumbs), + ], + } + + +def build_sponsorship_json_ld() -> dict: + return { + "@context": "https://schema.org", + "@graph": [ + _website_node(), + { + "@type": "WebPage", + "@id": SPONSORSHIP_PUBLIC_URL, + "name": "Sponsor Awesome Python", + "url": SPONSORSHIP_PUBLIC_URL, + "description": SPONSORSHIP_DESCRIPTION, + "isPartOf": ISPARTOF_WEBSITE, + "inLanguage": "en", + }, + build_breadcrumb_json_ld( + [ + ("Awesome Python", SITE_URL), + ("Sponsorship", SPONSORSHIP_PUBLIC_URL), + ] + ), ], } @@ -548,14 +615,21 @@ def build(repo_root: Path) -> None: group_categories: Sequence[ParsedSection] | None = None, ) -> None: page_dir.mkdir(parents=True, exist_ok=True) - category_description = category_meta_description(category["name"], len(entries), category["description"]) + parent_name = parent_category["name"] if parent_category else None + category_title = category_meta_title(category["name"], parent_name) + category_description = category_meta_description(category["name"], len(entries), category["description"], parent_name) + breadcrumbs = [("Awesome Python", SITE_URL)] + if parent_category: + breadcrumbs.append((parent_category["name"], category_public_url(parent_category))) + breadcrumbs.append((category["name"], category_url)) category_json_ld = json.dumps( - build_category_json_ld(category["name"], category_url, category_description, entries), + build_category_json_ld(category_title.removesuffix(" - Awesome Python"), category_url, category_description, entries, breadcrumbs), ensure_ascii=False, ).replace(" None: hero_stats.append(f"{repo_stars}+ stars on GitHub") hero_stats.append(f"Updated {build_date.strftime('%B %d, %Y')}") (sponsorship_dir / "index.html").write_text( - tpl_sponsorship.render(hero_stats=hero_stats), + tpl_sponsorship.render( + hero_stats=hero_stats, + sponsorship_description=SPONSORSHIP_DESCRIPTION, + sponsorship_json_ld=json.dumps(build_sponsorship_json_ld(), ensure_ascii=False).replace("{{ sponsorship_json_ld | safe }} +{% endblock %} {% block header %}
    diff --git a/website/tests/test_build.py b/website/tests/test_build.py index caada782..5649f295 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -569,7 +569,7 @@ class TestBuild: assert data["@context"] == "https://schema.org" graph = {node["@type"]: node for node in data["@graph"]} - assert set(graph) == {"WebSite", "CollectionPage"} + assert set(graph) == {"WebSite", "CollectionPage", "BreadcrumbList"} assert graph["WebSite"]["@id"] == "https://awesome-python.com/#website" collection = graph["CollectionPage"] assert collection["name"] == "Widgets Python Libraries" @@ -588,6 +588,12 @@ class TestBuild: positions = sorted(item["position"] for item in item_list["itemListElement"]) assert positions == [1, 2] + breadcrumbs = graph["BreadcrumbList"]["itemListElement"] + assert breadcrumbs == [ + {"@type": "ListItem", "position": 1, "name": "Awesome Python", "item": "https://awesome-python.com/"}, + {"@type": "ListItem", "position": 2, "name": "Widgets", "item": "https://awesome-python.com/categories/widgets/"}, + ] + def test_group_page_falls_back_to_default_description_in_json_ld(self, tmp_path): readme = textwrap.dedent("""\ # T @@ -685,9 +691,79 @@ class TestBuild: assert "

    Synchronous

    " in sync assert "category-breadcrumb" in sync + parser = HeadMetadataParser() + parser.feed(sync) + assert parser.title.strip() == "Synchronous for Web Frameworks - Awesome Python" + assert parser.meta_by_name["description"] == "Explore 1 curated Python projects in Synchronous for Web Frameworks. Part of the Awesome Python catalog." + + marker = '", start) + graph = {node["@type"]: node for node in json.loads(sync[start:end])["@graph"]} + assert graph["CollectionPage"]["name"] == "Synchronous for Web Frameworks" + assert graph["BreadcrumbList"]["itemListElement"] == [ + {"@type": "ListItem", "position": 1, "name": "Awesome Python", "item": "https://awesome-python.com/"}, + { + "@type": "ListItem", + "position": 2, + "name": "Web Frameworks", + "item": "https://awesome-python.com/categories/web-frameworks/", + }, + { + "@type": "ListItem", + "position": 3, + "name": "Synchronous", + "item": "https://awesome-python.com/categories/web-frameworks/synchronous/", + }, + ] + parent = (site / "categories" / "web-frameworks" / "index.html").read_text(encoding="utf-8") assert "category-breadcrumb" not in parent + def test_sponsorship_page_contains_json_ld(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + ## Projects + + **Tools** + + ## Widgets + + - [w1](https://example.com/w1) - A widget. + + # Contributing + + Done. + """) + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + build(tmp_path) + + site = tmp_path / "website" / "output" + html = (site / "sponsorship" / "index.html").read_text(encoding="utf-8") + parser = HeadMetadataParser() + parser.feed(html) + + assert parser.title.strip() == "Sponsor Awesome Python" + assert parser.meta_by_name["description"] == ( + "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use." + ) + assert parser.links_by_rel["canonical"] == "https://awesome-python.com/sponsorship/" + + marker = '", start) + graph = {node["@type"]: node for node in json.loads(html[start:end])["@graph"]} + + assert set(graph) == {"WebSite", "WebPage", "BreadcrumbList"} + assert graph["WebPage"]["@id"] == "https://awesome-python.com/sponsorship/" + assert graph["WebPage"]["url"] == "https://awesome-python.com/sponsorship/" + assert graph["BreadcrumbList"]["itemListElement"] == [ + {"@type": "ListItem", "position": 1, "name": "Awesome Python", "item": "https://awesome-python.com/"}, + {"@type": "ListItem", "position": 2, "name": "Sponsorship", "item": "https://awesome-python.com/sponsorship/"}, + ] + def test_index_embeds_filter_urls_json(self, tmp_path): readme = textwrap.dedent("""\ # T From c0b80fd75fca6b683703f015388346c1386689de Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 7 Jun 2026 03:21:12 +0800 Subject: [PATCH 0627/1130] fix: surface sponsorship link in header --- website/static/style.css | 2 ++ website/templates/category.html | 1 + website/templates/index.html | 1 + website/templates/sponsorship.html | 3 ++- website/tests/test_build.py | 3 +++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/website/static/style.css b/website/static/style.css index bac5b584..93056570 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -236,7 +236,9 @@ kbd { .hero-topbar-actions { display: flex; + flex-wrap: wrap; align-items: center; + justify-content: flex-end; gap: 0.75rem; } diff --git a/website/templates/category.html b/website/templates/category.html index 39115449..6e4b03e7 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -23,6 +23,7 @@ rel="noopener" >Submit a project + Sponsorship
    diff --git a/website/templates/index.html b/website/templates/index.html index bf2abd21..e7f1c9ea 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -18,6 +18,7 @@ rel="noopener" >Submit a project + Sponsorship diff --git a/website/templates/sponsorship.html b/website/templates/sponsorship.html index aeda4ca8..224d87e7 100644 --- a/website/templates/sponsorship.html +++ b/website/templates/sponsorship.html @@ -22,6 +22,7 @@ rel="noopener" >Submit a project + Sponsorship @@ -48,7 +49,7 @@ >Email sponsorship@awesome-python.com ' not in category_html + assert 'Sponsorship' in category_html assert "

    Widgets

    " in category_html assert 'Widget libraries. Also see awesome-widgets.' in category_html assert 'href="https://example.com/w1"' in category_html @@ -481,6 +482,7 @@ class TestBuild: assert parser.meta_by_name["twitter:description"] == expected_description assert parser.meta_by_name["twitter:image"] == expected_image assert "\n Sponsorship' in html assert 'id="hero-category-heading">Browse by category' in html assert 'class="hero-category-link" href="/categories/ai-and-agents/"' in html @@ -750,6 +752,7 @@ class TestBuild: "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use." ) assert parser.links_by_rel["canonical"] == "https://awesome-python.com/sponsorship/" + assert 'Sponsorship' in html marker = ' + + diff --git a/.gitignore b/.gitignore index 5fbd39f1..ab400a02 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,6 @@ docs/* !docs/adr/ # agents -.agents/ -.claude/skills/ .superpowers/ .playwright-cli/ skills-lock.json From fc88ebb89997fcf04c78e8e6776b944bdf4e7ba7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 15:41:55 +0800 Subject: [PATCH 0671/1130] feat: add BigQuery-based PyPI downloads fetcher Provides per-sitting download evidence for prune sweeps, per the shortlist-reform tooling plan. Shells out to the bq CLI against bigquery-public-data.pypi.file_downloads, parses entry names from README.md via readme_parser, and supports --dry-run and --names-file. Merges results into the gitignored cache at website/data/pypi_downloads.tsv. The table is clustered on file.project, so scanned bytes grow with the IN-list size: a dry run against the full README (~530 names) scanned 1.21 TB, past the 1 TB/month free tier. Per-sitting --names-file fetches are used instead of one big query. Co-Authored-By: Claude --- website/fetch_pypi_downloads.py | 94 +++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 website/fetch_pypi_downloads.py diff --git a/website/fetch_pypi_downloads.py b/website/fetch_pypi_downloads.py new file mode 100644 index 00000000..e79c3620 --- /dev/null +++ b/website/fetch_pypi_downloads.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Fetch last-30-day PyPI download counts for README entries via BigQuery. + +Shells out to the `bq` CLI (Google Cloud SDK) against the public +`bigquery-public-data.pypi.file_downloads` table. The table is clustered on +file.project, so scanned bytes grow with the IN-list: a few dozen names +prune to tens of GB while the full README (~530 names) balloons to over +1 TB — past the free tier. Fetch per curation sitting with --names-file +instead of all at once. Results are cached to data/pypi_downloads.tsv +(merged with any existing rows); names with no PyPI rows are written as +NOT_FOUND. Always --dry-run first to check the scan estimate. + +Usage: python fetch_pypi_downloads.py [--dry-run] [--names-file FILE] +""" + +import re +import subprocess +import sys +from json import loads +from pathlib import Path + +from readme_parser import parse_readme + +DATA_DIR = Path(__file__).parent / "data" +OUT_FILE = DATA_DIR / "pypi_downloads.tsv" +README_PATH = Path(__file__).parent.parent / "README.md" + +# PyPI normalizes names to lowercase with runs of -, _, . collapsed to -. +PYPI_NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$") + + +def normalize(name: str) -> str: + return re.sub(r"[-_.]+", "-", name.lower()) + + +def collect_names(readme_text: str) -> list[str]: + names = set() + for group in parse_readme(readme_text): + for section in group["categories"]: + for entry in section["entries"]: + normalized = normalize(entry["name"]) + if PYPI_NAME_RE.match(normalized): + names.add(normalized) + return sorted(names) + + +def fetch(names: list[str], dry_run: bool) -> dict[str, int]: + in_list = ", ".join(f"'{name}'" for name in names) + query = ( + "SELECT file.project AS project, COUNT(*) AS downloads " + "FROM `bigquery-public-data.pypi.file_downloads` " + "WHERE DATE(timestamp) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) " + "AND CURRENT_DATE() " + f"AND file.project IN ({in_list}) " + "GROUP BY file.project" + ) + cmd = ["bq", "query", "--use_legacy_sql=false", "--format=json"] + if dry_run: + cmd.append("--dry_run") + cmd.append(query) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(result.stderr, file=sys.stderr) + sys.exit(1) + if dry_run: + print(result.stdout.strip() or result.stderr.strip()) + sys.exit(0) + rows = loads(result.stdout) + return {row["project"]: int(row["downloads"]) for row in rows} + + +def main() -> None: + dry_run = "--dry-run" in sys.argv + if "--names-file" in sys.argv: + names_path = Path(sys.argv[sys.argv.index("--names-file") + 1]) + names = sorted({normalize(line) for line in names_path.read_text().split() if line}) + else: + names = collect_names(README_PATH.read_text()) + print(f"Querying {len(names)} package names...") + counts = fetch(names, dry_run) + merged: dict[str, str] = {} + if OUT_FILE.exists(): + for line in OUT_FILE.read_text().splitlines(): + name, _, value = line.partition("\t") + merged[name] = value + for name in names: + merged[name] = str(counts.get(name, "NOT_FOUND")) + OUT_FILE.write_text("\n".join(f"{name}\t{value}" for name, value in sorted(merged.items())) + "\n") + found = sum(1 for name in names if name in counts) + print(f"Done. {found}/{len(names)} names found on PyPI. Cached to {OUT_FILE}") + + +if __name__ == "__main__": + main() From bb53dfea9d723f1d14391bf8f5fde51af7fc5ba0 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:31:34 +0800 Subject: [PATCH 0672/1130] docs: require live verification of keep/drop reasons Qualitative keep/drop reasoning previously relied on training-data recollections that went unverified while only download numbers were checked live, as happened in the Data & Science preview. Key Rules now require every keep/drop reason to be verified against current online data at decision time. Co-Authored-By: Claude --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 402cf791..a951543b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,4 +28,5 @@ An opinionated list of Python frameworks, libraries, tools, and resources. Publi - A shortlist, not a catalog: per use case, up to 3 obvious choices plus up to 2 challengers, hard maximum 5. - One project per PR. - One entry per commit when adding or deleting entries. Exception: a prune sweep is one commit per section, its body listing each removal with its reason. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections alone are not evidence; verify before stating, and label anything unverifiable as a judgment call. - README.md is the single source of content truth. From 3ae3483d3626dbd618969f379266360d652051e1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:53:12 +0800 Subject: [PATCH 0673/1130] docs: reinstate crewai in AI and Agents orchestration Re-added as a Challenger alongside pydantic-ai. The maintainer reviewed the committed-drops audit (all 36 removals re-verified against live data 2026-08-15) and flipped crewai back: 19.4M downloads/month, 57.1K stars, repo active same-day. The original 'buzz peaked' drop was judgment the audit data did not support. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 080b7485..ada818a5 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Orchestration - [langchain](https://github.com/langchain-ai/langchain) - Building applications with LLMs through composability. - [langgraph](https://github.com/langchain-ai/langgraph) - Low-level orchestration framework for building stateful, long-running LLM agents. + - [crewai](https://github.com/crewAIInc/crewAI) - A framework for orchestrating role-playing autonomous AI agents for collaborative task solving. - [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - Vendor Agent SDKs - [claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-python) - Anthropic's Python SDK for building AI agents on Claude Code's harness — custom tools, in-process MCP servers, hooks. From 9290f1815302c6247b5109106c05277d130470dd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:53:46 +0800 Subject: [PATCH 0674/1130] feat: reinstate bokeh as Plotting challenger The maintainer re-verified all 36 removals from the committed-drops audit against live data on 2026-08-15 and flipped bokeh back: 8.4M downloads/month, 20.4K stars, repo pushed the same day, the healthiest project among all audited drops. Placed as the second Challenger after altair, bringing Plotting to the hard max of 5 entries. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ada818a5..19515899 100644 --- a/README.md +++ b/README.md @@ -525,6 +525,7 @@ _Libraries for visualizing data. Also see [awesome-javascript](https://github.co - [plotly](https://github.com/plotly/plotly.py) - Interactive graphing library for Python. - [seaborn](https://github.com/mwaskom/seaborn) - Statistical data visualization using Matplotlib. - [altair](https://github.com/vega/altair) - Declarative statistical visualization library for Python. + - [bokeh](https://github.com/bokeh/bokeh) - Interactive Web Plotting for Python. - Specialized - [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. From 1463ba82867787ce8eb3f83a10deb3a70a1e401d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:57:39 +0800 Subject: [PATCH 0675/1130] prune: sweep Data Analysis to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15, all rows live-verified). Keeps: pandas and polars as Obvious Choices, ibis as Challenger — renamed to ibis-framework per the PyPI-name convention (maintainer-confirmed; bare 'ibis' on PyPI is an unrelated template engine). aws-sdk-pandas returns as a sub-item under pandas per maintainer direction (sub-items sit outside the cap, awesome-pytest pattern). Removed (downloads are PyPI last-30-days via BigQuery, 2026-08-15): - data-profiling (18.2K/month under fg-data-profiling): YData's profiler, but the listed name is not on PyPI and a profiling layer is not an obvious choice for this use case - datasette (178.6K/month): data publishing and exploration tool, a different job - desbordante (3.4K/month): no adoption - modin (1.6M/month): the pandas-at-scale job moved to polars and dask; repo quiet since 2026-02 - pathway (16.5K/month): stars far ahead of usage (62.5K stars); ETL job, mis-homed here - percentify (182.2K/month): personal project, 11-month-old repo, 418 stars — count not credible against the footprint Co-Authored-By: Claude --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 19515899..605c073c 100644 --- a/README.md +++ b/README.md @@ -480,18 +480,10 @@ _Libraries for serializing complex data types._ _Libraries for data analysis._ -- [aws-sdk-pandas](https://github.com/aws/aws-sdk-pandas) - Pandas on AWS. -- [data-profiling](https://github.com/Data-Centric-AI-Community/fg-data-profiling) - Generate detailed data profiling reports for pandas DataFrames. -- [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. - -- [percentify](https://github.com/data-centt/percentify) - Exploratory stats and data-quality diagnostics/profiler, that supports both pandas and Polars DataFrames inputs. - + - [aws-sdk-pandas](https://github.com/aws/aws-sdk-pandas) - [polars](https://github.com/pola-rs/polars) - A fast DataFrame library implemented in Rust with a Python API. +- [ibis-framework](https://github.com/ibis-project/ibis) - A portable Python dataframe library with a single API for 20+ backends. ### Data Ingestion / ETL From 54c0c0c34afc331be441ba4c32fbd59ffdfbe7d5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:57:55 +0800 Subject: [PATCH 0676/1130] prune: sweep Data Ingestion / ETL to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15, all rows live-verified). General keeps dlt (sole entry). Financial Data keeps akshare and yfinance as Obvious Choices with edgartools and openbb as Challengers (openbb on the platform judgment: 71.9K stars against 132K pip installs). Removed (downloads are PyPI last-30-days via BigQuery, 2026-08-15): - lumibot (168.2K/month): algorithmic trading and backtesting framework with broker execution — not financial data; vertical app (TradingAgents precedent) Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 605c073c..d5c23f39 100644 --- a/README.md +++ b/README.md @@ -493,10 +493,9 @@ _Libraries for data extraction, transformation, and loading pipelines across mul - [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. - 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. - - [lumibot](https://github.com/Lumiwealth/lumibot) - Algorithmic trading framework for backtesting and live deployment across stocks, options, crypto, futures, and forex. - - [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. + - [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. ### Data Validation From 9103f59cbb50189043a9a0f43f0f86d9118cce3a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:58:22 +0800 Subject: [PATCH 0677/1130] prune: sweep Data Validation to the shortlist cap Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15, all rows live-verified). Keeps jsonschema, pandera, and pydantic as Obvious Choices for their distinct validation jobs. Removed (downloads are PyPI last-30-days via BigQuery, 2026-08-15): - cerberus (6.7M/month): pre-pydantic era passed; repo alive but slow - voluptuous (8.0M/month): pre-pydantic era passed; repo description opens with "CONTRIBUTIONS ONLY" (maintenance mode) Co-Authored-By: Claude --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index d5c23f39..d2a051c2 100644 --- a/README.md +++ b/README.md @@ -501,11 +501,9 @@ _Libraries for data extraction, transformation, and loading pipelines across mul _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](https://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. -- [voluptuous](https://github.com/alecthomas/voluptuous) - A Python data validation library primarily intended for validating data from untrusted sources. ### Data Visualization From 9631207f543eb2379f59a351c8118fee30524b6b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:58:37 +0800 Subject: [PATCH 0678/1130] prune: sweep Geolocation to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15, all rows live-verified). Keeps geojson, geopandas, and geopy as Obvious Choices; geodjango kept by maintainer flip and listed last as Challenger (the cap allows three obvious choices). Removed (downloads are PyPI last-30-days via BigQuery, 2026-08-15): - django-countries (2.3M/month): country choices for Django forms and models — Django form tooling, not geolocation work Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d2a051c2..2e681ebb 100644 --- a/README.md +++ b/README.md @@ -527,11 +527,10 @@ _Libraries for visualizing data. Also see [awesome-javascript](https://github.co _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://github.com/django/django) - A world-class geographic web framework that is part of [Django](https://docs.djangoproject.com/en/dev/ref/contrib/gis/). - [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. +- [geodjango](https://github.com/django/django) - A world-class geographic web framework that is part of [Django](https://docs.djangoproject.com/en/dev/ref/contrib/gis/). ### Science From 0c1409dea85ff2167f784ba251f2f20a28a17944 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:59:05 +0800 Subject: [PATCH 0679/1130] prune: sweep Science to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15, all rows live-verified). Restructure first: Core narrows to numba, numpy, scipy; mint Symbolic Mathematics (sympy), Statistics (statsmodels), Graphs and Networks (networkx), and Computational Geometry (shapely) — five co-obvious tools doing five different jobs, the decision-13 pattern. Other shrinks to a two-entry grab-bag; colour renamed to colour-science per the PyPI-name convention (bare 'colour' on PyPI is an unrelated color-conversion library). Then cap. Removed (downloads are PyPI last-30-days via BigQuery, 2026-08-15): - cclib (29.7K/month): computational-chemistry log parsing, small audience - openbabel (54.9K/month pip; conda-forge adds only 1.8M all-time): legacy toolbox next to rdkit - pydy (1.2K/month): near-zero usage - PythonRobotics: educational algorithm compilation ("sample codes and textbook" per its own description), not a library; not on PyPI - pathsim (6.3K/month): no adoption Co-Authored-By: Claude --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2e681ebb..7b836c30 100644 --- a/README.md +++ b/README.md @@ -540,28 +540,27 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [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. +- Symbolic Mathematics - [sympy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. +- Statistics + - [statsmodels](https://github.com/statsmodels/statsmodels) - Statistical modeling and econometrics in Python. - 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 - [mesa](https://github.com/mesa/mesa) - An agent-based modeling framework for building, analyzing, and visualizing complex system simulations. - - [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. +- Graphs and Networks - [networkx](https://github.com/networkx/networkx) - A high-productivity software for complex networks. +- Computational Geometry - [shapely](https://github.com/shapely/shapely) - Manipulation and analysis of geometric objects in the Cartesian plane. +- Other + - [colour-science](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. ### Quantum Computing From c9abd51467e3ffa4f86f325401da3d0754270ec4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 15 Aug 2026 23:59:21 +0800 Subject: [PATCH 0680/1130] style: apply challenger ordering and entry format to Quantum Computing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per ADR-0001, maintainer-adjudicated at the Data & Science preview review (2026-08-15). No removals — all four entries keep: qiskit and qutip as Obvious Choices (circuit SDK and quantum physics simulation, distinct jobs), cirq and pennylane as Challengers, ordered per the Challenger-ordering rule. Format fixes bundled per the Key Rules bundling exception: three em-dash separators normalized to hyphens, Cirq lowercased to its PyPI name. Co-Authored-By: Claude --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7b836c30..dfb968d7 100644 --- a/README.md +++ b/README.md @@ -566,10 +566,10 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi _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. +- [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. +- [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. **Developer Tools** From 846a6bdb91dfcc1bfb5f42efa67be176ad918d37 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 00:02:30 +0800 Subject: [PATCH 0681/1130] docs: order entries by downloads/month within tiers, not alphabetically Maintainer decision 2026-08-16, amending the ordering half of the Challenger-marking rule: within a use case, obvious choices still come first and challengers still follow (position stays the marker), but each tier now orders by PyPI downloads per month descending instead of alphabetically. Entries without a download signal (stdlib modules, agent skill packs) sort last within their tier, alphabetically. Updated in CONTRIBUTING.md (Entry Ordering), CONTEXT.md (Challenger), and CLAUDE.md (Key Rules). Co-Authored-By: Claude --- CLAUDE.md | 2 +- CONTEXT.md | 2 +- CONTRIBUTING.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a951543b..da1c684b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ An opinionated list of Python frameworks, libraries, tools, and resources. Publi ## Key Rules -- Ordering within a use case: obvious choices first (alphabetically), then challengers (alphabetically). See CONTRIBUTING.md. +- Ordering within a use case: obvious choices first, then challengers, each tier by PyPI downloads/month descending; no-signal entries last in tier, alphabetically. See CONTRIBUTING.md. - A shortlist, not a catalog: per use case, up to 3 obvious choices plus up to 2 challengers, hard maximum 5. - One project per PR. - One entry per commit when adding or deleting entries. Exception: a prune sweep is one commit per section, its body listing each removal with its reason. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. diff --git a/CONTEXT.md b/CONTEXT.md index d89804b3..cab584e1 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -41,7 +41,7 @@ The only admission path into a full Use Case: the PR names the entry it replaces _Avoid_: One-in-one-out (informal alias) **Challenger**: -An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first (alphabetically), then Challengers (alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. +An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept removed entirely) **Split**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2367c766..c16c0a3b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ Use the **PyPI package name** as the display name so developers can copy it dire ### Entry Ordering -Within a use case, the obvious choices are listed first, alphabetically; challengers follow, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. +Within a use case, the obvious choices are listed first, ordered by PyPI downloads per month from high to low; challengers follow, in the same order. Entries without a download signal (standard-library modules, agent skill packs) sort last within their tier, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. ## Changing the Structure From 46b7b57d6b52f3af498c787c3ce6559a8de5b1c4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 00:03:46 +0800 Subject: [PATCH 0682/1130] style: reorder swept sections by downloads/month within tiers Apply the new ordering rule (downloads/month descending within each tier, no-signal entries last alphabetically) to all ten swept sections. Sixteen use cases change order: Vendor Agent SDKs, LLM Inference and Serving, Speech, File Conversion, PDF, Markdown, Data Visualization Specialized and Dashboards, Testing Frameworks and Mock, Financial Data, Data Validation, Geolocation, Science Core and Simulation and Modeling, Quantum Computing challengers. Unswept sections keep their current order until their own sweeps. Bundled as a single categorization commit per the Key Rules bundling exception. Co-Authored-By: Claude --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index dfb968d7..0857aed4 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,8 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - [crewai](https://github.com/crewAIInc/crewAI) - A framework for orchestrating role-playing autonomous AI agents for collaborative task solving. - [pydantic-ai](https://github.com/pydantic/pydantic-ai) - A Python agent framework for building generative AI applications with structured schemas. - Vendor Agent SDKs - - [claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-python) - Anthropic's Python SDK for building AI agents on Claude Code's harness — custom tools, in-process MCP servers, hooks. - [openai-agents](https://github.com/openai/openai-agents-python) - OpenAI's framework for building and managing AI agents. + - [claude-agent-sdk](https://github.com/anthropics/claude-agent-sdk-python) - Anthropic's Python SDK for building AI agents on Claude Code's harness — custom tools, in-process MCP servers, hooks. - Personal Assistants - [hermes-agent](https://github.com/nousresearch/hermes-agent) - An adaptive personal AI assistant that grows with you. - Prompt Optimization @@ -155,9 +155,9 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Pre-trained Models - [transformers](https://github.com/huggingface/transformers) - A framework that lets you easily use pre-trained transformer models for NLP, vision, and audio tasks. - LLM Inference and Serving - - [mlx-lm](https://github.com/ml-explore/mlx-lm) - Run and fine-tune large language models on Apple Silicon with MLX. - [sglang](https://github.com/sgl-project/sglang) - A high-performance serving framework for large language models and multimodal models. - [vllm](https://github.com/vllm-project/vllm) - A high-throughput and memory-efficient inference and serving engine for LLMs. + - [mlx-lm](https://github.com/ml-explore/mlx-lm) - Run and fine-tune large language models on Apple Silicon with MLX. - LLM Gateways - [LiteLLM](https://github.com/BerriAI/litellm) - Call 100+ LLMs using OpenAI format. - Image and Video Generation @@ -165,8 +165,8 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - Fine-tuning - [unsloth](https://github.com/unslothai/unsloth) - A library for faster LLM fine-tuning and training with reduced memory usage. - Speech - - [funasr](https://github.com/modelscope/FunASR) - Industrial-grade speech recognition toolkit with 170x realtime speed, 50+ languages, speaker diarization, and emotion detection. - [openai-whisper](https://github.com/openai/whisper) - A general-purpose automatic speech recognition model trained on 680k hours of multilingual and multitask supervised data. + - [funasr](https://github.com/modelscope/FunASR) - Industrial-grade speech recognition toolkit with 170x realtime speed, 50+ languages, speaker diarization, and emotion detection. - [vibevoice](https://github.com/microsoft/VibeVoice) - A family of open-source voice AI models from Microsoft for text-to-speech and long-form speech recognition. ### Deep Learning @@ -492,8 +492,8 @@ _Libraries for data extraction, transformation, and loading pipelines across mul - General - [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. - Financial Data - - [akshare](https://github.com/akfamily/akshare) - A financial data interface library, built for human beings! - [yfinance](https://github.com/ranaroussi/yfinance) - Easy Pythonic way to download market and financial data from Yahoo Finance. + - [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. @@ -501,9 +501,9 @@ _Libraries for data extraction, transformation, and loading pipelines across mul _Libraries for validating data. Used for forms in many cases._ +- [pydantic](https://github.com/pydantic/pydantic) - Data validation using Python type hints. - [jsonschema](https://github.com/python-jsonschema/jsonschema) - An implementation of [JSON Schema](https://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 @@ -517,19 +517,19 @@ _Libraries for visualizing data. Also see [awesome-javascript](https://github.co - [bokeh](https://github.com/bokeh/bokeh) - Interactive Web Plotting for Python. - Specialized - [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. - - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. - [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](https://www.graphviz.org/). + - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. - 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. + - [gradio](https://github.com/gradio-app/gradio) - Build and share machine learning apps, all in Python. ### Geolocation _Libraries for geocoding addresses and working with latitudes and longitudes._ -- [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. +- [geojson](https://github.com/jazzband/geojson) - Python bindings and utilities for GeoJSON. - [geodjango](https://github.com/django/django) - A world-class geographic web framework that is part of [Django](https://docs.djangoproject.com/en/dev/ref/contrib/gis/). ### Science @@ -537,9 +537,9 @@ _Libraries for geocoding addresses and working with latitudes and longitudes._ _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. + - [numba](https://github.com/numba/numba) - Python JIT compiler to LLVM aimed at scientific Python. - Symbolic Mathematics - [sympy](https://github.com/sympy/sympy) - A Python library for symbolic mathematics. - Statistics @@ -551,9 +551,9 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [astropy](https://github.com/astropy/astropy) - A community Python library for Astronomy. - [obspy](https://github.com/obspy/obspy) - A Python toolbox for seismology. - Simulation and Modeling - - [mesa](https://github.com/mesa/mesa) - An agent-based modeling framework for building, analyzing, and visualizing complex system simulations. - [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. + - [mesa](https://github.com/mesa/mesa) - An agent-based modeling framework for building, analyzing, and visualizing complex system simulations. - Graphs and Networks - [networkx](https://github.com/networkx/networkx) - A high-productivity software for complex networks. - Computational Geometry @@ -568,8 +568,8 @@ _Libraries for quantum computing._ - [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. -- [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. +- [cirq](https://github.com/quantumlib/Cirq) - A Google-developed framework focused on hardware-aware quantum circuit design for NISQ devices. **Developer Tools** @@ -628,9 +628,9 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- _Libraries for testing codebases and generating test data. Also see [awesome-python-testing](https://github.com/cleder/awesome-python-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. - [awesome-pytest](https://github.com/augustogoulart/awesome-pytest) + - [hypothesis](https://github.com/HypothesisWorks/hypothesis) - Hypothesis is an advanced Quickcheck style property based testing library. - [robotframework](https://github.com/robotframework/robotframework) - A generic test automation framework. - Test Runners - [tox](https://github.com/tox-dev/tox) - Auto builds and tests distributions in multiple Python versions @@ -643,11 +643,11 @@ _Libraries for testing codebases and generating test data. Also see [awesome-pyt - API Testing - [schemathesis](https://github.com/schemathesis/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications. - Mock + - [responses](https://github.com/getsentry/responses) - A utility library for mocking out the requests Python library. - [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. - - [responses](https://github.com/getsentry/responses) - A utility library for mocking out the requests Python library. - - [respx](https://github.com/lundberg/respx) - Mock HTTPX with awesome request patterns and response side effects. - [vcrpy](https://github.com/kevin1024/vcrpy) - Record and replay HTTP interactions on your tests. + - [respx](https://github.com/lundberg/respx) - Mock HTTPX with awesome request patterns and response side effects. - 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`) @@ -902,8 +902,8 @@ _Libraries for parsing and manipulating specific text formats._ - [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. - File Conversion - - [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. - [markitdown](https://github.com/microsoft/markitdown) - Python tool for converting files and office documents to Markdown. + - [docling](https://github.com/docling-project/docling) - Library for converting documents into structured data. - Excel - [openpyxl](https://openpyxl.readthedocs.io/en/stable/) - A library for reading and writing Excel 2010 xlsx/xlsm/xltx/xltm files. - [xlsxwriter](https://github.com/jmcnamara/XlsxWriter) - A Python module for creating Excel .xlsx files. @@ -912,14 +912,14 @@ _Libraries for parsing and manipulating specific text formats._ - PowerPoint - [python-pptx](https://github.com/scanny/python-pptx) - Python library for creating and updating PowerPoint (.pptx) files. - PDF - - [pdfminer.six](https://github.com/pdfminer/pdfminer.six) - Pdfminer.six is a community maintained fork of the original PDFMiner. - [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. + - [pdfminer.six](https://github.com/pdfminer/pdfminer.six) - Pdfminer.six is a community maintained fork of the original PDFMiner. - HTML-to-PDF - [weasyprint](https://github.com/Kozea/WeasyPrint) - A visual rendering engine for HTML and CSS that can export to PDF. - Markdown - - [markdown](https://github.com/Python-Markdown/markdown) - A Python implementation of John Gruber’s 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/Python-Markdown/markdown) - A Python implementation of John Gruber’s Markdown. - [mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown. - Data Formats - [pyyaml](https://github.com/yaml/pyyaml) - YAML implementations for Python. From 59cc42e2c39165ae44b925002f552e18083abe6a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 00:38:48 +0800 Subject: [PATCH 0683/1130] docs: sync AGENTS.md with the shortlist reform rules AGENTS.md still carried pre-reform Key Rules (mandatory alphabetical ordering, "quality over quantity" lanes-era language, no prune-sweep commit exception, no live-verification rule). Bring it in line with CLAUDE.md and CONTRIBUTING.md: downloads-descending tier ordering, 3+2 cap, sweep-commit exception, verification rule. Also record the UV_PYTHON=3.13 workaround for Python-3.14-only machines in both files' Makefile notes, and complete CLAUDE.md's Makefile target list. Co-Authored-By: Claude --- AGENTS.md | 13 +++++++------ CLAUDE.md | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 29c863bc..646889dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,11 +6,11 @@ An opinionated list of Python frameworks, libraries, tools, and resources. Publi ## Entry Guidelines -**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for acceptance criteria, quality requirements, rejection rules, and entry format. Apply these rules whenever adding or removing an entry, whether reviewing a PR or committing directly. +**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for admission rules, quality requirements, rejection rules, and entry format. Apply these rules whenever adding or removing an entry, whether reviewing a PR or committing directly. ## Structure -- **README.md**: Source of truth for catalog entries and README sponsor placements. Hierarchical categories with alphabetically ordered entries. +- **README.md**: Source of truth for catalog entries and README sponsor placements. Hierarchical categories; entries ordered per the Key Rules below. - **CONTRIBUTING.md**: Submission guidelines and review criteria. - **SPONSORSHIP.md**: Sponsor tiers, placement rules, and the editorial-independence policy. `website/templates/sponsorship.html` separately defines which sponsorship content appears on the published website page. - **website/**: Static site generator that builds awesome-python.com from README.md. @@ -19,13 +19,14 @@ An opinionated list of Python frameworks, libraries, tools, and resources. Publi - `readme_parser.py`: Markdown-to-structured-data parser. - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. - `tests/`: Pytest tests for the build pipeline. -- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. +- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. On machines with only Python 3.14, prefix uv-based targets with `UV_PYTHON=3.13` (watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`). - **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. ## Key Rules -- Alphabetical ordering within categories is mandatory. -- Quality over quantity. Only "awesome" projects. +- Ordering within a use case: obvious choices first, then challengers, each tier by PyPI downloads/month descending; no-signal entries last in tier, alphabetically. See CONTRIBUTING.md. +- A shortlist, not a catalog: per use case, up to 3 obvious choices plus up to 2 challengers, hard maximum 5. - One project per PR. -- One entry per commit when adding or deleting entries. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. +- One entry per commit when adding or deleting entries. Exception: a prune sweep is one commit per section, its body listing each removal with its reason. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections alone are not evidence; verify before stating, and label anything unverifiable as a judgment call. - README.md is the source of truth for catalog entries and README sponsor placements; treat `SPONSORSHIP.md` and `website/templates/sponsorship.html` as separate sponsorship content surfaces. diff --git a/CLAUDE.md b/CLAUDE.md index da1c684b..52b9391c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,7 @@ An opinionated list of Python frameworks, libraries, tools, and resources. Publi - `readme_parser.py`: Markdown-to-structured-data parser. - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. - `tests/`: Pytest tests for the build pipeline. -- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make fetch_github_stars`. +- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. On machines with only Python 3.14, prefix uv-based targets with `UV_PYTHON=3.13` (watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`). - **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. ## Key Rules From 83f6cebd93e1113eaf1bf1e9b26818ff1c974fa1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 00:39:29 +0800 Subject: [PATCH 0684/1130] feat: add audit-the-list skill for recurring section audits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Packages the audit process proven across the shortlist-reform sweeps as a reusable skill: resolve scope from the arguments (AskUserQuestion when ambiguous or absent), fetch live evidence for every entry (BigQuery downloads, repo state, PyPI metadata), draft verdicts with restructure-before-cap and tier promotions/demotions, review through the verdict-preview page, execute one commit per section on explicit go, and record durable conclusions into CONTRIBUTING.md, CLAUDE.md, AGENTS.md, and CONTEXT.md. CONTEXT.md gains the Audit glossary term (the reform sweeps were the first Audits). Rules stay single-sourced in CONTRIBUTING.md — the skill carries process only. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 39 ++++++++++++++++++++++++++ CONTEXT.md | 6 ++++ 2 files changed, 45 insertions(+) create mode 100644 .claude/skills/audit-the-list/SKILL.md diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md new file mode 100644 index 00000000..5cbc6702 --- /dev/null +++ b/.claude/skills/audit-the-list/SKILL.md @@ -0,0 +1,39 @@ +--- +name: audit-the-list +description: Audit README.md sections against the shortlist rules — re-verify every entry's verdict with live data, promote or demote challengers, restructure oversized use cases, prune, and evaluate proposed additions. Fire when the maintainer asks to audit, sweep, prune, re-check, or maintain sections, or asks whether an entry (or a proposed one) deserves its slot. +argument-hint: [all sections or specific sections] +--- + +# Audit the list + +An Audit re-runs the admission test over live sections of README.md: every entry re-verified against current data, tiers reassessed, structure reconsidered, then the maintainer adjudicates and the result is committed. The rules live in CONTRIBUTING.md (Admission, Evidence, Entry Ordering) and CONTEXT.md (vocabulary) — this skill is the process that applies them, not a second copy of them. Entry changes land only on the maintainer's explicit go. + +## 1. Scope + +Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — AskUserQuestion whether it means every section or only never-audited ones; prior audits are recorded in git history (`git log --oneline --grep="sweep\|audit"`). With no arguments, AskUserQuestion listing the never-audited Thematic Groups. Batch the work one Thematic Group per sitting. Done when the section list is settled. + +## 2. Evidence + +Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): + +- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads.py --names-file --dry-run` first, then without `--dry-run`. The script's docstring carries the BigQuery cost constraints — read it before batching. `https://pypistats.org/api/packages/{name}/recent` for a handful of spot-checks, paced 8s or slower. +- **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. +- **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. + +Done when every entry in scope has downloads (or a stated no-signal reason), repo state, and a confirmed PyPI name. + +## 3. Verdicts + +Draft a verdict and reason for every entry, restructure before cap: decide Splits, mints, and re-homes first — cutting before restructuring destroys entries — then apply the cap tier by tier, including promotions and demotions between obvious choice and challenger. Ground every reason in the fetched evidence; label anything unverifiable as a judgment call. Where fresh evidence contradicts the standing verdict, say so in the reason instead of silently keeping the seed. + +## 4. Review and go + +Run the verdict-preview skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go. + +## 5. Execute + +One commit per section: body lists each removal with its reason and downloads figure; restructures, tier moves, and reorders ride the same commit. Format-only outcomes (no removals) are a single style commit. `UV_PYTHON=3.13 make test` before every commit, `UV_PYTHON=3.13 make build` after the last one. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes. + +## 6. Record + +A conclusion that outlives the sitting goes into the repo before the audit ends: admission or evidence rules into CONTRIBUTING.md, repo process and environment facts into CLAUDE.md and AGENTS.md (kept in sync), vocabulary into CONTEXT.md. Done when nothing the next audit needs is stranded in conversation. diff --git a/CONTEXT.md b/CONTEXT.md index cab584e1..8fae6cb8 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -46,3 +46,9 @@ _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept remo **Split**: Maintainer-only restructuring of an oversized Use Case into finer Use Cases (new Subcategories), considered before any trimming when the size reflects genuinely distinct jobs. A submitter's entry PR can never perform a Split. + +### Maintenance + +**Audit**: +The recurring maintenance pass over one or more Sections: every Entry's verdict re-verified against current evidence, Challengers promoted or demoted, oversized Use Cases Split, drops and admissions re-decided. The maintainer adjudicates through an interactive preview, and entry changes land only on their explicit go. The 2026-08 shortlist-reform sweeps were the first Audits. +_Avoid_: Sweep (reform-era name for a Section's first Audit) From 7f0e9239042518a988504077a09501790b88e88f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 00:41:57 +0800 Subject: [PATCH 0685/1130] refactor: rename verdict-preview skill to preview-verdicts Verb-first naming per maintainer preference. Directory, frontmatter name, and the audit-the-list reference updated together; template.html moves with the directory unchanged. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- .claude/skills/{verdict-preview => preview-verdicts}/SKILL.md | 2 +- .../skills/{verdict-preview => preview-verdicts}/template.html | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename .claude/skills/{verdict-preview => preview-verdicts}/SKILL.md (99%) rename .claude/skills/{verdict-preview => preview-verdicts}/template.html (100%) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index 5cbc6702..a66851bb 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -28,7 +28,7 @@ Draft a verdict and reason for every entry, restructure before cap: decide Split ## 4. Review and go -Run the verdict-preview skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go. +Run the preview-verdicts skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go. ## 5. Execute diff --git a/.claude/skills/verdict-preview/SKILL.md b/.claude/skills/preview-verdicts/SKILL.md similarity index 99% rename from .claude/skills/verdict-preview/SKILL.md rename to .claude/skills/preview-verdicts/SKILL.md index 1572abc5..af0de9c1 100644 --- a/.claude/skills/verdict-preview/SKILL.md +++ b/.claude/skills/preview-verdicts/SKILL.md @@ -1,5 +1,5 @@ --- -name: verdict-preview +name: preview-verdicts description: Generate the interactive keep/drop verdict preview (HTML page with per-row feedback controls) whenever a prune sweep, batch entry edit, or restructure needs maintainer review before touching README.md — and process the feedback JSON the maintainer pastes back. --- diff --git a/.claude/skills/verdict-preview/template.html b/.claude/skills/preview-verdicts/template.html similarity index 100% rename from .claude/skills/verdict-preview/template.html rename to .claude/skills/preview-verdicts/template.html From a1ed550c2dba6d0dda473c127936d9266c5e8003 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:16:21 +0800 Subject: [PATCH 0686/1130] fix: filter BigQuery PyPI download query on project, not file.project Live bq show verified the pypi.file_downloads table clusters on the top-level project column, not file.project as the docstring claimed. Filtering on project (values verified identical to file.project across 408M rows, zero mismatches) gets cluster pruning and cuts the full-README scan estimate from >1.2TB to ~275GB upper bound, with actual billed bytes lower still (33.7GB measured for a single name) - so full sweeps now fit the 1 TiB/month free tier. Also adds --maximum_bytes_billed=400GB as a safety cap, enforced by BigQuery pre-run against the dry-run upper-bound estimate. Co-Authored-By: Claude --- website/fetch_pypi_downloads.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/website/fetch_pypi_downloads.py b/website/fetch_pypi_downloads.py index e79c3620..ca8923c2 100644 --- a/website/fetch_pypi_downloads.py +++ b/website/fetch_pypi_downloads.py @@ -2,13 +2,21 @@ """Fetch last-30-day PyPI download counts for README entries via BigQuery. Shells out to the `bq` CLI (Google Cloud SDK) against the public -`bigquery-public-data.pypi.file_downloads` table. The table is clustered on -file.project, so scanned bytes grow with the IN-list: a few dozen names -prune to tens of GB while the full README (~530 names) balloons to over -1 TB — past the free tier. Fetch per curation sitting with --names-file -instead of all at once. Results are cached to data/pypi_downloads.tsv -(merged with any existing rows); names with no PyPI rows are written as -NOT_FOUND. Always --dry-run first to check the scan estimate. +`bigquery-public-data.pypi.file_downloads` table. The table is partitioned +on timestamp and clustered on the top-level `project` column — filter on +`project`, never `file.project`: both hold identical values (verified +2026-08-16, 408M rows on one day, zero mismatches), but only a `project` +filter gets cluster pruning, and the `file` record costs ~4x more to scan. +A 30-day query dry-runs at up to ~275 GB (the pre-pruning upper bound for +a full-README IN list; small lists estimate lower); actual billed bytes +shrink further with cluster pruning (33.7 GB measured for a single name), +so even a full-README sweep fits the 1 TiB/month free tier. +MAX_BYTES_BILLED is a safety net against accidentally unpartitioned +queries; BigQuery enforces it against the pre-run upper bound, so it must +stay above the dry-run estimate or every query fails. Results are cached +to data/pypi_downloads.tsv (merged with any existing rows); names with no +PyPI rows are written as NOT_FOUND. Always --dry-run first to check the +scan estimate. Usage: python fetch_pypi_downloads.py [--dry-run] [--names-file FILE] """ @@ -24,6 +32,7 @@ from readme_parser import parse_readme DATA_DIR = Path(__file__).parent / "data" OUT_FILE = DATA_DIR / "pypi_downloads.tsv" README_PATH = Path(__file__).parent.parent / "README.md" +MAX_BYTES_BILLED = 400_000_000_000 # PyPI normalizes names to lowercase with runs of -, _, . collapsed to -. PYPI_NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$") @@ -47,14 +56,14 @@ def collect_names(readme_text: str) -> list[str]: def fetch(names: list[str], dry_run: bool) -> dict[str, int]: in_list = ", ".join(f"'{name}'" for name in names) query = ( - "SELECT file.project AS project, COUNT(*) AS downloads " + "SELECT project, COUNT(*) AS downloads " "FROM `bigquery-public-data.pypi.file_downloads` " "WHERE DATE(timestamp) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) " "AND CURRENT_DATE() " - f"AND file.project IN ({in_list}) " - "GROUP BY file.project" + f"AND project IN ({in_list}) " + "GROUP BY project" ) - cmd = ["bq", "query", "--use_legacy_sql=false", "--format=json"] + cmd = ["bq", "query", "--use_legacy_sql=false", "--format=json", f"--maximum_bytes_billed={MAX_BYTES_BILLED}"] if dry_run: cmd.append("--dry_run") cmd.append(query) From fd79813ddefd5f74ecbf8a4d12cc878f0ec13b96 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:17:59 +0800 Subject: [PATCH 0687/1130] feat: switch default PyPI download source to ClickPy BigQuery required a personal Google Cloud account, so only the maintainer could run it and overruns cost real money. ClickPy is ClickHouse's free, keyless public mirror of the same PyPI download dataset, so it becomes the default source with a single batched query covering the full README in under a second. BigQuery stays available behind a new --bigquery flag as the canonical-source cross-check; --dry-run now only applies to that path. Live-tested: ClickPy full path (2/2 names), the --dry-run guard, and the BigQuery dry-run. Co-Authored-By: Claude --- website/fetch_pypi_downloads.py | 71 +++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/website/fetch_pypi_downloads.py b/website/fetch_pypi_downloads.py index ca8923c2..80f9528b 100644 --- a/website/fetch_pypi_downloads.py +++ b/website/fetch_pypi_downloads.py @@ -1,24 +1,36 @@ #!/usr/bin/env python3 -"""Fetch last-30-day PyPI download counts for README entries via BigQuery. +"""Fetch last-30-day PyPI download counts for README entries. -Shells out to the `bq` CLI (Google Cloud SDK) against the public -`bigquery-public-data.pypi.file_downloads` table. The table is partitioned -on timestamp and clustered on the top-level `project` column — filter on -`project`, never `file.project`: both hold identical values (verified -2026-08-16, 408M rows on one day, zero mismatches), but only a `project` -filter gets cluster pruning, and the `file` record costs ~4x more to scan. -A 30-day query dry-runs at up to ~275 GB (the pre-pruning upper bound for -a full-README IN list; small lists estimate lower); actual billed bytes -shrink further with cluster pruning (33.7 GB measured for a single name), -so even a full-README sweep fits the 1 TiB/month free tier. -MAX_BYTES_BILLED is a safety net against accidentally unpartitioned -queries; BigQuery enforces it against the pre-run upper bound, so it must -stay above the dry-run estimate or every query fails. Results are cached -to data/pypi_downloads.tsv (merged with any existing rows); names with no -PyPI rows are written as NOT_FOUND. Always --dry-run first to check the -scan estimate. +Default source is ClickPy, ClickHouse's public mirror of the PyPI download +dataset (sql-clickhouse.clickhouse.com, user `demo`, no key, no personal +account): one batched query covers the full README in under a second. +Playground limits — 60 queries/hour/IP, 1000 result rows — sit far above +a full-README sweep. The per-day table is a SummingMergeTree, so the +query must sum(count) GROUP BY, never count raw rows. Counts include +mirror/CI traffic, same as BigQuery's; never mix these figures with +pypistats.org, which excludes mirrors by default. -Usage: python fetch_pypi_downloads.py [--dry-run] [--names-file FILE] +--bigquery switches to the canonical source ClickPy mirrors: +`bigquery-public-data.pypi.file_downloads` via the `bq` CLI (needs a +personal GCP account, so it is a maintainer-local cross-check, not the +default). The table is partitioned on timestamp and clustered on the +top-level `project` column — filter on `project`, never `file.project`: +both hold identical values (verified 2026-08-16, 408M rows on one day, +zero mismatches), but only a `project` filter gets cluster pruning, and +the `file` record costs ~4x more to scan. A 30-day query dry-runs at up +to ~275 GB (the pre-pruning upper bound for a full-README IN list; small +lists estimate lower); actual billed bytes shrink further with cluster +pruning (33.7 GB measured for a single name), so even a full-README sweep +fits the 1 TiB/month free tier. MAX_BYTES_BILLED is a safety net against +accidentally unpartitioned queries; BigQuery enforces it against the +pre-run upper bound, so it must stay above the dry-run estimate or every +query fails. --dry-run (BigQuery only) prints the scan estimate without +running. + +Results are cached to data/pypi_downloads.tsv (merged with any existing +rows); names with no PyPI rows are written as NOT_FOUND. + +Usage: python fetch_pypi_downloads.py [--bigquery] [--dry-run] [--names-file FILE] """ import re @@ -27,12 +39,14 @@ import sys from json import loads from pathlib import Path +import httpx from readme_parser import parse_readme DATA_DIR = Path(__file__).parent / "data" OUT_FILE = DATA_DIR / "pypi_downloads.tsv" README_PATH = Path(__file__).parent.parent / "README.md" MAX_BYTES_BILLED = 400_000_000_000 +CLICKPY_URL = "https://sql-clickhouse.clickhouse.com/?user=demo" # PyPI normalizes names to lowercase with runs of -, _, . collapsed to -. PYPI_NAME_RE = re.compile(r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$") @@ -53,7 +67,20 @@ def collect_names(readme_text: str) -> list[str]: return sorted(names) -def fetch(names: list[str], dry_run: bool) -> dict[str, int]: +def fetch_clickpy(names: list[str]) -> dict[str, int]: + in_list = ", ".join(f"'{name}'" for name in names) + query = ( + "SELECT project, sum(count) AS downloads " + "FROM pypi.pypi_downloads_per_day " + f"WHERE project IN ({in_list}) AND date >= today() - 30 " + "GROUP BY project FORMAT JSON" + ) + resp = httpx.post(CLICKPY_URL, content=query, timeout=60) + resp.raise_for_status() + return {row["project"]: int(row["downloads"]) for row in resp.json()["data"]} + + +def fetch_bigquery(names: list[str], dry_run: bool) -> dict[str, int]: in_list = ", ".join(f"'{name}'" for name in names) query = ( "SELECT project, COUNT(*) AS downloads " @@ -80,13 +107,17 @@ def fetch(names: list[str], dry_run: bool) -> dict[str, int]: def main() -> None: dry_run = "--dry-run" in sys.argv + use_bigquery = "--bigquery" in sys.argv + if dry_run and not use_bigquery: + print("Error: --dry-run only applies to --bigquery.", file=sys.stderr) + sys.exit(1) if "--names-file" in sys.argv: names_path = Path(sys.argv[sys.argv.index("--names-file") + 1]) names = sorted({normalize(line) for line in names_path.read_text().split() if line}) else: names = collect_names(README_PATH.read_text()) print(f"Querying {len(names)} package names...") - counts = fetch(names, dry_run) + counts = fetch_bigquery(names, dry_run) if use_bigquery else fetch_clickpy(names) merged: dict[str, str] = {} if OUT_FILE.exists(): for line in OUT_FILE.read_text().splitlines(): From fcac01955c981a0d047315f7d2e381872d952a01 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:19:47 +0800 Subject: [PATCH 0688/1130] feat: add pepy.tech spot-check helper for download audits Maintainer registered a free pepy API key (PEPY_TECH_API_KEY in the gitignored repo-root .env). fetch_pepy_downloads.py sums the most recent 30 days from the v2 per-day data, throttled to the free tier's 5 requests/minute, and prints TSV without touching the single-source cache file. The audit-the-list skill's downloads bullet is rewritten in the same commit because the ClickPy-default change made its old --dry-run-first instruction fail; it now documents all three sources (ClickPy, BigQuery, pepy/pypistats) and the never-mix-mirror-counting rule. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- website/fetch_pepy_downloads.py | 68 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 website/fetch_pepy_downloads.py diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index a66851bb..c87cd05f 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -16,7 +16,7 @@ Resolve the scope from the arguments. Named sections mean exactly those, whether Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): -- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads.py --names-file --dry-run` first, then without `--dry-run`. The script's docstring carries the BigQuery cost constraints — read it before batching. `https://pypistats.org/api/packages/{name}/recent` for a handful of spot-checks, paced 8s or slower. +- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads.py --names-file ` — default source is ClickPy (free, keyless, one batched query covers the full README). `--bigquery` switches to the canonical cross-check on the maintainer's own GCP account; there, run `--dry-run` first and read the docstring's cost constraints before batching. Spot-checks: `uv run python fetch_pepy_downloads.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute) or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. - **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. diff --git a/website/fetch_pepy_downloads.py b/website/fetch_pepy_downloads.py new file mode 100644 index 00000000..cd763f6c --- /dev/null +++ b/website/fetch_pepy_downloads.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Spot-check last-30-day PyPI download counts via the pepy.tech API. + +Cross-check for a handful of packages during audits — not for full-README +sweeps: the free API key is throttled to 5 requests/minute (10 burst), so +this script sleeps 12s between requests and 530 names would take ~2 hours +(use fetch_pypi_downloads.py for bulk). Reads PEPY_TECH_API_KEY from the +environment, falling back to the repo-root .env. The v2 endpoint returns +~90 days of per-day per-version counts; this script sums the most recent +30 days present in the response across all versions. pepy counts include +mirror/CI traffic (CI filtering is a paid pepy feature), matching the +ClickPy/BigQuery figures; pypistats.org excludes mirrors, so never mix +the two in one comparison. Results print to stdout as TSV and are not +cached — data/pypi_downloads.tsv stays single-source. + +Usage: python fetch_pepy_downloads.py NAME [NAME ...] +""" + +import os +import sys +import time +from pathlib import Path + +import httpx +from fetch_pypi_downloads import normalize + +ENV_FILE = Path(__file__).parent.parent / ".env" +PEPY_URL = "https://api.pepy.tech/api/v2/projects/{name}" +SECONDS_BETWEEN_REQUESTS = 12 + + +def load_api_key() -> str: + key = os.environ.get("PEPY_TECH_API_KEY", "") + if not key and ENV_FILE.exists(): + for line in ENV_FILE.read_text().splitlines(): + name, sep, value = line.partition("=") + if sep and name.strip() == "PEPY_TECH_API_KEY": + key = value.strip() + if not key: + print("Error: PEPY_TECH_API_KEY not set (environment or repo-root .env).", file=sys.stderr) + sys.exit(1) + return key + + +def last_30_day_total(downloads_per_day: dict[str, dict[str, int]]) -> int: + recent_days = sorted(downloads_per_day)[-30:] + return sum(sum(downloads_per_day[day].values()) for day in recent_days) + + +def main() -> None: + if len(sys.argv) < 2: + print("Usage: python fetch_pepy_downloads.py NAME [NAME ...]", file=sys.stderr) + sys.exit(1) + names = [normalize(name) for name in sys.argv[1:]] + with httpx.Client(headers={"X-API-Key": load_api_key()}, timeout=30) as client: + for i, name in enumerate(names): + if i: + time.sleep(SECONDS_BETWEEN_REQUESTS) + resp = client.get(PEPY_URL.format(name=name)) + if resp.status_code == 404: + print(f"{name}\tNOT_FOUND") + continue + resp.raise_for_status() + print(f"{name}\t{last_30_day_total(resp.json()['downloads'])}") + + +if __name__ == "__main__": + main() From f39d00da3112d1227cfc0fbadeedc6cf5c1dd56c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:32:28 +0800 Subject: [PATCH 0689/1130] audit: sweep Web Frameworks to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16 at the preview review, flipping pyramid to keep (enters as Synchronous Challenger beside fasthtml; the three Obvious slots stay flask, django, bottle). Both tiers reordered by downloads/month per the ordering rule. Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - masonite (26.4K/month): no adoption; Laravel-style niche - microdot (10.4K/month): MicroPython-targeted, genuinely small audience - robyn (50.9K/month): installs never followed the buzz Co-Authored-By: Claude --- README.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0857aed4..0d5a8d8d 100644 --- a/README.md +++ b/README.md @@ -236,23 +236,20 @@ _Libraries for building recommender systems._ _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/wsvincent/awesome-django) - [flask](https://github.com/pallets/flask) - A microframework for Python. - [awesome-flask](https://github.com/humiaozuzu/awesome-flask) + - [django](https://github.com/django/django) - The most popular web framework in Python. + - [awesome-django](https://github.com/wsvincent/awesome-django) + - [bottle](https://github.com/bottlepy/bottle) - A fast and simple micro-framework distributed as a single file with no dependencies. - [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. + - [litestar](https://github.com/litestar-org/litestar) - Production-ready, capable and extensible ASGI Web framework. + - [reflex](https://github.com/reflex-dev/reflex) - A framework for building reactive, full-stack web applications entirely with Python. ### Web APIs From b26c30d4a929b280648320472fa0a48249e3f28c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:32:49 +0800 Subject: [PATCH 0690/1130] audit: sweep Web APIs to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16 at the preview review, flipping django-modern-rest to keep (enters as the Django Challenger; Obvious slots stay django-rest-framework, django-ninja, strawberry-django). Tiers reordered by downloads/month. Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - falcon (1.8M/month): active, but not named unprompted since fastapi/litestar; judgment call - sanic (1.8M/month): same judgment as falcon - declining mindshare, successor to nothing - webargs (3.8M/month): request-parsing helper, not an API framework; wrong job for the section Co-Authored-By: Claude --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0d5a8d8d..cf583afd 100644 --- a/README.md +++ b/README.md @@ -256,19 +256,16 @@ _Traditional full stack web frameworks. Also see [Web APIs](#web-apis)._ _Libraries for building RESTful and GraphQL APIs._ - Django - - [django-modern-rest](https://github.com/wemake-services/django-modern-rest) - Modern REST with speed, types, async, `msgspec`, `pydantic` and other goodies! - - [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. + - [django-ninja](https://github.com/vitalik/django-ninja) - Fast, Django REST framework based on type hints and Pydantic. - [strawberry-django](https://github.com/strawberry-graphql/strawberry-django) - Strawberry GraphQL integration with Django. + - [django-modern-rest](https://github.com/wemake-services/django-modern-rest) - Modern REST with speed, types, async, `msgspec`, `pydantic` and other goodies! - 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 web server and web framework that's written to go fast. + - [connexion](https://github.com/spec-first/connexion) - A spec-first framework that automatically handles requests based on your OpenAPI specification. - [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 From fb35807e8342bad8a92b49d7aa62b9d22359a659 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:33:18 +0800 Subject: [PATCH 0691/1130] audit: sweep Web Servers to the shortlist cap First Audit of the section. Restructure first: the RPC Subcategory is not an "ASGI and WSGI compatible web server" - it moves to Web APIs, whose description gains RPC; grpcio rides the move. ASGI reordered by downloads/month (uvicorn Obvious; granian, hypercorn Challengers - hypercorn kept with a watch, repo quiet since 2025-11). Maintainer-adjudicated 2026-08-16. Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - daphne (6.5M/month): installed as Django Channels' dependency, not chosen standalone; judgment call - uwsgi (2.0M/month): officially in maintenance mode; repo quiet since 2025-10 - rpyc (1.6M/month): repo last push 2025-08-14, exactly 12 months - at the abandonment line Co-Authored-By: Claude --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index cf583afd..59ca7f6a 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,7 @@ _Traditional full stack web frameworks. Also see [Web APIs](#web-apis)._ ### Web APIs -_Libraries for building RESTful and GraphQL APIs._ +_Libraries for building RESTful, GraphQL, and RPC APIs._ - Django - [django-rest-framework](https://github.com/encode/django-rest-framework) - A powerful and flexible toolkit to build web APIs. @@ -266,23 +266,20 @@ _Libraries for building RESTful and GraphQL APIs._ - [fastapi](https://github.com/fastapi/fastapi) - A modern, fast, web framework for building APIs with standard Python type hints. - [connexion](https://github.com/spec-first/connexion) - A spec-first framework that automatically handles requests based on your OpenAPI specification. - [strawberry](https://github.com/strawberry-graphql/strawberry) - A GraphQL library that leverages Python type annotations for schema definition. +- RPC + - [grpcio](https://github.com/grpc/grpc) - HTTP/2-based RPC framework with Python bindings, built by Google. ### Web Servers _ASGI and WSGI compatible web servers._ - ASGI - - [daphne](https://github.com/django/daphne) - An HTTP, HTTP/2 and WebSocket protocol server for ASGI and ASGI-HTTP. + - [uvicorn](https://github.com/Kludex/uvicorn) - A lightning-fast ASGI server implementation, using uvloop and httptools. - [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 From 69e03b7e47da5291245f1428303dd4694a3a2125 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:33:33 +0800 Subject: [PATCH 0692/1130] audit: sweep WebSocket to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16 at the preview review, flipping autobahn-python to keep (enters as the Challenger; Obvious slots are websockets, channels, flask-socketio). Reordered by downloads/month within tiers. Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - picows (66.3K/month): 290 stars; Challenger bar (adoption trajectory) not met Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59ca7f6a..6012e1b0 100644 --- a/README.md +++ b/README.md @@ -285,11 +285,10 @@ _ASGI and WSGI compatible web servers._ _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). +- [websockets](https://github.com/python-websockets/websockets) - A library for building WebSocket servers and clients with a focus on correctness and simplicity. - [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. -- [picows](https://github.com/tarasko/picows) - Fastest WebSocket clients and servers with a frame level interface for the most demanding use-cases. -- [websockets](https://github.com/python-websockets/websockets) - A library for building WebSocket servers and clients with a focus on correctness and simplicity. +- [autobahn-python](https://github.com/crossbario/autobahn-python) - WebSocket & WAMP for Python on Twisted and [asyncio](https://docs.python.org/3/library/asyncio.html). ### Template Engines From 6b9a1cca57ec604b6ef45800fdcaae531d9feb48 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:33:54 +0800 Subject: [PATCH 0693/1130] audit: sweep Admin Panels to the shortlist cap First Audit of the section (flat Section = one Use Case, 7 entries over the cap). Restructure first: flower is the Celery monitoring dashboard, not an admin-panel library - it re-homes to Task Queues as a sub-item under celery (outside the cap, aws-sdk-pandas precedent; Task Queues sits at the hard max 5). Kept: flask-admin, django-unfold (Obvious), django-grappelli (Challenger), ordered by downloads/month. Maintainer-adjudicated 2026-08-16. Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - ajenti (4.5K/month; its ajenti-panel package adds 4.5K): Linux server control panel application, not a Python admin library - func-to-web (3.5K/month): repo created 2025-10, 425 stars; no adoption - jet-bridge (3.7K/month): bridge into the Jet Admin SaaS, not a library readers seek here Co-Authored-By: Claude --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6012e1b0..381b0967 100644 --- a/README.md +++ b/README.md @@ -323,13 +323,9 @@ _Libraries for implementing authentication schemes._ _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. -- [flower](https://github.com/mher/flower) - Real-time monitor and web admin for Celery. -- [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). +- [django-unfold](https://github.com/unfoldadmin/django-unfold) - Elevate your Django admin with a stunning modern interface, powerful features, and seamless user experience. +- [django-grappelli](https://github.com/sehmaschine/django-grappelli) - A jazzy skin for the Django Admin-Interface. ### CMS @@ -733,6 +729,7 @@ _Frameworks and libraries for Distributed Computing._ _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) - [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. From 4c57da829db2ce819505ca771bdd1144c8c11fed Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:34:07 +0800 Subject: [PATCH 0694/1130] audit: sweep CMS to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16. Reordered by downloads/month (wagtail, django-cms). Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - indico (3.2K/month): event-management application (CERN), not a CMS library; readers under CMS want wagtail/django-cms - judgment call Co-Authored-By: Claude --- README.md | 3 +-- ...h_pypi_downloads.py => fetch_pypi_downloads_via_clickpy.py} | 0 ...etch_pepy_downloads.py => fetch_pypi_downloads_via_pepy.py} | 0 3 files changed, 1 insertion(+), 2 deletions(-) rename website/{fetch_pypi_downloads.py => fetch_pypi_downloads_via_clickpy.py} (100%) rename website/{fetch_pepy_downloads.py => fetch_pypi_downloads_via_pepy.py} (100%) diff --git a/README.md b/README.md index 381b0967..08bd8b96 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,8 @@ _Libraries for administrative interfaces._ _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. +- [django-cms](https://github.com/django-cms/django-cms) - The easy-to-use and developer-friendly enterprise CMS powered by Django. ### Static Site Generators diff --git a/website/fetch_pypi_downloads.py b/website/fetch_pypi_downloads_via_clickpy.py similarity index 100% rename from website/fetch_pypi_downloads.py rename to website/fetch_pypi_downloads_via_clickpy.py diff --git a/website/fetch_pepy_downloads.py b/website/fetch_pypi_downloads_via_pepy.py similarity index 100% rename from website/fetch_pepy_downloads.py rename to website/fetch_pypi_downloads_via_pepy.py From e18d52155e7048c48cfb332546ca5234c60516d6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:34:20 +0800 Subject: [PATCH 0695/1130] audit: sweep Static Site Generators to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16. Reordered by downloads/month (pelican, nikola). Removed (downloads are PyPI last-30-day via BigQuery, fetched 2026-08-16): - lektor (12.5K/month): maintained (pushed 2026-08) but never named unprompted; close call vs nikola - judgment call Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 08bd8b96..caad9210 100644 --- a/README.md +++ b/README.md @@ -338,9 +338,8 @@ _Content Management Systems._ _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. +- [nikola](https://github.com/getnikola/nikola) - A static website and blog generator. **HTTP & Scraping** From e7e17a8e9777e2c939de5a16ec3b74c1f4186bb7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:34:41 +0800 Subject: [PATCH 0696/1130] style: reorder Authentication and Web Asset Management by downloads Format-only outcomes of the 2026-08-16 Web Development Audit (no removals): OAuth entries reordered by downloads/month (oauthlib, authlib, django-allauth Obvious; django-oauth-toolkit Challenger), Web Asset Management reordered (django-storages, django-compressor) and its description gains "storing" to cover django-storages' actual job. Template Engines, JWT, Permissions, WebSocket keeps already sat in rule order. Co-Authored-By: Claude --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index caad9210..62355489 100644 --- a/README.md +++ b/README.md @@ -299,20 +299,20 @@ _Libraries and tools for templating and lexing._ ### Web Asset Management -_Tools for managing, compressing and minifying website assets._ +_Tools for managing, storing, 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. +- [django-compressor](https://github.com/django-compressor/django-compressor) - Compresses linked and inline JavaScript or CSS into a single cached file. ### Authentication _Libraries for implementing authentication schemes._ - OAuth + - [oauthlib](https://github.com/oauthlib/oauthlib) - A generic and thorough implementation of the OAuth request-signing logic. - [authlib](https://github.com/authlib/authlib) - A comprehensive library for building OAuth, OpenID Connect, and JWT/JWS/JWE/JWK/JWA. - [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 From 7447549805a0742e9ad2672ee41e96d68b15e41e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:35:17 +0800 Subject: [PATCH 0697/1130] docs: define Sub-item in CONTEXT.md The device now has two precedents (aws-sdk-pandas under pandas, flower under celery in the 2026-08-16 Web Development Audit) plus the awesome-* links; future Audits need the term - an indented link that holds no slot and rides its parent outside the Cap. Co-Authored-By: Claude --- CONTEXT.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index 8fae6cb8..68322b70 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -9,6 +9,9 @@ The editorial domain of awesome-python: which projects the list carries, how the **Entry**: A single listed project: `- [name](url) - Description.` The unit that is admitted, displaced, or pruned. Named by PyPI package name when one exists, else by repository name. +**Sub-item**: +An indented link under an Entry (awesome-django under django, aws-sdk-pandas under pandas, flower under celery). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement for a companion project whose job is inseparable from its parent's. + **Thematic Group**: A bold group line (e.g. "AI & ML", "Web Development") that clusters Sections in the TOC and the Projects body. From 5297061a4f23301184daa1167e43e37bd5e02ae1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:35:24 +0800 Subject: [PATCH 0698/1130] refactor: rename download-fetch scripts to source-suffixed scheme fetch_pypi_downloads.py becomes fetch_pypi_downloads_via_clickpy.py and fetch_pepy_downloads.py becomes fetch_pypi_downloads_via_pepy.py, ahead of splitting the BigQuery path into its own file. Updates the usage strings, the cross-file import, and the audit-the-list skill's references to match. Pure rename, no behavior change. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- website/fetch_pypi_downloads_via_clickpy.py | 2 +- website/fetch_pypi_downloads_via_pepy.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index c87cd05f..a29da0a6 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -16,7 +16,7 @@ Resolve the scope from the arguments. Named sections mean exactly those, whether Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): -- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads.py --names-file ` — default source is ClickPy (free, keyless, one batched query covers the full README). `--bigquery` switches to the canonical cross-check on the maintainer's own GCP account; there, run `--dry-run` first and read the docstring's cost constraints before batching. Spot-checks: `uv run python fetch_pepy_downloads.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute) or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. +- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py --names-file ` — default source is ClickPy (free, keyless, one batched query covers the full README). `--bigquery` switches to the canonical cross-check on the maintainer's own GCP account; there, run `--dry-run` first and read the docstring's cost constraints before batching. Spot-checks: `uv run python fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute) or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. - **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index 80f9528b..1cae54a7 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -30,7 +30,7 @@ running. Results are cached to data/pypi_downloads.tsv (merged with any existing rows); names with no PyPI rows are written as NOT_FOUND. -Usage: python fetch_pypi_downloads.py [--bigquery] [--dry-run] [--names-file FILE] +Usage: python fetch_pypi_downloads_via_clickpy.py [--bigquery] [--dry-run] [--names-file FILE] """ import re diff --git a/website/fetch_pypi_downloads_via_pepy.py b/website/fetch_pypi_downloads_via_pepy.py index cd763f6c..998f260f 100644 --- a/website/fetch_pypi_downloads_via_pepy.py +++ b/website/fetch_pypi_downloads_via_pepy.py @@ -4,7 +4,7 @@ Cross-check for a handful of packages during audits — not for full-README sweeps: the free API key is throttled to 5 requests/minute (10 burst), so this script sleeps 12s between requests and 530 names would take ~2 hours -(use fetch_pypi_downloads.py for bulk). Reads PEPY_TECH_API_KEY from the +(use fetch_pypi_downloads_via_clickpy.py for bulk). Reads PEPY_TECH_API_KEY from the environment, falling back to the repo-root .env. The v2 endpoint returns ~90 days of per-day per-version counts; this script sums the most recent 30 days present in the response across all versions. pepy counts include @@ -13,7 +13,7 @@ ClickPy/BigQuery figures; pypistats.org excludes mirrors, so never mix the two in one comparison. Results print to stdout as TSV and are not cached — data/pypi_downloads.tsv stays single-source. -Usage: python fetch_pepy_downloads.py NAME [NAME ...] +Usage: python fetch_pypi_downloads_via_pepy.py NAME [NAME ...] """ import os @@ -22,7 +22,7 @@ import time from pathlib import Path import httpx -from fetch_pypi_downloads import normalize +from fetch_pypi_downloads_via_clickpy import normalize ENV_FILE = Path(__file__).parent.parent / ".env" PEPY_URL = "https://api.pepy.tech/api/v2/projects/{name}" @@ -49,7 +49,7 @@ def last_30_day_total(downloads_per_day: dict[str, dict[str, int]]) -> int: def main() -> None: if len(sys.argv) < 2: - print("Usage: python fetch_pepy_downloads.py NAME [NAME ...]", file=sys.stderr) + print("Usage: python fetch_pypi_downloads_via_pepy.py NAME [NAME ...]", file=sys.stderr) sys.exit(1) names = [normalize(name) for name in sys.argv[1:]] with httpx.Client(headers={"X-API-Key": load_api_key()}, timeout=30) as client: From 5ece46f262ad56a16ddbc457be586514cd9de867 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:39:14 +0800 Subject: [PATCH 0699/1130] docs: cover awesome-* links in the Sub-item definition Follow-up to d07440f: the definition's placement clause described only companion projects (flower/celery), but awesome-* also-see lists are Sub-items too. Co-Authored-By: Claude --- CONTEXT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTEXT.md b/CONTEXT.md index 68322b70..f4b23b2c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -10,7 +10,7 @@ The editorial domain of awesome-python: which projects the list carries, how the A single listed project: `- [name](url) - Description.` The unit that is admitted, displaced, or pruned. Named by PyPI package name when one exists, else by repository name. **Sub-item**: -An indented link under an Entry (awesome-django under django, aws-sdk-pandas under pandas, flower under celery). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement for a companion project whose job is inseparable from its parent's. +An indented link under an Entry (awesome-django under django, aws-sdk-pandas under pandas, flower under celery). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement, for a companion project whose job is inseparable from its parent's (flower monitors celery) or an awesome-* also-see list. **Thematic Group**: A bold group line (e.g. "AI & ML", "Web Development") that clusters Sections in the TOC and the Projects body. From 1abfb1af42139e2d1fc003130a36f54bd86d5c0f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:57:16 +0800 Subject: [PATCH 0700/1130] refactor: split download fetchers into writer and cross-check scripts Authored by the parallel fetch-scripts session (its intended 0cffb5f never landed; the content rode into an audit commit by accident and is extracted here): fetch_pypi_downloads_via_clickpy.py becomes the flagless full-README sweep and sole writer of data/pypi_downloads.tsv; new fetch_pypi_downloads_via_bigquery.py is a print-only cross-check taking explicit names behind a 400 GB billing cap; audit-the-list SKILL.md step 2 updated to match. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- website/fetch_pypi_downloads_via_bigquery.py | 72 ++++++++++++++++ website/fetch_pypi_downloads_via_clickpy.py | 91 ++++---------------- 3 files changed, 89 insertions(+), 76 deletions(-) create mode 100644 website/fetch_pypi_downloads_via_bigquery.py diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index a29da0a6..be48f239 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -16,7 +16,7 @@ Resolve the scope from the arguments. Named sections mean exactly those, whether Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): -- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py --names-file ` — default source is ClickPy (free, keyless, one batched query covers the full README). `--bigquery` switches to the canonical cross-check on the maintainer's own GCP account; there, run `--dry-run` first and read the docstring's cost constraints before batching. Spot-checks: `uv run python fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute) or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. +- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. - **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. diff --git a/website/fetch_pypi_downloads_via_bigquery.py b/website/fetch_pypi_downloads_via_bigquery.py new file mode 100644 index 00000000..21f69479 --- /dev/null +++ b/website/fetch_pypi_downloads_via_bigquery.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Cross-check last-30-day PyPI download counts for given names via BigQuery. + +Queries the canonical source ClickPy mirrors — +`bigquery-public-data.pypi.file_downloads` via the `bq` CLI — and prints +namecount TSV to stdout. Maintainer-local (needs a personal GCP +account) and print-only: data/pypi_downloads.tsv is written solely by +fetch_pypi_downloads_via_clickpy.py. + +Targeted cross-checks only — full-README sweeps belong to ClickPy. The +dry-run scan estimate scales with the IN list (measured 2026-08-16: +~38 GB for 2 names, ~275 GB for 53, ~1.2 TB for the full README — past +both MAX_BYTES_BILLED and the 1 TiB/month free tier), and BigQuery +enforces MAX_BYTES_BILLED against that pre-run estimate, so oversized +name lists fail before billing. Actual billed bytes come in far lower +via cluster pruning (33.7 GB for a single name). The table is +partitioned on timestamp and clustered on the top-level `project` +column — filter on `project`, never `file.project`: both hold identical +values (verified 2026-08-16, 408M rows on one day, zero mismatches), but +only a `project` filter gets cluster pruning, and the `file` record +costs ~4x more to scan. Always --dry-run first to check the estimate. + +Usage: python fetch_pypi_downloads_via_bigquery.py [--dry-run] NAME [NAME ...] +""" + +import subprocess +import sys +from json import loads + +from fetch_pypi_downloads_via_clickpy import normalize + +MAX_BYTES_BILLED = 400_000_000_000 + + +def fetch_bigquery(names: list[str], dry_run: bool) -> dict[str, int]: + in_list = ", ".join(f"'{name}'" for name in names) + query = ( + "SELECT project, COUNT(*) AS downloads " + "FROM `bigquery-public-data.pypi.file_downloads` " + "WHERE DATE(timestamp) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) " + "AND CURRENT_DATE() " + f"AND project IN ({in_list}) " + "GROUP BY project" + ) + cmd = ["bq", "query", "--use_legacy_sql=false", "--format=json", f"--maximum_bytes_billed={MAX_BYTES_BILLED}"] + if dry_run: + cmd.append("--dry_run") + cmd.append(query) + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + print(result.stderr, file=sys.stderr) + sys.exit(1) + if dry_run: + print(result.stdout.strip() or result.stderr.strip()) + sys.exit(0) + rows = loads(result.stdout) + return {row["project"]: int(row["downloads"]) for row in rows} + + +def main() -> None: + dry_run = "--dry-run" in sys.argv + names = sorted({normalize(arg) for arg in sys.argv[1:] if arg != "--dry-run"}) + if not names: + print("Usage: python fetch_pypi_downloads_via_bigquery.py [--dry-run] NAME [NAME ...]", file=sys.stderr) + sys.exit(1) + counts = fetch_bigquery(names, dry_run) + for name in names: + print(f"{name}\t{counts.get(name, 'NOT_FOUND')}") + + +if __name__ == "__main__": + main() diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index 1cae54a7..e88815ac 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -1,42 +1,26 @@ #!/usr/bin/env python3 -"""Fetch last-30-day PyPI download counts for README entries. +"""Fetch last-30-day PyPI download counts for all README entries via ClickPy. -Default source is ClickPy, ClickHouse's public mirror of the PyPI download -dataset (sql-clickhouse.clickhouse.com, user `demo`, no key, no personal +ClickPy is ClickHouse's public mirror of the PyPI download dataset +(sql-clickhouse.clickhouse.com, user `demo`, no key, no personal account): one batched query covers the full README in under a second. Playground limits — 60 queries/hour/IP, 1000 result rows — sit far above a full-README sweep. The per-day table is a SummingMergeTree, so the query must sum(count) GROUP BY, never count raw rows. Counts include -mirror/CI traffic, same as BigQuery's; never mix these figures with -pypistats.org, which excludes mirrors by default. +mirror/CI traffic; never mix these figures with pypistats.org, which +excludes mirrors by default. ---bigquery switches to the canonical source ClickPy mirrors: -`bigquery-public-data.pypi.file_downloads` via the `bq` CLI (needs a -personal GCP account, so it is a maintainer-local cross-check, not the -default). The table is partitioned on timestamp and clustered on the -top-level `project` column — filter on `project`, never `file.project`: -both hold identical values (verified 2026-08-16, 408M rows on one day, -zero mismatches), but only a `project` filter gets cluster pruning, and -the `file` record costs ~4x more to scan. A 30-day query dry-runs at up -to ~275 GB (the pre-pruning upper bound for a full-README IN list; small -lists estimate lower); actual billed bytes shrink further with cluster -pruning (33.7 GB measured for a single name), so even a full-README sweep -fits the 1 TiB/month free tier. MAX_BYTES_BILLED is a safety net against -accidentally unpartitioned queries; BigQuery enforces it against the -pre-run upper bound, so it must stay above the dry-run estimate or every -query fails. --dry-run (BigQuery only) prints the scan estimate without -running. +This script is the sole writer of data/pypi_downloads.tsv and rewrites it +from scratch each run, so entries removed from README.md drop out +naturally; names with no PyPI rows are written as NOT_FOUND. Cross-checks +against other sources (fetch_pypi_downloads_via_bigquery.py, +fetch_pypi_downloads_via_pepy.py) print to stdout and never touch the +cache. -Results are cached to data/pypi_downloads.tsv (merged with any existing -rows); names with no PyPI rows are written as NOT_FOUND. - -Usage: python fetch_pypi_downloads_via_clickpy.py [--bigquery] [--dry-run] [--names-file FILE] +Usage: python fetch_pypi_downloads_via_clickpy.py """ import re -import subprocess -import sys -from json import loads from pathlib import Path import httpx @@ -45,7 +29,6 @@ from readme_parser import parse_readme DATA_DIR = Path(__file__).parent / "data" OUT_FILE = DATA_DIR / "pypi_downloads.tsv" README_PATH = Path(__file__).parent.parent / "README.md" -MAX_BYTES_BILLED = 400_000_000_000 CLICKPY_URL = "https://sql-clickhouse.clickhouse.com/?user=demo" # PyPI normalizes names to lowercase with runs of -, _, . collapsed to -. @@ -80,54 +63,12 @@ def fetch_clickpy(names: list[str]) -> dict[str, int]: return {row["project"]: int(row["downloads"]) for row in resp.json()["data"]} -def fetch_bigquery(names: list[str], dry_run: bool) -> dict[str, int]: - in_list = ", ".join(f"'{name}'" for name in names) - query = ( - "SELECT project, COUNT(*) AS downloads " - "FROM `bigquery-public-data.pypi.file_downloads` " - "WHERE DATE(timestamp) BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) " - "AND CURRENT_DATE() " - f"AND project IN ({in_list}) " - "GROUP BY project" - ) - cmd = ["bq", "query", "--use_legacy_sql=false", "--format=json", f"--maximum_bytes_billed={MAX_BYTES_BILLED}"] - if dry_run: - cmd.append("--dry_run") - cmd.append(query) - result = subprocess.run(cmd, capture_output=True, text=True) - if result.returncode != 0: - print(result.stderr, file=sys.stderr) - sys.exit(1) - if dry_run: - print(result.stdout.strip() or result.stderr.strip()) - sys.exit(0) - rows = loads(result.stdout) - return {row["project"]: int(row["downloads"]) for row in rows} - - def main() -> None: - dry_run = "--dry-run" in sys.argv - use_bigquery = "--bigquery" in sys.argv - if dry_run and not use_bigquery: - print("Error: --dry-run only applies to --bigquery.", file=sys.stderr) - sys.exit(1) - if "--names-file" in sys.argv: - names_path = Path(sys.argv[sys.argv.index("--names-file") + 1]) - names = sorted({normalize(line) for line in names_path.read_text().split() if line}) - else: - names = collect_names(README_PATH.read_text()) + names = collect_names(README_PATH.read_text()) print(f"Querying {len(names)} package names...") - counts = fetch_bigquery(names, dry_run) if use_bigquery else fetch_clickpy(names) - merged: dict[str, str] = {} - if OUT_FILE.exists(): - for line in OUT_FILE.read_text().splitlines(): - name, _, value = line.partition("\t") - merged[name] = value - for name in names: - merged[name] = str(counts.get(name, "NOT_FOUND")) - OUT_FILE.write_text("\n".join(f"{name}\t{value}" for name, value in sorted(merged.items())) + "\n") - found = sum(1 for name in names if name in counts) - print(f"Done. {found}/{len(names)} names found on PyPI. Cached to {OUT_FILE}") + counts = fetch_clickpy(names) + OUT_FILE.write_text("\n".join(f"{name}\t{counts.get(name, 'NOT_FOUND')}" for name in names) + "\n") + print(f"Done. {len(counts)}/{len(names)} names found on PyPI. Cached to {OUT_FILE}") if __name__ == "__main__": From c2ba7d462c76e40ea2891a8ca0abe1d4addf6eb5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:57:16 +0800 Subject: [PATCH 0701/1130] audit: sweep HTTP Clients to the shortlist cap First Audit of the section. Restructure first, maintainer-chosen at the preview review: the flat section splits into Clients and a minted URL Manipulation Subcategory holding yarl (661.9M/month via BigQuery, aio-libs, active 2026-08) and httpx.URL as a no-signal pointer to the URL class bundled with the kept httpx. Client tiers: requests, httpx, aiohttp Obvious; urllib3 in the Challenger slot by structure (transport layer under requests, count inflated by that), ordered by downloads/month. Maintainer-adjudicated 2026-08-16. Removed (downloads are PyPI last-30-day via ClickPy, fetched 2026-08-16): - furl (6.3M/month via BigQuery, same source as the yarl comparison): URL manipulation, not an HTTP client; stdlib urllib.parse covers parsing/joining/encoding and the minted Subcategory carries the ergonomic successors - yarl outpulls it 105x; repo quiet since 2026-02 - httptap (1.7K/month): CLI diagnostic tool, repo created 2025-10, 790 stars; no adoption Co-Authored-By: Claude --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 62355489..1a1821ad 100644 --- a/README.md +++ b/README.md @@ -347,12 +347,14 @@ _Static site generator is a software that takes some text + templates as input a _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. -- [httptap](https://github.com/ozeranskii/httptap) - Dissects an HTTP request into DNS, TCP, TLS, wait, and transfer phases and renders the timings as a waterfall. -- [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. +- General + - [requests](https://github.com/psf/requests) - HTTP Requests for Humans. + - [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python. + - [aiohttp](https://github.com/aio-libs/aiohttp) - Asynchronous HTTP client/server framework for asyncio and Python. + - [urllib3](https://github.com/urllib3/urllib3) - A HTTP library with thread-safe connection pooling, file post support, sanity friendly. +- URL Manipulation + - [yarl](https://github.com/aio-libs/yarl) - Yet another URL library. + - [httpx.URL](https://www.python-httpx.org/api/) - The immutable URL class bundled with HTTPX. ### Web Scraping From fdf66eb1f70489a131c7544d9acd37d2411bc997 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:54:20 +0800 Subject: [PATCH 0702/1130] audit: sweep Web Scraping to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16. Both Subcategories reordered by downloads/month (Frameworks: browser-use, scrapy, crawl4ai; Content Extraction: feedparser, html2text with a watch - repo quiet since 2025-10 - and trafilatura). Removed (downloads are PyPI last-30-day via ClickPy, fetched 2026-08-16): - mechanicalsoup (162K/month): stateful-browsing era superseded by browser automation - crawlberg (10.6K/month): xberg-io coordinated self-promotion (the org behind the standing rejection rule); repo created 2026-03, 155 stars - website-downloader (352/month): hobby mirroring script, 174 stars - micawber (209K/month): oEmbed extraction, small audience next to the kept three - sumy (168K/month): summarization, not content extraction - wrong job for the Subcategory Co-Authored-By: Claude --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 1a1821ad..2e750e48 100644 --- a/README.md +++ b/README.md @@ -362,16 +362,11 @@ _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. - - [crawlberg](https://github.com/xberg-io/crawlberg) - A high-performance web crawling engine with a Rust core, headless-browser fallback, and built-in robots.txt and sitemap parsing. - - [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. - - [website-downloader](https://github.com/PKHarsimran/website-downloader) - A modern wget --mirror / HTTrack alternative that turns whole websites into browsable offline copies. + - [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. - 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 From d2d344fbd24578867b60bad2778896aadb6dcf60 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:54:32 +0800 Subject: [PATCH 0703/1130] audit: sweep Email to the shortlist cap First Audit of the section; maintainer adjudicated 2026-08-16. yagmail (346K/month, last push 2026-05) stays as the sole entry - the Cap is a ceiling, not a floor. Removed (downloads are PyPI last-30-day via ClickPy, fetched 2026-08-16): - modoboa (7.6K/month): mail-hosting platform application, not a library (ajenti/indico precedent); deployments do not flow through pip - judgment call Co-Authored-By: Claude --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2e750e48..9b6d8ee9 100644 --- a/README.md +++ b/README.md @@ -373,7 +373,6 @@ _Libraries to automate web scraping and extract web content._ _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** From 9fc4f003fedd3b7830208c3263a144ba5a86418f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 01:55:09 +0800 Subject: [PATCH 0704/1130] style: rename HTTP Clients' General subcategory to Clients Maintainer follow-up to the 2026-08-16 HTTP & Scraping Audit: "HTTP Clients / General" read redundant next to URL Manipulation. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b6d8ee9..0397542d 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ _Static site generator is a software that takes some text + templates as input a _Libraries for working with HTTP._ -- General +- Clients - [requests](https://github.com/psf/requests) - HTTP Requests for Humans. - [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python. - [aiohttp](https://github.com/aio-libs/aiohttp) - Asynchronous HTTP client/server framework for asyncio and Python. From 41f89f7f1149321261d91dc8741c32fe7ce9c030 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:23:56 +0800 Subject: [PATCH 0705/1130] audit: sweep ORM to the shortlist cap Relational Databases had 7 entries against the cap of 5. Removed: - dataset (2,316,477 downloads/month) - dict-store convenience wrapper, not the ORM job - pony (653,675 downloads/month) - fading mindshare, no successor trajectory; repo still active, this is a cap call not an abandonment call - tortoise-orm (599,872 downloads/month, flat) - async niche now served by SQLAlchemy asyncio and sqlmodel Downloads are PyPI last-30-day via ClickPy fetched 2026-08-16. Both subcategories reordered per entry-ordering rule: obvious choices by downloads descending (sqlalchemy 423.9M, django.db.models riding django 56.1M, peewee 50.8M; pynamodb 4.1M, mongoengine 2.5M) then challengers (sqlmodel 22.3M; beanie 1.6M). Co-Authored-By: Claude --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0397542d..28b836b5 100644 --- a/README.md +++ b/README.md @@ -382,18 +382,15 @@ _Libraries for sending and parsing email, and mail server management._ _Libraries that implement Object-Relational Mapping or data mapping techniques._ - Relational Databases - - [django.db.models](https://github.com/django/django) - The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/). - [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. + - [django.db.models](https://github.com/django/django) - The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/). - [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/). + - [mongoengine](https://github.com/MongoEngine/mongoengine) - A Python Object-Document-Mapper for working with MongoDB. + - [beanie](https://github.com/BeanieODM/beanie) - An asynchronous Python object-document mapper (ODM) for MongoDB. ### Database Drivers From b8dd6538c250c1aabf288ef5ee21e7dd2e5d1f9b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:26:28 +0800 Subject: [PATCH 0706/1130] style: restructure Database Drivers section Database & Storage audit (maintainer adjudicated 2026-08-16), format-only outcome, no removals: - Mint a ClickHouse subcategory between SQLite and Other Relational Databases, moving clickhouse-driver into it; two ClickHouse clients justify their own use case (official clickhouse-connect lands next as its own add commit). - Rename entry redis-py to redis per the PyPI-package-name naming rule in CONTRIBUTING.md; also fixes a NOT_FOUND row in every downloads sweep. - Reorder MySQL (pymysql 96.4M downloads/month before mysqlclient 20.4M) and NoSQL Databases (redis 277M pepy, pymongo 116.6M, cassandra-driver 8.0M, then django-mongodb-backend 31K as challenger) per the entry-ordering rule. Co-Authored-By: Claude --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 28b836b5..c9f85f53 100644 --- a/README.md +++ b/README.md @@ -397,21 +397,22 @@ _Libraries that implement Object-Relational Mapping or data mapping techniques._ _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. + - [mysqlclient](https://github.com/PyMySQL/mysqlclient) - MySQL connector with Python 3 support ([mysql-python](https://sourceforge.net/projects/mysql-python/) fork). - 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 - [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. +- Other Relational Databases - [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 + - [redis](https://github.com/redis/redis-py) - The Python client for Redis. + - [pymongo](https://github.com/mongodb/mongo-python-driver) - The official Python client for MongoDB. - [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 From 0cc4e0eed4d588379d0779efb7499b8d118fa392 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:27:18 +0800 Subject: [PATCH 0707/1130] feat: add asyncpg to Database Drivers > PostgreSQL MagicStack's asyncio PostgreSQL driver, 122,401,686 downloads/month (pepy.tech, fetched 2026-08-16). Verified as obvious choice #2 behind psycopg (130.8M downloads/month, same source) in the Database & Storage audit; last push 2026-02, inside the 12-month Active rule. Its absence was a gap in the list. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c9f85f53..16203b79 100644 --- a/README.md +++ b/README.md @@ -401,6 +401,7 @@ _Libraries for connecting and operating databases._ - [mysqlclient](https://github.com/PyMySQL/mysqlclient) - MySQL connector with Python 3 support ([mysql-python](https://sourceforge.net/projects/mysql-python/) fork). - PostgreSQL - [awesome-postgres](https://github.com/dhamaniasad/awesome-postgres) - [psycopg](https://github.com/psycopg/psycopg) - The most popular PostgreSQL adapter for Python. + - [asyncpg](https://github.com/MagicStack/asyncpg) - A fast PostgreSQL Database Client Library for Python/asyncio. - 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. From 774da7236e35e241f75ec51d5e9fe7673b28b67f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:28:05 +0800 Subject: [PATCH 0708/1130] feat: add clickhouse-connect to Database Drivers > ClickHouse Database & Storage audit (2026-08-16): official ClickHouse client at 31,797,323 downloads/month has overtaken community clickhouse-driver at 18,405,937/month (pepy.tech, same source, fetched 2026-08-16). Listed first per entry-ordering rule; clickhouse-driver stays second tier. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 16203b79..adf4882b 100644 --- a/README.md +++ b/README.md @@ -406,6 +406,7 @@ _Libraries for connecting and operating databases._ - [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. - ClickHouse + - [clickhouse-connect](https://github.com/ClickHouse/clickhouse-connect) - The official ClickHouse client, with SQLAlchemy and Superset connectors. - [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. - Other Relational Databases - [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. From 9b28c566bfbfaa0d1b857c0eba39e104bc9dc72a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:28:45 +0800 Subject: [PATCH 0709/1130] feat: add pyodbc to Other Relational Databases SQL Server / ODBC standard for two decades, 42,911,527 downloads/month (pepy.tech, fetched 2026-08-16). Its absence was a gap in the list from the Database & Storage audit, not a verdict on the project. Listed as obvious choice #1, before mssql-python (3.3M/month challenger), per entry-ordering rule. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index adf4882b..676e8e60 100644 --- a/README.md +++ b/README.md @@ -409,6 +409,7 @@ _Libraries for connecting and operating databases._ - [clickhouse-connect](https://github.com/ClickHouse/clickhouse-connect) - The official ClickHouse client, with SQLAlchemy and Superset connectors. - [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. - Other Relational Databases + - [pyodbc](https://github.com/mkleehammer/pyodbc) - An ODBC bridge for connecting to SQL Server and any other ODBC-accessible database. - [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 - [redis](https://github.com/redis/redis-py) - The Python client for Redis. From 442291d74a3941c0c0b5745181353882e35484e8 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:29:30 +0800 Subject: [PATCH 0710/1130] feat: add oracledb to Database Drivers > Other Relational Databases Oracle's official driver, successor to cx_Oracle. 26,856,874 downloads/month (pepy.tech, fetched 2026-08-16), from the Database & Storage audit (maintainer adjudicated 2026-08-16). Placed as obvious choice #2 between pyodbc (42.9M) and challenger mssql-python (3.3M) per entry-ordering rule. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 676e8e60..8a3e3667 100644 --- a/README.md +++ b/README.md @@ -410,6 +410,7 @@ _Libraries for connecting and operating databases._ - [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. - Other Relational Databases - [pyodbc](https://github.com/mkleehammer/pyodbc) - An ODBC bridge for connecting to SQL Server and any other ODBC-accessible database. + - [oracledb](https://github.com/oracle/python-oracledb) - The official Python driver for Oracle Database, successor to cx_Oracle. - [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 - [redis](https://github.com/redis/redis-py) - The Python client for Redis. From 3a3b20c03debe880259b0e9e5bf8de901ae1215b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:30:29 +0800 Subject: [PATCH 0711/1130] audit: sweep Database to the shortlist cap Split the flat Database section into three subcategories (Analytical, Vector, Key-Value & Document) since its 7 entries exceeded the shortlist cap of 5 and the size reflected genuinely distinct jobs: - Analytical: duckdb (obvious choice, 61,026,806 downloads/month), chdb (challenger, 3,564,786 downloads/month) - Vector: chromadb (obvious choice, 13,048,822 downloads/month), zvec (challenger on star trajectory, 44,737 downloads/month, 15.4K stars in 8 months) - Key-Value & Document: tinydb (obvious choice, 6,194,854 downloads/month) Removed: - pickledb (29,446 downloads/month) - toy key-value store - ZODB (197,044 downloads/month) - Zope-era native object database nobody names unprompted in 2026; repo still active, this is a relevance call, not an abandonment call Rewrote the section description from "Databases implemented in Python" to "In-process databases usable directly from Python" because duckdb, chdb, and zvec are not themselves implemented in Python, and the written-in-Python scope test was already removed from CONTRIBUTING.md. Downloads are PyPI last-30-day via ClickPy, fetched 2026-08-16. Co-Authored-By: Claude --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8a3e3667..7f7f48e1 100644 --- a/README.md +++ b/README.md @@ -420,15 +420,16 @@ _Libraries for connecting and operating databases._ ### Database -_Databases implemented in Python._ +_In-process databases usable directly from Python._ -- [chdb](https://github.com/chdb-io/chdb) - In-process OLAP SQL engine with the full ClickHouse dialect, zero-copy pandas/Arrow interop, and federation to remote ClickHouse clusters via `remoteSecure()`. -- [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. -- [zvec](https://github.com/alibaba/zvec) - An embedded vector database for on-device RAG and edge AI, the SQLite of vector databases. +- Analytical + - [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. + - [chdb](https://github.com/chdb-io/chdb) - In-process OLAP SQL engine with the full ClickHouse dialect, zero-copy pandas/Arrow interop, and federation to remote ClickHouse clusters via `remoteSecure()`. +- Vector + - [chromadb](https://github.com/chroma-core/chroma) - An open-source embedding database for building AI applications with embeddings and semantic search. + - [zvec](https://github.com/alibaba/zvec) - An embedded vector database for on-device RAG and edge AI, the SQLite of vector databases. +- Key-Value & Document + - [tinydb](https://github.com/msiemens/tinydb) - A tiny, document-oriented database. ### Caching From b7b633d509dc9c3e728372401e72905bc3c0e1ca Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:31:17 +0800 Subject: [PATCH 0712/1130] feat: add lancedb to Database & Storage > Vector Adds lancedb as obvious choice #2 in Vector, from the Database & Storage audit (maintainer adjudicated 2026-08-16): embedded vector database for multimodal AI, 9,315,269 downloads/month (pepy.tech, fetched 2026-08-16), 11.2K stars, very active (pushed 2026-08-15). Same-source check keeps chromadb first (13.3M pepy); zvec stays last as trajectory challenger. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7f7f48e1..bf06dc31 100644 --- a/README.md +++ b/README.md @@ -427,6 +427,7 @@ _In-process databases usable directly from Python._ - [chdb](https://github.com/chdb-io/chdb) - In-process OLAP SQL engine with the full ClickHouse dialect, zero-copy pandas/Arrow interop, and federation to remote ClickHouse clusters via `remoteSecure()`. - Vector - [chromadb](https://github.com/chroma-core/chroma) - An open-source embedding database for building AI applications with embeddings and semantic search. + - [lancedb](https://github.com/lancedb/lancedb) - A developer-friendly embedded retrieval database for multimodal AI. - [zvec](https://github.com/alibaba/zvec) - An embedded vector database for on-device RAG and edge AI, the SQLite of vector databases. - Key-Value & Document - [tinydb](https://github.com/msiemens/tinydb) - A tiny, document-oriented database. From 02735b6ced75d686cc2897ff802ebd036dfe2f2a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:32:02 +0800 Subject: [PATCH 0713/1130] style: reorder Caching by downloads, rename python-diskcache to diskcache Caching section audit (maintainer adjudicated 2026-08-16, format-only): - Renamed python-diskcache to diskcache to match its PyPI package name per CONTRIBUTING.md naming rule; also fixes a NOT_FOUND row in every downloads sweep. - Reordered by monthly downloads (pepy): cachetools (338,290,732) and diskcache (38,755,627) as obvious choices, then dogpile.cache (2,460,237) and django-cacheops (1,728,494) as second-tier challengers. - Maintainer confirmed keeping diskcache despite the repo being dormant since 2024-08: 38.8M downloads/month and nothing succeeds it at its job. Co-Authored-By: Claude --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bf06dc31..48f1deb0 100644 --- a/README.md +++ b/README.md @@ -437,9 +437,9 @@ _In-process databases usable directly from Python._ _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. +- [diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis. - [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. +- [django-cacheops](https://github.com/Suor/django-cacheops) - A slick ORM cache with automatic granular event-driven invalidation. ### Search From 7ac2950dc63a7c14a68062ff646b0b996141e0cb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:32:47 +0800 Subject: [PATCH 0714/1130] audit: sweep Search to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed pysolr (352,069 downloads/month via ClickPy, 347,571 pepy same-source, fetched 2026-08-16) — lightweight Apache Solr wrapper, the Solr era passed. Renamed elasticsearch-py to elasticsearch per the PyPI-package-name naming rule in CONTRIBUTING.md; also fixes a NOT_FOUND row in every downloads sweep. Reordered: elasticsearch (54,160,230/month pepy) obvious choice first, django-haystack (194,205/month pepy) kept and moved to second tier per maintainer's tier call during verdict review. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 48f1deb0..296401ec 100644 --- a/README.md +++ b/README.md @@ -445,9 +445,8 @@ _Libraries for caching data._ _Libraries and software for indexing and performing search queries on data._ +- [elasticsearch](https://github.com/elastic/elasticsearch-py) - The official low-level Python client for [Elasticsearch](https://www.elastic.co/products/elasticsearch). - [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 From f9539567030d527ce117c36506e3dff594a4ca3a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:33:25 +0800 Subject: [PATCH 0715/1130] feat: add opensearch-py to Search as obvious choice Database & Storage audit (2026-08-16): opensearch-py is the official low-level client of the OpenSearch fork, 52,432,831 downloads/month, near parity with elasticsearch at 54,160,230/month (both pepy.tech, fetched 2026-08-16). Placed after elasticsearch, before second-tier django-haystack, per entry-ordering rule. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 296401ec..d96248bf 100644 --- a/README.md +++ b/README.md @@ -446,6 +446,7 @@ _Libraries for caching data._ _Libraries and software for indexing and performing search queries on data._ - [elasticsearch](https://github.com/elastic/elasticsearch-py) - The official low-level Python client for [Elasticsearch](https://www.elastic.co/products/elasticsearch). +- [opensearch-py](https://github.com/opensearch-project/opensearch-py) - The official low-level Python client for [OpenSearch](https://opensearch.org/). - [django-haystack](https://github.com/django-haystack/django-haystack) - Modular search for Django. ### Serialization From 509fcaa38420704674606646c8405a22ede503ef Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:34:08 +0800 Subject: [PATCH 0716/1130] feat: add meilisearch to Search as a challenger Official Python client for the Meilisearch engine, 1,107,508 downloads/month (pepy.tech, fetched 2026-08-16). Admitted on the engine's developer-tool adoption trajectory; flagged as the weakest add of the Database & Storage audit (maintainer adjudicated 2026-08-16) and a judgment call in the reviewed verdicts. Placed after the obvious choices (elasticsearch, opensearch-py), before second-tier django-haystack, per entry-ordering rule. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d96248bf..4c49e77f 100644 --- a/README.md +++ b/README.md @@ -447,6 +447,7 @@ _Libraries and software for indexing and performing search queries on data._ - [elasticsearch](https://github.com/elastic/elasticsearch-py) - The official low-level Python client for [Elasticsearch](https://www.elastic.co/products/elasticsearch). - [opensearch-py](https://github.com/opensearch-project/opensearch-py) - The official low-level Python client for [OpenSearch](https://opensearch.org/). +- [meilisearch](https://github.com/meilisearch/meilisearch-python) - The official Python client for the [Meilisearch](https://www.meilisearch.com/) search engine. - [django-haystack](https://github.com/django-haystack/django-haystack) - Modular search for Django. ### Serialization From 745347dcddbcf170ad94d59dfe22fbcbeb7cc3cb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:34:52 +0800 Subject: [PATCH 0717/1130] style: reorder Serialization by downloads Audit outcome of the Database & Storage sitting, maintainer adjudicated 2026-08-16, format-only. Reordered from alphabetical to the entry-ordering rule: obvious choices by downloads/month descending, then challenger. msgpack 290,175,972; orjson 234,189,977; marshmallow 122,497,803; msgspec (challenger) 43,745,974. PyPI last-30-day downloads via ClickPy, fetched 2026-08-16. Co-Authored-By: Claude --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c49e77f..6afb341b 100644 --- a/README.md +++ b/README.md @@ -454,10 +454,10 @@ _Libraries and software for indexing and performing search queries on data._ _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. -- [msgspec](https://github.com/msgspec/msgspec) - A fast serialization and validation library with built-in support for JSON, MessagePack, YAML, and TOML. - [orjson](https://github.com/ijl/orjson) - Fast, correct JSON library. +- [marshmallow](https://github.com/marshmallow-code/marshmallow) - A lightweight library for converting complex objects to and from simple Python datatypes. +- [msgspec](https://github.com/msgspec/msgspec) - A fast serialization and validation library with built-in support for JSON, MessagePack, YAML, and TOML. **Data & Science** From 5da1d16de1c8ef17f653d31f9f11c5e6a431c333 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:40:44 +0800 Subject: [PATCH 0718/1130] docs: define Second Tier in CONTEXT.md The 2026-08-16 Database & Storage audit exposed a vocabulary gap: four adjudicated entries (clickhouse-driver, dogpile.cache, django-cacheops, django-haystack) are neither Obvious Choices nor trajectory-backed Challengers but demoted incumbents occupying Challenger slots, and the sitting had to improvise the term. The maintainer ratified it during review. A demoted incumbent counts against the two Challenger slots, but the adoption-trajectory bar gates only new admissions, not demotions. Also from the same sitting: psycopg2 was deliberately left unlisted under Database Drivers > PostgreSQL (282M downloads/month as psycopg2-binary, pepy 2026-08-16) because psycopg 3 is the new-work answer and v2 is legacy install base. Recorded here so the next PostgreSQL audit knows it was a decision, not an oversight. Co-Authored-By: Claude --- CONTEXT.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index f4b23b2c..c6deb3cc 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -47,6 +47,9 @@ _Avoid_: One-in-one-out (informal alias) An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept removed entirely) +**Second Tier**: +A Challenger slot holding a demoted incumbent instead of a rising successor — a former Obvious Choice or fading community standard an Audit kept below the current Obvious Choices (clickhouse-driver behind the official client, dogpile.cache and django-cacheops behind cachetools and diskcache, django-haystack in Search). It counts against the two Challenger slots and is marked by position like any Challenger; the adoption-trajectory bar gates only new admissions, not demotions. + **Split**: Maintainer-only restructuring of an oversized Use Case into finer Use Cases (new Subcategories), considered before any trimming when the size reflects genuinely distinct jobs. A submitter's entry PR can never perform a Split. From bc3e7a08cd7b46b22061b3453283ecd00964470d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:45:31 +0800 Subject: [PATCH 0719/1130] docs: condense AGENTS.md and CLAUDE.md for progressive disclosure Trim repository overview and structure sections into a brief intro plus focused Entry Rules and Gotchas sections, dropping restated CONTRIBUTING.md content and Makefile/pyproject boilerplate. Co-Authored-By: Claude --- AGENTS.md | 33 ++++++++------------------------- CLAUDE.md | 33 ++++++++------------------------- 2 files changed, 16 insertions(+), 50 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 646889dc..af55f4d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,32 +1,15 @@ # AGENTS.md -## Repository Overview +An opinionated shortlist of Python frameworks, libraries, tools, and resources, published at [awesome-python.com](https://awesome-python.com/). README.md is the single source of truth for catalog entries and README sponsor placements; `website/` renders it into the static site. -An opinionated list of Python frameworks, libraries, tools, and resources. Published at [awesome-python.com](https://awesome-python.com/). +## Entry Rules -## Entry Guidelines +[CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. -**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for admission rules, quality requirements, rejection rules, and entry format. Apply these rules whenever adding or removing an entry, whether reviewing a PR or committing directly. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. +- Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). `website/templates/sponsorship.html` separately defines the sponsorship content on the published website page. -## Structure +## Gotchas -- **README.md**: Source of truth for catalog entries and README sponsor placements. Hierarchical categories; entries ordered per the Key Rules below. -- **CONTRIBUTING.md**: Submission guidelines and review criteria. -- **SPONSORSHIP.md**: Sponsor tiers, placement rules, and the editorial-independence policy. `website/templates/sponsorship.html` separately defines which sponsorship content appears on the published website page. -- **website/**: Static site generator that builds awesome-python.com from README.md. - - `build.py`: Parses README.md and renders HTML via Jinja2 templates. - - `fetch_github_stars.py`: Fetches star counts into `website/data/`. - - `readme_parser.py`: Markdown-to-structured-data parser. - - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. - - `tests/`: Pytest tests for the build pipeline. -- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. On machines with only Python 3.14, prefix uv-based targets with `UV_PYTHON=3.13` (watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`). -- **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. - -## Key Rules - -- Ordering within a use case: obvious choices first, then challengers, each tier by PyPI downloads/month descending; no-signal entries last in tier, alphabetically. See CONTRIBUTING.md. -- A shortlist, not a catalog: per use case, up to 3 obvious choices plus up to 2 challengers, hard maximum 5. -- One project per PR. -- One entry per commit when adding or deleting entries. Exception: a prune sweep is one commit per section, its body listing each removal with its reason. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. -- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections alone are not evidence; verify before stating, and label anything unverifiable as a judgment call. -- README.md is the source of truth for catalog entries and README sponsor placements; treat `SPONSORSHIP.md` and `website/templates/sponsorship.html` as separate sponsorship content surfaces. +- On machines with only Python 3.14, prefix uv-based make targets with `UV_PYTHON=3.13` — watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`. diff --git a/CLAUDE.md b/CLAUDE.md index 52b9391c..67117dd9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,32 +1,15 @@ # CLAUDE.md -## Repository Overview +An opinionated shortlist of Python frameworks, libraries, tools, and resources, published at [awesome-python.com](https://awesome-python.com/). README.md is the single source of content truth; `website/` renders it into the static site. -An opinionated list of Python frameworks, libraries, tools, and resources. Published at [awesome-python.com](https://awesome-python.com/). +## Entry Rules -## Entry Guidelines +[CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. -**Refer to [CONTRIBUTING.md](CONTRIBUTING.md)** for admission rules, quality requirements, rejection rules, and entry format. Apply these rules whenever adding or removing an entry, whether reviewing a PR or committing directly. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. +- Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). -## Structure +## Gotchas -- **README.md**: Source of truth. Hierarchical categories; entries ordered per the Key Rules below. -- **CONTRIBUTING.md**: Submission guidelines and review criteria. -- **SPONSORSHIP.md**: Sponsor tiers, placement rules, and the editorial-independence policy. Sponsor content sits in the README header and must never influence which projects get listed. -- **website/**: Static site generator that builds awesome-python.com from README.md. - - `build.py`: Parses README.md and renders HTML via Jinja2 templates. - - `fetch_github_stars.py`: Fetches star counts into `website/data/`. - - `readme_parser.py`: Markdown-to-structured-data parser. - - `templates/`, `static/`: Jinja2 templates and CSS/JS assets. - - `tests/`: Pytest tests for the build pipeline. -- **Makefile**: `make install`, `make build`, `make preview`, `make test`, `make lint`, `make format`, `make typecheck`, `make fetch_github_stars`. On machines with only Python 3.14, prefix uv-based targets with `UV_PYTHON=3.13` (watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`). -- **pyproject.toml**: Uses `uv` for dependency management. Python >=3.13. - -## Key Rules - -- Ordering within a use case: obvious choices first, then challengers, each tier by PyPI downloads/month descending; no-signal entries last in tier, alphabetically. See CONTRIBUTING.md. -- A shortlist, not a catalog: per use case, up to 3 obvious choices plus up to 2 challengers, hard maximum 5. -- One project per PR. -- One entry per commit when adding or deleting entries. Exception: a prune sweep is one commit per section, its body listing each removal with its reason. Format, wording, or categorization changes across multiple entries may be bundled in a single commit. -- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections alone are not evidence; verify before stating, and label anything unverifiable as a judgment call. -- README.md is the single source of content truth. +- On machines with only Python 3.14, prefix uv-based make targets with `UV_PYTHON=3.13` — watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`. From 6c93b1e588d749b1c153a5ed284c221437ce618c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:45:34 +0800 Subject: [PATCH 0720/1130] docs: update audit-the-list SKILL.md wording Broaden argument-hint to cover thematic groups alongside sections, and backtick tool/skill names for consistency. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index be48f239..bad39c19 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -1,7 +1,7 @@ --- name: audit-the-list description: Audit README.md sections against the shortlist rules — re-verify every entry's verdict with live data, promote or demote challengers, restructure oversized use cases, prune, and evaluate proposed additions. Fire when the maintainer asks to audit, sweep, prune, re-check, or maintain sections, or asks whether an entry (or a proposed one) deserves its slot. -argument-hint: [all sections or specific sections] +argument-hint: [all or specific groups or sections] --- # Audit the list @@ -10,7 +10,7 @@ An Audit re-runs the admission test over live sections of README.md: every entry ## 1. Scope -Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — AskUserQuestion whether it means every section or only never-audited ones; prior audits are recorded in git history (`git log --oneline --grep="sweep\|audit"`). With no arguments, AskUserQuestion listing the never-audited Thematic Groups. Batch the work one Thematic Group per sitting. Done when the section list is settled. +Resolve the scope from the arguments. Named sections mean exactly those, whether or not they were audited before (a re-run is how a past verdict gets rechecked). `all` is ambiguous — `AskUserQuestion` whether it means every section or only never-audited ones; prior audits are recorded in git history (`git log --oneline --grep="sweep\|audit"`). With no arguments, AskUserQuestion listing the never-audited Thematic Groups. Batch the work one Thematic Group per sitting. Done when the section list is settled. ## 2. Evidence @@ -28,7 +28,7 @@ Draft a verdict and reason for every entry, restructure before cap: decide Split ## 4. Review and go -Run the preview-verdicts skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go. +Run the `preview-verdicts` skill: it generates the interactive review page and defines how the maintainer's feedback JSON comes back and gets processed. Their verdicts are final. Before touching README.md, surface what the feedback implies but does not decide — cap overflows, homeless entries after a Split, tier for a tierless flip — as named assumptions or questions, then get an explicit go. ## 5. Execute From c6957d4c21015b9297cd00c68a496ca438737eab Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:52:03 +0800 Subject: [PATCH 0721/1130] docs: require WebSearch evidence for tier judgments Verifying keep/drop reasons with download counts alone isn't enough to judge obvious-choice vs challenger tiers; the maintainer requires WebSearch evidence of adoption trajectory and community sentiment too. Also rewrites the repo overview in CLAUDE.md and mirrors it to AGENTS.md per the kept-in-sync convention. Co-Authored-By: Claude --- AGENTS.md | 6 ++++-- CLAUDE.md | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index af55f4d9..14695865 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,12 +1,14 @@ # AGENTS.md -An opinionated shortlist of Python frameworks, libraries, tools, and resources, published at [awesome-python.com](https://awesome-python.com/). README.md is the single source of truth for catalog entries and README sponsor placements; `website/` renders it into the static site. +An opinionated guide to the best Python frameworks, libraries, tools, and resources. + +[README.md](README.md) is the single source of truth for catalog entries and README sponsor placements; `website/` renders it into the static site: [awesome-python.com](https://awesome-python.com/). ## Entry Rules [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. -- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). `website/templates/sponsorship.html` separately defines the sponsorship content on the published website page. diff --git a/CLAUDE.md b/CLAUDE.md index 67117dd9..47ac9487 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,14 @@ # CLAUDE.md -An opinionated shortlist of Python frameworks, libraries, tools, and resources, published at [awesome-python.com](https://awesome-python.com/). README.md is the single source of content truth; `website/` renders it into the static site. +An opinionated guide to the best Python frameworks, libraries, tools, and resources. + +[README.md](README.md) is the single source of content truth; `website/` renders it into the static site: [awesome-python.com](https://awesome-python.com/). ## Entry Rules [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. -- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). From 36c61798c499f1cdac9042d949a8f971a0ce426f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:53:24 +0800 Subject: [PATCH 0722/1130] update wordings --- .claude/skills/audit-the-list/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index bad39c19..57285d1d 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -1,7 +1,7 @@ --- name: audit-the-list description: Audit README.md sections against the shortlist rules — re-verify every entry's verdict with live data, promote or demote challengers, restructure oversized use cases, prune, and evaluate proposed additions. Fire when the maintainer asks to audit, sweep, prune, re-check, or maintain sections, or asks whether an entry (or a proposed one) deserves its slot. -argument-hint: [all or specific groups or sections] +argument-hint: [all or specific sections] --- # Audit the list From a2da2103455eeabbadcaa822313a8c19656a0b9a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 02:58:20 +0800 Subject: [PATCH 0723/1130] feat: stamp pypi downloads cache with fetched_at and header row Adds a header row (name, downloads, fetched_at) to data/pypi_downloads.tsv and stamps every row with the sweep date, so audits can tell evidence age and skip re-fetching when the cache is less than 7 days old. The sweep itself costs ~1s, so freshness is checked by the reader (audit-the-list skill) instead of skip logic in the fetch script. SKILL.md documents the 7-day freshness rule. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- website/fetch_pypi_downloads_via_clickpy.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index 57285d1d..961b3c7f 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -16,7 +16,7 @@ Resolve the scope from the arguments. Named sections mean exactly those, whether Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): -- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. +- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run; header row, every row stamped with its `fetched_at` date). A cache whose `fetched_at` is within the last 7 days is current enough for verdicts — skip the sweep; older than that, re-run it (costs ~1s). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. - **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index e88815ac..e384b365 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -12,7 +12,10 @@ excludes mirrors by default. This script is the sole writer of data/pypi_downloads.tsv and rewrites it from scratch each run, so entries removed from README.md drop out -naturally; names with no PyPI rows are written as NOT_FOUND. Cross-checks +naturally; names with no PyPI rows are written as NOT_FOUND. The file +starts with a header row (name, downloads, fetched_at) and every row +carries the sweep date: a cache fetched within the last 7 days is current +enough for audit verdicts, so only re-run when older. Cross-checks against other sources (fetch_pypi_downloads_via_bigquery.py, fetch_pypi_downloads_via_pepy.py) print to stdout and never touch the cache. @@ -21,6 +24,7 @@ Usage: python fetch_pypi_downloads_via_clickpy.py """ import re +from datetime import date from pathlib import Path import httpx @@ -67,7 +71,9 @@ def main() -> None: names = collect_names(README_PATH.read_text()) print(f"Querying {len(names)} package names...") counts = fetch_clickpy(names) - OUT_FILE.write_text("\n".join(f"{name}\t{counts.get(name, 'NOT_FOUND')}" for name in names) + "\n") + fetched_at = date.today().isoformat() + rows = "\n".join(f"{name}\t{counts.get(name, 'NOT_FOUND')}\t{fetched_at}" for name in names) + OUT_FILE.write_text(f"name\tdownloads\tfetched_at\n{rows}\n") print(f"Done. {len(counts)}/{len(names)} names found on PyPI. Cached to {OUT_FILE}") From 8814a857426653100082e42a8d8fb17c2da8f412 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 03:21:04 +0800 Subject: [PATCH 0724/1130] feat: store verdict preview pages in repo-local ./tmp Multi-day reviews need to survive reboots, which system /tmp does not guarantee. ./tmp is git-ignored so the generated pages never land in commits. Co-Authored-By: Claude --- .claude/skills/preview-verdicts/SKILL.md | 2 +- .gitignore | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.claude/skills/preview-verdicts/SKILL.md b/.claude/skills/preview-verdicts/SKILL.md index af0de9c1..938a02d5 100644 --- a/.claude/skills/preview-verdicts/SKILL.md +++ b/.claude/skills/preview-verdicts/SKILL.md @@ -15,7 +15,7 @@ Maintainer review happens through an interactive HTML page: one row per entry wi - `verdict` is `keep` or `drop`, seeded from the current adjudication or dry-run. - `reason` is plain language the maintainer reads cold — no invented shorthand. When fresh evidence contradicts the seeded verdict (a big download count on a drop, a dead repo on a keep), say so in that row's reason instead of silently changing the seed. 2. Copy `template.html` (sibling of this file) and replace the placeholders: `__TITLE__` (page title), `__SUB__` (sub-header: scope, seed provenance, fetch date, and the standing instruction to flip/comment then Copy feedback), `__KEY__` (localStorage key), `__DATA__` (the array). `__KEY__` must be unique per review — slug plus date, e.g. `awesome-python-science-2026-09-01` — because saved state under a reused key bleeds a previous review's flips into rows with the same section and entry name. -3. Write the page to `/tmp/awesome-python--preview.html`, `open` it, and tell the maintainer the path and the return path: flip or comment rows (they highlight yellow), press **Copy feedback**, paste the JSON into the chat. Done when the page is open and the return path is stated. +3. Write the page to `tmp/awesome-python--preview.html` in the repo root (git-ignored; create the directory if needed), `open` it, and tell the maintainer the path and the return path: flip or comment rows (they highlight yellow), press **Copy feedback**, paste the JSON into the chat. Done when the page is open and the return path is stated. ## Process the pasted feedback diff --git a/.gitignore b/.gitignore index ab400a02..b4d552e4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ docs/* !docs/adr/ # agents +tmp/ .superpowers/ .playwright-cli/ skills-lock.json From 3e90482f01b223550b69eb8e30ab0a4f9d7af4c2 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:52:55 +0800 Subject: [PATCH 0725/1130] audit: sweep Code Analysis to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed code2flow (36,486 downloads/month, last push 2025-07 — fails the obvious-choice bar, same test as dropped peers). Removed pytype (919,951 downloads/month — Google sunset, maintenance mode, Python 3.12 is the last supported version). Merged Code Linters + Code Formatters into one Linters and Formatters use case (ruff is the 2026 de facto standard for both; tiers: ruff/black/isort obvious choices, pylint/flake8 second tier). Re-homed bandit to new Security Linters subcategory (security linting is a distinct job from style linting). Demoted typeshed to sub-item under mypy (stub infrastructure every checker consumes, not itself a checker holding a slot). Promoted ty to obvious choice (maintainer call), order mypy/ty then pyrefly challenger. Reordered Code Analysis subcategory vulture first (12.3M/month obvious choice, prospector 485K and repowise 33K challengers — repowise kept on maintainer flip). Co-Authored-By: Claude --- README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6afb341b..c08fc9ea 100644 --- a/README.md +++ b/README.md @@ -584,29 +584,27 @@ _Interactive Python interpreters (REPL)._ _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. + - [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. - [prospector](https://github.com/prospector-dev/prospector) - A tool to analyze Python code. - [repowise](https://github.com/repowise-dev/repowise) - Codebase intelligence that indexes repos into dependency graphs, git history, and auto-generated docs with dead code detection. - - [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. -- Code Formatters +- Linters and Formatters + - [ruff](https://github.com/astral-sh/ruff) - An extremely fast Python linter and code formatter. - [black](https://github.com/psf/black) - The uncompromising Python code formatter. - [isort](https://github.com/PyCQA/isort) - A Python utility / library to sort imports. - - [ruff](https://github.com/astral-sh/ruff) - An extremely fast Python linter and code formatter. + - [pylint](https://github.com/pylint-dev/pylint) - A fully customizable source code analyzer. + - [flake8](https://github.com/PyCQA/flake8) - A wrapper around `pycodestyle`, `pyflakes` and McCabe. + - [awesome-flake8-extensions](https://github.com/DmytroLitvinov/awesome-flake8-extensions) - Refactoring - [rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. +- Security Linters + - [bandit](https://github.com/PyCQA/bandit) - A tool designed to find common security issues in Python code. - Type Checkers - [awesome-python-typing](https://github.com/typeddjango/awesome-python-typing) - [mypy](https://github.com/python/mypy) - Check variable types during compile time. - - [pyrefly](https://github.com/facebook/pyrefly) - A fast type checker and language server for Python. + - [typeshed](https://github.com/python/typeshed) - [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. + - [pyrefly](https://github.com/facebook/pyrefly) - A fast type checker and language server for Python. - 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 From 5e3d77826a987e8316d21cfd3978364537475e6b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:53:30 +0800 Subject: [PATCH 0726/1130] feat: add pyright to Type Checkers Adjudicated by maintainer 2026-08-16: obvious choice (39.4M downloads/month via ClickPy; 2026 sources call it the strong default -- conformance lead, Pylance engine, editor integration). Ordered after ty (41.2M/month) per downloads-descending obvious-choice tier. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c08fc9ea..6ead29ff 100644 --- a/README.md +++ b/README.md @@ -602,6 +602,7 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [mypy](https://github.com/python/mypy) - Check variable types during compile time. - [typeshed](https://github.com/python/typeshed) - [ty](https://github.com/astral-sh/ty) - An extremely fast Python type checker and language server. + - [pyright](https://github.com/microsoft/pyright) - Full-featured static type checker for Python from Microsoft, the engine behind Pylance. - [pyrefly](https://github.com/facebook/pyrefly) - A fast type checker and language server for Python. - Type Annotations Generators - [monkeytype](https://github.com/Instagram/MonkeyType) - A system for Python that generates static type annotations by collecting runtime types. From b4960530ad83ec0784a78c02a0ac8a40b7ed6adf Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:54:10 +0800 Subject: [PATCH 0727/1130] audit: sweep Debugging Tools to the shortlist cap Developer Tools audit adjudicated by maintainer 2026-08-16: - removed manhole (1,060,111 downloads/month but last push 2024-07, past the 12-month activity line; also mis-homed under Tracing, it is an inspection socket not a tracer) - removed memory_graph (13,393 downloads/month, added 2025-12 by the project's own author with no displacement argued, teaching aid, fails the obvious-choice bar) - renamed python-hunter to hunter (PyPI package name is hunter; the python-hunter name on PyPI is a squatter) - reordered Others by downloads: django-debug-toolbar 9.1M, icecream 1.95M, flask-debugtoolbar 1.24M Co-Authored-By: Claude --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6ead29ff..1f4a25e3 100644 --- a/README.md +++ b/README.md @@ -649,16 +649,14 @@ _Libraries for debugging code._ - [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. + - [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. + - [flask-debugtoolbar](https://github.com/pallets-eco/flask-debugtoolbar) - A port of the django-debug-toolbar to flask. ### Build Tools From 3a65a020c4e01c3a8f244d2618fb6e4fdc6e9baa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:54:56 +0800 Subject: [PATCH 0728/1130] feat: add memray to Profiler Bloomberg memory profiler that tracks allocations in Python code, native extensions, and the interpreter itself. 15.6M downloads/month via ClickPy; the obvious choice for memory profiling work per maintainer audit 2026-08-16. Ordered after py-spy (28.5M/month) per downloads-descending tier. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1f4a25e3..cce922e3 100644 --- a/README.md +++ b/README.md @@ -652,6 +652,7 @@ _Libraries for debugging code._ - [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. + - [memray](https://github.com/bloomberg/memray) - A memory profiler that tracks allocations in Python code, native extensions, and the interpreter itself. - [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. From d316a8ee2adfe6848e04da1bb7c6b12d7b705945 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:55:29 +0800 Subject: [PATCH 0729/1130] feat: add pyinstrument to Profiler Developer Tools audit adjudicated by maintainer 2026-08-16: obvious choice per ClickPy (10.6M downloads/month), a statistical wall-clock profiler and common first reach for request-level profiling. Ordered after memray (15.6M/month), before challenger scalene (400K/month). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cce922e3..a0081856 100644 --- a/README.md +++ b/README.md @@ -653,6 +653,7 @@ _Libraries for debugging code._ - Profiler - [py-spy](https://github.com/benfred/py-spy) - A sampling profiler for Python programs. Written in Rust. - [memray](https://github.com/bloomberg/memray) - A memory profiler that tracks allocations in Python code, native extensions, and the interpreter itself. + - [pyinstrument](https://github.com/joerick/pyinstrument) - A statistical wall-clock profiler with low overhead and readable call-tree output. - [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. From 81256884939c25e32c53a4c5ea5869044a528cfe Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:56:08 +0800 Subject: [PATCH 0730/1130] audit: sweep Build Tools to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed bitbake (no PyPI distribution, ships with Yocto — its users are embedded-Linux developers, fails the Serves Python Developers scope test). Removed platformio (1,255,169 downloads/month — embedded and IoT toolchain written in Python but used for C and C++ firmware work, same scope test as bitbake). Removed pybuilder (51,511 downloads/month — searches confirm niche adoption, small community, fails the obvious-choice bar). Reordered remaining entries: invoke (85.4M) and scons (924K) as obvious choices, doit (803K) as challenger. Co-Authored-By: Claude --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index a0081856..c649ac80 100644 --- a/README.md +++ b/README.md @@ -664,12 +664,9 @@ _Libraries for debugging code._ _Compile software from source code._ -- [bitbake](https://github.com/openembedded/bitbake) - A make-like build tool for embedded Linux. -- [doit](https://github.com/pydoit/doit) - A task runner and build tool. - [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. - [scons](https://github.com/SCons/scons) - A software construction tool. +- [doit](https://github.com/pydoit/doit) - A task runner and build tool. ### Documentation From 71eee5d240299b4e6a8a091525b22327f6e53a5f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:56:39 +0800 Subject: [PATCH 0731/1130] feat: add ipython to Interactive Interpreter Obvious choice per maintainer audit 2026-08-16: 175.7M downloads/month via ClickPy, the default enhanced REPL and the kernel under Jupyter, yet missing from the README. Placed first per downloads-descending obvious-choice tier (jupyter 17.5M follows). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c649ac80..d95c5475 100644 --- a/README.md +++ b/README.md @@ -574,6 +574,7 @@ _Python implementation of data structures, algorithms and design patterns. Also _Interactive Python interpreters (REPL)._ +- [ipython](https://github.com/ipython/ipython) - A powerful interactive Python shell, and the kernel behind Jupyter notebooks. - [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. From 2d0f5da2794083d1519e6ef64dc90bcc54b7c1a6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:57:11 +0800 Subject: [PATCH 0732/1130] feat: add mkdocs-material as sub-item under mkdocs Adjudicated by maintainer 2026-08-16: mkdocs-material pulls 17.6M downloads/month via ClickPy, nearly equal to mkdocs itself, and is the theme most mkdocs sites run. Listed as a sub-item since its job is inseparable from its parent. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d95c5475..75e2cd48 100644 --- a/README.md +++ b/README.md @@ -675,6 +675,7 @@ _Libraries for generating project documentation._ - [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. - [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. + - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. - [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. - [awesome-sphinxdoc](https://github.com/ygzgxyz/awesome-sphinxdoc) From 166c9c97df118522d83d587c1ed8d835db7b1f4e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:57:52 +0800 Subject: [PATCH 0733/1130] style: reorder Algorithms and Design Patterns and Documentation by downloads Developer Tools audit adjudicated by maintainer 2026-08-16, format-only outcome for the two sections with no removals. Algorithms: sortedcontainers (278.7M/month) first, then educational no-signal repos algorithms and thealgorithms alphabetically. Design Patterns: transitions (2.9M/month) before no-signal python-patterns. Documentation: sphinx (85.9M), mkdocs (17.8M), diagrams (6.8M) as obvious choices, pdoc (2.1M) as challenger. Co-Authored-By: Claude --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 75e2cd48..180682b8 100644 --- a/README.md +++ b/README.md @@ -563,12 +563,12 @@ _Libraries for quantum computing._ _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. + - [algorithms](https://github.com/keon/algorithms) - Minimal examples of data structures and algorithms. - [thealgorithms](https://github.com/TheAlgorithms/Python) - All Algorithms implemented in Python. - Design Patterns - - [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. + - [python-patterns](https://github.com/faif/python-patterns) - A collection of design patterns in Python. ### Interactive Interpreter @@ -673,12 +673,12 @@ _Compile software from source code._ _Libraries for generating project documentation._ -- [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. -- [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. - - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) -- [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. - [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. - [awesome-sphinxdoc](https://github.com/ygzgxyz/awesome-sphinxdoc) +- [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. + - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) +- [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. +- [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. **DevOps** From fb7383cd9b46ad623d28339bc52518eed183ac3c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 11:58:58 +0800 Subject: [PATCH 0734/1130] docs: point Build Tools readers to Package Management uv-build was deliberately not admitted to Build Tools since PEP 517 backends are a different job; that structure decision is deferred to the Python Toolchain sitting. Add a cross-reference so readers looking for Python packaging/build tooling (uv build backend, hatch) land on Package Management instead. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 180682b8..62076ecd 100644 --- a/README.md +++ b/README.md @@ -663,7 +663,7 @@ _Libraries for debugging code._ ### Build Tools -_Compile software from source code._ +_Compile software from source code. If you're looking for Python packaging/build tools, see [Package Management](#package-management)._ - [invoke](https://github.com/pyinvoke/invoke) - A tool for managing shell-oriented subprocesses and organizing executable Python code into CLI-invokable tasks. - [scons](https://github.com/SCons/scons) - A software construction tool. From a610fcec61598a0b1645118ac67bd11f2e312894 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:13:54 +0800 Subject: [PATCH 0735/1130] audit: sweep DevOps Tools to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit removed openstack (mirror-index repo aggregating OpenStack projects — nothing installable, not configuration management; its daily pushes are mirror automation) removed kubesdk (2,135 downloads/month, 105 stars, repo created 2025-10, entered via PR #3166 — nowhere near obvious choice or challenger; maintainer also declined adding the official docker and kubernetes clients, so no Containers use case) renamed cloudinit to cloud-init (repo name; not on PyPI), saltstack to salt (PyPI name; Broadcom-sponsored, kept as Second Tier challenger — PyPI 102K/month undercounts onedir and distro installs), sentry-python to sentry-sdk (PyPI name; 178.5M/month), borg to borgbackup (PyPI name; the bare borg package on PyPI is unrelated) reordered every subcategory by tier and downloads: Cloud Providers boto3 3.4B then awscli 264.3M; Configuration Management ansible 13.0M and no-signal cloud-init obvious choices, pyinfra 271K and salt challengers; Deployment fabric 12.7M then chalice 930K; Monitoring and Processes psutil 391.5M, sentry-sdk, supervisor 16.8M, challenger sh 12.0M; Other pre-commit 198.0M, borgbackup 76K, chaostoolkit 18K Co-Authored-By: Claude --- README.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 62076ecd..cd6b14d7 100644 --- a/README.md +++ b/README.md @@ -687,27 +687,25 @@ _Libraries for generating project documentation._ _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. + - [awscli](https://github.com/aws/aws-cli) - Universal Command Line Interface for 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://github.com/openstack/openstack) - Open source software for building private and public clouds. + - [cloud-init](https://github.com/canonical/cloud-init) - A multi-distribution package that handles early initialization of a cloud instance. - [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. + - [salt](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. + - [chalice](https://github.com/aws/chalice) - A Python serverless microframework for AWS. - 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. + - [sentry-sdk](https://github.com/getsentry/sentry-python) - Sentry SDK for Python. - [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX. + - [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. - 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. - - [kubesdk](https://github.com/puzl-cloud/kubesdk) - A modern async-first Kubernetes client and API model generator. - [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks. + - [borgbackup](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. ### Distributed Computing From b06104c97e3a23d9a6841ea2edc269511a176b51 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:14:25 +0800 Subject: [PATCH 0736/1130] feat: add azure-sdk-for-python to Cloud Providers DevOps audit adjudicated by maintainer 2026-08-16: obvious choice. Umbrella repo published as per-service packages, so no single download figure exists; azure-identity alone is 230.4M downloads/month. Repo-name entry used per the naming rule since there is no single pip name. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cd6b14d7..6950daa2 100644 --- a/README.md +++ b/README.md @@ -689,6 +689,7 @@ _Software and libraries for DevOps._ - Cloud Providers - [boto3](https://github.com/boto/boto3) - Python interface to Amazon Web Services. - [awscli](https://github.com/aws/aws-cli) - Universal Command Line Interface for Amazon Web Services. + - [azure-sdk-for-python](https://github.com/Azure/azure-sdk-for-python) - Microsoft Azure SDK for Python, published as per-service packages. - Configuration Management - [ansible](https://github.com/ansible/ansible) - A radically simple IT automation platform. - [cloud-init](https://github.com/canonical/cloud-init) - A multi-distribution package that handles early initialization of a cloud instance. From 77e7b62d938291d43af935cff63814dbdb753a2f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:15:10 +0800 Subject: [PATCH 0737/1130] feat: add google-cloud-python to Cloud Providers DevOps audit adjudicated by maintainer 2026-08-16: obvious-choice umbrella repo published as per-service packages, so no single download figure applies (google-cloud-storage alone is 253.1M downloads/month). Repo-name entry per the naming rule, placed after azure-sdk-for-python per no-signal alphabetical ordering within tier. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6950daa2..eb054c54 100644 --- a/README.md +++ b/README.md @@ -690,6 +690,7 @@ _Software and libraries for DevOps._ - [boto3](https://github.com/boto/boto3) - Python interface to Amazon Web Services. - [awscli](https://github.com/aws/aws-cli) - Universal Command Line Interface for Amazon Web Services. - [azure-sdk-for-python](https://github.com/Azure/azure-sdk-for-python) - Microsoft Azure SDK for Python, published as per-service packages. + - [google-cloud-python](https://github.com/googleapis/google-cloud-python) - Google Cloud client libraries for Python, published as per-service packages. - Configuration Management - [ansible](https://github.com/ansible/ansible) - A radically simple IT automation platform. - [cloud-init](https://github.com/canonical/cloud-init) - A multi-distribution package that handles early initialization of a cloud instance. From 4fc32225a92b5de382f71551988cc4e072fb7642 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:15:51 +0800 Subject: [PATCH 0738/1130] audit: sweep Distributed Computing to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed luigi (1,546,263 downloads/month, repo active with a push 2026-07) — the drop is editorial: Spotify itself moved to Flyte, 2026 orchestration comparisons no longer name luigi, and the Workflow Orchestration slots in Job Schedulers go to airflow, prefect, dagster. Flattened the sole Batch Processing subcategory — the label added nothing, the section is a single use case. Reordered by tier and downloads: obvious choices ray 62.6M, pyspark 48.3M, dask 30.0M; challengers joblib 216.9M raw (scikit-learn-dependency-inflated, judgment call — raw count still sorts it first in tier) and mpi4py 1.0M (HPC standard, specific audience not small). Co-Authored-By: Claude --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index eb054c54..72081921 100644 --- a/README.md +++ b/README.md @@ -713,13 +713,11 @@ _Software and libraries for DevOps._ _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. +- [ray](https://github.com/ray-project/ray/) - A system for parallel and distributed Python that unifies the machine learning ecosystem. +- [pyspark](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/) Python API. +- [dask](https://github.com/dask/dask) - A flexible parallel computing library for analytic computing. +- [joblib](https://github.com/joblib/joblib) - A set of tools to provide lightweight pipelining in Python. +- [mpi4py](https://github.com/mpi4py/mpi4py) - Python bindings for MPI. ### Task Queues From 83c723d61f45e796e6c120cbbdb3a0b22547e0bc Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:16:27 +0800 Subject: [PATCH 0739/1130] style: reorder Task Queues by tier and downloads DevOps audit adjudicated 2026-08-16, format-only (no removals). Obvious choices: celery 58.4M, rq 18.1M, dramatiq 1.6M (promoted -- 2026 sources call it the top celery alternative with closest feature parity). Challengers: huey 20.9M (demoted -- raw count highest but 2026 sources file it as the lightweight small-app option), taskiq 2.0M. flower sub-item rides celery. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72081921..bf7e8ed5 100644 --- a/README.md +++ b/README.md @@ -725,9 +725,9 @@ _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) +- [rq](https://github.com/rq/rq) - Simple job queues for Python. - [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. - [taskiq](https://github.com/taskiq-python/taskiq) - Distributed task queue with native asyncio support and pluggable brokers. ### Messaging From e5ce98c0f2ef5297b47fa5dd6246a17a0cf30296 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:16:59 +0800 Subject: [PATCH 0740/1130] feat: add confluent-kafka to Messaging DevOps audit adjudicated by maintainer 2026-08-16: 61.8M downloads/month via ClickPy, librdkafka-backed, 2026 sources call it the production Kafka default. kafka-python is unmaintained and was excluded. Placed first per downloads-descending obvious-choice tier; pika and paho-mqtt follow in subsequent commits. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bf7e8ed5..fc0f2a70 100644 --- a/README.md +++ b/README.md @@ -734,6 +734,7 @@ _Libraries for working with task queues._ _Libraries for working with message brokers and event streaming._ +- [confluent-kafka](https://github.com/confluentinc/confluent-kafka-python) - Confluent's Python client for Apache Kafka, built on librdkafka. - [faststream](https://github.com/ag2ai/faststream) - A framework for building asynchronous services over Apache Kafka, RabbitMQ, NATS, MQTT and Redis. ### Job Schedulers From 28b6d1398b4b597c5e5c3461dfe3e6a65738d540 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:17:28 +0800 Subject: [PATCH 0741/1130] feat: add pika to Messaging DevOps audit adjudicated by maintainer 2026-08-16: pika is the standard RabbitMQ/AMQP 0-9-1 client, 13.7M downloads/month via ClickPy. Ordered after confluent-kafka (61.8M/month) per downloads-descending tier. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fc0f2a70..d2e574fc 100644 --- a/README.md +++ b/README.md @@ -735,6 +735,7 @@ _Libraries for working with task queues._ _Libraries for working with message brokers and event streaming._ - [confluent-kafka](https://github.com/confluentinc/confluent-kafka-python) - Confluent's Python client for Apache Kafka, built on librdkafka. +- [pika](https://github.com/pika/pika) - Pure-Python RabbitMQ/AMQP 0-9-1 client library. - [faststream](https://github.com/ag2ai/faststream) - A framework for building asynchronous services over Apache Kafka, RabbitMQ, NATS, MQTT and Redis. ### Job Schedulers From 7fd495730d4952574af9dcbcf5b008b0dfc55573 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:17:59 +0800 Subject: [PATCH 0742/1130] feat: add paho-mqtt to Messaging Obvious choice per DevOps audit adjudicated by maintainer 2026-08-16: 9.1M downloads/month via ClickPy, Eclipse Foundation client described by 2026 sources as the bedrock of MQTT in Python. Ordered after pika (13.7M/month), before challenger faststream (1.5M/month). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d2e574fc..d4c766a7 100644 --- a/README.md +++ b/README.md @@ -736,6 +736,7 @@ _Libraries for working with message brokers and event streaming._ - [confluent-kafka](https://github.com/confluentinc/confluent-kafka-python) - Confluent's Python client for Apache Kafka, built on librdkafka. - [pika](https://github.com/pika/pika) - Pure-Python RabbitMQ/AMQP 0-9-1 client library. +- [paho-mqtt](https://github.com/eclipse-paho/paho.mqtt.python) - The Eclipse Paho MQTT client for Python. - [faststream](https://github.com/ag2ai/faststream) - A framework for building asynchronous services over Apache Kafka, RabbitMQ, NATS, MQTT and Redis. ### Job Schedulers From 03d9139c35b6ea94642f120437b1fcd20f954a78 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:18:40 +0800 Subject: [PATCH 0743/1130] audit: sweep Job Schedulers to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the flat section into two use cases — Task Scheduling (in-process scheduling) and Workflow Orchestration (data-pipeline orchestrators) — six entries were covering two distinct jobs. Removed SpiffWorkflow (59,736 downloads/month — BPMN engine, a different job than scheduling or orchestration, small audience; active repo, drop is a judgment call). Kept schedule by explicit maintainer override (9.4M downloads/month, 12.3K stars, but no commits since 2024-05 — past the 12-month activity line; the maintainer overrode the activity rule, schedule slots as challenger behind apscheduler). Renamed airflow to apache-airflow (PyPI name — the bare airflow package is a 38K/month shim). Ordering: Task Scheduling apscheduler 55.5M obvious choice then schedule 9.4M challenger; Workflow Orchestration obvious choices apache-airflow 20.9M, prefect 14.0M, dagster 10.2M. luigi's removal from Distributed Computing rode that section's commit; flytekit was proposed and declined. Co-Authored-By: Claude --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d4c766a7..1900fdcb 100644 --- a/README.md +++ b/README.md @@ -743,12 +743,13 @@ _Libraries for working with message brokers and event streaming._ _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. +- Task Scheduling + - [apscheduler](https://github.com/agronholm/apscheduler) - A light but powerful in-process task scheduler that lets you schedule functions. + - [schedule](https://github.com/dbader/schedule) - Python job scheduling for humans. +- Workflow Orchestration + - [apache-airflow](https://github.com/apache/airflow) - Airflow is a platform to programmatically author, schedule and monitor workflows. + - [prefect](https://github.com/PrefectHQ/prefect) - A modern workflow orchestration framework that makes it easy to build, schedule and monitor robust data pipelines. + - [dagster](https://github.com/dagster-io/dagster) - An orchestration platform for the development, production, and observation of data assets. ### Logging From 635a73580264676893bbb88a1ce33e92dea9ecdb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:19:12 +0800 Subject: [PATCH 0744/1130] audit: sweep Logging to the shortlist cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed logfmter (197,351 downloads/month, 107 stars) — logfmt formatting is a small audience and it is not a credible successor to any obvious choice; judgment call. Reordered by tier and downloads: obvious choices structlog 114.5M (2026 consensus production pick), loguru 82.5M (developer-experience pick), and stdlib logging last as a no-signal entry — kept because stdlib logging is itself the obvious choice for the use case, the stdlib rule's yes-case. Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1900fdcb..ee584e50 100644 --- a/README.md +++ b/README.md @@ -755,10 +755,9 @@ _Libraries for scheduling jobs._ _Libraries for generating and working with logs._ -- [logfmter](https://github.com/josheppinette/python-logfmter) - A standard library compatible logfmt formatter. -- [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. +- [loguru](https://github.com/Delgan/loguru) - Library which aims to bring enjoyable logging in Python. +- [logging](https://docs.python.org/3/library/logging.html) - (Python standard library) Logging facility for Python. ### Network Virtualization From b8cacb558c984f40c88d6646f130af41b34b0554 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:19:44 +0800 Subject: [PATCH 0745/1130] audit: sweep Network Virtualization to the shortlist cap Removed mininet: no commits since 2024-07, past the 12-month activity line; 2026 sources describe it as fading with forks carrying development; its 3.7K/month PyPI row is not its real distribution channel. Reordered remaining obvious choices by downloads: scapy 4.4M then napalm 199K. Section rename to Networking was offered and the maintainer left the current name standing. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ee584e50..412aa51a 100644 --- a/README.md +++ b/README.md @@ -763,9 +763,8 @@ _Libraries for generating and working with logs._ _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. +- [napalm](https://github.com/napalm-automation/napalm) - Cross-vendor API to manipulate network devices. **CLI & GUI** From 3290ce586a69e2e8164ad242e81457f45f5dd217 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:26:20 +0800 Subject: [PATCH 0746/1130] style: merge Security Linters into Linters and Formatters bandit (29.5M downloads/month) sorts last in the second tier behind pylint 65.2M and flake8 56.7M. The merged use case now holds 6 entries, past the hard cap of 5, by explicit maintainer override -- the list's first cap override, accepted knowingly. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 412aa51a..6329333d 100644 --- a/README.md +++ b/README.md @@ -595,10 +595,9 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [pylint](https://github.com/pylint-dev/pylint) - A fully customizable source code analyzer. - [flake8](https://github.com/PyCQA/flake8) - A wrapper around `pycodestyle`, `pyflakes` and McCabe. - [awesome-flake8-extensions](https://github.com/DmytroLitvinov/awesome-flake8-extensions) + - [bandit](https://github.com/PyCQA/bandit) - A tool designed to find common security issues in Python code. - Refactoring - [rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. -- Security Linters - - [bandit](https://github.com/PyCQA/bandit) - A tool designed to find common security issues in Python code. - Type Checkers - [awesome-python-typing](https://github.com/typeddjango/awesome-python-typing) - [mypy](https://github.com/python/mypy) - Check variable types during compile time. - [typeshed](https://github.com/python/typeshed) From 35c68984e879a40e0480099c050805b82a5ec1f7 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:55:38 +0800 Subject: [PATCH 0747/1130] audit: sweep CLI Development, mint TUI Frameworks, drop cement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: textual and asciimatics move from Terminal Rendering to a minted TUI Frameworks subcategory — full-screen terminal UIs are a distinct job from rendering output. urwid joins them in the GUI Development commit, which dissolves that section's Terminal subcategory. Renames to canonical PyPI names: python-prompt-toolkit → prompt_toolkit, python-fire → fire (the PyPI package named python-fire is an unrelated name-squatter; Google's project installs as fire). Tiers: click (1.12B/mo), typer (366M/mo), argparse (stdlib) obvious choices; prompt_toolkit (246.8M/mo), fire (22M/mo) challengers. tqdm (670.1M/mo), rich (626.7M/mo) obvious choices; colorama (425.6M/mo, largely pip-transitive traffic, superseded by rich for new code) demoted to challenger (Second Tier) beside alive-progress (3.7M/mo). textual (535.4M/mo) obvious choice of TUI Frameworks, asciimatics (177.5K/mo) challenger. Removed: - cement — 565K downloads/month but 1.3K stars after 17 years (CI-shaped count); alive yet no longer anyone's unprompted answer against click, typer, fire, or prompt_toolkit. Judgment call. Co-Authored-By: Claude --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6329333d..fd305467 100644 --- a/README.md +++ b/README.md @@ -772,19 +772,19 @@ _Tools and libraries for Virtual Networking and SDN (Software Defined Networking _Libraries for building command-line applications._ - CLI 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. + - [argparse](https://docs.python.org/3/library/argparse.html) - (Python standard library) Command-line option and argument parsing. + - [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) - A library for building powerful interactive command lines. + - [fire](https://github.com/google/python-fire) - A library for creating command line interfaces from absolutely any Python object. - 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. + - [rich](https://github.com/Textualize/rich) - Python library for rich text and beautiful formatting in the terminal. Also provides a great `RichHandler` log handler. + - [colorama](https://github.com/tartley/colorama) - Cross-platform colored terminal text. + - [alive-progress](https://github.com/rsalmei/alive-progress) - A new kind of Progress Bar, with real-time throughput, eta and very cool animations. +- TUI Frameworks + - [textual](https://github.com/Textualize/textual) - A framework for building interactive user interfaces that run in the terminal and the browser. + - [asciimatics](https://github.com/peterbrittain/asciimatics) - A package to create full-screen text UIs (from interactive forms to ASCII animations). ### CLI Tools From 1a8e14c177563cf6d213ff4d74978c11fa3273e8 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:56:03 +0800 Subject: [PATCH 0748/1130] audit: sweep CLI Tools, dissolve Productivity Tools, drop thefuck, kaydet, doitlive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the Productivity Tools grab-bag dissolves into use cases — Project Scaffolding (cookiecutter 10.1M/mo obvious choice, copier 2.5M/mo challenger), Shells (xonsh 419.5K/mo), Downloaders (yt-dlp 17.7M/mo), Terminal Workflow (tmuxp 129.4K/mo). CLI Enhancements renames to Database CLIs (pgcli 636.6K/mo, mycli 124.9K/mo, litecli 74.2K/mo obvious choices; iredis 8.3K/mo challenger); HTTP Clients minted for httpie. httpie kept by explicit maintainer override of the 12-month activity line (repo dormant since 2024-12, last release 2024-11; 1.04M downloads/month) — second use of the schedule precedent. Removed: - thefuck — dormant since 2024-07, past the 12-month activity line; 7K downloads/month (most installs come from brew, not pip). - kaydet — 48 stars, 5.6K downloads/month; a personal diary tool nowhere near obvious-choice or challenger territory. - doitlive — 651 downloads/month against 3.6K stars; alive but effectively unused. Judgment call. Co-Authored-By: Claude --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index fd305467..8ac0e0bf 100644 --- a/README.md +++ b/README.md @@ -788,23 +788,24 @@ _Libraries for building command-line applications._ ### CLI Tools -_Useful CLI-based tools for productivity._ +_Useful CLI-based tools._ -- Productivity Tools +- Database CLIs + - [pgcli](https://github.com/dbcli/pgcli) - PostgreSQL CLI with autocompletion and syntax highlighting. + - [mycli](https://github.com/dbcli/mycli) - MySQL CLI with autocompletion and syntax highlighting. + - [litecli](https://github.com/dbcli/litecli) - SQLite CLI with autocompletion and syntax highlighting. + - [iredis](https://github.com/laixintao/iredis) - Redis CLI with autocompletion and syntax highlighting. +- Downloaders + - [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. +- HTTP Clients + - [httpie](https://github.com/httpie/cli) - A command line HTTP client, a user-friendly cURL replacement. +- Project Scaffolding - [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. - - [kaydet](https://github.com/miratcan/kaydet) - Queryable personal database and terminal diary with SQLite search, tags, metadata, and AI integration via MCP. - - [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. +- Shells - [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. +- Terminal Workflow + - [tmuxp](https://github.com/tmux-python/tmuxp) - A [tmux](https://github.com/tmux/tmux) session manager. ### GUI Development From 9bbe1cf6d0b55f37a11e75113c861bed5999ccbb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 12:56:44 +0800 Subject: [PATCH 0749/1130] audit: sweep GUI Development, split Desktop into Qt and Tkinter, drop enaml, buridan/ui, curses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the 12-entry Desktop use case splits — Qt (PySide6 3.9M/mo, PyQt6 2.7M/mo, co-obvious pair differing in licensing) and Tkinter (customtkinter 1.13M/mo, tkinter stdlib obvious choices; tkdesigner challenger, kept by maintainer decision) mint as subcategories; residual Desktop lands at cap with pygobject (607.6K/mo), wxPython (354.8K/mo), kivy (288.8K/mo) obvious choices and dearpygui (193.5K/mo), toga (12.9K/mo, BeeWare trajectory) challengers. Renames to canonical PyPI names: PyQt → PyQt6, pyside → PySide6 (the PyPI package named pyside is the dead Qt4-era PySide 1). Re-homes: urwid (11.3M/mo) moves to CLI Development > TUI Frameworks as a challenger and pyglet (2.9M/mo, windowing and multimedia for games) moves to Game Development, dissolving this section's Terminal subcategory. Web-based keeps pywebview (2.19M/mo), nicegui (1.2M/mo), flet (444.1K/mo) as obvious choices; gooey stays sole Wrappers entry. Removed: - enaml — 14.1K downloads/month, 1.6K stars after 13 years; a declarative Qt DSL nobody names unprompted. Judgment call. - buridan/ui — 248 stars, no PyPI package; a component-snippet collection for Reflex, not a GUI framework. Fails admission. - curses — standard-library rule: stdlib holds a slot only where it is itself the obvious choice, and for building TUIs that is textual. Co-Authored-By: Claude --- README.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 8ac0e0bf..8d4be269 100644 --- a/README.md +++ b/README.md @@ -784,6 +784,7 @@ _Libraries for building command-line applications._ - [alive-progress](https://github.com/rsalmei/alive-progress) - A new kind of Progress Bar, with real-time throughput, eta and very cool animations. - TUI Frameworks - [textual](https://github.com/Textualize/textual) - A framework for building interactive user interfaces that run in the terminal and the browser. + - [urwid](https://github.com/urwid/urwid) - A library for creating terminal GUI applications with strong support for widgets, events, rich colors, etc. - [asciimatics](https://github.com/peterbrittain/asciimatics) - A package to create full-screen text UIs (from interactive forms to ASCII animations). ### CLI Tools @@ -812,26 +813,22 @@ _Useful CLI-based tools._ _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. - - [tkdesigner](https://github.com/ParthJadhav/Tkinter-Designer) - Generates Tkinter interfaces from Figma designs using the Figma API. - - [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. + - [kivy](https://github.com/kivy/kivy) - A library for creating NUI applications, running on Windows, Linux, Mac OS X, Android and iOS. + - [dearpygui](https://github.com/hoffstadt/DearPyGui) - A Simple GPU accelerated Python GUI framework + - [toga](https://github.com/beeware/toga) - A Python native, OS native GUI toolkit. +- Qt + - [PySide6](https://github.com/pyside/pyside-setup) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt6 but it's the official binding with different licensing. + - [PyQt6](https://www.riverbankcomputing.com/static/Docs/PyQt6/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. +- Tkinter + - [customtkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter. + - [tkinter](https://docs.python.org/3/library/tkinter.html) - (Python standard library) The standard Python interface to the Tcl/Tk GUI toolkit. + - [tkdesigner](https://github.com/ParthJadhav/Tkinter-Designer) - Generates Tkinter interfaces from Figma designs using the Figma API. - Web-based - - [buridan/ui](https://github.com/LineIndent/ui) - Composable, themeable components designed for Reflex. Extend, override, and ship without fighting the framework. Open source. - - [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) - (Python standard library) The built-in wrapper for [ncurses](https://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. + - [nicegui](https://github.com/zauberzeug/nicegui) - An easy-to-use, Python-based UI framework, which shows up in your web browser. + - [flet](https://github.com/flet-dev/flet) - Cross-platform GUI framework for building modern apps in pure Python. - Wrappers - [gooey](https://github.com/chriskiehl/Gooey) - Turn command line programs into a full GUI application with one line. @@ -961,6 +958,7 @@ _Awesome game development libraries._ - [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. +- [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. - [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. From 41a1b4b0ae5ae80431848a3f84c46815e2720a44 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:13:57 +0800 Subject: [PATCH 0750/1130] audit: sweep Image Processing, split into use cases, drop pymatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the flat section splits into Barcodes and QR Codes (qrcode 30.1M/mo, python-barcode 3.4M/mo, both obvious choices — generating codes is a distinct job from manipulating images), General (pillow 552M/mo, scikit-image 28.5M/mo obvious choices; wand 1.4M/mo, pyvips 808.2K/mo challengers), and Image Serving (thumbor sole obvious choice — a deployed service, so its 22K/mo pip count under-measures use). Rename to canonical PyPI name: python-qrcode → qrcode. Removed: - pymatting — 3.3M downloads/month, but the traffic is transitive: rembg declares pymatting as a hard dependency (verified in rembg's PyPI metadata). A single-task alpha-matting library, 1.9K stars, quiet since 2026-04; not an obvious choice for image processing. Co-Authored-By: Claude --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8d4be269..f1212e44 100644 --- a/README.md +++ b/README.md @@ -922,14 +922,16 @@ _Libraries for file manipulation._ _Libraries for manipulating images._ -- [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](https://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](https://www.imagemagick.org/script/magick-wand.php), C API for ImageMagick. +- Barcodes and QR Codes + - [qrcode](https://github.com/lincolnloop/python-qrcode) - A pure Python QR Code generator. + - [python-barcode](https://github.com/WhyNotHugo/python-barcode) - Create barcodes in Python with no extra dependencies. +- General + - [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](https://www.pythonware.com/products/pil/) fork. + - [scikit-image](https://github.com/scikit-image/scikit-image) - A Python library for (scientific) image processing. + - [wand](https://github.com/emcconville/wand) - Python bindings for [MagickWand](https://www.imagemagick.org/script/magick-wand.php), C API for ImageMagick. + - [pyvips](https://github.com/libvips/pyvips) - A fast image processing library with low memory needs. +- Image Serving + - [thumbor](https://github.com/thumbor/thumbor) - A smart imaging service. It enables on-demand crop, re-sizing and flipping of images. ### Audio & Video Processing From 9970486a2aa6dce24ceb6ba300f7dd61599946f0 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:14:13 +0800 Subject: [PATCH 0751/1130] feat: add rembg to Image Processing > General MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-requested during the Media audit, replacing dropped pymatting with the tool whose traffic pymatting was carrying: rembg is the obvious choice for background removal — 4.1M downloads/month direct (pepy), 24.3K stars, pushed 2026-08, active release train. Takes the open third obvious-choice slot in General (no displacement needed), ordering after pillow and scikit-image. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f1212e44..089a8be6 100644 --- a/README.md +++ b/README.md @@ -928,6 +928,7 @@ _Libraries for manipulating images._ - General - [pillow](https://github.com/python-pillow/Pillow) - Pillow is the friendly [PIL](https://www.pythonware.com/products/pil/) fork. - [scikit-image](https://github.com/scikit-image/scikit-image) - A Python library for (scientific) image processing. + - [rembg](https://github.com/danielgatis/rembg) - A tool to remove image backgrounds. - [wand](https://github.com/emcconville/wand) - Python bindings for [MagickWand](https://www.imagemagick.org/script/magick-wand.php), C API for ImageMagick. - [pyvips](https://github.com/libvips/pyvips) - A fast image processing library with low memory needs. - Image Serving From 8f9c3884bf944146cb122b41af3302d4129e84d1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:14:37 +0800 Subject: [PATCH 0752/1130] audit: sweep Audio & Video Processing, re-home TTS to Speech, drop birdnet, matchering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: gtts and KittenTTS re-home to AI and Agents > Speech — TTS belongs with the speech models, not audio processing. gtts (3M/mo, a 2014 service wrapper, quiet since 2026-04) lands as Second Tier; kittentts (renamed to its canonical PyPI name, 15.3K stars within a year of creation, pip count 3.9K/mo under-measures model adoption) as challenger. Speech is now at exact cap (5/5) — future additions there require displacement. Audio keeps pydub (21.4M/mo) and librosa (11.7M/mo) as obvious choices. Video unchanged: moviepy (7M/mo) obvious choice, vidgear (25.3K/mo) challenger. Metadata reorders to tiers: mutagen (8.2M/mo) obvious choice; tinytag (5.2M/mo), beets (67.4K/mo, kept as challenger rather than minting a sole-entry subcategory — judgment call). Removed: - birdnet — 7.1K downloads/month, 1.7K stars; an applied bioacoustics tool with a genuinely small audience, not an audio-processing choice. Judgment call. - matchering — 16.1K downloads/month, 2.6K stars; a single-task automated-mastering tool with a small audience, no trajectory. Judgment call. Co-Authored-By: Claude --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 089a8be6..051a7ce8 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,8 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - [openai-whisper](https://github.com/openai/whisper) - A general-purpose automatic speech recognition model trained on 680k hours of multilingual and multitask supervised data. - [funasr](https://github.com/modelscope/FunASR) - Industrial-grade speech recognition toolkit with 170x realtime speed, 50+ languages, speaker diarization, and emotion detection. - [vibevoice](https://github.com/microsoft/VibeVoice) - A family of open-source voice AI models from Microsoft for text-to-speech and long-form speech recognition. + - [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. + - [kittentts](https://github.com/KittenML/KittenTTS) - Lightweight ONNX text-to-speech library with small CPU-friendly models. ### Deep Learning @@ -939,19 +941,15 @@ _Libraries for manipulating images._ _Libraries for manipulating audio, video, and their metadata._ - Audio - - [birdnet](https://github.com/birdnet-team/BirdNET-Analyzer) - Deep learning framework for acoustic species detection; identifies bird species from audio recordings using TensorFlow. - - [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. - - [KittenTTS](https://github.com/KittenML/KittenTTS) - Lightweight ONNX text-to-speech library with small CPU-friendly models. - - [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. + - [librosa](https://github.com/librosa/librosa) - Python library for audio and music analysis. - 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/tinytag/tinytag) - A library for reading music meta data of MP3, OGG, FLAC and Wave files. + - [beets](https://github.com/beetbox/beets) - A music library manager and [MusicBrainz](https://musicbrainz.org/) tagger. ### Game Development From 3f391b34ba5a2e29dfea0a1fdd1f0b95d65cbb19 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:14:54 +0800 Subject: [PATCH 0753/1130] audit: sweep Game Development, split into use cases, drop py-sdl2, pyopengl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the flat section splits into 3D Engines (panda3d 189.3K/mo, sole obvious choice), Game Frameworks (pygame 3.07M/mo and pyglet 2.9M/mo obvious choices; arcade 85.3K/mo challenger — pygame-ce joins as a challenger in its own addition commit), and Visual Novels (renpy sole obvious choice — its 4/mo pip count is meaningless since Ren'Py ships as its own SDK; ecosystem verified: 8,000+ published works, release 8.5.3 in 2026-05). Removed: - py-sdl2 — 71.1K downloads/month (as PySDL2), 344 stars; low-level SDL bindings whose job pygame-ce and pyglet cover for this list's audience. Judgment call. - pyopengl — 5.3M downloads/month, likely transitive scientific-visualization traffic (judgment call, mechanism unverified), 415 stars; raw OpenGL bindings are not a game-development obvious choice. Co-Authored-By: Claude --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 051a7ce8..729f8d69 100644 --- a/README.md +++ b/README.md @@ -955,13 +955,14 @@ _Libraries for manipulating audio, video, and their metadata._ _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. -- [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. -- [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. +- 3D Engines + - [panda3d](https://github.com/panda3d/panda3d) - 3D game engine developed by Disney. +- Game Frameworks + - [pygame](https://github.com/pygame/pygame) - Pygame is a set of Python modules designed for writing games. + - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. + - [arcade](https://github.com/pythonarcade/arcade) - Arcade is a modern Python framework for crafting games with compelling graphics and sound. +- Visual Novels + - [renpy](https://github.com/renpy/renpy) - A Visual Novel engine. **Python Language** From 8a3771e8955ff9d349e3c3fd594dd02b4ad62e7c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:15:08 +0800 Subject: [PATCH 0754/1130] feat: add pygame-ce to Game Development > Game Frameworks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Challenger to pygame: the community fork where long-time contributors now develop, a drop-in replacement (same import name), and community guidance for new projects favors it — adoption-trajectory evidence verified by web search. 642.5K downloads/month (pepy), 1.6K stars, created 2023, pushed 2026-08. Upstream pygame itself has been quiet since 2025-11. Slots into the open challenger position ahead of arcade (85.3K/mo). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 729f8d69..abc9dcbc 100644 --- a/README.md +++ b/README.md @@ -960,6 +960,7 @@ _Awesome game development libraries._ - Game Frameworks - [pygame](https://github.com/pygame/pygame) - Pygame is a set of Python modules designed for writing games. - [pyglet](https://github.com/pyglet/pyglet) - A cross-platform windowing and multimedia library for Python. + - [pygame-ce](https://github.com/pygame-community/pygame-ce) - An actively developed drop-in replacement with new features and performance improvements ([pygame](https://github.com/pygame/pygame) fork). - [arcade](https://github.com/pythonarcade/arcade) - Arcade is a modern Python framework for crafting games with compelling graphics and sound. - Visual Novels - [renpy](https://github.com/renpy/renpy) - A Visual Novel engine. From 9fa72c1ee63dc16bc5a205302b1a8e9b8c5c4a2a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:29:23 +0800 Subject: [PATCH 0755/1130] audit: sweep Implementations, drop ironpython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: cpython, micropython, pypy obvious choices (all no-signal — distributed outside pip — so alphabetical); cython (130.3M/mo, kept here by judgment as a Python-superset compiler with no better home) and pyodide (no pip signal; distributed via CDN and npm) challengers. Removed: - ironpython — latest release v3.4.2 (2024-12) still implements Python 3.4-era language, a decade behind CPython (verified from the release feed; the repo itself is active, so this is not an activity-line drop). 2.8K stars. Not an obvious choice for any current job. Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index abc9dcbc..da17f367 100644 --- a/README.md +++ b/README.md @@ -972,11 +972,10 @@ _Awesome game development libraries._ _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. -- [pyodide](https://github.com/pyodide/pyodide) - Python distribution for the browser and Node.js based on WebAssembly. - [pypy](https://github.com/pypy/pypy) - A very fast and compliant implementation of the Python language. +- [cython](https://github.com/cython/cython) - Optimizing Static Compiler for Python. +- [pyodide](https://github.com/pyodide/pyodide) - Python distribution for the browser and Node.js based on WebAssembly. ### Built-in Classes Enhancement From 6af6158287528e760e07f643174af02840a13d2b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:29:44 +0800 Subject: [PATCH 0756/1130] audit: sweep Functional Programming, drop coconut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: more-itertools (339.9M/mo), toolz (62.9M/mo), functools (stdlib, the first stop for FP utilities; no-signal, sorts last) obvious choices; funcy (7.3M/mo) and returns (1.37M/mo, typed monads/Result trajectory) challengers. Removed: - coconut — 14.4K downloads/month, 4.3K stars; a compiled language variant rather than a library, and nobody's unprompted answer for functional Python. Judgment call. Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da17f367..3c8dfd81 100644 --- a/README.md +++ b/README.md @@ -990,12 +990,11 @@ _Libraries for enhancing Python built-in classes._ _Functional Programming with Python._ -- [coconut](https://github.com/evhub/coconut) - A variant of Python built for simple, elegant, Pythonic functional programming. +- [more-itertools](https://github.com/more-itertools/more-itertools) - More routines for operating on iterables, beyond `itertools`. +- [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. - [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/more-itertools/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 From 19875cf356ce0a5e85d0c3c2087bf7bbf54cafdf Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:30:08 +0800 Subject: [PATCH 0757/1130] audit: sweep Asynchronous Programming, split into Async I/O and Parallelism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the flat section splits — async I/O and process/pool parallelism are distinct jobs. Parallelism holds concurrent.futures and multiprocessing (both standard library, both obvious choices, no-signal so alphabetical). Async I/O tiers: anyio (1.07B/mo — heavily transitive via httpx and Starlette, but independently the production-recommended structured- concurrency layer), uvloop (267.7M/mo), asyncio (stdlib, no-signal, sorts last; awesome-asyncio sub-item rides along) obvious choices; trio (53.8M/mo), gevent (49.6M/mo), twisted (15.1M/mo) challengers — gevent and twisted kept as Second Tier by explicit maintainer decision, and the use case holds 6 entries by explicit maintainer cap override (the list's second, after Linters and Formatters; same 3 obvious choices + 3 challengers shape). No removals. Co-Authored-By: Claude --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3c8dfd81..737dc2af 100644 --- a/README.md +++ b/README.md @@ -1000,15 +1000,17 @@ _Functional Programming with Python._ _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. +- Async I/O + - [anyio](https://github.com/agronholm/anyio) - A high-level async concurrency and networking framework that works on top of asyncio or trio. + - [uvloop](https://github.com/MagicStack/uvloop) - Ultra fast asyncio event loop. + - [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) + - [trio](https://github.com/python-trio/trio) - A friendly library for async concurrency and I/O. + - [gevent](https://github.com/gevent/gevent) - A coroutine-based Python networking library that uses [greenlet](https://github.com/python-greenlet/greenlet). + - [twisted](https://github.com/twisted/twisted) - An event-driven networking engine. +- Parallelism + - [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html) - (Python standard library) A high-level interface for asynchronously executing callables. + - [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) - (Python standard library) Process-based parallelism. ### Date and Time From 16077fc0e10ae23cbc4f6a98e0e7621d7e40d978 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:30:35 +0800 Subject: [PATCH 0758/1130] style: re-tier Built-in Classes Enhancement and Date and Time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No removals in either section — tier ordering and canonical-name renames only. Built-in Classes Enhancement: attrs (939M/mo), bidict (30.5M/mo) obvious choices; uuid-utils (81.2M/mo, challenger on judgment — the count is transitive-profile against 364 stars) and python-box (renamed from box, its canonical PyPI name; 6.9M/mo) challengers. Date and Time: python-dateutil (renamed from dateutil, its canonical PyPI name; 1.21B/mo), dateparser (46.4M/mo), zoneinfo (stdlib, the timezone answer since 3.9; no-signal, sorts last) obvious choices; pendulum (71.6M/mo, revived 2026-07 after a long slump — Second Tier-shaped) challenger. Co-Authored-By: Claude --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 737dc2af..c2d4b44c 100644 --- a/README.md +++ b/README.md @@ -983,8 +983,8 @@ _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. - [uuid-utils](https://github.com/aminalaee/uuid-utils) - A fast, Rust-backed drop-in replacement for Python's built-in `uuid` module, supporting RFC 9562 (UUIDv6, UUIDv7, and UUIDv8). +- [python-box](https://github.com/cdgriffith/Box) - Python dictionaries with advanced dot notation access. ### Functional Programming @@ -1016,10 +1016,10 @@ _Libraries for asynchronous, concurrent and parallel execution. Also see [awesom _Libraries for working with dates and times._ +- [python-dateutil](https://github.com/dateutil/dateutil) - Extensions to the standard Python [datetime](https://docs.python.org/3/library/datetime.html) module. - [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. +- [pendulum](https://github.com/python-pendulum/pendulum) - Python datetimes made easy. **Python Toolchain** From c00094add64eb157d5991d443742f4e90d726b16 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:30:53 +0800 Subject: [PATCH 0759/1130] feat: add whenever to Date and Time Challenger with verified adoption trajectory: 9M downloads/month (pepy), 2.4K stars, pushed 2026-08. Rust-backed and type-safe, with published benchmarks 10-100x faster than Pendulum and Arrow and a DST-correctness design aimed at the incumbents' known pitfalls. Slots into the second challenger position after pendulum. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c2d4b44c..e3d4795c 100644 --- a/README.md +++ b/README.md @@ -1020,6 +1020,7 @@ _Libraries for working with dates and times._ - [dateparser](https://github.com/scrapinghub/dateparser) - A Python parser for human-readable dates in dozens of languages. - [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. - [pendulum](https://github.com/python-pendulum/pendulum) - Python datetimes made easy. +- [whenever](https://github.com/ariebovenberg/whenever) - A modern datetime library, type-safe and DST-safe, backed by Rust. **Python Toolchain** From 15c4064ee1b82eea482766f2644204ed9083b9aa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:36:30 +0800 Subject: [PATCH 0760/1130] docs: amend Entry Ordering so standard-library modules sort first in tier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer rule change: built-ins lead their tier instead of sorting last with the other no-signal entries — a stdlib module that earns a slot is the default answer, so it reads first. Several stdlib modules in one tier stay alphabetical. Non-stdlib no-signal entries (agent skill packs, projects distributed outside PyPI) still sort last in tier. README reorder of already-audited sections follows in a style commit; unaudited sections pick the rule up at their own audit. Co-Authored-By: Claude --- CONTEXT.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index c6deb3cc..dd2b6dd5 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -44,7 +44,7 @@ The only admission path into a full Use Case: the PR names the entry it replaces _Avoid_: One-in-one-out (informal alias) **Challenger**: -An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. +An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (standard-library modules first in tier, other no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept removed entirely) **Second Tier**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c16c0a3b..bcdfd994 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,7 +73,7 @@ Use the **PyPI package name** as the display name so developers can copy it dire ### Entry Ordering -Within a use case, the obvious choices are listed first, ordered by PyPI downloads per month from high to low; challengers follow, in the same order. Entries without a download signal (standard-library modules, agent skill packs) sort last within their tier, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. +Within a use case, the obvious choices are listed first, ordered by PyPI downloads per month from high to low; challengers follow, in the same order. Standard-library modules sort first within their tier — built-ins lead — alphabetically when there are several. Other entries without a download signal (agent skill packs, projects distributed outside PyPI) sort last within their tier, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. ## Changing the Structure From 05a9dd4ed573533a07d410a5b5181341a96f53c5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:37:14 +0800 Subject: [PATCH 0761/1130] style: move standard-library entries to the top of their tiers Applies the amended Entry Ordering rule (stdlib first in tier) retroactively to all audited sections: sqlite3 (SQLite), mock (Mock), logging (Logging), tomllib (Data Formats), argparse (CLI Development), tkinter (Tkinter), functools (Functional Programming), asyncio with its awesome-asyncio sub-item (Async I/O), zoneinfo (Date and Time). Parallelism (concurrent.futures, multiprocessing) is an all-stdlib tier and stays alphabetical. Unaudited sections (difflib, mimetypes, pathlib, configparser) pick the rule up at their own audit. No entry additions or removals. Co-Authored-By: Claude --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e3d4795c..2c70b99b 100644 --- a/README.md +++ b/README.md @@ -405,8 +405,8 @@ _Libraries for connecting and operating databases._ - [psycopg](https://github.com/psycopg/psycopg) - The most popular PostgreSQL adapter for Python. - [asyncpg](https://github.com/MagicStack/asyncpg) - A fast PostgreSQL Database Client Library for Python/asyncio. - 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. + - [sqlite-utils](https://github.com/simonw/sqlite-utils) - Python CLI utility and library for manipulating SQLite databases. - ClickHouse - [clickhouse-connect](https://github.com/ClickHouse/clickhouse-connect) - The official ClickHouse client, with SQLAlchemy and Superset connectors. - [clickhouse-driver](https://github.com/mymarilyn/clickhouse-driver) - Python driver with native interface for ClickHouse. @@ -629,9 +629,9 @@ _Libraries for testing codebases and generating test data. Also see [awesome-pyt - API Testing - [schemathesis](https://github.com/schemathesis/schemathesis) - A tool for automatic property-based testing of web applications built with Open API / Swagger specifications. - Mock + - [mock](https://docs.python.org/3/library/unittest.mock.html) - (Python standard library) A mocking and patching library. - [responses](https://github.com/getsentry/responses) - A utility library for mocking out the requests Python library. - [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. - [vcrpy](https://github.com/kevin1024/vcrpy) - Record and replay HTTP interactions on your tests. - [respx](https://github.com/lundberg/respx) - Mock HTTPX with awesome request patterns and response side effects. - Object Factories @@ -756,9 +756,9 @@ _Libraries for scheduling jobs._ _Libraries for generating and working with logs._ +- [logging](https://docs.python.org/3/library/logging.html) - (Python standard library) Logging facility for Python. - [structlog](https://github.com/hynek/structlog) - Structured logging made easy. - [loguru](https://github.com/Delgan/loguru) - Library which aims to bring enjoyable logging in Python. -- [logging](https://docs.python.org/3/library/logging.html) - (Python standard library) Logging facility for Python. ### Network Virtualization @@ -774,9 +774,9 @@ _Tools and libraries for Virtual Networking and SDN (Software Defined Networking _Libraries for building command-line applications._ - CLI Development + - [argparse](https://docs.python.org/3/library/argparse.html) - (Python standard library) Command-line option and argument parsing. - [click](https://github.com/pallets/click/) - A package for creating beautiful command line interfaces in a composable way. - [typer](https://github.com/fastapi/typer) - Modern CLI framework that uses Python type hints. Built on Click and Pydantic. - - [argparse](https://docs.python.org/3/library/argparse.html) - (Python standard library) Command-line option and argument parsing. - [prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) - A library for building powerful interactive command lines. - [fire](https://github.com/google/python-fire) - A library for creating command line interfaces from absolutely any Python object. - Terminal Rendering @@ -824,8 +824,8 @@ _Libraries for working with graphical user interface applications._ - [PySide6](https://github.com/pyside/pyside-setup) - Qt for Python offers the official Python bindings for [Qt](https://www.qt.io/), same as PyQt6 but it's the official binding with different licensing. - [PyQt6](https://www.riverbankcomputing.com/static/Docs/PyQt6/) - Python bindings for the [Qt](https://www.qt.io/) cross-platform application and UI framework. - Tkinter - - [customtkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter. - [tkinter](https://docs.python.org/3/library/tkinter.html) - (Python standard library) The standard Python interface to the Tcl/Tk GUI toolkit. + - [customtkinter](https://github.com/tomschimansky/customtkinter) - A modern and customizable python UI-library based on Tkinter. - [tkdesigner](https://github.com/ParthJadhav/Tkinter-Designer) - Generates Tkinter interfaces from Figma designs using the Figma API. - Web-based - [pywebview](https://github.com/r0x0r/pywebview/) - A lightweight cross-platform native wrapper around a webview component. @@ -905,8 +905,8 @@ _Libraries for parsing and manipulating specific text formats._ - [markdown](https://github.com/Python-Markdown/markdown) - A Python implementation of John Gruber’s Markdown. - [mistune](https://github.com/lepture/mistune) - Fastest and full featured pure Python parsers of Markdown. - Data Formats - - [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. + - [pyyaml](https://github.com/yaml/pyyaml) - YAML implementations for Python. ### File Manipulation @@ -990,9 +990,9 @@ _Libraries for enhancing Python built-in classes._ _Functional Programming with Python._ +- [functools](https://docs.python.org/3/library/functools.html) - (Python standard library) Higher-order functions and operations on callable objects. - [more-itertools](https://github.com/more-itertools/more-itertools) - More routines for operating on iterables, beyond `itertools`. - [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. -- [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. - [returns](https://github.com/dry-python/returns) - A set of type-safe monads, transformers, and composition utilities. @@ -1001,10 +1001,10 @@ _Functional Programming with Python._ _Libraries for asynchronous, concurrent and parallel execution. Also see [awesome-asyncio](https://github.com/timofurrer/awesome-asyncio)._ - Async I/O - - [anyio](https://github.com/agronholm/anyio) - A high-level async concurrency and networking framework that works on top of asyncio or trio. - - [uvloop](https://github.com/MagicStack/uvloop) - Ultra fast asyncio event loop. - [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) + - [anyio](https://github.com/agronholm/anyio) - A high-level async concurrency and networking framework that works on top of asyncio or trio. + - [uvloop](https://github.com/MagicStack/uvloop) - Ultra fast asyncio event loop. - [trio](https://github.com/python-trio/trio) - A friendly library for async concurrency and I/O. - [gevent](https://github.com/gevent/gevent) - A coroutine-based Python networking library that uses [greenlet](https://github.com/python-greenlet/greenlet). - [twisted](https://github.com/twisted/twisted) - An event-driven networking engine. @@ -1016,9 +1016,9 @@ _Libraries for asynchronous, concurrent and parallel execution. Also see [awesom _Libraries for working with dates and times._ +- [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-dateutil](https://github.com/dateutil/dateutil) - Extensions to the standard Python [datetime](https://docs.python.org/3/library/datetime.html) module. - [dateparser](https://github.com/scrapinghub/dateparser) - A Python parser for human-readable dates in dozens of languages. -- [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. - [pendulum](https://github.com/python-pendulum/pendulum) - Python datetimes made easy. - [whenever](https://github.com/ariebovenberg/whenever) - A modern datetime library, type-safe and DST-safe, backed by Rust. From 5f6f1f020e3104f3f45ed42ec7a66e4e5b514af3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:38:22 +0800 Subject: [PATCH 0762/1130] docs: clarify that standard-library modules lead the use case outright MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer correction to the previous amendment: stdlib sorts at the top of the whole use case, not merely first within its tier. No README movement results — the stdlib admission rule (a standard-library module is listed only where it is itself the obvious choice) means every stdlib entry already sits in the first tier, so tier-top and use-case-top coincide; the rule text now states the intent directly. Co-Authored-By: Claude --- CONTEXT.md | 2 +- CONTRIBUTING.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index dd2b6dd5..d7fb6b53 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -44,7 +44,7 @@ The only admission path into a full Use Case: the PR names the entry it replaces _Avoid_: One-in-one-out (informal alias) **Challenger**: -An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (standard-library modules first in tier, other no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. +An entry (at most two per Use Case) that is not yet the Obvious Choice but is a credible successor to one. Marked by ordering, not description: within a Use Case, Obvious Choices are listed first, then Challengers, each tier by PyPI downloads/month descending (standard-library modules first in the Use Case outright — always Obvious Choices by the stdlib rule — other no-signal entries last in tier, alphabetically). Admission requires adoption-trajectory evidence, not popularity alone. _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept removed entirely) **Second Tier**: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcdfd994..281df0cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ Hard maximum: 5 entries per use case. This is a qualitative bar first and a nume **Evidence**: admission is decided by maintainer editorial judgment, informed primarily by PyPI download counts rather than GitHub stars. Judgment overrides the signal's known failure modes (CI-inflated counts, model releases consumed as weights rather than pip installs, large-but-specific audiences misread as "niche"). The maintainer's decision is final. -Looking for an exhaustive catalog instead? Follow the awesome-* lists linked under individual entries (for example awesome-python-testing) — they exist precisely so this list doesn't have to be one. +Looking for an exhaustive catalog instead? Follow the awesome-\* lists linked under individual entries (for example awesome-python-testing) — they exist precisely so this list doesn't have to be one. ## Entry Format Reference @@ -73,7 +73,7 @@ Use the **PyPI package name** as the display name so developers can copy it dire ### Entry Ordering -Within a use case, the obvious choices are listed first, ordered by PyPI downloads per month from high to low; challengers follow, in the same order. Standard-library modules sort first within their tier — built-ins lead — alphabetically when there are several. Other entries without a download signal (agent skill packs, projects distributed outside PyPI) sort last within their tier, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. +Within a use case, the obvious choices are listed first, ordered by PyPI downloads per month from high to low; challengers follow, in the same order. Standard-library modules sort first in the use case — built-ins lead, before everything else — alphabetically when there are several. (This never conflicts with tier order: a standard-library module is only listed where it is itself the obvious choice, so it always belongs to the first tier.) Other entries without a download signal (agent skill packs, projects distributed outside PyPI) sort last within their tier, alphabetically. There is no marker in the entry text — position is the marker, so the last entries of a use case may be its challengers. ## Changing the Structure From a4eba37b1ff100f74b49e02091fb5adba39aa348 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:52:22 +0800 Subject: [PATCH 0763/1130] audit: sweep Environment Management, drop KillPy, pyenv-win becomes a sub-item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: virtualenv (545.7M/mo, much of it transitive via pip and tox), uv (194.1M/mo, dual-listed here and in Package Managers by maintainer acceptance), pyenv (no pip signal — distributed via git and brew; 45.0K stars) all obvious choices. pyenv-win converts to an indented sub-item under pyenv (the flower/celery companion pattern — its job is inseparable from its parent's), freeing a slot without losing the pointer. Removed: - KillPy — 124 stars, created 2025, 3.7K downloads/month; nowhere near obvious-choice or challenger territory. Co-Authored-By: Claude --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2c70b99b..9794a826 100644 --- a/README.md +++ b/README.md @@ -1028,11 +1028,10 @@ _Libraries for working with dates and times._ _Libraries for Python version and virtual environment management._ -- [KillPy](https://github.com/Tlaloc-Es/killpy) - Analyze, detect, and clean unused Python environments and pipx packages. -- [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management. -- [pyenv-win](https://github.com/pyenv-win/pyenv-win) - Pyenv for Windows. -- [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. +- [uv](https://github.com/astral-sh/uv) - An extremely fast Python version, package and project manager, written in Rust. +- [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management. + - [pyenv-win](https://github.com/pyenv-win/pyenv-win) ### Package Management From 1b51907189bacb85533b8bd74a2835ec5acaa538 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:52:39 +0800 Subject: [PATCH 0764/1130] audit: sweep Package Management, mint Package Managers subcategory, cap override to 6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the flat section becomes two subcategories — Package Managers (this commit) and Build Backends (entering next as three addition commits; the pyproject build-backend choice is a distinct job from installing packages). Package Managers tiers: pip (689.4M/mo), uv (194.1M/mo, the 2026 default recommendation for new projects), poetry (79M/mo) obvious choices; hatch (22.2M/mo — kept by explicit maintainer flip against the seeded drop), pipx (7.1M/mo, Second Tier — uv tool covers the job for uv users), conda (no pip signal, the data-science distribution standard; sorts last) challengers. The use case holds 6 entries by explicit maintainer cap override — the list's third, same 3 obvious choices + 3 challengers shape as Linters and Async I/O. No removals. Co-Authored-By: Claude --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9794a826..c7faa911 100644 --- a/README.md +++ b/README.md @@ -1037,12 +1037,13 @@ _Libraries for Python version and virtual environment management._ _Libraries for package and dependency management._ -- [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. -- [hatch](https://github.com/pypa/hatch) - Modern, extensible Python project manager for environments, builds, and publishing. -- [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 Managers + - [pip](https://github.com/pypa/pip) - The package installer for Python. + - [uv](https://github.com/astral-sh/uv) - An extremely fast Python version, package and project manager, written in Rust. + - [poetry](https://github.com/python-poetry/poetry) - Python dependency management and packaging made easy. + - [hatch](https://github.com/pypa/hatch) - Modern, extensible Python project manager for environments, builds, and publishing. + - [pipx](https://github.com/pypa/pipx) - Install and Run Python Applications in Isolated Environments. Like `npx` in Node.js. + - [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. ### Package Repositories From 1a36f7742603425022f4bad520169b40d1a2e97d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:52:51 +0800 Subject: [PATCH 0765/1130] feat: add setuptools to Package Management > Build Backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mints the Build Backends subcategory — the deferred agenda item from the Developer Tools sitting (2026-08-16), landing in its designated Python Toolchain slot. setuptools is the incumbent obvious choice: 1.51B downloads/month (pepy), half the ecosystem still builds with it, pushed 2026-08. Co-Authored-By: Claude --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c7faa911..65bc8d3d 100644 --- a/README.md +++ b/README.md @@ -1044,6 +1044,8 @@ _Libraries for package and dependency management._ - [hatch](https://github.com/pypa/hatch) - Modern, extensible Python project manager for environments, builds, and publishing. - [pipx](https://github.com/pypa/pipx) - Install and Run Python Applications in Isolated Environments. Like `npx` in Node.js. - [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. +- Build Backends + - [setuptools](https://github.com/pypa/setuptools) - The historical and still most widely used pyproject build backend. ### Package Repositories From 0ec6f492f48c2aedf919a87fbe28eef7f73bf96d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:53:03 +0800 Subject: [PATCH 0766/1130] feat: add hatchling to Package Management > Build Backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The modern default backend — PyPA docs and project templates reach for it: 507M downloads/month (pepy), pushed 2026-08. Obvious choice alongside setuptools; ordering after it by downloads. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 65bc8d3d..bbc1a920 100644 --- a/README.md +++ b/README.md @@ -1046,6 +1046,7 @@ _Libraries for package and dependency management._ - [conda](https://github.com/conda/conda/) - Cross-platform, Python-agnostic binary package manager. - Build Backends - [setuptools](https://github.com/pypa/setuptools) - The historical and still most widely used pyproject build backend. + - [hatchling](https://github.com/pypa/hatch) - Modern, extensible build backend from the hatch project. ### Package Repositories From 947b93943abb16bd9ba1d1ea2b9d434068e2cfcd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:53:17 +0800 Subject: [PATCH 0767/1130] feat: add uv-build to Package Management > Build Backends Challenger: uv's own backend at 26.5M downloads/month (pepy) within a year of release, riding uv's adoption trajectory. Deferred by the maintainer from the Developer Tools sitting; this closes that agenda item. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bbc1a920..490698df 100644 --- a/README.md +++ b/README.md @@ -1047,6 +1047,7 @@ _Libraries for package and dependency management._ - Build Backends - [setuptools](https://github.com/pypa/setuptools) - The historical and still most widely used pyproject build backend. - [hatchling](https://github.com/pypa/hatch) - Modern, extensible build backend from the hatch project. + - [uv-build](https://github.com/astral-sh/uv) - uv's fast, minimal build backend for pure-Python projects. ### Package Repositories From 9f9bd8db1fddd11c365c3263180f5b36cfad9d8f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:53:35 +0800 Subject: [PATCH 0768/1130] style: split Distribution into Executables and Obfuscation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-directed restructure: instead of dropping pyarmor as mis-homed, an Obfuscation subcategory is minted for it (sole obvious choice — 501.5K/mo, 5.2K stars, pushed 2026-08); code obfuscation is a distinct job from building executables. Executables tiers: pyinstaller (13.1M/mo), Nuitka (512K/mo) obvious choices; shiv (487.3K/mo), cx-Freeze (221K/mo) challengers. No removals. Co-Authored-By: Claude --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 490698df..da004ba1 100644 --- a/README.md +++ b/README.md @@ -1061,11 +1061,13 @@ _Local PyPI repository server and proxies._ _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. +- Executables + - [pyinstaller](https://github.com/pyinstaller/pyinstaller) - Converts Python programs into stand-alone executables (cross-platform). + - [Nuitka](https://github.com/Nuitka/Nuitka) - Compiles Python programs into high-performance standalone executables (cross-platform, supports all Python versions). + - [shiv](https://github.com/linkedin/shiv) - A command line utility for building fully self-contained zipapps (PEP 441), but with all their dependencies included. + - [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. +- Obfuscation + - [pyarmor](https://github.com/dashingsoft/pyarmor) - A tool used to obfuscate python scripts, bind obfuscated scripts to fixed machine or expire obfuscated scripts. ### Configuration Files From 5a8c062f6d4150222b8e6f5648f55b06fc63cf2d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:53:51 +0800 Subject: [PATCH 0769/1130] audit: sweep Configuration Files, drop python-decouple MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: configparser (stdlib, leads the use case under the stdlib-first ordering rule), python-dotenv (782.2M/mo) obvious choices — pydantic-settings joins them in its own addition commit; hydra-core (23.8M/mo — renamed from hydra to its canonical PyPI name; the cache's bare hydra row is an ancient unrelated package; link updated to hydra-ecosystem/hydra, where the facebookresearch repo now redirects, target verified by description) and dynaconf (6.8M/mo) challengers. Removed: - python-decouple — repo dormant since 2024-11, last release 3.8 in 2023-03, three and a half years past any release and past the 12-month activity line. 8.4M downloads/month, 3.0K stars. Displaced by pydantic-settings (495M/mo) entering next. Co-Authored-By: Claude --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da004ba1..25b7d63a 100644 --- a/README.md +++ b/README.md @@ -1074,10 +1074,9 @@ _Libraries to create packaged executables for release distribution._ _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. +- [hydra-core](https://github.com/hydra-ecosystem/hydra) - Hydra is a framework for elegantly configuring complex applications. +- [dynaconf](https://github.com/dynaconf/dynaconf) - Dynaconf is a configuration manager with plugins for Django, Flask and FastAPI. **Security** From ee0caca89253536ae4c08d2ba875017efa58c3da Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 13:54:05 +0800 Subject: [PATCH 0770/1130] feat: add pydantic-settings to Configuration Files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Displacement of the dropped python-decouple: same job — settings from environment variables and files — done with validation and types on the pydantic ecosystem's momentum. 495M downloads/month (pepy), pushed 2026-08. Obvious choice, ordering after python-dotenv. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 25b7d63a..aac3ebff 100644 --- a/README.md +++ b/README.md @@ -1075,6 +1075,7 @@ _Libraries for storing and parsing configuration options._ - [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. - [python-dotenv](https://github.com/theskumar/python-dotenv) - Reads key-value pairs from a `.env` file and sets them as environment variables. +- [pydantic-settings](https://github.com/pydantic/pydantic-settings) - Settings management using Pydantic models with validation, loading from environment variables and secrets files. - [hydra-core](https://github.com/hydra-ecosystem/hydra) - Hydra is a framework for elegantly configuring complex applications. - [dynaconf](https://github.com/dynaconf/dynaconf) - Dynaconf is a configuration manager with plugins for Django, Flask and FastAPI. From cf38a34ed4b1ea0994d700a92ce793dd2699e461 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:03:35 +0800 Subject: [PATCH 0771/1130] refactor: re-home pre-commit to Code Analysis > Git Hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-adjudicated close of the standing flag from the Developer Tools sitting: pre-commit is developer-workflow tooling (a git-hook framework orchestrating linters), not ops — it leaves the DevOps Tools > Other grab-bag for a minted sole-entry Git Hooks subcategory in Code Analysis. Placement avoids stretching the Linters and Formatters cap override (already 6 by maintainer word). No additions or removals — a pure re-home. Co-Authored-By: Claude --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index aac3ebff..c251b007 100644 --- a/README.md +++ b/README.md @@ -590,6 +590,8 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. - [prospector](https://github.com/prospector-dev/prospector) - A tool to analyze Python code. - [repowise](https://github.com/repowise-dev/repowise) - Codebase intelligence that indexes repos into dependency graphs, git history, and auto-generated docs with dead code detection. +- Git Hooks + - [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks. - Linters and Formatters - [ruff](https://github.com/astral-sh/ruff) - An extremely fast Python linter and code formatter. - [black](https://github.com/psf/black) - The uncompromising Python code formatter. @@ -706,7 +708,6 @@ _Software and libraries for DevOps._ - [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX. - [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. - Other - - [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks. - [borgbackup](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. From 283f1425b3d73ffd494edc80b4093d6536bf6a8e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:03:50 +0800 Subject: [PATCH 0772/1130] style: fix display-name casing to canonical PyPI names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-approved batch: gtts → gTTS, twisted → Twisted, cython → Cython, per the naming convention (display name = canonical PyPI package name, verified via the PyPI JSON API). All three were caught post-commit in earlier sittings and parked awaiting explicit word. Co-Authored-By: Claude --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c251b007..ff3302fe 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents - [openai-whisper](https://github.com/openai/whisper) - A general-purpose automatic speech recognition model trained on 680k hours of multilingual and multitask supervised data. - [funasr](https://github.com/modelscope/FunASR) - Industrial-grade speech recognition toolkit with 170x realtime speed, 50+ languages, speaker diarization, and emotion detection. - [vibevoice](https://github.com/microsoft/VibeVoice) - A family of open-source voice AI models from Microsoft for text-to-speech and long-form speech recognition. - - [gtts](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. + - [gTTS](https://github.com/pndurette/gTTS) - Python library and CLI tool for converting text to speech using Google Translate TTS. - [kittentts](https://github.com/KittenML/KittenTTS) - Lightweight ONNX text-to-speech library with small CPU-friendly models. ### Deep Learning @@ -975,7 +975,7 @@ _Implementations of Python._ - [cpython](https://github.com/python/cpython) - Default, most widely used 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. -- [cython](https://github.com/cython/cython) - Optimizing Static Compiler for Python. +- [Cython](https://github.com/cython/cython) - Optimizing Static Compiler for Python. - [pyodide](https://github.com/pyodide/pyodide) - Python distribution for the browser and Node.js based on WebAssembly. ### Built-in Classes Enhancement @@ -1008,7 +1008,7 @@ _Libraries for asynchronous, concurrent and parallel execution. Also see [awesom - [uvloop](https://github.com/MagicStack/uvloop) - Ultra fast asyncio event loop. - [trio](https://github.com/python-trio/trio) - A friendly library for async concurrency and I/O. - [gevent](https://github.com/gevent/gevent) - A coroutine-based Python networking library that uses [greenlet](https://github.com/python-greenlet/greenlet). - - [twisted](https://github.com/twisted/twisted) - An event-driven networking engine. + - [Twisted](https://github.com/twisted/twisted) - An event-driven networking engine. - Parallelism - [concurrent.futures](https://docs.python.org/3/library/concurrent.futures.html) - (Python standard library) A high-level interface for asynchronously executing callables. - [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) - (Python standard library) Process-based parallelism. From 438881adba615fb738b48f43f06070fb4d4bd756 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:12:29 +0800 Subject: [PATCH 0773/1130] =?UTF-8?q?style:=20audit=20Cryptography=20and?= =?UTF-8?q?=20Penetration=20Testing=20=E2=80=94=20renames=20and=20re-tiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No removals in either section. Cryptography: adds the missing italic section description; reorders to downloads — cryptography (1.48B/mo), pynacl (241.7M/mo), paramiko (155.4M/mo; SSH kept here as secure-protocols-adjacent rather than minting a sole-entry use case) — all obvious choices. Penetration Testing: tiers mitmproxy (10.8M/mo) and sqlmap (official PyPI package verified, 65.3K/mo under-measures git-based usage; 38.2K stars) obvious choices; sherlock-project (renamed from sherlock — the bare PyPI name is an unrelated distributed-lock library; 115.9K/mo, 89.6K stars) and social-engineer-toolkit (renamed from setoolkit — no PyPI package, so the naming convention falls back to the repository name; no download signal, judged by ecosystem standing) challengers. Co-Authored-By: Claude --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ff3302fe..fa7ebfff 100644 --- a/README.md +++ b/README.md @@ -1084,18 +1084,20 @@ _Libraries for storing and parsing configuration options._ ### Cryptography +_Libraries for cryptographic primitives and secure protocols._ + - [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. +- [paramiko](https://github.com/paramiko/paramiko) - The leading native Python SSHv2 protocol 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. +- [sherlock-project](https://github.com/sherlock-project/sherlock) - Hunt down social media accounts by username across social networks. +- [social-engineer-toolkit](https://github.com/trustedsec/social-engineer-toolkit) - A toolkit for social engineering. ### Web Security From fe7006edccca554d9f3176b1d05e5121827aa7f6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:12:46 +0800 Subject: [PATCH 0774/1130] feat: add pip-audit, minting the Supply Chain Security section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PyPA-official dependency vulnerability scanner: 30.9M downloads/month (pepy; largely CI traffic, which is the use case), 1.3K stars, pushed 2026-08. Verified 2026 guidance names it the free baseline over the older commercial safety. Sole obvious choice. This mints the reform's first new section — an explicit maintainer override of decision 18 (all minted use cases are subcategories), accepted in the audit preview. TOC line, heading, and description ride this commit per the Build Backends pattern; placed alphabetically in the Security group. Co-Authored-By: Claude --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index fa7ebfff..f9c97614 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour - [Cryptography](#cryptography) - [Penetration Testing](#penetration-testing) +- [Supply Chain Security](#supply-chain-security) - [Web Security](#web-security) **Other** @@ -1099,6 +1100,12 @@ _Frameworks and tools for penetration testing._ - [sherlock-project](https://github.com/sherlock-project/sherlock) - Hunt down social media accounts by username across social networks. - [social-engineer-toolkit](https://github.com/trustedsec/social-engineer-toolkit) - A toolkit for social engineering. +### Supply Chain Security + +_Tools for auditing dependencies against known vulnerabilities._ + +- [pip-audit](https://github.com/pypa/pip-audit) - Audits Python environments and dependency trees for known vulnerabilities, using the PyPI Advisory Database and OSV. + ### Web Security _Libraries for application-layer web security._ From 913b380cc849a494dae369b159992acf9d3f3b51 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:15:21 +0800 Subject: [PATCH 0775/1130] feat: add uv audit to Supply Chain Security MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-proposed during the Security sitting: uv's built-in vulnerability and malware scanning (announced 2026-06, OSV-backed, 4-10x faster than pip-audit on typical projects). Enters as a no-signal challenger behind pip-audit — not a package, so this is a subcommand pointer entry linking the CLI docs (httpx.URL precedent). Astral marks the feature preview/unstable; kept by explicit maintainer override of the production-ready quality bar — the first stability override. The description carries the preview label. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f9c97614..06654d94 100644 --- a/README.md +++ b/README.md @@ -1105,6 +1105,7 @@ _Frameworks and tools for penetration testing._ _Tools for auditing dependencies against known vulnerabilities._ - [pip-audit](https://github.com/pypa/pip-audit) - Audits Python environments and dependency trees for known vulnerabilities, using the PyPI Advisory Database and OSV. +- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - uv's built-in dependency vulnerability and malware scanning backed by OSV (preview). ### Web Security From 16e17d62baca49c292bb1de7027d9c5cc75750ea Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:15:42 +0800 Subject: [PATCH 0776/1130] style: drop the preview label from uv audit's description Maintainer word: no preview marker in the entry text. The stability override itself stands unchanged. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 06654d94..263e21d3 100644 --- a/README.md +++ b/README.md @@ -1105,7 +1105,7 @@ _Frameworks and tools for penetration testing._ _Tools for auditing dependencies against known vulnerabilities._ - [pip-audit](https://github.com/pypa/pip-audit) - Audits Python environments and dependency trees for known vulnerabilities, using the PyPI Advisory Database and OSV. -- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - uv's built-in dependency vulnerability and malware scanning backed by OSV (preview). +- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - uv's built-in dependency vulnerability and malware scanning backed by OSV. ### Web Security From 0194cbbe13a0eb0f1da2a920f14f8f7bdcfd6998 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:26:42 +0800 Subject: [PATCH 0777/1130] audit: sweep Hardware, drop synology-api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: bleak (2.5M/mo) and pynput (2.2M/mo) obvious choices; jumpstarter (1.5K/mo, 211 stars, created 2026-01) kept as a challenger by explicit maintainer flip against the seeded drop. Removed: - synology-api — 579 stars, 14.5K downloads/month; a single-vendor NAS API wrapper, not an obvious choice for any hardware job a general reader has. Judgment call. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 263e21d3..375f11e1 100644 --- a/README.md +++ b/README.md @@ -1120,9 +1120,8 @@ _Libraries for application-layer web security._ _Libraries for programming with hardware._ - [bleak](https://github.com/hbldh/bleak) - A cross platform Bluetooth Low Energy Client for Python using asyncio. -- [jumpstarter](https://github.com/jumpstarter-dev/jumpstarter) - A hardware-in-the-loop testing framework with a Python client library for automated testing on real and virtual hardware. - [pynput](https://github.com/moses-palmer/pynput) - A library to control and monitor input devices. -- [synology-api](https://github.com/N4S4/synology-api) - Python wrapper for Synology NAS APIs: Surveillance Station, File Station, Download Station, Docker, and 50+ other endpoints. +- [jumpstarter](https://github.com/jumpstarter-dev/jumpstarter) - A hardware-in-the-loop testing framework with a Python client library for automated testing on real and virtual hardware. ### Microsoft Windows From f8e594a9b4b14d640ce10ee4de7acf88541136a1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:27:02 +0800 Subject: [PATCH 0778/1130] audit: sweep Miscellaneous, drop tryton, re-home itsdangerous to Cryptography MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: itsdangerous moves to Security > Cryptography as a challenger — HMAC-based data signing fits "cryptographic primitives and secure protocols" better than the grab-bag. Kept past the 12-month activity line (repo quiet since 2025-06, last release 2024-04) as mature-stable on the sortedcontainers precedent: 222.8M downloads/month, the signing answer, no successor. Miscellaneous keeps blinker (192.2M/mo) and boltons (26.5M/mo) as obvious choices. Removed: - tryton — 10.9K downloads/month, and that PyPI package is the desktop client (the framework server is trytond); the linked GitHub repo is a self-described 216-star mirror; and the ERP obvious choice by adoption is Odoo (~7M users vs hundreds-to-thousands of Tryton deployments, web-verified), which enters in the next commit. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 375f11e1..b7ce34d2 100644 --- a/README.md +++ b/README.md @@ -1090,6 +1090,7 @@ _Libraries for cryptographic primitives and secure protocols._ - [cryptography](https://github.com/pyca/cryptography) - A package designed to expose cryptographic primitives and recipes to Python developers. - [pynacl](https://github.com/pyca/pynacl) - Python binding to the Networking and Cryptography (NaCl) library. - [paramiko](https://github.com/paramiko/paramiko) - The leading native Python SSHv2 protocol library. +- [itsdangerous](https://github.com/pallets/itsdangerous) - Various helpers to pass trusted data to untrusted environments. ### Penetration Testing @@ -1137,8 +1138,6 @@ _Useful libraries or tools that don't fit in the categories above._ - [blinker](https://github.com/pallets-eco/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 From a2659635af676dbb7485b9a89487523a1b0ba25a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:27:23 +0800 Subject: [PATCH 0779/1130] feat: add odoo, minting the ERP section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Python ERP by adoption: about 7M users across editions, 50+ app modules, 53.7K stars, pushed daily (web-verified). Not pip-distributed — the PyPI odoo package is a dateless placeholder — so no download signal; judged by ecosystem and displayed by repository name (renpy precedent). Sole obvious choice. Second structure override of decision 18 by maintainer word (Supply Chain Security precedent): ERP lands as a new section in the Other group rather than a slot in the Miscellaneous grab-bag, replacing the dropped tryton. TOC line, heading, and description ride this commit. Co-Authored-By: Claude --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index b7ce34d2..bbcf23f0 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour **Other** +- [ERP](#erp) - [Hardware](#hardware) - [Microsoft Windows](#microsoft-windows) - [Miscellaneous](#miscellaneous) @@ -1116,6 +1117,12 @@ _Libraries for application-layer web security._ **Other** +### ERP + +_Enterprise resource planning frameworks._ + +- [odoo](https://github.com/odoo/odoo) - A suite of open source business apps: CRM, e-commerce, accounting, inventory, and thousands of community modules. + ### Hardware _Libraries for programming with hardware._ From 660d74623728db1610fefba83aee7f48f0469a79 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:30:54 +0800 Subject: [PATCH 0780/1130] style: move the ERP section to the Web Development group MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer challenge upheld: Odoo is a ready-made web application platform you extend, the sibling concept of CMS and Admin Panels — so the section belongs beside them, not in the Other grab-bag (its first placement was inertia from tryton's Miscellaneous home). TOC and body both move; the group's section tail stays alphabetical (Admin Panels, CMS, ERP, Static Site Generators). Co-Authored-By: Claude --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index bbcf23f0..d40980d1 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour - [Authentication](#authentication) - [Admin Panels](#admin-panels) - [CMS](#cms) +- [ERP](#erp) - [Static Site Generators](#static-site-generators) **HTTP & Scraping** @@ -121,7 +122,6 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour **Other** -- [ERP](#erp) - [Hardware](#hardware) - [Microsoft Windows](#microsoft-windows) - [Miscellaneous](#miscellaneous) @@ -338,6 +338,12 @@ _Content Management Systems._ - [wagtail](https://github.com/wagtail/wagtail) - A Django content management system. - [django-cms](https://github.com/django-cms/django-cms) - The easy-to-use and developer-friendly enterprise CMS powered by Django. +### ERP + +_Enterprise resource planning frameworks._ + +- [odoo](https://github.com/odoo/odoo) - A suite of open source business apps: CRM, e-commerce, accounting, inventory, and thousands of community modules. + ### Static Site Generators _Static site generator is a software that takes some text + templates as input and produces HTML files on the output._ @@ -1117,12 +1123,6 @@ _Libraries for application-layer web security._ **Other** -### ERP - -_Enterprise resource planning frameworks._ - -- [odoo](https://github.com/odoo/odoo) - A suite of open source business apps: CRM, e-commerce, accounting, inventory, and thousands of community modules. - ### Hardware _Libraries for programming with hardware._ From 34c550b8645b60f23847dabaa8c4d1a49ce96e71 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:47:44 +0800 Subject: [PATCH 0781/1130] style: split Deep Learning into Frameworks and Reinforcement Learning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No removals. Frameworks tiers: pytorch (96.6M/mo as PyPI torch — the display name stays pytorch by explicit maintainer word, a deliberate exception to the naming convention; the bare pytorch PyPI package is a squatting placeholder), tensorflow (19.2M/mo — production incumbent, flagged as a Second Tier demotion candidate for the next audit), keras (18.6M/mo, backend-agnostic since Keras 3) obvious choices; jax (21.8M/mo, TPU/performance trajectory) and pytorch-lightning (11M/mo) challengers. Landscape verified: PyTorch is the 2026 default with 85% research share. stable-baselines3 moves into the minted Reinforcement Learning subcategory — RL is a distinct job; gymnasium joins it next commit. Co-Authored-By: Claude --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d40980d1..7969498c 100644 --- a/README.md +++ b/README.md @@ -177,12 +177,14 @@ _Libraries for building AI applications, LLM integrations, and autonomous agents _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](https://github.com/pytorch/pytorch) - Tensors and Dynamic neural networks in Python with strong GPU acceleration. -- [pytorch-lightning](https://github.com/Lightning-AI/pytorch-lightning) - Deep learning framework to train, deploy, and ship AI products Lightning fast. -- [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. +- Frameworks + - [pytorch](https://github.com/pytorch/pytorch) - Tensors and Dynamic neural networks in Python with strong GPU acceleration. + - [tensorflow](https://github.com/tensorflow/tensorflow) - The most popular Deep Learning framework created by Google. + - [keras](https://github.com/keras-team/keras) - A high-level deep learning library with support for JAX, TensorFlow, and PyTorch backends. + - [jax](https://github.com/jax-ml/jax) - A library for high-performance numerical computing with automatic differentiation and JIT compilation. + - [pytorch-lightning](https://github.com/Lightning-AI/pytorch-lightning) - Deep learning framework to train, deploy, and ship AI products Lightning fast. +- Reinforcement Learning + - [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) - PyTorch implementations of Stable Baselines (deep) reinforcement learning algorithms. ### Machine Learning From b95ce7e9955b0b558ae82861ec22a0c5c431ec5c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:47:56 +0800 Subject: [PATCH 0782/1130] feat: add gymnasium to Deep Learning > Reinforcement Learning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RL environments standard: community successor to OpenAI Gym (unmaintained since 2022; few maintained RL libraries still support old Gym — web-verified). 6.5M downloads/month (pepy), 12.3K stars, pushed 2026-08. Obvious choice beside stable-baselines3, ordering first by downloads. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7969498c..add6a1f4 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ _Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learni - [jax](https://github.com/jax-ml/jax) - A library for high-performance numerical computing with automatic differentiation and JIT compilation. - [pytorch-lightning](https://github.com/Lightning-AI/pytorch-lightning) - Deep learning framework to train, deploy, and ship AI products Lightning fast. - Reinforcement Learning + - [gymnasium](https://github.com/Farama-Foundation/Gymnasium) - A standard API for reinforcement learning environments with popular reference environments ([gym](https://github.com/openai/gym) successor). - [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) - PyTorch implementations of Stable Baselines (deep) reinforcement learning algorithms. ### Machine Learning From 7d1c5c8d0083f6ed23bdff7f00ed8ca24a7c3cc6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:48:21 +0800 Subject: [PATCH 0783/1130] audit: sweep Machine Learning, three-way split, drop h2o, mindsdb, scikit-lego, TabGAN, spark.ml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the 12-entry flat section splits into General (scikit-learn 234.7M/mo obvious choice; pgmpy 843.7K/mo and feature-engine — renamed from feature_engine to its canonical PyPI name, 297.1K/mo — challengers), Gradient Boosting (xgboost 52M/mo, lightgbm 26.5M/mo, catboost 6.3M/mo, all obvious choices; lightgbm's lightgbm-org link verified current — microsoft/LightGBM redirects there), and Time Series Forecasting (timesfm sole — a foundation model judged by ecosystem adoption, 285K/mo and 27.6K stars; prophet and darts are named absences, deliberately not added this sitting). Removed: - h2o — 215.1K downloads/month, 7.5K stars, and the repo is active; the drop is purely editorial: no longer anyone's unprompted answer against scikit-learn and the boosting trio. Judgment call. - mindsdb — the linked repo redirects to mindsdb/mindshub, a "models workspace"; the AI-layer-for-databases product this entry described no longer exists (verified). 23.9K downloads/month. - scikit-lego — 72.5K downloads/month, 1.4K stars; a grab-bag of sklearn extras that never became an unprompted answer. Judgment. - TabGAN — 574 stars, 2.3K downloads/month. Nowhere near the bar. - spark.ml — duplicate in all but name: pyspark is already listed in the audited DevOps group, same repo, same pip install. Structural. Co-Authored-By: Claude --- README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index add6a1f4..1cc12b1d 100644 --- a/README.md +++ b/README.md @@ -191,18 +191,16 @@ _Frameworks for Neural Networks and Deep Learning. Also see [awesome-deep-learni _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/minds) - 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. -- [scikit-lego](https://github.com/koaning/scikit-lego) - A collection of lego bricks for scikit-learn pipelines. -- [spark.ml](https://github.com/apache/spark) - [Apache Spark](https://spark.apache.org/)'s scalable [Machine Learning library](https://spark.apache.org/docs/latest/ml-guide.html) for distributed computing. -- [TabGAN](https://github.com/Diyago/Tabular-data-generation) - Synthetic tabular data generation using GANs, Diffusion Models, and LLMs. -- [timesfm](https://github.com/google-research/timesfm) - A pretrained foundation model from Google Research for time-series forecasting. -- [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. +- General + - [scikit-learn](https://github.com/scikit-learn/scikit-learn) - The most popular Python library for Machine Learning with extensive documentation and community support. + - [pgmpy](https://github.com/pgmpy/pgmpy) - A Python library for probabilistic graphical models and Bayesian networks. + - [feature-engine](https://github.com/feature-engine/feature_engine) - sklearn compatible API with the widest toolset for feature engineering and selection. +- Gradient Boosting + - [xgboost](https://github.com/dmlc/xgboost) - A scalable, portable, and distributed gradient boosting library. + - [lightgbm](https://github.com/lightgbm-org/LightGBM) - A fast, distributed, high performance gradient boosting framework. + - [catboost](https://github.com/catboost/catboost) - A fast, scalable, high performance gradient boosting on decision trees library. +- Time Series Forecasting + - [timesfm](https://github.com/google-research/timesfm) - A pretrained foundation model from Google Research for time-series forecasting. ### Natural Language Processing From c83b98c37e6c90d9ad2aae1cd2524c5222d3ec15 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:48:38 +0800 Subject: [PATCH 0784/1130] audit: sweep Natural Language Processing, drop funnlp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit General tiers: nltk (71.4M/mo), spacy (25.4M/mo) obvious choices; gensim (6M/mo, quiet since 2025-11 — watch) and stanza (1.1M/mo) challengers. Chinese: jieba kept as mature-stable past the 12-month activity line (repo quiet since 2024-08, last release 0.42.1 in 2020-01) on the sortedcontainers precedent — the fourth such keep: 3.3M downloads/month, 35.1K stars, still the Chinese segmentation answer with no successor. Removed: - funnlp — three independent grounds: a link-collection rather than a library; repo quiet since 2024-05, past the 12-month line; 55 downloads/month. Its 82.5K stars measure the bookmark, not a tool. Co-Authored-By: Claude --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 1cc12b1d..00163f10 100644 --- a/README.md +++ b/README.md @@ -207,12 +207,11 @@ _Libraries for Machine Learning. Also see [awesome-machine-learning](https://git _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. + - [gensim](https://github.com/piskvorky/gensim) - Topic Modeling for Humans. - [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 From 67c9f7ae93bcd56b3ddf6c0e0eb58d9d09c7667f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 14:48:57 +0800 Subject: [PATCH 0785/1130] style: split Computer Vision into General and OCR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No removals. General tiers: opencv-python (renamed from opencv to the canonical pip package this entry already linked; 55.9M/mo) and ultralytics (8.4M/mo, 60.7K stars) obvious choices; kornia (3.1M/mo) and fiftyone (253.7K/mo — dataset tooling rather than a vision algorithm library, kept as the unprompted answer for that adjacent job) challengers. OCR minted as a distinct job: pytesseract (24M/mo) and easyocr (3.6M/mo, quiet since 2025-12 — watch) obvious choices. Co-Authored-By: Claude --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 00163f10..089fa010 100644 --- a/README.md +++ b/README.md @@ -218,12 +218,14 @@ _Libraries for working with human languages._ _Libraries for Computer Vision._ -- [easyocr](https://github.com/JaidedAI/EasyOCR) - Ready-to-use OCR with 40+ languages supported. -- [fiftyone](https://github.com/voxel51/fiftyone) - The open-source tool for building high-quality datasets and computer vision models. -- [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). -- [ultralytics](https://github.com/ultralytics/ultralytics) - Ultralytics YOLO for object detection, segmentation, pose estimation, and classification with state-of-the-art accuracy and speed. +- General + - [opencv-python](https://github.com/opencv/opencv-python) - Open Source Computer Vision Library. + - [ultralytics](https://github.com/ultralytics/ultralytics) - Ultralytics YOLO for object detection, segmentation, pose estimation, and classification with state-of-the-art accuracy and speed. + - [kornia](https://github.com/kornia/kornia/) - Open Source Differentiable Computer Vision Library for PyTorch. + - [fiftyone](https://github.com/voxel51/fiftyone) - The open-source tool for building high-quality datasets and computer vision models. +- OCR + - [pytesseract](https://github.com/madmaze/pytesseract) - A wrapper for [Google Tesseract OCR](https://github.com/tesseract-ocr). + - [easyocr](https://github.com/JaidedAI/EasyOCR) - Ready-to-use OCR with 40+ languages supported. ### Recommender Systems From da4383a06f71431ccc2d053059b0d262073c6833 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:09:02 +0800 Subject: [PATCH 0786/1130] audit: sweep Text Processing, dissolve General, drop textdistance, nameparser, user-agents, tree-sitter-language-pack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure: the 10-entry General grab-bag dissolves — Encoding and Unicode (chardet 224.1M/mo obvious choice, joined by charset-normalizer next commit; ftfy 14.4M/mo kept as the fifth mature-stable past-line keep, repo and release both 2024-10), Internationalization (babel 135.2M/mo sole), Transliteration and Slugs (python-slugify 87.7M/mo, unidecode 31.8M/mo), and a residual General (difflib stdlib-first, pyfiglet 6.2M/mo judgment keep). pypinyin (1.9M/mo) and pangu.py (14.9K/mo as PyPI pangu — display name kept by explicit maintainer word, the second deliberate naming exception after pytorch; kept on sole-tool judgment for CJK spacing) re-home to Natural Language Processing > Chinese as challengers beside jieba. Parser re-tiers: pygments (1.25B/mo), pyparsing (422.3M/mo), sqlparse (146.8M/mo) obvious choices; phonenumbers (renamed from python-phonenumbers, 39.4M/mo) and parsy (4M/mo) challengers. Unique identifiers reorders to shortuuid then sqids. Removed: - textdistance — last release 2024-07 (25 months) and repo quiet since 2025-04, past the 12-month line; displaced by rapidfuzz (181.7M/mo vs 2.5M), entering in its own commit. - python-nameparser — 3.3M downloads/month (as nameparser) and an active repo; editorial drop at cap: the domain-parser class is trimmed to the giant, phonenumbers. Judgment call. - python-user-agents — repo quiet since 2023-02, three and a half years past the 12-month line. - tree-sitter-language-pack — coordinated multi-entry self-promotion (automatic-rejection rule): PyPI provenance verified to xberg-io, the org that previously planted xberg and liter-llm. 6.6M/mo is real but the rule stands; its sibling drops from HTML Manipulation. Co-Authored-By: Claude --- README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 089fa010..dc936e31 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,8 @@ _Libraries for working with human languages._ - [stanza](https://github.com/stanfordnlp/stanza) - The Stanford NLP Group's official Python library, supporting 60+ languages. - Chinese - [jieba](https://github.com/fxsjy/jieba) - The most popular Chinese text segmentation library. + - [pypinyin](https://github.com/mozillazg/python-pinyin) - Convert Chinese hanzi (漢字) to pinyin (拼音). + - [pangu.py](https://github.com/vinta/pangu.py) - Paranoid text spacing. ### Computer Vision @@ -851,29 +853,26 @@ _Libraries for working with graphical user interface applications._ _Libraries for parsing and manipulating plain texts._ -- General - - [babel](https://github.com/python-babel/babel) - An internationalization library for Python. +- Encoding and Unicode - [chardet](https://github.com/chardet/chardet) - Python 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. +- General + - [difflib](https://docs.python.org/3/library/difflib.html) - (Python standard library) Helpers for computing deltas. - [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. +- Internationalization + - [babel](https://github.com/python-babel/babel) - An internationalization library for Python. - Parser - - [parsy](https://github.com/python-parsy/parsy) - Easy, generic parser combinator library for creating parsers. - [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. - - [tree-sitter-language-pack](https://github.com/xberg-io/tree-sitter-language-pack) - A comprehensive collection of tree-sitter parsers for 300+ languages, distributed as prebuilt wheels. + - [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) - Parsing, formatting, storing and validating international phone numbers. + - [parsy](https://github.com/python-parsy/parsy) - Easy, generic parser combinator library for creating parsers. +- Transliteration and Slugs + - [python-slugify](https://github.com/un33k/python-slugify) - A Python slugify library that translates unicode to ASCII. + - [unidecode](https://github.com/avian2/unidecode) - ASCII transliterations of Unicode text. +- Unique identifiers + - [shortuuid](https://github.com/skorokithakis/shortuuid) - A generator library for concise, unambiguous and URL-safe UUIDs. + - [sqids](https://github.com/sqids/sqids-python) - A library for generating short unique IDs from numbers. ### HTML Manipulation From 043cb6d377e2b03090b8bf6d7852645a2e908d63 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:09:18 +0800 Subject: [PATCH 0787/1130] feat: add charset-normalizer to Text Processing > Encoding and Unicode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ecosystem's default encoding detector — requests switched to it in 2021 and 2026 guidance names it the choice for new projects (web-verified). 1.73B downloads/month (pepy; heavily requests-transitive, but default-status is the point), pushed 2026-08. Co-obvious with chardet, which retains a verified accuracy claim — the PyQt/PySide pair shape. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc936e31..daae4b00 100644 --- a/README.md +++ b/README.md @@ -854,6 +854,7 @@ _Libraries for working with graphical user interface applications._ _Libraries for parsing and manipulating plain texts._ - Encoding and Unicode + - [charset-normalizer](https://github.com/jawah/charset_normalizer) - Universal character encoding detector, the default of the requests ecosystem. - [chardet](https://github.com/chardet/chardet) - Python character encoding detector. - [ftfy](https://github.com/rspeer/python-ftfy) - Makes Unicode text less broken and more consistent automagically. - General From db9c26234239c6f8d33be85371c4d0ee762224a3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:09:32 +0800 Subject: [PATCH 0788/1130] feat: add rapidfuzz, minting Text Processing > Fuzzy Matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The industry's fuzzy string matching answer (web-verified: the production recommendation over thefuzz — same API, MIT license, C++ speed — and preferred over textdistance for string metrics). 181.7M downloads/month (pepy), 4.1K stars, pushed 2026-08. Sole obvious choice; the subcategory label rides this commit so it is never empty, completing the displacement of textdistance. Co-Authored-By: Claude --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index daae4b00..4e54b7eb 100644 --- a/README.md +++ b/README.md @@ -857,6 +857,8 @@ _Libraries for parsing and manipulating plain texts._ - [charset-normalizer](https://github.com/jawah/charset_normalizer) - Universal character encoding detector, the default of the requests ecosystem. - [chardet](https://github.com/chardet/chardet) - Python character encoding detector. - [ftfy](https://github.com/rspeer/python-ftfy) - Makes Unicode text less broken and more consistent automagically. +- Fuzzy Matching + - [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) - Rapid fuzzy string matching using various string metrics, with a C++ core. - General - [difflib](https://docs.python.org/3/library/difflib.html) - (Python standard library) Helpers for computing deltas. - [pyfiglet](https://github.com/pwaller/pyfiglet) - An implementation of figlet written in Python. From ad698c28765d7c9125a286baffe3b27d9d4bc010 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:09:52 +0800 Subject: [PATCH 0789/1130] audit: sweep HTML Manipulation, drop html-to-markdown, pyquery, tinycss2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tiers: beautifulsoup4 (renamed from beautifulsoup — the bare PyPI name is the abandoned bs3 shim; 451.4M/mo, docs link per the PyQt precedent), lxml (401.3M/mo), xmltodict (124.5M/mo) obvious choices; markupsafe (820.5M/mo — the section's biggest raw count, but jinja-transitive infrastructure, so challenger on judgment; watch: quiet since 2025-09) and justhtml (67.8K/mo, 1.1K stars in two years — trajectory judgment on a young pure-Python HTML5 parser) challengers. Removed: - html-to-markdown — coordinated multi-entry self-promotion (automatic-rejection rule): PyPI provenance verified to xberg-io, the org's fourth planted entry overall. 1.5M downloads/month is real but the rule stands. - pyquery — 2.2M downloads/month and an active repo (pushed 2026-07); editorial drop at cap: the jQuery-style API is the least-reached-for of the keeps. Judgment call. - tinycss2 — 110.5M downloads/month is transitive (weasyprint declares it a hard dependency, verified in PyPI metadata) against 190 stars; a CSS parser mis-homed in an HTML/XML section with no better home. Judgment call. Co-Authored-By: Claude --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4e54b7eb..4c8203d0 100644 --- a/README.md +++ b/README.md @@ -881,14 +881,11 @@ _Libraries for parsing and manipulating plain texts._ _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. -- [html-to-markdown](https://github.com/xberg-io/html-to-markdown) - A fast, CommonMark-compliant HTML to Markdown converter with a Rust core, tolerant of malformed HTML. -- [justhtml](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. +- [beautifulsoup4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) - Providing Pythonic idioms for iterating, searching, and modifying HTML or XML. - [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. -- [tinycss2](https://github.com/Kozea/tinycss2) - A low-level CSS parser and generator written in Python. - [xmltodict](https://github.com/martinblech/xmltodict) - Working with XML feel like you are working with JSON. +- [markupsafe](https://github.com/pallets/markupsafe) - Implements a XML/HTML/XHTML Markup safe string for Python. +- [justhtml](https://github.com/EmilStenstrom/justhtml/) - A pure Python HTML5 parser that just works. ### File Format Processing From 15cd51c04ceeae73e0782705334bcf20c1f7764c Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:10:10 +0800 Subject: [PATCH 0790/1130] style: re-tier File Manipulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No removals. mimetypes and pathlib (standard library) lead alphabetically under the stdlib-first rule; watchfiles (389.3M/mo, partly uvicorn-transitive — the riser) completes the obvious choices; watchdog (113.3M/mo, the demoted incumbent, Second Tier) and python-magic (32.3M/mo) challengers. Co-Authored-By: Claude --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4c8203d0..033730f8 100644 --- a/README.md +++ b/README.md @@ -924,9 +924,9 @@ _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. +- [watchdog](https://github.com/gorakhargosh/watchdog) - API and shell utilities to monitor file system events. +- [python-magic](https://github.com/ahupp/python-magic) - A Python interface to the libmagic file type identification library. **Media** From 5249b756ce5b8424ece4202ba8573169b678dff5 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:21:08 +0800 Subject: [PATCH 0791/1130] refactor: re-home fasthtml to Web Frameworks > Asynchronous MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer-adjudicated close of the standing flag: fasthtml runs on Starlette and Uvicorn (ASGI), so Synchronous was the wrong shelf. It lands as a third challenger behind starlette and tornado's obvious choices — the use case holds 5 with 3 challengers by explicit maintainer override (Async I/O precedent; the awesome-fasthtml sub-item rides along). 1.19M downloads/month as python-fasthtml. Co-Authored-By: Claude --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 033730f8..6b31695b 100644 --- a/README.md +++ b/README.md @@ -251,13 +251,13 @@ _Traditional full stack web frameworks. Also see [Web APIs](#web-apis)._ - [bottle](https://github.com/bottlepy/bottle) - A fast and simple micro-framework distributed as a single file with no dependencies. - [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) - Asynchronous - [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. - [litestar](https://github.com/litestar-org/litestar) - Production-ready, capable and extensible ASGI Web framework. - [reflex](https://github.com/reflex-dev/reflex) - A framework for building reactive, full-stack web applications entirely with Python. + - [fasthtml](https://github.com/AnswerDotAI/fasthtml) - The fastest way to create an HTML app. + - [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) ### Web APIs From e5f7b5bc2eb35fad07f5f2ffffa4620c01cffb14 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:21:21 +0800 Subject: [PATCH 0792/1130] style: update graphify link to the moved Graphify-Labs repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The safishamsi/graphify URL is a stale redirect — the repo moved to Graphify-Labs/graphify (verified via the GitHub API). Maintainer declined the Agent Skills re-home; the entry stays in Data Visualization > Specialized with its link fixed. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b31695b..4ee5c99e 100644 --- a/README.md +++ b/README.md @@ -517,7 +517,7 @@ _Libraries for visualizing data. Also see [awesome-javascript](https://github.co - Specialized - [cartopy](https://github.com/SciTools/cartopy) - A cartographic python library with matplotlib support. - [pygraphviz](https://github.com/pygraphviz/pygraphviz/) - Python interface to [Graphviz](https://www.graphviz.org/). - - [graphify](https://github.com/safishamsi/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. + - [graphify](https://github.com/Graphify-Labs/graphify) - Turn any folder of code, SQL schemas, docs, papers, images, or videos into a queryable knowledge graph. - Dashboards and Apps - [streamlit](https://github.com/streamlit/streamlit) - A framework which lets you build dashboards, generate reports, or create chat apps in minutes. - [gradio](https://github.com/gradio-app/gradio) - Build and share machine learning apps, all in Python. From 441f206d71814a99848c6b260df288890c9057c3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:24:38 +0800 Subject: [PATCH 0793/1130] feat: add pathway to Data Ingestion / ETL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-admission on maintainer word, reversing the Data Analysis sweep's drop (f3c920d — the xlsxwriter reversal precedent): the drop was partly a mis-homing casualty, since its honest home, an ETL use case, did not exist then. The repo self-describes as a Python ETL framework for stream processing and LLM/RAG pipelines: 62.5K stars, pushed daily; 16.5K downloads/month is weak for the star count and noted. Enters as challenger behind dlt (7.8M/mo). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4ee5c99e..95b44b46 100644 --- a/README.md +++ b/README.md @@ -490,6 +490,7 @@ _Libraries for data extraction, transformation, and loading pipelines across mul - General - [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. + - [pathway](https://github.com/pathwaycom/pathway) - Python ETL framework for stream processing, real-time analytics, LLM pipelines, and RAG. - Financial Data - [yfinance](https://github.com/ranaroussi/yfinance) - Easy Pythonic way to download market and financial data from Yahoo Finance. - [akshare](https://github.com/akfamily/akshare) - A financial data interface library, built for human beings! From f60d5b4a08c25b437548cd3746393609b06b2e73 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:25:15 +0800 Subject: [PATCH 0794/1130] style: move fasthtml back to Web Frameworks > Synchronous MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer reversal of c0a31ce, restoring the entry and its awesome-fasthtml sub-item to their prior position. The challenger-limit override that rode the move is withdrawn with it — Asynchronous returns to 4 entries within the standard cap shape. Co-Authored-By: Claude --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 95b44b46..5540980d 100644 --- a/README.md +++ b/README.md @@ -251,13 +251,13 @@ _Traditional full stack web frameworks. Also see [Web APIs](#web-apis)._ - [bottle](https://github.com/bottlepy/bottle) - A fast and simple micro-framework distributed as a single file with no dependencies. - [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) - Asynchronous - [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. - [litestar](https://github.com/litestar-org/litestar) - Production-ready, capable and extensible ASGI Web framework. - [reflex](https://github.com/reflex-dev/reflex) - A framework for building reactive, full-stack web applications entirely with Python. - - [fasthtml](https://github.com/AnswerDotAI/fasthtml) - The fastest way to create an HTML app. - - [awesome-fasthtml](https://github.com/amosgyamfi/awesome-fasthtml) ### Web APIs From 194b386d06f2ef2d0fc694f8af95b20bf0f3213a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:25:15 +0800 Subject: [PATCH 0795/1130] docs: codify the Override rule in CONTRIBUTING and CONTEXT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves decision 10's reservation on maintainer word: the 3+2/5 cap numbers stay as written — across the full prune they held everywhere except a handful of explicit overrides — and the override practice itself becomes a written rule: the maintainer may exceed any limit for a specific entry or use case by explicit decision, case-by-case, carrying no weight for submissions. CONTEXT.md gains the matching Override vocabulary entry. Co-Authored-By: Claude --- CONTEXT.md | 3 +++ CONTRIBUTING.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/CONTEXT.md b/CONTEXT.md index d7fb6b53..986b6f4d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -50,6 +50,9 @@ _Avoid_: Rising Star (old lane name), Hidden Gem (old lane name — concept remo **Second Tier**: A Challenger slot holding a demoted incumbent instead of a rising successor — a former Obvious Choice or fading community standard an Audit kept below the current Obvious Choices (clickhouse-driver behind the official client, dogpile.cache and django-cacheops behind cachetools and diskcache, django-haystack in Search). It counts against the two Challenger slots and is marked by position like any Challenger; the adoption-trajectory bar gates only new admissions, not demotions. +**Override**: +A maintainer decision, made in an Audit and explicitly recorded, that keeps an entry or use-case shape past a written limit — the activity line, the stability bar, a cap, or a structure rule. Case-by-case: an Override never loosens the written rule for any other entry, and submitters cannot cite one. + **Split**: Maintainer-only restructuring of an oversized Use Case into finer Use Cases (new Subcategories), considered before any trimming when the size reflects genuinely distinct jobs. A submitter's entry PR can never perform a Split. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 281df0cd..089f21b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,6 +23,8 @@ Each use case lists at most: Hard maximum: 5 entries per use case. This is a qualitative bar first and a numeric backstop second — most use cases should carry fewer. +**Overrides**: the maintainer may exceed any limit on this page — the caps, the activity requirement, the stability requirement — for a specific entry or use case by explicit decision. An override is case-by-case; it does not loosen these rules for submissions, and citing one in a PR carries no weight. + **Displacement**: once a use case is at its cap, the only way in is to name the entry your project replaces and argue that yours does that entry's job better. One in, one out. **Standard library**: a standard-library module is listed only where the stdlib is itself the obvious choice for the use case (tomllib yes, unittest no). From 39eeab7dff302e8eedf7ba7b1f6a429d707bce16 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:15 +0800 Subject: [PATCH 0796/1130] docs: warn about wrong-package PyPI download counts The pypi downloads sweep looks up counts by README display name; when the display name differs from the canonical package, the row silently measures an unrelated squatter or a dead predecessor. Document the failure mode in both the fetcher's docstring and the audit skill so famous entries with off-looking counts get identity-verified before being cited. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 2 +- website/fetch_pypi_downloads_via_clickpy.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index 961b3c7f..4c2b576c 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -18,7 +18,7 @@ Fetch live evidence for every entry in scope before judging anything (CLAUDE.md - **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run; header row, every row stamped with its `fetched_at` date). A cache whose `fetched_at` is within the last 7 days is current enough for verdicts — skip the sweep; older than that, re-run it (costs ~1s). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. -- **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. +- **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. Wrong-package cache rows are common: the sweep looks up README display names, so when the display name differs from the canonical package, the row silently measures an unrelated squatter or a dead predecessor. When a famous entry shows a count that looks off (too small, or absurdly round), verify identity before citing it, and fetch the canonical package's count via pepy. Done when every entry in scope has downloads (or a stated no-signal reason), repo state, and a confirmed PyPI name. diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index e384b365..1e7c1a26 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -12,7 +12,11 @@ excludes mirrors by default. This script is the sole writer of data/pypi_downloads.tsv and rewrites it from scratch each run, so entries removed from README.md drop out -naturally; names with no PyPI rows are written as NOT_FOUND. The file +naturally; names with no PyPI rows are written as NOT_FOUND. Counts are +looked up by README display name: when the display name differs from the +canonical PyPI package, the row silently measures the wrong package (a +squatter or a dead predecessor) — verify identity at +pypi.org/pypi/{name}/json before citing a count. The file starts with a header row (name, downloads, fetched_at) and every row carries the sweep date: a cache fetched within the last 7 days is current enough for audit verdicts, so only re-run when older. Cross-checks From c034c88e91bcf4e7ca9212979ff1248141da580e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:21 +0800 Subject: [PATCH 0797/1130] docs: add audit log registry for maintainer overrides Git history already archives every removal's reason via commit body, but it can't be scanned at a glance. docs/audit-logs.md is the at-a-glance register of overrides (naming exceptions, mature-stable keeps) allowed by CONTRIBUTING.md. Drop the docs/* gitignore exclusion (and stale .superpowers/ and skills-lock.json entries) so the file and future doc additions outside docs/adr/ can be tracked. Co-Authored-By: Claude --- .gitignore | 8 +------- docs/audit-logs.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 docs/audit-logs.md diff --git a/.gitignore b/.gitignore index b4d552e4..1f2c6f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,6 @@ __pycache__/ website/output/ website/data/ -# docs (except decision records) -docs/* -!docs/adr/ - # agents -tmp/ -.superpowers/ .playwright-cli/ -skills-lock.json +tmp/ diff --git a/docs/audit-logs.md b/docs/audit-logs.md new file mode 100644 index 00000000..93fad35e --- /dev/null +++ b/docs/audit-logs.md @@ -0,0 +1,33 @@ +# Audit Log + +awesome-python is audited section by section. Every entry gets re-verified against live data, and every removal lands in a commit whose body carries the reason. Git history is the archive. This file is the at-a-glance register of maintainer decisions that a single commit can't show. + +## Overrides + +[CONTRIBUTING.md](../CONTRIBUTING.md) allows the maintainer to exceed any limit for a specific entry or use case. Each override is recorded here. + +### Naming Exceptions + +Display names follow the canonical PyPI package name. These entries keep a different name by maintainer decision: + +- autobahn-python -- `autobahn`. +- django-rest-framework -- `djangorestframework`. +- django-rules -- `rules`. +- fasthtml -- `python-fasthtml`. +- jinja -- `Jinja2`. +- mem0 -- `mem0ai`. +- pangu.py -- `pangu`. +- playwright-python -- `playwright`. +- pytorch -- `torch`. +- strawberry -- `strawberry-graphql`. +- strawberry-django -- `strawberry-graphql-django`. + +### Mature-stable Keeps + +These entries sit past the 12-month activity requirement without an override. Each one is kept by editorial judgment: mature, stable, and no successor exists. + +- sortedcontainers +- jinja +- itsdangerous +- jieba +- ftfy From 3e43fa13badd00583d7b4e22e11196bb79feadc9 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:25 +0800 Subject: [PATCH 0798/1130] docs: clarify cross-section re-homes and resources scope in audit rules Cross-section re-homes now ride the originating audit's commit instead of needing a separate one, since both sides of the move land in one diff. Also note that Resources sections are out of audit scope and never parsed by the website, so they're not project entries subject to the one-entry-per-commit rule. Co-Authored-By: Claude --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 47ac9487..1cf71b73 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,8 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. - Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. -- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. +- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. Cross-section re-homes ride the originating audit's commit (both sides of the move in one diff). +- Resources sections are not project entries: out of audit scope, and the website never parses them. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). ## Gotchas From deae0e0b728ab7fa405f60411e4d0f7827647b28 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:29 +0800 Subject: [PATCH 0799/1130] docs: broaden Obvious Choice signal and note Cap has no floor Extend the known failure-mode list for PyPI download counts beyond model weights to any project consumed outside pip (SDK downloads like renpy, deployed services like thumbor). Also clarify that the per-Use- Case Cap is a ceiling, not a floor: a freshly minted Use Case may hold a single entry. Co-Authored-By: Claude --- CONTEXT.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 986b6f4d..cda364e5 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -33,11 +33,11 @@ The scope test for what belongs on the list at all: Python developers use it in _Avoid_: Python-first, written-in-Python (old requirement — removed) **Obvious Choice**: -An entry an experienced Python developer would name unprompted when asked "what do I use for [Use Case]?". Certified by maintainer editorial judgment, informed primarily by PyPI download counts rather than GitHub stars; judgment overrides the signal's known failure modes (CI-inflated counts, model releases consumed as weights rather than pip installs, large-but-specific audiences misread as "niche"). A standard-library module holds a slot only when it is itself the Obvious Choice for the Use Case, not merely relevant to it. +An entry an experienced Python developer would name unprompted when asked "what do I use for [Use Case]?". Certified by maintainer editorial judgment, informed primarily by PyPI download counts rather than GitHub stars; judgment overrides the signal's known failure modes (CI-inflated counts, model releases consumed as weights rather than pip installs — extended to any project consumed outside pip: SDK downloads like renpy, deployed services like thumbor — and large-but-specific audiences misread as "niche"). A standard-library module holds a slot only when it is itself the Obvious Choice for the Use Case, not merely relevant to it. _Avoid_: Industry Standard (old lane name), awesome (unfalsifiable) **Cap**: -The per-Use-Case entry limit: up to 3 Obvious Choices plus up to 2 Challengers, hard maximum 5. A qualitative bar first, a numeric backstop second. +The per-Use-Case entry limit: up to 3 Obvious Choices plus up to 2 Challengers, hard maximum 5. A qualitative bar first, a numeric backstop second — and a ceiling, not a floor: a minted Use Case may hold a single entry. **Displacement**: The only admission path into a full Use Case: the PR names the entry it replaces and argues the newcomer does that entry's job better. One in, one out. From 2c50d9e63a0abe76ad07cc12549791b43051949e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:31 +0800 Subject: [PATCH 0800/1130] docs: document gitignored output and orphan-key behavior Note that data/github_stars.json is gitignored and fetched by CI at deploy time, so local runs are preview-only and should never be committed; entries removed from README.md just leave harmless orphan keys behind. Co-Authored-By: Claude --- website/fetch_github_stars.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/website/fetch_github_stars.py b/website/fetch_github_stars.py index 48aaacf7..3f451885 100644 --- a/website/fetch_github_stars.py +++ b/website/fetch_github_stars.py @@ -1,5 +1,10 @@ #!/usr/bin/env python3 -"""Fetch GitHub star counts and owner info for all GitHub repos in README.md.""" +"""Fetch GitHub star counts and owner info for all GitHub repos in README.md. + +The output file (data/github_stars.json) is gitignored; CI fetches it at +deploy time, so local runs are for previews only — never commit the data. +Entries removed from README.md leave harmless orphan keys behind. +""" import json import os From 2c2ce2fcaca7a3e46e4798cae811bdb11afc4f93 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 15:57:36 +0800 Subject: [PATCH 0801/1130] docs: document empirically verified README parsing behavior Capture parser quirks worth knowing before editing README.md: everything above is ignored, new subcategories need no parser change, a standalone all-bold paragraph becomes a Thematic Group marker, prose after leaks into llms.txt, and the build's "Total entries" figure counts sub-items rather than just entries. Co-Authored-By: Claude --- website/readme_parser.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/website/readme_parser.py b/website/readme_parser.py index 61bf1e98..84887d0f 100644 --- a/website/readme_parser.py +++ b/website/readme_parser.py @@ -1,4 +1,13 @@ -"""Parse README.md into structured section data using markdown-it-py AST.""" +"""Parse README.md into structured section data using markdown-it-py AST. + +Empirically verified behavior worth knowing before editing README.md: +everything above `## Projects` is ignored; new subcategories need no +parser change (a bullet without a leading link plus indented entries); +a standalone all-bold paragraph inside the Projects region becomes a +Thematic Group marker; prose placed right after `## Categories` leaks +into llms.txt; the build's "Total entries" figure counts sub-items +(indented links under an entry), not just entries. +""" from __future__ import annotations From cfb60a633f449e53dd2b91235c5721101403d08a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:06:34 +0800 Subject: [PATCH 0802/1130] docs: codify dual-listing policy in CONTRIBUTING.md The maintainer decided how duplicate entries across categories should be handled (e.g. uv listed in both Environment Management and Package Management): each slot must earn its place independently, entries are listed in full with identical lines rather than a cross-reference, description edits update every copy in the same commit, and each slot is audited on its own. Co-Authored-By: Claude --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 089f21b4..1678cbd5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,8 @@ Hard maximum: 5 entries per use case. This is a qualitative bar first and a nume **Displacement**: once a use case is at its cap, the only way in is to name the entry your project replaces and argue that yours does that entry's job better. One in, one out. +**Dual-listing**: a tool may hold entries in multiple use cases, but only when it earns its slot in each independently — uv sits in both Environment Management and Package Management. List the full entry in each home with identical lines; never a "see X above" note, since the website only renders list items. Description edits update every copy in the same commit. Each slot is audited on its own: dropping one home keeps the other, and dropping the tool entirely removes all copies in one commit. Dual-listing is a maintainer decision; a PR adding a second home for an existing entry is treated as a duplicate. + **Standard library**: a standard-library module is listed only where the stdlib is itself the obvious choice for the use case (tomllib yes, unittest no). **Evidence**: admission is decided by maintainer editorial judgment, informed primarily by PyPI download counts rather than GitHub stars. Judgment overrides the signal's known failure modes (CI-inflated counts, model releases consumed as weights rather than pip installs, large-but-specific audiences misread as "niche"). The maintainer's decision is final. From cccfa183a9a224e230b8cd1352ed56b033a0b6f3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:11:00 +0800 Subject: [PATCH 0803/1130] docs: rewrite PR template to match shortlist rules The old template used a stars-based tier system (Industry Standard / Rising Star / Hidden Gem) that contradicted the current CONTRIBUTING.md, which judges entries by obvious-choice/challenger tiers, favors PyPI downloads over stars, and requires Displacement when a use case is at its cap. The new template reflects those rules and adds a checklist item pointing contributors to CONTRIBUTING.md. Co-Authored-By: Claude --- .github/PULL_REQUEST_TEMPLATE.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05c3b804..865e26ad 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,24 +1,26 @@ ## Project -[Project Name](url) +[project-name](url) ## Checklist +- [ ] I read [CONTRIBUTING.md](https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md) - awesome-python is a shortlist, not a catalog - [ ] One project per PR - [ ] PR title format: `Add project-name` -- [ ] Entry format: `- [project-name](url) - Description ending with period.` -- [ ] Description is concise and short +- [ ] Entry format: `- [pypi-name](https://github.com/owner/repo) - Description ending with period.` +- [ ] Display name is the PyPI package name +- [ ] Placed in an existing use case (new sections and subcategories are maintainer-only) +- [ ] Meets all Quality Requirements: active, stable, documented, at least 1 month old -## Why This Project Is Awesome +## Which Tier -Which criterion does it meet? (pick one) +Pick one: -- [ ] **Industry Standard** - The go-to tool for a specific use case -- [ ] **Rising Star** - 5000+ stars in < 2 years, significant adoption -- [ ] **Hidden Gem** - Exceptional quality, solves niche problems elegantly +- [ ] **Obvious choice** - a tool an experienced Python developer would name unprompted for this use case +- [ ] **Challenger** - not yet the obvious choice, but a credible successor to one. Give adoption-trajectory evidence, not popularity alone. Explain: -## How It Differs +## Displacement -If similar entries exist, what makes this one unique? +If the use case is at its cap, name the entry your project replaces and argue that yours does that entry's job better. One in, one out. From 68b15a4644eefab12c849c3cf5b9c05e976b482e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:13:43 +0800 Subject: [PATCH 0804/1130] docs: rephrase obvious-choice criterion in PR template Reuses CONTRIBUTING.md's plainer 'would name when asked' phrasing instead of 'unprompted', per maintainer feedback that 'unprompted' didn't sound right. Co-Authored-By: Claude --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 865e26ad..41890f46 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,7 +16,7 @@ Pick one: -- [ ] **Obvious choice** - a tool an experienced Python developer would name unprompted for this use case +- [ ] **Obvious choice** - a tool an experienced Python developer would name when asked "what do I use for this?" - [ ] **Challenger** - not yet the obvious choice, but a credible successor to one. Give adoption-trajectory evidence, not popularity alone. Explain: From 08fdfa6a88ac1d403d36dd3b2c23b7ed74723012 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:14:36 +0800 Subject: [PATCH 0805/1130] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5540980d..71867232 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Awesome Python](https://awesome-python.com/) -An opinionated guide to the best Python frameworks, libraries, tools, and resources. +The definitive list that answers "I want to do X in Python, which tool should I use?" - An opinionated guide to the best Python frameworks, libraries, tools, and projects. **Visit the [website](https://awesome-python.com/) to search and filter projects more easily.** From 0e9ae0cb04c04a4390dba589c89500eac132504e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:25:10 +0800 Subject: [PATCH 0806/1130] clean up --- CONTRIBUTING.md | 4 ++-- docs/audit-logs.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1678cbd5..e90db05a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,7 +18,7 @@ A **use case** is one distinct job a reader needs done. Use cases are defined by Each use case lists at most: -- **Up to 3 obvious choices** — tools an experienced Python developer would name unprompted when asked "what do I use for this?" +- **Up to 3 obvious choices** — tools an experienced Python developer would name when asked "what do I use for this?" - **Up to 2 challengers** — tools that are not yet the obvious choice but are credible successors to one. Admission as a challenger requires adoption-trajectory evidence, not popularity alone. Hard maximum: 5 entries per use case. This is a qualitative bar first and a numeric backstop second — most use cases should carry fewer. @@ -27,7 +27,7 @@ Hard maximum: 5 entries per use case. This is a qualitative bar first and a nume **Displacement**: once a use case is at its cap, the only way in is to name the entry your project replaces and argue that yours does that entry's job better. One in, one out. -**Dual-listing**: a tool may hold entries in multiple use cases, but only when it earns its slot in each independently — uv sits in both Environment Management and Package Management. List the full entry in each home with identical lines; never a "see X above" note, since the website only renders list items. Description edits update every copy in the same commit. Each slot is audited on its own: dropping one home keeps the other, and dropping the tool entirely removes all copies in one commit. Dual-listing is a maintainer decision; a PR adding a second home for an existing entry is treated as a duplicate. +**Dual-listing**: a tool may hold entries in multiple use cases, but only when it earns its slot in each independently. List the full entry in each home with identical lines; never a "see X above" note, since the website only renders list items. Description edits update every copy in the same commit. Each slot is audited on its own: dropping one home keeps the other, and dropping the tool entirely removes all copies in one commit. Dual-listing is a maintainer decision; a PR adding a second home for an existing entry is treated as a duplicate. **Standard library**: a standard-library module is listed only where the stdlib is itself the obvious choice for the use case (tomllib yes, unittest no). diff --git a/docs/audit-logs.md b/docs/audit-logs.md index 93fad35e..80407a2f 100644 --- a/docs/audit-logs.md +++ b/docs/audit-logs.md @@ -26,8 +26,8 @@ Display names follow the canonical PyPI package name. These entries keep a diffe These entries sit past the 12-month activity requirement without an override. Each one is kept by editorial judgment: mature, stable, and no successor exists. -- sortedcontainers -- jinja +- ftfy - itsdangerous - jieba -- ftfy +- jinja +- sortedcontainers From f13075b2aff0414c088520db1b6e274f3f623642 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:44:38 +0800 Subject: [PATCH 0807/1130] refactor: re-home aws-sdk-pandas from pandas sub-item to ETL General Sub-item policy reserves sub-items for awesome-* links. aws-sdk-pandas promoted out as awswrangler in Data Ingestion / ETL > General (85.3M downloads/mo, 10x dlt, active). Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71867232..5043d37f 100644 --- a/README.md +++ b/README.md @@ -480,7 +480,6 @@ _Libraries for serializing complex data types._ _Libraries for data analysis._ - [pandas](https://github.com/pandas-dev/pandas) - A library providing high-performance, easy-to-use data structures and data analysis tools. - - [aws-sdk-pandas](https://github.com/aws/aws-sdk-pandas) - [polars](https://github.com/pola-rs/polars) - A fast DataFrame library implemented in Rust with a Python API. - [ibis-framework](https://github.com/ibis-project/ibis) - A portable Python dataframe library with a single API for 20+ backends. @@ -489,6 +488,7 @@ _Libraries for data analysis._ _Libraries for data extraction, transformation, and loading pipelines across multiple sources and destinations._ - General + - [awswrangler](https://github.com/aws/aws-sdk-pandas) - Pandas integration with AWS services like Athena, Glue, Redshift, S3, and DynamoDB. - [dlt](https://github.com/dlt-hub/dlt) - A Python library for building data pipelines with automatic schema inference, incremental loading, and support for multiple sources and destinations. - [pathway](https://github.com/pathwaycom/pathway) - Python ETL framework for stream processing, real-time analytics, LLM pipelines, and RAG. - Financial Data From 63a1eadd30755834dc4ede328831f4469f72e6c1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:45:26 +0800 Subject: [PATCH 0808/1130] docs: remove typeshed sub-item from under mypy Not a tool readers install: type checkers bundle it automatically as a stub collection, it has no PyPI package, and no standalone use case. The Type Checkers subcategory label already links to awesome-python-typing for ecosystem depth. Co-Authored-By: Claude --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5043d37f..949de42f 100644 --- a/README.md +++ b/README.md @@ -617,7 +617,6 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [rope](https://github.com/python-rope/rope) - Rope is a python refactoring library. - Type Checkers - [awesome-python-typing](https://github.com/typeddjango/awesome-python-typing) - [mypy](https://github.com/python/mypy) - Check variable types during compile time. - - [typeshed](https://github.com/python/typeshed) - [ty](https://github.com/astral-sh/ty) - An extremely fast Python type checker and language server. - [pyright](https://github.com/microsoft/pyright) - Full-featured static type checker for Python from Microsoft, the engine behind Pylance. - [pyrefly](https://github.com/facebook/pyrefly) - A fast type checker and language server for Python. From b0434abae6efdaf907f11394a2b7660510bfd20a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:45:57 +0800 Subject: [PATCH 0809/1130] refactor: promote mkdocs-material to full Documentation entry Was a sub-item under mkdocs. By downloads it ranks second in the section at 17.6M/mo (ClickPy), above mkdocs' 17.4M, and it powers FastAPI, Pydantic, and Ruff/Polars docs (27,269 stars, pushed 2026-08-09). Documentation now sits at its 5-entry cap. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 949de42f..975fd976 100644 --- a/README.md +++ b/README.md @@ -690,8 +690,8 @@ _Libraries for generating project documentation._ - [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. - [awesome-sphinxdoc](https://github.com/ygzgxyz/awesome-sphinxdoc) +- [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - A documentation framework and Material Design theme built on MkDocs. - [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. - - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. - [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. From 49b229bab454e63396209105311b22f96d23af96 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:46:31 +0800 Subject: [PATCH 0810/1130] refactor: promote flower from celery sub-item to full DevOps Monitoring entry Flower isn't a task queue, so nesting it under celery misclassified it; Task Queues is also at its entry cap. Monitoring and Processes is its honest home, ranking fourth by downloads (12.35M/mo ClickPy, between supervisor 17.0M and sh 11.8M), and Celery's own docs name it the recommended monitor. Repo pushed 2026-08-16 with 7,232 stars. This fills Monitoring and Processes to its 5-entry cap. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 975fd976..8201f704 100644 --- a/README.md +++ b/README.md @@ -718,6 +718,7 @@ _Software and libraries for DevOps._ - [psutil](https://github.com/giampaolo/psutil) - A cross-platform process and system utilities module. - [sentry-sdk](https://github.com/getsentry/sentry-python) - Sentry SDK for Python. - [supervisor](https://github.com/Supervisor/supervisor) - Supervisor process control system for UNIX. + - [flower](https://github.com/mher/flower) - A real-time monitor and web admin for Celery task queues. - [sh](https://github.com/amoffat/sh) - A full-fledged subprocess replacement for Python. - Other - [borgbackup](https://github.com/borgbackup/borg) - A deduplicating archiver with compression and encryption. @@ -738,7 +739,6 @@ _Frameworks and libraries for Distributed Computing._ _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) - [rq](https://github.com/rq/rq) - Simple job queues for Python. - [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. From ecd9dea9e41c189b6881c09455f1226bf98e1de3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:47:02 +0800 Subject: [PATCH 0811/1130] refactor: move pyenv-win from pyenv sub-item to full entry Re-homed pyenv-win from a pyenv sub-item (Environment Management) to a full entry in Microsoft Windows, placed before winpython by downloads (25.8k/mo vs 172). Actively maintained, pushed 2026-08-14, 7,360 stars. Maintainer preference is to move sub-items to a fitting category rather than delete. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8201f704..a001b8f0 100644 --- a/README.md +++ b/README.md @@ -1041,7 +1041,6 @@ _Libraries for Python version and virtual environment management._ - [virtualenv](https://github.com/pypa/virtualenv) - A tool to create isolated Python environments. - [uv](https://github.com/astral-sh/uv) - An extremely fast Python version, package and project manager, written in Rust. - [pyenv](https://github.com/pyenv/pyenv) - Simple Python version management. - - [pyenv-win](https://github.com/pyenv-win/pyenv-win) ### Package Management @@ -1138,6 +1137,7 @@ _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. +- [pyenv-win](https://github.com/pyenv-win/pyenv-win) - A Python version manager for Windows ([pyenv](https://github.com/pyenv/pyenv) fork). - [winpython](https://github.com/winpython/winpython) - Portable development environment for Windows 10/11. ### Miscellaneous From c39e4d6bf5246034952536d988ebdbae4001cb7e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:47:34 +0800 Subject: [PATCH 0812/1130] docs: restrict sub-items to awesome-* also-see links Removed the companion-project clause from the Sub-item definition in CONTEXT.md's vocabulary. Its examples (aws-sdk-pandas under pandas, flower under celery) went stale this sitting: those companions were promoted, re-homed, or deleted. Per the maintainer's 2026-08-16 policy decision, sub-items are now reserved for awesome-* also-see links only - a companion project must earn a full Entry in its proper Use Case or not be listed. Co-Authored-By: Claude --- CONTEXT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTEXT.md b/CONTEXT.md index cda364e5..434afbc7 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -10,7 +10,7 @@ The editorial domain of awesome-python: which projects the list carries, how the A single listed project: `- [name](url) - Description.` The unit that is admitted, displaced, or pruned. Named by PyPI package name when one exists, else by repository name. **Sub-item**: -An indented link under an Entry (awesome-django under django, aws-sdk-pandas under pandas, flower under celery). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement, for a companion project whose job is inseparable from its parent's (flower monitors celery) or an awesome-* also-see list. +An indented awesome-* also-see link under an Entry (awesome-django under django, awesome-pytest under pytest). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement, and only for awesome-* lists: a companion project either earns a full Entry in its proper Use Case or is not listed. **Thematic Group**: A bold group line (e.g. "AI & ML", "Web Development") that clusters Sections in the TOC and the Projects body. From 24d6c2896b2a5b9bd24518231465c0f46b1ba8c0 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:58:34 +0800 Subject: [PATCH 0813/1130] build: upgrade project to Python 3.14 watchdog 6.0.0 (last release 2024-11-01) ships no cp314 macOS wheel, and uv has no per-package build allowlist under no-build = true, so the preview file watcher moves to watchfiles, which ships cp314 wheels. watchfiles now lives in its own preview dependency group. UV_PYTHON=3.13 is no longer needed on machines that only have 3.14. Co-Authored-By: Claude --- AGENTS.md | 4 --- CLAUDE.md | 4 --- Makefile | 7 ++-- pyproject.toml | 7 ++-- uv.lock | 90 +++++++++++++++++++++++++++----------------------- 5 files changed, 55 insertions(+), 57 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 14695865..31f45e60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,3 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour - Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). `website/templates/sponsorship.html` separately defines the sponsorship content on the published website page. - -## Gotchas - -- On machines with only Python 3.14, prefix uv-based make targets with `UV_PYTHON=3.13` — watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`. diff --git a/CLAUDE.md b/CLAUDE.md index 1cf71b73..5da7ad70 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,3 @@ An opinionated guide to the best Python frameworks, libraries, tools, and resour - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. Cross-section re-homes ride the originating audit's commit (both sides of the move in one diff). - Resources sections are not project entries: out of audit scope, and the website never parses them. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). - -## Gotchas - -- On machines with only Python 3.14, prefix uv-based make targets with `UV_PYTHON=3.13` — watchdog 6.0.0 ships no 3.14 wheel and the project sets `no-build`. diff --git a/Makefile b/Makefile index 6430945e..8debe6a9 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,7 @@ build: uv run python website/build.py preview: build - uv run watchmedo shell-command \ - --patterns='*.md;*.html;*.css;*.js;*.py' \ - --recursive \ - --wait --drop \ - --command='uv run python website/build.py' \ + uv run watchfiles \ + '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 diff --git a/pyproject.toml b/pyproject.toml index a1f09178..86ee03bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "An opinionated list of awesome Python frameworks, libraries, soft authors = [{ name = "Vinta Chen", email = "vinta.chen@gmail.com" }] readme = "README.md" license = "MIT" -requires-python = ">=3.13" +requires-python = ">=3.14" dependencies = [] [project.urls] @@ -16,11 +16,12 @@ Repository = "https://github.com/vinta/awesome-python" build = ["httpx==0.28.1", "jinja2==3.1.6", "markdown-it-py==4.0.0"] lint = ["ruff==0.15.6", "ty==0.0.33"] test = ["pytest==9.0.3"] +preview = ["watchfiles==1.2.0"] dev = [ { include-group = "build" }, { include-group = "lint" }, { include-group = "test" }, - "watchdog==6.0.0", + { include-group = "preview" }, ] [tool.uv] @@ -34,7 +35,7 @@ only-binary = [":all:"] line-length = 200 [tool.ty.environment] -python-version = "3.13" +python-version = "3.14" root = ["website"] [tool.ty.terminal] diff --git a/uv.lock b/uv.lock index 6ac5047c..c7651f9b 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.13" +requires-python = ">=3.14" [options] exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. @@ -36,12 +36,15 @@ dev = [ { name = "pytest" }, { name = "ruff" }, { name = "ty" }, - { name = "watchdog" }, + { name = "watchfiles" }, ] lint = [ { name = "ruff" }, { name = "ty" }, ] +preview = [ + { name = "watchfiles" }, +] test = [ { name = "pytest" }, ] @@ -61,12 +64,13 @@ dev = [ { name = "pytest", specifier = "==9.0.3" }, { name = "ruff", specifier = "==0.15.6" }, { name = "ty", specifier = "==0.0.33" }, - { name = "watchdog", specifier = "==6.0.0" }, + { name = "watchfiles", specifier = "==1.2.0" }, ] lint = [ { name = "ruff", specifier = "==0.15.6" }, { name = "ty", specifier = "==0.0.33" }, ] +preview = [{ name = "watchfiles", specifier = "==1.2.0" }] test = [{ name = "pytest", specifier = "==9.0.3" }] [[package]] @@ -172,28 +176,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, @@ -320,22 +302,48 @@ wheels = [ ] [[package]] -name = "watchdog" -version = "6.0.0" +name = "watchfiles" +version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, ] From 0d13cd27f1ffdb457481a84bb88857d505e70d81 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 16:59:09 +0800 Subject: [PATCH 0814/1130] build: use >= floors instead of == pins for dependency groups Exact reproducibility already lives in uv.lock via 'uv sync --locked', so == in pyproject.toml only duplicates the lockfile and blocks 'uv lock --upgrade'. Locked versions are unchanged. Co-Authored-By: Claude --- pyproject.toml | 8 ++++---- uv.lock | 28 ++++++++++++++-------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 86ee03bf..30ce8fc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,10 +13,10 @@ 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", "ty==0.0.33"] -test = ["pytest==9.0.3"] -preview = ["watchfiles==1.2.0"] +build = ["httpx>=0.28.1", "jinja2>=3.1.6", "markdown-it-py>=4.0.0"] +lint = ["ruff>=0.15.6", "ty>=0.0.33"] +test = ["pytest>=9.0.3"] +preview = ["watchfiles>=1.2.0"] dev = [ { include-group = "build" }, { include-group = "lint" }, diff --git a/uv.lock b/uv.lock index c7651f9b..a3d6793c 100644 --- a/uv.lock +++ b/uv.lock @@ -53,25 +53,25 @@ test = [ [package.metadata.requires-dev] build = [ - { name = "httpx", specifier = "==0.28.1" }, - { name = "jinja2", specifier = "==3.1.6" }, - { name = "markdown-it-py", specifier = "==4.0.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "markdown-it-py", specifier = ">=4.0.0" }, ] dev = [ - { name = "httpx", specifier = "==0.28.1" }, - { name = "jinja2", specifier = "==3.1.6" }, - { name = "markdown-it-py", specifier = "==4.0.0" }, - { name = "pytest", specifier = "==9.0.3" }, - { name = "ruff", specifier = "==0.15.6" }, - { name = "ty", specifier = "==0.0.33" }, - { name = "watchfiles", specifier = "==1.2.0" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "markdown-it-py", specifier = ">=4.0.0" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "ruff", specifier = ">=0.15.6" }, + { name = "ty", specifier = ">=0.0.33" }, + { name = "watchfiles", specifier = ">=1.2.0" }, ] lint = [ - { name = "ruff", specifier = "==0.15.6" }, - { name = "ty", specifier = "==0.0.33" }, + { name = "ruff", specifier = ">=0.15.6" }, + { name = "ty", specifier = ">=0.0.33" }, ] -preview = [{ name = "watchfiles", specifier = "==1.2.0" }] -test = [{ name = "pytest", specifier = "==9.0.3" }] +preview = [{ name = "watchfiles", specifier = ">=1.2.0" }] +test = [{ name = "pytest", specifier = ">=9.0.3" }] [[package]] name = "certifi" From e9329d4d1e2497f7b14c9885004419b343a03421 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 17:50:33 +0800 Subject: [PATCH 0815/1130] fix: correct hydra-core repo URL to facebookresearch/hydra The entry linked hydra-ecosystem/hydra, an unrelated W3C Hydra API toolkit, while the entry name and description describe facebookresearch's Hydra configuration framework, mixing the wrong repo's stars with the right package's identity. Found during the downloads-column identity sweep. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a001b8f0..f510f538 100644 --- a/README.md +++ b/README.md @@ -1085,7 +1085,7 @@ _Libraries for storing and parsing configuration options._ - [configparser](https://docs.python.org/3/library/configparser.html) - (Python standard library) INI file parser. - [python-dotenv](https://github.com/theskumar/python-dotenv) - Reads key-value pairs from a `.env` file and sets them as environment variables. - [pydantic-settings](https://github.com/pydantic/pydantic-settings) - Settings management using Pydantic models with validation, loading from environment variables and secrets files. -- [hydra-core](https://github.com/hydra-ecosystem/hydra) - Hydra is a framework for elegantly configuring complex applications. +- [hydra-core](https://github.com/facebookresearch/hydra) - Hydra is a framework for elegantly configuring complex applications. - [dynaconf](https://github.com/dynaconf/dynaconf) - Dynaconf is a configuration manager with plugins for Django, Flask and FastAPI. **Security** From b440e64cd0a5cd75ebff0029b250c43b4137d70f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 17:51:11 +0800 Subject: [PATCH 0816/1130] fix: resolve PyPI download counts through curated package overrides A pypi.org identity sweep of all 438 cached rows (project_urls/home_page vs entry GitHub URL) found download counts were looked up by README display name, so entries whose name differs from the canonical package silently measured squatters or dead predecessors: pytorch measured a squatter (169,737/mo vs torch's 94M), jinja measured Jinja1 (3,168 vs jinja2's 736M), django-rest-framework a dead alias package (real: djangorestframework), django-rules an abandoned fork (real: rules), strawberry an unrelated bookmarking service (real: strawberry-graphql), devpi a deprecated metapackage (mapped to devpi-server). New curated website/data/pypi_name_overrides.json maps normalized README name to the real package, or null for projects not pip-installable whose name is squatted or a relic (cpython, pyenv, renpy, python-patterns, winpython); also maps mem0 to mem0ai, fasthtml to python-fasthtml, and playwright-python to playwright. All three fetch scripts resolve names through it; the clickpy TSV cache gains a package column recording what each row actually measured. .gitignore switches website/data/ to website/data/* with a negation so the curated overrides file is tracked while caches stay ignored. Co-Authored-By: Claude --- .gitignore | 3 +- website/data/pypi_name_overrides.json | 16 +++++++ website/fetch_pypi_downloads_via_bigquery.py | 16 +++++-- website/fetch_pypi_downloads_via_clickpy.py | 44 ++++++++++++++------ website/fetch_pypi_downloads_via_pepy.py | 13 ++++-- 5 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 website/data/pypi_name_overrides.json diff --git a/.gitignore b/.gitignore index 1f2c6f1e..ba307609 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,8 @@ __pycache__/ # website website/output/ -website/data/ +website/data/* +!website/data/pypi_name_overrides.json # agents .playwright-cli/ diff --git a/website/data/pypi_name_overrides.json b/website/data/pypi_name_overrides.json new file mode 100644 index 00000000..47a400a0 --- /dev/null +++ b/website/data/pypi_name_overrides.json @@ -0,0 +1,16 @@ +{ + "cpython": null, + "devpi": "devpi-server", + "django-rest-framework": "djangorestframework", + "django-rules": "rules", + "fasthtml": "python-fasthtml", + "jinja": "jinja2", + "mem0": "mem0ai", + "playwright-python": "playwright", + "pyenv": null, + "python-patterns": null, + "pytorch": "torch", + "renpy": null, + "strawberry": "strawberry-graphql", + "winpython": null +} diff --git a/website/fetch_pypi_downloads_via_bigquery.py b/website/fetch_pypi_downloads_via_bigquery.py index 21f69479..e8b5bbfc 100644 --- a/website/fetch_pypi_downloads_via_bigquery.py +++ b/website/fetch_pypi_downloads_via_bigquery.py @@ -3,7 +3,8 @@ Queries the canonical source ClickPy mirrors — `bigquery-public-data.pypi.file_downloads` via the `bq` CLI — and prints -namecount TSV to stdout. Maintainer-local (needs a personal GCP +namecount TSV to stdout. Names resolve through +data/pypi_name_overrides.json, matching the cache sweep. Maintainer-local (needs a personal GCP account) and print-only: data/pypi_downloads.tsv is written solely by fetch_pypi_downloads_via_clickpy.py. @@ -27,7 +28,7 @@ import subprocess import sys from json import loads -from fetch_pypi_downloads_via_clickpy import normalize +from fetch_pypi_downloads_via_clickpy import resolve MAX_BYTES_BILLED = 400_000_000_000 @@ -59,7 +60,16 @@ def fetch_bigquery(names: list[str], dry_run: bool) -> dict[str, int]: def main() -> None: dry_run = "--dry-run" in sys.argv - names = sorted({normalize(arg) for arg in sys.argv[1:] if arg != "--dry-run"}) + names = set() + for arg in sys.argv[1:]: + if arg == "--dry-run": + continue + pkg = resolve(arg) + if pkg is None: + print(f"{arg}: not pip-installable per pypi_name_overrides.json, skipping", file=sys.stderr) + else: + names.add(pkg) + names = sorted(names) if not names: print("Usage: python fetch_pypi_downloads_via_bigquery.py [--dry-run] NAME [NAME ...]", file=sys.stderr) sys.exit(1) diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index 1e7c1a26..6934dc01 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -13,20 +13,24 @@ excludes mirrors by default. This script is the sole writer of data/pypi_downloads.tsv and rewrites it from scratch each run, so entries removed from README.md drop out naturally; names with no PyPI rows are written as NOT_FOUND. Counts are -looked up by README display name: when the display name differs from the -canonical PyPI package, the row silently measures the wrong package (a -squatter or a dead predecessor) — verify identity at -pypi.org/pypi/{name}/json before citing a count. The file -starts with a header row (name, downloads, fetched_at) and every row -carries the sweep date: a cache fetched within the last 7 days is current -enough for audit verdicts, so only re-run when older. Cross-checks -against other sources (fetch_pypi_downloads_via_bigquery.py, +looked up by README display name; when the display name differs from the +canonical PyPI package (a squatter or a dead predecessor would be +measured otherwise), add the mapping to the curated +data/pypi_name_overrides.json — normalized README name to the real +package, or null for projects that are not pip-installable so their +row is never queried. The file starts with a header row (name, package, +downloads, fetched_at) — package is the PyPI package the row actually +measured ("-" for null overrides) — and every row carries the sweep +date: a cache fetched within the last 7 days is current enough for +audit verdicts, so only re-run when older. Cross-checks against other +sources (fetch_pypi_downloads_via_bigquery.py, fetch_pypi_downloads_via_pepy.py) print to stdout and never touch the cache. Usage: python fetch_pypi_downloads_via_clickpy.py """ +import json import re from datetime import date from pathlib import Path @@ -36,6 +40,7 @@ from readme_parser import parse_readme DATA_DIR = Path(__file__).parent / "data" OUT_FILE = DATA_DIR / "pypi_downloads.tsv" +OVERRIDES_FILE = DATA_DIR / "pypi_name_overrides.json" README_PATH = Path(__file__).parent.parent / "README.md" CLICKPY_URL = "https://sql-clickhouse.clickhouse.com/?user=demo" @@ -47,6 +52,16 @@ def normalize(name: str) -> str: return re.sub(r"[-_.]+", "-", name.lower()) +def load_overrides() -> dict[str, str | None]: + return json.loads(OVERRIDES_FILE.read_text()) + + +def resolve(name: str) -> str | None: + """Map a README display name to the PyPI package to measure. None = not pip-installable.""" + normalized = normalize(name) + return load_overrides().get(normalized, normalized) + + def collect_names(readme_text: str) -> list[str]: names = set() for group in parse_readme(readme_text): @@ -73,12 +88,15 @@ def fetch_clickpy(names: list[str]) -> dict[str, int]: def main() -> None: names = collect_names(README_PATH.read_text()) - print(f"Querying {len(names)} package names...") - counts = fetch_clickpy(names) + overrides = load_overrides() + packages = {name: overrides.get(name, name) for name in names} + query_names = sorted({pkg for pkg in packages.values() if pkg}) + print(f"Querying {len(query_names)} package names...") + counts = fetch_clickpy(query_names) fetched_at = date.today().isoformat() - rows = "\n".join(f"{name}\t{counts.get(name, 'NOT_FOUND')}\t{fetched_at}" for name in names) - OUT_FILE.write_text(f"name\tdownloads\tfetched_at\n{rows}\n") - print(f"Done. {len(counts)}/{len(names)} names found on PyPI. Cached to {OUT_FILE}") + rows = "\n".join(f"{name}\t{pkg or '-'}\t{counts.get(pkg, 'NOT_FOUND') if pkg else 'NOT_FOUND'}\t{fetched_at}" for name, pkg in packages.items()) + OUT_FILE.write_text(f"name\tpackage\tdownloads\tfetched_at\n{rows}\n") + print(f"Done. {len(counts)}/{len(query_names)} names found on PyPI. Cached to {OUT_FILE}") if __name__ == "__main__": diff --git a/website/fetch_pypi_downloads_via_pepy.py b/website/fetch_pypi_downloads_via_pepy.py index 998f260f..55eb7593 100644 --- a/website/fetch_pypi_downloads_via_pepy.py +++ b/website/fetch_pypi_downloads_via_pepy.py @@ -7,7 +7,8 @@ this script sleeps 12s between requests and 530 names would take ~2 hours (use fetch_pypi_downloads_via_clickpy.py for bulk). Reads PEPY_TECH_API_KEY from the environment, falling back to the repo-root .env. The v2 endpoint returns ~90 days of per-day per-version counts; this script sums the most recent -30 days present in the response across all versions. pepy counts include +30 days present in the response across all versions. Names resolve +through data/pypi_name_overrides.json, matching the cache sweep. pepy counts include mirror/CI traffic (CI filtering is a paid pepy feature), matching the ClickPy/BigQuery figures; pypistats.org excludes mirrors, so never mix the two in one comparison. Results print to stdout as TSV and are not @@ -22,7 +23,7 @@ import time from pathlib import Path import httpx -from fetch_pypi_downloads_via_clickpy import normalize +from fetch_pypi_downloads_via_clickpy import resolve ENV_FILE = Path(__file__).parent.parent / ".env" PEPY_URL = "https://api.pepy.tech/api/v2/projects/{name}" @@ -51,7 +52,13 @@ def main() -> None: if len(sys.argv) < 2: print("Usage: python fetch_pypi_downloads_via_pepy.py NAME [NAME ...]", file=sys.stderr) sys.exit(1) - names = [normalize(name) for name in sys.argv[1:]] + names = [] + for arg in sys.argv[1:]: + pkg = resolve(arg) + if pkg is None: + print(f"{arg}: not pip-installable per pypi_name_overrides.json, skipping", file=sys.stderr) + else: + names.append(pkg) with httpx.Client(headers={"X-API-Key": load_api_key()}, timeout=30) as client: for i, name in enumerate(names): if i: From cc804b1de2b85e7dcc46f04e4c334f6d37d4cbca Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 17:52:04 +0800 Subject: [PATCH 0817/1130] feat: add Downloads/Month column to website table Sourced from website/data/pypi_downloads.tsv the same way github_stars.json feeds the stars column. The new sortable column sits between GitHub Stars and Last Commit on the homepage and category pages, formatted with thousands separators like stars, with an em dash when no PyPI data exists. Rows are matched by normalized README display name; Built-in entries never show counts since same-named PyPI packages are stdlib backports (e.g. the asyncio package). Below 960px the column hides and the count moves into the expand row, mirroring the existing Last Commit treatment. main.js gains the downloads sort branch and URL param. The deploy workflow fetches the TSV via the new make fetch_pypi_downloads target with a daily actions/cache fallback, mirroring the stars fetch, but non-fatal: the column degrades to dashes when the fetch fails, unlike stars which the build requires. Co-Authored-By: Claude --- .github/workflows/deploy-website.yml | 19 +++++++++++ Makefile | 3 ++ website/build.py | 23 +++++++++++++ website/static/main.js | 13 +++++++- website/static/style.css | 17 ++++++++-- website/templates/category.html | 15 +++++++-- website/templates/index.html | 15 +++++++-- website/tests/test_build.py | 49 ++++++++++++++++++++++++++++ 8 files changed, 146 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index dd748bb5..0c23761c 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -71,6 +71,25 @@ jobs: fi python -m json.tool website/data/github_stars.json > /dev/null + - name: Restore PyPI download data cache + uses: actions/cache/restore@v4 + with: + path: website/data/pypi_downloads.tsv + key: pypi-downloads-${{ steps.date.outputs.today }} + restore-keys: pypi-downloads- + + - name: Fetch PyPI downloads + id: fetch-downloads + continue-on-error: true + run: make fetch_pypi_downloads + + - name: Save PyPI download data cache + if: steps.fetch-downloads.outcome == 'success' + uses: actions/cache/save@v4 + with: + path: website/data/pypi_downloads.tsv + key: pypi-downloads-${{ steps.date.outputs.today }} + - name: Build website run: make build diff --git a/Makefile b/Makefile index 8debe6a9..5a07ad91 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ install: fetch_github_stars: uv run python website/fetch_github_stars.py +fetch_pypi_downloads: + uv run python website/fetch_pypi_downloads_via_clickpy.py + test: uv run pytest website/tests/ -v diff --git a/website/build.py b/website/build.py index 347c4fb0..cba524fa 100644 --- a/website/build.py +++ b/website/build.py @@ -11,6 +11,7 @@ from datetime import UTC, datetime from pathlib import Path from typing import TypedDict +from fetch_pypi_downloads_via_clickpy import normalize from jinja2 import Environment, FileSystemLoader from readme_parser import AlsoSee, ParsedGroup, ParsedSection, parse_readme, parse_sponsors, slugify @@ -52,6 +53,7 @@ class TemplateEntry(TypedDict): groups: list[str] subcategories: list[TemplateSubcategory] stars: int | None + downloads: int | None owner: str | None last_commit_at: str | None source_type: str | None @@ -96,6 +98,22 @@ def load_stars(path: Path) -> dict[str, dict]: return {} +def load_downloads(path: Path) -> dict[str, int]: + """Load last-30-day download counts from the TSV cache, keyed by normalized README name. + + Columns: name, package, downloads, fetched_at. Skips the header and + NOT_FOUND rows. Returns empty dict if the file doesn't exist. + """ + if not path.exists(): + return {} + downloads: dict[str, int] = {} + for line in path.read_text(encoding="utf-8").splitlines()[1:]: + parts = line.split("\t") + if len(parts) >= 3 and parts[2].isdigit(): + downloads[parts[0]] = int(parts[2]) + return downloads + + def sort_entries(entries: Sequence[TemplateEntry]) -> list[TemplateEntry]: """Sort entries by stars descending, then name ascending. @@ -481,6 +499,7 @@ def extract_entries( groups=[], subcategories=[], stars=None, + downloads=None, owner=None, last_commit_at=None, source_type=detect_source_type(entry["url"]), @@ -535,6 +554,7 @@ def build(repo_root: Path) -> None: build_date = datetime.now(UTC) stars_data = load_stars(website / "data" / "github_stars.json") + downloads_data = load_downloads(website / "data" / "pypi_downloads.tsv") repo_self = stars_data.get("vinta/awesome-python", {}) repo_stars = None @@ -551,6 +571,9 @@ def build(repo_root: Path) -> None: entry["stars"] = sd["stars"] entry["owner"] = sd["owner"] entry["last_commit_at"] = sd.get("last_commit_at", "") + # Built-in entries would hit same-named PyPI backports (e.g. asyncio), not the stdlib. + if entry.get("source_type") != "Built-in": + entry["downloads"] = downloads_data.get(normalize(entry["name"])) entries = sort_entries(entries) category_urls = {cat["name"]: category_path(cat) for cat in categories} diff --git a/website/static/main.js b/website/static/main.js index d5b337b3..073bb6c4 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -238,6 +238,14 @@ function getSortValue(row, col) { const num = parseInt(text, 10); return isNaN(num) ? -1 : num; } + if (col === "downloads") { + const text = row + .querySelector(".col-downloads") + .textContent.trim() + .replace(/,/g, ""); + const num = parseInt(text, 10); + return isNaN(num) ? -1 : num; + } if (col === "commit-time") { const attr = row.querySelector(".col-commit").getAttribute("data-commit"); return attr ? new Date(attr).getTime() : 0; @@ -467,7 +475,10 @@ if (backToTop) { const order = params.get("order"); if (q && searchInput) searchInput.value = q; if ( - (sort === "name" || sort === "stars" || sort === "commit-time") && + (sort === "name" || + sort === "stars" || + sort === "downloads" || + sort === "commit-time") && (order === "desc" || order === "asc") ) { activeSort = { col: sort, order: order }; diff --git a/website/static/style.css b/website/static/style.css index 93056570..1cd3a9f9 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -893,6 +893,14 @@ th[data-sort].sort-asc::after { letter-spacing: 0.02em; } +.col-downloads { + width: 9.5rem; + text-align: right; + white-space: nowrap; + font-variant-numeric: tabular-nums; + color: var(--ink-soft); +} + .col-commit { width: 9rem; white-space: nowrap; @@ -1041,7 +1049,8 @@ th[data-sort].sort-asc::after { color: var(--line-strong); } -.expand-commit { +.expand-commit, +.expand-downloads { display: none; } @@ -1606,11 +1615,13 @@ th[data-sort].sort-asc::after { justify-self: start; } - .col-commit { + .col-commit, + .col-downloads { display: none; } - .expand-commit { + .expand-commit, + .expand-downloads { display: inline; } diff --git a/website/templates/category.html b/website/templates/category.html index 6e4b03e7..51542767 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -120,6 +120,9 @@
    + @@ -156,6 +159,10 @@ >{{ entry.source_type }}{% else %}—{% endif %} + - {% endif %} - + @@ -207,6 +210,10 @@ >{{ entry.source_type }}{% else %}—{% endif %} + - {% endif %} - - + @@ -153,16 +153,16 @@ else %}{{ category.name }}{% endif %} + - + @@ -204,16 +204,16 @@ else %}{{ entry.categories[0] }}{% endif %} + - diff --git a/website/templates/index.html b/website/templates/index.html index 35329f43..2bbe703a 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -175,7 +175,7 @@ {% for entry in entries %} diff --git a/website/tests/test_build.py b/website/tests/test_build.py index e93ca3ce..e930b440 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -445,7 +445,7 @@ class TestBuild: - [My-Lib](https://github.com/org/mylib) - On PyPI. - [no-pypi](https://example.com/none) - Not on PyPI. - - [asyncio](https://docs.python.org/3/library/asyncio.html) - Built-in. + - [asyncio](https://docs.python.org/3/library/asyncio.html) - Stdlib. # Contributing @@ -466,7 +466,7 @@ class TestBuild: html = (tmp_path / "website" / "output" / "index.html").read_text(encoding="utf-8") assert "1,234,567" in html - # Built-in entries never show PyPI counts: the asyncio row is the backport package + # Stdlib entries never show PyPI counts: the asyncio row is the backport package assert "26,305,454" not in html # Default sort: entries with download counts come first assert html.index("My-Lib") < html.index("no-pypi") @@ -1060,7 +1060,7 @@ class TestSortEntries: def test_builtin_between_starred_and_unstarred(self): entries = [ - _template_entry("builtin", None, "Built-in"), + _template_entry("builtin", None, "Stdlib"), _template_entry("starred", 100), _template_entry("unstarred", None), ] @@ -1078,7 +1078,7 @@ class TestDetectSourceType: assert detect_source_type("https://github.com/psf/requests") is None def test_stdlib_url(self): - assert detect_source_type("https://docs.python.org/3/library/asyncio.html") == "Built-in" + assert detect_source_type("https://docs.python.org/3/library/asyncio.html") == "Stdlib" def test_gitlab_url(self): assert detect_source_type("https://gitlab.com/org/repo") == "GitLab" @@ -1167,7 +1167,7 @@ class TestExtractEntries: groups = parse_readme(readme) categories = [c for g in groups for c in g["categories"]] entries = extract_entries(categories, groups) - assert entries[0]["source_type"] == "Built-in" + assert entries[0]["source_type"] == "Stdlib" def test_subcategory_includes_slug_and_url(self): readme = textwrap.dedent("""\ From 733021ae5bceefa4f8fbbde5933273660327847a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 01:09:20 +0800 Subject: [PATCH 0847/1130] feat: add Bundled badge for entries shipped inside a larger project django.db.models, geodjango, httpx.URL and uv audit were rendering "Not on PyPI" alongside eighteen genuinely standalone projects that simply are not packaged on PyPI, conflating two different reasons for a missing download count. These four entries now carry a "(part of X)" description prefix in README.md, mirroring the existing "(Python standard library)" convention. build.py reads that prefix into a bundled flag that both templates render as a "Bundled" badge. The prefix approach was chosen over a separate data file so README.md stays the single source of content truth, and over inferring from the entry name because geodjango is neither dotted nor spaced and would have been missed. Redundant tail wording was trimmed from the httpx.URL, geodjango and uv audit descriptions now that the prefix names the parent. The new entry format is documented in CONTRIBUTING.md and the vocabulary in CONTEXT.md. Co-Authored-By: Claude --- CONTEXT.md | 4 ++++ CONTRIBUTING.md | 8 ++++++++ README.md | 8 ++++---- website/build.py | 4 ++++ website/templates/category.html | 7 +++---- website/templates/index.html | 7 +++---- website/tests/test_build.py | 6 ++++++ 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 434afbc7..8d489c10 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -12,6 +12,10 @@ A single listed project: `- [name](url) - Description.` The unit that is admitte **Sub-item**: An indented awesome-* also-see link under an Entry (awesome-django under django, awesome-pytest under pytest). Not an Entry: it holds no slot, does not count against the Cap, and rides its parent — re-homed or removed with it. Maintainer-only placement, and only for awesome-* lists: a companion project either earns a full Entry in its proper Use Case or is not listed. +**Bundled Entry**: +An Entry for a feature that ships inside a larger library, framework, or tool rather than as its own PyPI package (django.db.models, geodjango, httpx.URL, uv audit). Marked in README.md by a `(part of X)` description prefix, which the website reads to render a "Bundled" badge where a download count would go. It holds a normal slot and is audited like any other Entry; only its download signal is absent. +_Avoid_: Pointer entry (reform-era name) + **Thematic Group**: A bold group line (e.g. "AI & ML", "Web Development") that clusters Sections in the TOC and the Projects body. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e90db05a..d66b00d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,14 @@ Use the **PyPI package name** as the display name so developers can copy it dire - [module](https://docs.python.org/3/library/module.html) - (Python standard library) Description. ``` +### Bundled Feature + +A feature that ships inside a larger library, framework, or tool rather than as its own PyPI package. The `(part of X)` prefix is parsed by the website, which renders a "Bundled" badge in place of a download count, so write it exactly in that form and name the parent as developers say it. + +```markdown +- [name](url) - (part of Parent) Description ending with period. +``` + ### Fork of Another Project ```markdown diff --git a/README.md b/README.md index e056ad84..2243300e 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ _Libraries for working with HTTP._ - [httpx2](https://github.com/pydantic/httpx2) - HTTP/1.1 and HTTP/2 client with sync and async APIs, maintained by Pydantic ([httpx](https://github.com/encode/httpx) fork). - URL Manipulation - [yarl](https://github.com/aio-libs/yarl) - Yet another URL library. - - [httpx.URL](https://www.python-httpx.org/api/) - The immutable URL class bundled with HTTPX. + - [httpx.URL](https://www.python-httpx.org/api/) - (part of HTTPX) The immutable URL class. ### Web Scraping @@ -399,7 +399,7 @@ _Libraries that implement Object-Relational Mapping or data mapping techniques._ - Relational Databases - [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) - The Python SQL Toolkit and Object Relational Mapper. - [awesome-sqlalchemy](https://github.com/dahlia/awesome-sqlalchemy) - - [django.db.models](https://github.com/django/django) - The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/). + - [django.db.models](https://github.com/django/django) - (part of Django) The Django [ORM](https://docs.djangoproject.com/en/dev/topics/db/models/). - [peewee](https://github.com/coleifer/peewee) - A small, expressive ORM. - [sqlmodel](https://github.com/fastapi/sqlmodel) - SQLModel is based on Python type annotations, and powered by Pydantic and SQLAlchemy. - NoSQL Databases @@ -531,7 +531,7 @@ _Libraries for geocoding addresses and working with latitudes and longitudes._ - [geopandas](https://github.com/geopandas/geopandas) - Python tools for geographic data (GeoSeries/GeoDataFrame) built on pandas. - [geopy](https://github.com/geopy/geopy) - Python Geocoding Toolbox. - [geojson](https://github.com/jazzband/geojson) - Python bindings and utilities for GeoJSON. -- [geodjango](https://github.com/django/django) - A world-class geographic web framework that is part of [Django](https://docs.djangoproject.com/en/dev/ref/contrib/gis/). +- [geodjango](https://github.com/django/django) - (part of Django) A world-class [geographic web framework](https://docs.djangoproject.com/en/dev/ref/contrib/gis/). ### Science @@ -1118,7 +1118,7 @@ _Frameworks and tools for penetration testing._ _Tools for auditing dependencies against known vulnerabilities._ - [pip-audit](https://github.com/pypa/pip-audit) - Audits Python environments and dependency trees for known vulnerabilities, using the PyPI Advisory Database and OSV. -- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - uv's built-in dependency vulnerability and malware scanning backed by OSV. +- [uv audit](https://docs.astral.sh/uv/reference/cli/#uv-audit) - (part of uv) Built-in dependency vulnerability and malware scanning backed by OSV. ### Web Security diff --git a/website/build.py b/website/build.py index 8912281f..e3e45ad9 100644 --- a/website/build.py +++ b/website/build.py @@ -31,6 +31,8 @@ SPONSORSHIP_PATH = "/sponsorship/" SPONSORSHIP_PUBLIC_URL = f"{SITE_URL}sponsorship/" SPONSORSHIP_DESCRIPTION = "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use." +BUNDLED_PREFIX_RE = re.compile(r"^\(part of ") + SOURCE_TYPE_DOMAINS = { "docs.python.org": BUILTIN_FILTER, "gitlab.com": "GitLab", @@ -57,6 +59,7 @@ class TemplateEntry(TypedDict): owner: str | None last_commit_at: str | None source_type: str | None + bundled: bool also_see: list[AlsoSee] @@ -520,6 +523,7 @@ def extract_entries( owner=None, last_commit_at=None, source_type=detect_source_type(entry["url"]), + bundled=bool(BUNDLED_PREFIX_RE.match(entry["description"])), also_see=entry["also_see"], ) seen[key] = existing diff --git a/website/templates/category.html b/website/templates/category.html index 6e8c247c..2eca8665 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -156,10 +156,9 @@ {% if entry.description %} - + ' in html + + subcategory_html = (site / "widgets" / "small" / "index.html").read_text(encoding="utf-8") + assert 'data-default-sort="editorial"' in subcategory_html + assert "group-row" not in subcategory_html + assert subcategory_html.index(">w2w1Machine Learning') + dl_heading = html.index('Deep Learning') + assert ml_heading < html.index(">ml1dl1ml1Use w1 for most apps.

    \n

    How to choose:

    \n
      \n
    • Small apps: w1
    • \n
    • Big apps: w2
    • \n
    \n" + assert guide_html == "

    Configure w1 once.

    \n

    Pin w2.

    \n" + assert lead == "Use w1 for most apps." + + def test_keeps_everything_above_table_without_how_to_choose_list(self, tmp_path): + path = tmp_path / "widgets.md" + path.write_text("Use w1.\n\n- Small apps: w1\n\nConfigure w1 once.\n", encoding="utf-8") + intro_html, guide_html, _ = load_category_intro(path) + assert "Configure w1 once." in intro_html + assert guide_html == "" + + def test_returns_empty_strings_without_intro_file(self, tmp_path): + assert load_category_intro(tmp_path / "missing.md") == ("", "", "") From d5febbea1f99eedbe9a97e5dc662e700d49930eb Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 26 Sep 2026 23:34:01 +0800 Subject: [PATCH 0887/1130] docs: document category page group rows and guide band Co-Authored-By: Claude --- DESIGN.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DESIGN.md b/DESIGN.md index 1e678f2b..56ea32bc 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -140,7 +140,7 @@ Hard-won sizing rules (do not relax): Depth comes from **tonal layers**, not heavy shadows. - The page is a quiet warm canvas (`--bg-page`). The content shell is slightly brighter paper (`--bg-paper`). The sponsor band, CTA backgrounds, and inline decorative blocks step up to `--bg-paper-strong`. -- The hero is the one place that uses real atmosphere: subtle grid, slow sheen, warm radial gradients on a dark earthy ground (`--hero-bg-start` → `--hero-bg-mid` → `--hero-bg-end`). The sheen and any other motion respect `prefers-reduced-motion`. +- The hero and the category guide band are the only places with real atmosphere: warm gradients on a dark earthy ground (`--hero-bg-start` → `--hero-bg-mid` → `--hero-bg-end`). Only the hero adds the subtle grid and slow sheen. The sheen and any other motion respect `prefers-reduced-motion`. - The footer is a single tonal block in `--footer-bg`, no internal gradients. - Two depth treatments are allowed and only these two. The search input combines a 1px inset highlight (`--search-inset`) with a soft warm drop shadow (`--search-shadow`, intensified by `--search-focus-shadow` on focus). The primary CTA button (`.hero-action-primary`) carries a warm drop shadow for press affordance. Both shadows are soft, warm-tinted, and tied to interactive elements. No new drop shadows on cards, panels, rows, or static decoration. - No glassmorphism as default decoration. @@ -160,8 +160,11 @@ The shape language is overwhelmingly **pill on small, zero radius on large**. The component vocabulary is small and table-led. Source of truth: `website/static/style.css`. - **Table-driven index** (the hero of the page). Sticky header, sortable columns, click-to-expand rows that indent under the Name column. Modeled on placestoread.xyz. Not a card grid. +- **Group rows**. Section, subcategory, and group pages list rows in README order. Section pages add one H2 row per use case; group pages add one per section, linking to that section's page. A note replaces the sort arrow until someone sorts a column, which flattens the list and hides the group rows. - **Filter tags** (`.tag`). `--accent-soft` background with `--accent-deep` text. Pill shape. Hover swaps to `--highlight` background with `--tag-hover-border` border and ink text. Active state uses the warm `--tag-active-start` → `--tag-active-end` gradient with hero-ink text. Tag variants (`tag-group`, `tag-source`) inherit the base `.tag` style today and differ only at narrow widths (`tag-group` hides under 960px). Add a new variant only when a real visual difference is needed. - **Hero**. Magazine-cover headline, dark earthy ground, kicker and proof microcopy, primary CTA button using `--hero-btn-start` / `--hero-btn-end`. Subtle grid plus slow sheen. Respects `prefers-reduced-motion`. +- **Jump links**. One per use case in a category hero, plus one for the guide. Underlined like the intro's links, with a trailing ↓. Never tag-styled: tags filter the table or open another page, jump links only scroll. +- **Guide band**. Everything in a category intro after its "How to choose" list. It sits below the table on the hero's dark gradient, with its H2 above the text. - **Sponsor band**. Sits in the README header on `--bg-paper-strong`. Editorial layout, not a logo wall. Sponsor links share the global accent treatment. - **CTA**. Warm `--cta-bg`, full-bleed within shell. The button itself uses accent tokens. - **Footer**. Dark warm charcoal, part of the same system. Footer links share the global hover and focus treatment. From 2930319d48384a58f3ebaa921e833caae765b455 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:16:51 +0800 Subject: [PATCH 0888/1130] docs: add CMS category intro Explains when to pick Wagtail (developer-defined page types) versus django CMS (editors composing pages live), based on each project's own documentation. Co-Authored-By: Claude --- website/data/category_intros/cms.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 website/data/category_intros/cms.md diff --git a/website/data/category_intros/cms.md b/website/data/category_intros/cms.md new file mode 100644 index 00000000..fa32524f --- /dev/null +++ b/website/data/category_intros/cms.md @@ -0,0 +1,12 @@ +Both Python CMS picks run on Django: Wagtail for page types your developers define, like articles, and django CMS for editors building pages on the live site. + +How to choose: + +- Content types your developers define, like articles and events: Wagtail +- Editors composing pages from reusable components on the live site: django CMS + +Wagtail is [not an instant website in a box](https://docs.wagtail.org/en/stable/getting_started/the_zen_of_wagtail.html#wagtail-is-not-an-instant-website-in-a-box): expect to write code. Start a project with [`wagtail start`](https://docs.wagtail.org/en/stable/getting_started/quick_install.html). Each page type is [a Django model that inherits from `Page`](https://docs.wagtail.org/en/stable/topics/pages.html), so give each kind of content its own type with its own fields. An event page with a date and a location [can show up in a calendar](https://docs.wagtail.org/en/stable/getting_started/the_zen_of_wagtail.html#a-cms-should-get-information-out-of-an-editor-s-head-and-into-a-database-as-efficiently-and-directly-as-possible), and a styled heading on a generic page can't. Use [StreamField](https://docs.wagtail.org/en/stable/topics/streamfield.html) for pages without a fixed structure, like blog posts, and [snippets](https://docs.wagtail.org/en/stable/topics/snippets/index.html) for content that doesn't need its own page, like headers and footers. For a headless site, use its [built-in API](https://docs.wagtail.org/en/stable/advanced_topics/api/index.html). + +In django CMS, [editors compose pages from plugins](https://docs.django-cms.org/en/stable/explanation/philosophy.html#three-disciplines-three-surfaces) in a toolbar on the live site, and developers write standard Django. Each template [declares its placeholders](https://docs.django-cms.org/en/stable/tutorials/02-templates-placeholders.html) with `{% placeholder %}`, and `CMS_TEMPLATES` lists the templates editors can pick from. For a region that is the same on every page, like a footer, use [`{% static_alias %}`](https://docs.django-cms.org/en/stable/tutorials/02-templates-placeholders.html#a-reusable-region-with-static-alias), so the content is stored once. For content from your own app, ask [where it lives](https://docs.django-cms.org/en/stable/explanation/composition.html). If it fits in a placeholder on a page, write a plugin. If it has its own list view, detail view, and URL, mount it as an app with an apphook. The core [publishes as you edit](https://docs.django-cms.org/en/stable/explanation/publishing.html), which is rarely enough for a production site with editors, so add a versioning package. + +Both are built on Django. Wagtail [deploys like a Django site](https://docs.wagtail.org/en/stable/deployment/index.html). You can add either one to an existing Django project: Wagtail [integrates into one](https://docs.wagtail.org/en/stable/getting_started/integrating_into_django.html), and django CMS [doesn't make you rebuild around it](https://docs.django-cms.org/en/stable/explanation/philosophy.html#implications-for-projects). Neither fits a very small or static site. For a two-page brochure, django CMS calls itself [overkill](https://docs.django-cms.org/en/stable/explanation/philosophy.html#when-django-cms-may-not-be-the-right-fit), and a static site generator is lighter. From 5216fb3c1a0a4c721091a4db219d30ed6b275072 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:17:34 +0800 Subject: [PATCH 0889/1130] docs: add Image Processing category intro Co-Authored-By: Claude --- .../data/category_intros/image-processing.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 website/data/category_intros/image-processing.md diff --git a/website/data/category_intros/image-processing.md b/website/data/category_intros/image-processing.md new file mode 100644 index 00000000..0a7bbcdb --- /dev/null +++ b/website/data/category_intros/image-processing.md @@ -0,0 +1,30 @@ +Pillow handles everyday edits, so it's the default Python image processing library. Use scikit-image for scientific analysis, and pyvips when memory is tight. + +How to choose: + +- Resizing, cropping, and converting images: Pillow +- Scientific image analysis on NumPy arrays: scikit-image +- Large images, or many of them, with little memory: pyvips +- ImageMagick's features from Python: Wand +- Removing photo backgrounds: rembg +- Resizing and cropping images on demand over HTTP: thumbor +- QR codes: qrcode +- Barcodes: python-barcode + +Pillow is [ideal for batch processing](https://pillow.readthedocs.io/en/stable/handbook/overview.html), like making thumbnails and converting between file formats. Open each file in a `with Image.open(path) as im:` block. Opening is [fast and independent of the file size](https://pillow.readthedocs.io/en/stable/handbook/tutorial.html), since Pillow reads the pixels only when it has to. To fit an image to a size, use `ImageOps.contain()`, `cover()`, `fit()`, or `pad()`. `thumbnail()` works too, but it [changes the image in place](https://pillow.readthedocs.io/en/stable/handbook/tutorial.html#relative-resizing). + +scikit-image [aims to be the reference library for scientific image analysis](https://scikit-image.org/docs/stable/about/values.html) in Python, and puts science ahead of photo editing. Images are plain NumPy arrays, so [standard NumPy operations](https://scikit-image.org/docs/stable/user_guide/numpy_images.html) work on them. To change an image's dtype, use `img_as_float()` or `img_as_ubyte()`, [never `astype`](https://scikit-image.org/docs/stable/user_guide/data_types.html), which doesn't rescale the values to the new dtype's range. + +pyvips builds a pipeline of operations and runs it only when you write the result. It streams the image a section at a time, so it [doesn't keep whole images in memory](https://github.com/libvips/pyvips). To shrink an image, use `pyvips.Image.thumbnail()` instead of resizing: it [loads and resizes in one step](https://www.libvips.org/API/current/developer-checklist.html), which is faster and uses less memory. When you read an image from top to bottom, open it with [`access="sequential"`](https://libvips.github.io/pyvips/intro.html). + +Wand is a [ctypes-based ImageMagick binding](https://docs.wand-py.org/en/latest/), so install ImageMagick's MagickWand library first. Its objects are resources like open files: [use them in a `with` block](https://docs.wand-py.org/en/latest/guide/resource.html) so they get closed. Wand's docs say to [never use Wand directly in an HTTP service](https://docs.wand-py.org/en/latest/guide/security.html) or on any public server. Hand the images to a background worker through a queue, and limit ImageMagick's resources and formats in its `policy.xml`. + +rembg runs as a [CLI, a Python library, an HTTP server, or a Docker container](https://github.com/danielgatis/rembg). In code, create a session once with `new_session()` and pass it to each `remove()` call, since `remove` otherwise [starts a new session every call](https://github.com/danielgatis/rembg/blob/main/USAGE.md). The model weights [carry their own licenses](https://github.com/danielgatis/rembg), separate from rembg's MIT license, so check the one you use before you ship it in a commercial product. + +thumbor is an HTTP server: you [set the size and crop in the image URL](https://github.com/thumbor/thumbor), and it [detects faces and important features](https://thumbor.readthedocs.io/en/latest/) to crop around them. Set a `SECURITY_KEY` so [every URL is signed](https://thumbor.readthedocs.io/en/latest/security.html) and nobody can tamper with it, and build those URLs in Python with [libthumbor](https://thumbor.readthedocs.io/en/latest/libraries.html). In production, [turn off `ALLOW_UNSAFE_URL`](https://thumbor.readthedocs.io/en/latest/configuration.html) and run [more than one instance](https://thumbor.readthedocs.io/en/latest/hosting.html) behind a load balancer. + +qrcode makes a QR code in one call: `qrcode.make("Some data")`. For more control, use the `QRCode` class, and pass `version=None` with `make(fit=True)` to pick the size for you. For SVG output, the README [recommends the path factory](https://github.com/lincolnloop/python-qrcode), `SvgPathImage`. If you style the code or embed an image, set error correction to high, since styled codes aren't guaranteed to work with all readers. + +python-barcode writes SVG with [no external dependencies](https://python-barcode.readthedocs.io/en/latest/). For PNG and other images, install the `python-barcode[images]` extra, which adds Pillow. Its docs [recommend SVG](https://python-barcode.readthedocs.io/en/latest/getting-started.html) unless your target can't use it, since vectors scale better. It calculates the checksum for you. + +Treat every uploaded image as untrusted. Pillow [guards against decompression bombs](https://pillow.readthedocs.io/en/stable/reference/Image.html) with a pixel limit, and its `Image.open(formats=...)` argument restricts the formats it tries. With pyvips, check the image dimensions before you process it, and [block the loaders](https://www.libvips.org/API/current/developer-checklist.html) libvips hasn't tested for security. With Wand, check each file's [magic bytes](https://docs.wand-py.org/en/latest/guide/security.html), never its extension or MIME type. From a9bb827b4abcd610cf8d2f129ae91ba7ea47d7fa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:17:54 +0800 Subject: [PATCH 0890/1130] docs: add Code Analysis category intro Ruff for linting and formatting, a type checker alongside it, and pre-commit to run them, with per-tool guidance sourced from each project's own docs. Co-Authored-By: Claude --- website/data/category_intros/code-analysis.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 website/data/category_intros/code-analysis.md diff --git a/website/data/category_intros/code-analysis.md b/website/data/category_intros/code-analysis.md new file mode 100644 index 00000000..4da863c7 --- /dev/null +++ b/website/data/category_intros/code-analysis.md @@ -0,0 +1,43 @@ +Run Ruff for Python code analysis: it lints and formats. Add a type checker, since Ruff doesn't check types, and pre-commit to run Ruff on every commit. + +How to choose: + +- Rules on which modules may import which: Import Linter +- Dead code: Vulture, or repowise to index the repo for your agent +- Deeply nested functions that are hard to read: complexipy +- Several analysis tools behind one command: Prospector +- Checks before every commit: pre-commit +- Linting, formatting, and import sorting: Ruff +- Formatting without Ruff: Black, with isort on its black profile +- Deeper inference and checks of your own: Pylint +- Flake8 plugins Ruff doesn't have: Flake8 +- Security issues: Bandit +- Refactoring: Rope +- Type checking: mypy, or Pyright, ty, or Pyrefly to also check unannotated code +- Type hints from the types your code sees at runtime: MonkeyType + +Ruff lints and formats in one tool, and its FAQ lists what it [can replace](https://docs.astral.sh/ruff/faq/#which-tools-does-ruff-replace): Flake8 and dozens of its plugins, Black, and isort. To sort imports and format, [run the linter, then the formatter](https://docs.astral.sh/ruff/formatter/#sorting-imports): `ruff check --select I --fix`, then `ruff format`. When you turn on a new rule in an existing codebase, [`--add-noqa`](https://docs.astral.sh/ruff/tutorial/#adding-rules) marks the current violations, so the rule only applies to new code. + +Pick one formatter and stay with it. Ruff's formatter is designed as a drop-in replacement for Black, but it's [not meant to be used interchangeably with Black](https://docs.astral.sh/ruff/formatter/) over time. If you pick Black, set isort's black profile [in a config file at the root of your repo](https://isort.readthedocs.io/en/latest/configuration/black_compatibility.html), so it applies however isort runs. To adopt Black, [reformat everything in one commit](https://black.readthedocs.io/en/stable/guides/introducing_black_to_your_project.html) and list that commit in `.git-blame-ignore-revs`, so `git blame` skips it. + +Ruff can be a [drop-in replacement for Flake8](https://docs.astral.sh/ruff/faq/#how-does-ruffs-linter-compare-to-flake8) when your code is formatted with Black and uses few or no Flake8 plugins. Keep Flake8 when you depend on a plugin Ruff doesn't cover. In pre-commit, install the plugin [through `additional_dependencies`](https://flake8.pycqa.org/en/latest/user/using-hooks.html). + +Pylint [doesn't trust your type hints](https://pylint.readthedocs.io/en/stable/). It infers the actual values instead, which makes it slower but finds more issues in code that isn't fully typed. You can also write plugins for checks of your own. On a legacy project, start with `--errors-only`, then turn more messages on over time. Because of its speed, Pylint's docs suggest running it [in CI or a pre-push hook](https://pylint.readthedocs.io/en/stable/user_guide/installation/pre-commit-integration.html), not on every commit. + +Bandit [finds common security issues](https://github.com/PyCQA/bandit) by walking each file's syntax tree. On an existing project, [save a baseline](https://bandit.readthedocs.io/en/latest/start.html) to ignore the findings you've judged to be non-issues. When you skip a line, write `# nosec B602` instead of a bare `# nosec`, so [a new issue on that line still shows up](https://bandit.readthedocs.io/en/latest/config.html). + +Ruff is [a linter, not a type checker](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-mypy-or-pyright-or-pyre), so add one. mypy is [designed for gradual typing](https://mypy.readthedocs.io/en/stable/), and by default it skips functions without annotations. On an existing codebase, its docs suggest [starting with part of the code](https://mypy.readthedocs.io/en/stable/existing_code.html), running it in CI early, turning on `check_untyped_defs` as soon as you can, and aiming for `mypy --strict`. + +Pyright, ty, and Pyrefly each come with a language server for your editor, and they check unannotated code too. Pyright [checks all code regardless of annotations](https://github.com/microsoft/pyright/blob/main/docs/mypy-comparison.md). Commit its config, [run it in CI](https://github.com/microsoft/pyright/blob/main/docs/getting-started.md), and turn on strict mode file by file with `# pyright: strict`. + +ty is [designed for adoption](https://docs.astral.sh/ty/), with support for partially typed code. Add it as a [dev dependency](https://docs.astral.sh/ty/installation/), so everyone runs the same version. Pick Pyrefly when your code uses [Pydantic, Django, or attrs](https://pyrefly.org/en/docs/compare/) and you don't want the overhead of plugins. To switch to it, `pyrefly init` [migrates your existing type checker config](https://pyrefly.org/en/docs/installation/), and `pyrefly suppress` marks the current errors as ignored, so you start from a clean check. + +Import Linter checks [contracts on your imports](https://import-linter.readthedocs.io/en/stable/), such as layers: higher layers may import lower ones, not the other way around. Vulture finds unused code. For its false positives, it recommends [a whitelist over `noqa` comments](https://github.com/jendrikseipp/vulture). After you delete dead code, run it again, since it may find more. complexipy measures how hard code is for people to understand, and its docs say to [run it alongside Ruff](https://complexipy.com/comparison-with-ruff/): Ruff catches wide functions, complexipy catches deep ones. On a large existing codebase, [take a snapshot](https://complexipy.com/usage-guide/) first, so only new complexity fails. + +Prospector wraps several analysis tools and aims to be [useful out of the box](https://github.com/prospector-dev/prospector). repowise indexes your repo for coding agents and developers, with dead code and git history in the same index. It's licensed under the [AGPL, or a commercial license](https://github.com/repowise-dev/repowise). + +Rope is a refactoring library, and its wiki suggests [starting with its language server plugin](https://github.com/python-rope/rope/wiki/How-to-use-Rope-in-my-IDE-or-Text-editor%3F) before the native editor integrations. MonkeyType records the types your code sees at runtime and writes annotations from them, which its docs call [an informative first draft](https://monkeytype.readthedocs.io/en/latest/) for you to check and fix. mypy's docs suggest [collecting types from test runs](https://mypy.readthedocs.io/en/stable/existing_code.html) this way. + +pre-commit runs your hooks [before every commit](https://pre-commit.com/). Run `pre-commit install` after every clone, `pre-commit run --all-files` when you add a hook, and the same command in CI. With Ruff's hooks, [put the lint hook with `--fix` before the formatter](https://docs.astral.sh/ruff/integrations/#pre-commit), since its fixes can leave code that needs reformatting. + +The checkers here do static code analysis: they check your code without running it. MonkeyType is the exception, since it records types while your code runs. Run Ruff [in your editor](https://docs.astral.sh/ruff/editors/) and on every commit, and Pylint and your type checker in CI. From 88cfcf6f4da79fc0e797b0918893c6fa28d7391d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:27:02 +0800 Subject: [PATCH 0891/1130] docs: rewrite Computer Vision category intro Covers OpenCV as the default, Ultralytics YOLO for detection/segmentation/pose models and its AGPL-3.0/Enterprise License terms, Kornia for GPU-batch vision ops, FiftyOne for dataset curation, and pytesseract vs EasyOCR for OCR. Co-Authored-By: Claude --- .../data/category_intros/computer-vision.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/website/data/category_intros/computer-vision.md b/website/data/category_intros/computer-vision.md index 048fe971..26e1e35b 100644 --- a/website/data/category_intros/computer-vision.md +++ b/website/data/category_intros/computer-vision.md @@ -1,24 +1,24 @@ -For a Python computer vision library, start with OpenCV, and add Ultralytics YOLO to detect objects. For OCR, use pytesseract for scans and EasyOCR for photos. +Start with OpenCV when you need a Python computer vision library for images and video. To train and run detection models, use Ultralytics YOLO. How to choose: -- Reading, transforming, and writing images and video: OpenCV -- Detecting, segmenting, or tracking objects with a pretrained model: Ultralytics YOLO, which is AGPL-3.0 unless you buy an Enterprise License -- Image processing and augmentation on GPU batches, inside a PyTorch model or training loop: Kornia -- Browsing a dataset, finding label mistakes, and seeing where a model fails: FiftyOne -- Scanned pages and documents: pytesseract, on top of a Tesseract engine you install yourself -- Text in photos, without a separate OCR engine to install: EasyOCR +- Image and video processing: OpenCV +- Detection, segmentation, and pose models: Ultralytics YOLO +- Vision ops inside a PyTorch model: Kornia +- Dataset curation and model evaluation: FiftyOne +- OCR on clean, printed documents: pytesseract +- OCR on text in photos: EasyOCR -OpenCV ships as four wheels on PyPI, and you install exactly one, since they all use the same `cv2` namespace. On a server or in Docker, pick `opencv-python-headless`, which leaves out the GUI libraries. The opencv-python README says [you should always use it](https://github.com/opencv/opencv-python#installation-and-usage) unless you call `cv2.imshow`. The wheels are CPU-only; for CUDA, you build OpenCV from source. +OpenCV comes as [four pip packages that share the `cv2` namespace](https://github.com/opencv/opencv-python), so install only one: opencv-python for the main modules, or opencv-contrib-python to add the extra modules. If you never call `cv2.imshow` or you build your GUI with another toolkit, install the headless variant of either one, which also makes Docker images smaller. -With Ultralytics YOLO, load a pretrained checkpoint with `YOLO()`, fine-tune it with `model.train()`, and ship it with `model.export(format="onnx")`. Check the license before you build a product on it. Ultralytics says an Enterprise License is ["required if you want to use Ultralytics YOLO without open-sourcing your entire project"](https://www.ultralytics.com/license), even for models you trained yourself. +Ultralytics YOLO covers the [whole life of a model](https://docs.ultralytics.com/modes/): train, validate, predict, export, and track, from Python or the `yolo` command. Its docs recommend [starting training from a pretrained model](https://docs.ultralytics.com/modes/train/). To deploy, [export it](https://docs.ultralytics.com/modes/export/) to ONNX, TensorRT, CoreML, or another format. The code and the models you train with it are [AGPL-3.0](https://www.ultralytics.com/license), so unless you open-source your whole project, you need an Enterprise License. -Kornia works on PyTorch tensors, so its operators ["run wherever the tensor lives"](https://kornia.readthedocs.io/en/latest/) and take a whole batch in one call. Gradients flow through them too, so they can sit inside your model or your loss. Its augmentations expect [float tensors in [0, 1]](https://kornia.readthedocs.io/en/latest/augmentation.html): one still in [0, 255] doesn't raise an error, it just clips. +Kornia is a [differentiable computer vision library like OpenCV, with strong GPU support](https://kornia.readthedocs.io/en/latest/get-started/introduction.html). Every operator works on PyTorch tensors and supports autograd, so vision ops can run on the GPU and sit inside your training loop. -With FiftyOne, load your images and your model's predictions into a `fo.Dataset` and browse them with `fo.launch_app()`. Then run `compute_mistakenness()` to rank likely label mistakes, which [create an artificial ceiling](https://docs.voxel51.com/brain/index.html) on how good your model can get. Before you trust a test score, run `compute_leaky_splits()` too: duplicates across train and test make it easy to [overestimate your model](https://docs.voxel51.com/brain/index.html#leaky-splits). +FiftyOne works on the data side of a model. [Load your dataset and your model's predictions into it](https://docs.voxel51.com/user_guide/basics.html), then see where the model succeeds and fails, and find mistakes in your labels. It [integrates with Ultralytics](https://docs.voxel51.com/integrations/ultralytics.html), so you can run and fine-tune YOLO models on FiftyOne datasets. -pytesseract wraps the `tesseract` command, so install Tesseract separately and make sure it's on your `PATH`. Pass `lang=` for anything but English. Tesseract works best at 300 DPI or more, so scale small images up. It also [expects a page of text](https://tesseract-ocr.github.io/tessdoc/ImproveQuality.html) by default, so for a single line, pass `config="--psm 7"`. +pytesseract [wraps the Tesseract OCR engine](https://github.com/madmaze/pytesseract), which you install on its own, then put on your PATH or point `tesseract_cmd` at. Tesseract suits clean, printed text and needs no GPU. To get better results, [improve the image first](https://tesseract-ocr.github.io/tessdoc/ImproveQuality.html): Tesseract works best at 300 DPI or more, and retraining rarely helps unless you use an unusual font or a new language. -With EasyOCR, create one `easyocr.Reader` and reuse it: loading the model takes a while, and it [needs to be run only once](https://github.com/JaidedAI/EasyOCR#usage). +EasyOCR is a [general OCR that reads text in photos as well as in documents](https://www.jaided.ai/easyocr), in dozens of languages. It handles text in photos, where Tesseract struggles, but it's slow without a GPU. Create a `Reader` for your languages [once](https://github.com/JaidedAI/EasyOCR) and reuse it for every image, since that call loads the model into memory. -Watch the channel order when you pass images between libraries. OpenCV [loads color images as BGR](https://docs.opencv.org/4.x/d4/da8/group__imgcodecs.html). Ultralytics YOLO and EasyOCR take OpenCV images as they are, but pytesseract assumes RGB, so convert first with `cv2.cvtColor(img, cv2.COLOR_BGR2RGB)`. +Ultralytics YOLO, Kornia, and EasyOCR run on PyTorch. When you need a specific CUDA build, install PyTorch before the library, as [Ultralytics](https://docs.ultralytics.com/quickstart/) and [Kornia](https://kornia.readthedocs.io/en/latest/get-started/installation.html) recommend. EasyOCR's README says the same for Windows. From 3c2afa13bbdd400bcee0291f93c2273f8a446786 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:27:20 +0800 Subject: [PATCH 0892/1130] docs: rewrite Audio & Video Processing category intro Covers librosa for audio analysis, MoviePy for scripted video editing, VidGear for real-time video, Mutagen vs tinytag for tag read/write and licensing, and beets as a CLI tag/organization tool. Co-Authored-By: Claude --- .../category_intros/audio-video-processing.md | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/website/data/category_intros/audio-video-processing.md b/website/data/category_intros/audio-video-processing.md index 55c34c36..857e36a1 100644 --- a/website/data/category_intros/audio-video-processing.md +++ b/website/data/category_intros/audio-video-processing.md @@ -1,25 +1,27 @@ -For a Python audio processing library, use librosa to analyze audio and music. For video, use MoviePy to edit clips in code, or VidGear for live streams. +Each job has its own pick: librosa is the Python audio processing library for music analysis, MoviePy edits video from a script, and Mutagen tags audio files. How to choose: -- Audio and music analysis, such as beat tracking or spectrograms: librosa -- Quick cuts, fades, and format conversion: pydub -- Editing video in code, such as cuts, titles, and compositing: MoviePy -- Webcams, live streams, and screen capture in real time: VidGear -- Reading tags, with one API for every format and an MIT license: tinytag -- Writing tags: Mutagen, which is GPL-licensed -- Organizing a whole music collection and fixing its tags from MusicBrainz: beets +- Cutting, joining, and fading audio files: pydub +- Music and audio analysis: librosa +- Editing video or making GIFs from a script: MoviePy +- Real-time video from cameras and network streams: VidGear +- Reading and writing tags across formats: Mutagen +- Reading tags only: tinytag +- Tagging and organizing your music collection: beets -With librosa, `librosa.load()` resamples everything to 22050 Hz by default. Pass `sr=None` to [keep the file's native sampling rate](https://librosa.org/doc/latest/api/generated/librosa.load.html), or `offset` and `duration` to load only part of a file. +pydub gives you a simple, high-level interface to cut, join, and fade audio. It opens and saves WAV files in pure Python, but [other formats like MP3 need FFmpeg](https://github.com/jiaaro/pydub#dependencies), so install FFmpeg with it. An AudioSegment is [immutable](https://github.com/jiaaro/pydub#quickstart): every operation returns a new one, so you can chain them, and every length and position is in milliseconds. -With pydub, load a file with `AudioSegment.from_file()`, slice it in milliseconds, and save it with `export()`. WAV works in pure Python, but every other format [needs FFmpeg](https://github.com/jiaaro/pydub#dependencies). The standard library dropped the `audioop` module pydub imports, so [install `audioop-lts`](https://github.com/jiaaro/pydub/issues/863) next to it. +librosa gives you [the foundational algorithms and tools for music information retrieval](https://librosa.org/doc/latest/index.html). By default, `librosa.load` resamples the signal and mixes stereo down to mono, and those defaults [suit most analysis tasks](https://librosa.org/doc/latest/auto_tutorials/01-intro/01-load.html); pass `sr=None` to keep the file's own sampling rate. When you need more control than `load` gives you, such as writing files, [its docs recommend using its audio I/O backend directly](https://librosa.org/doc/latest/ioformats.html). -A MoviePy script loads clips, changes them with `subclipped()` and the `with_*` methods, and calls `write_videofile()`. Open file clips in a `with` block, since each file clip [locks the file](https://zulko.github.io/moviepy/user_guide/loading.html) until it's closed. To only convert a video file, MoviePy's docs tell you to call FFmpeg directly: it's ["faster and more memory-efficient"](https://zulko.github.io/moviepy/getting_started/quick_presentation.html). +MoviePy is for [automating video editing](https://zulko.github.io/moviepy/getting_started/quick_presentation.html): processing many videos, composing them in complicated ways, or making videos and GIFs on a web server. A script loads clips, modifies them, puts them together, and writes the result. Modifying a clip [returns a new clip and leaves the original alone](https://zulko.github.io/moviepy/user_guide/modifying.html), and the computation happens at the final render. Open file clips in a `with` block, or [call `close()`](https://zulko.github.io/moviepy/user_guide/loading.html) when you're done, since each one holds a subprocess and a lock on the file. MoviePy can't stream video. For frame-by-frame analysis, its docs send you to a computer vision library. -MoviePy can't stream video, such as reading from a webcam, so use VidGear for that. It [needs OpenCV](https://abhitronix.github.io/vidgear/latest/installation/pip_install/) installed first. Its gears keep OpenCV's read loop: `CamGear(source=0).start()`, call `read()` until it returns `None`, then `stop()`. +VidGear is a framework for [real-time media applications](https://abhitronix.github.io/vidgear/latest/) built on OpenCV and FFmpeg. All its APIs [keep OpenCV's coding syntax](https://abhitronix.github.io/vidgear/latest/switch_from_cv/). Each task has [its own gear](https://abhitronix.github.io/vidgear/latest/gears/): CamGear reads cameras, network streams, and streaming sites in multiple threads. WriteGear writes frames to a video file or network stream, and StreamGear transcodes video into adaptive streaming formats. [Install OpenCV first](https://abhitronix.github.io/vidgear/latest/installation/pip_install/), since the core functions need it. -tinytag only reads tags: ["Support for changing/writing metadata will not be added."](https://github.com/tinytag/tinytag) To write them, use Mutagen. `mutagen.File()` guesses the format for you, and Mutagen [saves ID3 tags as v2.4](https://mutagen.readthedocs.io/en/latest/user/id3.html) unless you open and save them with `v2_version=3`. +Mutagen reads and writes tags with [roughly the same API across all tag formats](https://mutagen.readthedocs.io/en/latest/). `mutagen.File` [guesses the file type](https://mutagen.readthedocs.io/en/latest/user/gettingstarted.html). ID3 tags in MP3 files are highly structured; for common keys, use [the simpler EasyID3 interface](https://mutagen.readthedocs.io/en/latest/user/id3.html). Mutagen is licensed under GPL-2.0-or-later; if you only read tags, MIT-licensed tinytag avoids that. -beets runs from the command line: set `directory` and `library` in its config file, then run `beet import`. Its getting started guide recommends the autotagger and [importing a few albums at a time](https://beets.readthedocs.io/en/stable/guides/main.html). It also warns that importing can modify and move your files, so back them up first. +tinytag only reads metadata, and [writing support will not be added](https://github.com/tinytag/tinytag): its README points you to Mutagen for that. It's pure Python with no dependencies and gives you the same API for every format. `TinyTag.get()` returns an object with attributes like `artist` and `duration`. -For long files, `librosa.stream()` reads audio [block by block](https://librosa.org/doc/latest/api/generated/librosa.stream.html) instead of loading it all into memory; set `center=False` in the analyses you run on those blocks. pydub [loads the whole file into RAM](https://github.com/jiaaro/pydub/issues/51#issuecomment-35839094), so pass `start_second` and `duration` to `from_file()` when you only need a slice. +beets is a command-line music library manager, not a library you import: it [catalogs your collection and improves its metadata](https://beets.io/) as it goes. Install it [as a standalone tool](https://beets.readthedocs.io/en/stable/guides/installation.html), isolated from your system Python and other packages. `beet import` can modify and move your files, so [back up first and import a few albums at a time](https://beets.readthedocs.io/en/stable/guides/main.html). [Plugins](https://beets.readthedocs.io/en/stable/plugins/index.html) add commands, fetch extra data during import, and add metadata sources. + +FFmpeg sits under most of these projects: pydub needs it for any format other than WAV, MoviePy runs on it, and VidGear's WriteGear and StreamGear wrap it. When you only want to convert a video file or turn images into a movie, [call FFmpeg directly](https://zulko.github.io/moviepy/getting_started/quick_presentation.html). MoviePy's own docs say it's faster and uses less memory than going through MoviePy. From ee2874a463dfbfff27782ed2b12a8320e3fdd037 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:27:25 +0800 Subject: [PATCH 0893/1130] docs: rewrite Testing category intro Covers pytest as the default, a how-to-choose item per README subcategory, and a guide on Hypothesis, Playwright, tox/Nox, mocks, and coverage. Co-Authored-By: Claude --- website/data/category_intros/testing.md | 51 ++++++++++++++++--------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/website/data/category_intros/testing.md b/website/data/category_intros/testing.md index e7034229..2afb74b8 100644 --- a/website/data/category_intros/testing.md +++ b/website/data/category_intros/testing.md @@ -1,28 +1,41 @@ -Use pytest as your Python testing framework. Add Hypothesis to find the edge cases you missed, and Playwright to test in a real browser. +Plain assert statements and fixtures make pytest the Python testing framework for new code. Add Hypothesis for property-based tests, Playwright for browsers. How to choose: -- Unit and integration tests: pytest -- Property-based tests: Hypothesis -- Running the suite across Python versions or dependency sets: tox, or nox if you'd rather configure it in Python -- End-to-end browser tests: Playwright -- An existing Selenium suite, or tests spread across many machines with Selenium Grid: Selenium, or SeleniumBase for a pytest-ready framework on top of it -- Acceptance tests in a readable keyword syntax: Robot Framework -- Load testing: Locust -- An API with an OpenAPI or GraphQL schema: Schemathesis -- Faking HTTP: responses for Requests, RESPX for HTTPX, VCR.py to record and replay real traffic -- Freezing the clock: freezegun -- Test data: factory_boy for ORM models, Polyfactory for dataclasses and Pydantic models, Faker or Mimesis for single fake values -- Coverage: coverage.py +- Writing tests: pytest, plus Hypothesis for edge cases; Robot Framework for non-programmers +- Tests across Python versions: tox, or Nox to configure them in Python +- Browser tests: Playwright; Selenium or SeleniumBase for WebDriver suites +- Load tests written in Python: Locust +- Tests generated from an OpenAPI or GraphQL schema: Schemathesis +- Mocking: unittest.mock; responses, RESPX, or VCR.py for HTTP; FreezeGun for time +- Test objects: factory_boy for ORM models, Polyfactory for type hints +- Code coverage: Coverage.py +- Fake data: Faker or Mimesis -With pytest, write tests as plain functions with `assert`, and share setup through fixtures, which pytest says [offer dramatic improvements](https://docs.pytest.org/en/stable/explanation/fixtures.html) over xUnit-style setup and teardown. For a new project, its good practices recommend [the `importlib` import mode](https://docs.pytest.org/en/stable/explanation/goodpractices.html) and a `src` layout. You don't have to rewrite an old unittest suite first: pytest [runs it as is](https://docs.pytest.org/en/stable/how-to/unittest.html), so you can move it over one file at a time. +pytest lets you write tests with [plain `assert` statements](https://docs.pytest.org/en/stable/) and shows you what failed. It also runs your unittest suites as they are, so you can [move an old suite over bit by bit](https://docs.pytest.org/en/stable/how-to/unittest.html). Share setup through [fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html): use `yield` fixtures for teardown, and put the ones several test modules need in `conftest.py`. For a new project, pytest's docs recommend a src layout and the [importlib import mode](https://docs.pytest.org/en/stable/explanation/goodpractices.html). -A Hypothesis test is a pytest test with `@given` on top. You describe the inputs with strategies, and Hypothesis picks the values, [including edge cases you might not have thought about](https://hypothesis.readthedocs.io/en/latest/). It still works with fixtures and `parametrize`. +Hypothesis adds property-based tests to pytest or unittest. You describe the inputs with a strategy passed to [`@given`](https://hypothesis.readthedocs.io/en/latest/quickstart.html), and Hypothesis picks which ones to try, including edge cases you didn't think of. It's [an addition to unit tests, not always a replacement](https://hypothesis.readthedocs.io/en/latest/tutorial/introduction.html): start with round trips like encode/decode, and with tests you already parametrize. Use the [most general strategy](https://hypothesis.readthedocs.io/en/latest/explanation/domain.html) your test should pass for. -For the browser, Playwright calls its pytest plugin, pytest-playwright, [the recommended way to write end-to-end tests](https://playwright.dev/python/docs/intro). Find elements by what the user sees, [starting with `get_by_role()`](https://playwright.dev/python/docs/locators), and assert with `expect()`, which keeps retrying until the condition is met or it times out. +Robot Framework is a [keyword-driven framework for acceptance testing](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html). Tests are tables of keywords, and you build higher-level keywords out of existing ones. That suits teams where people who don't write Python read or write the tests. -Run tox or nox locally and in CI. pytest's own docs point to tox because it [tests the installed package, not your checkout](https://docs.pytest.org/en/stable/explanation/goodpractices.html), which catches packaging mistakes. +tox and Nox both run your tests in separate virtual environments, one per Python version or task. Configure tox [in TOML](https://tox.wiki/en/latest/tutorial/getting-started.html), in `tox.toml` or `pyproject.toml`. It tests the installed package, not your checkout, so it [catches packaging mistakes](https://docs.pytest.org/en/stable/explanation/goodpractices.html). Nox is configured in Python, in a `noxfile.py`, and tox's own docs point you to it [if tox configuration is too limiting](https://tox.wiki/en/latest/explanation.html). -For coverage, run `coverage run --branch -m pytest`. You don't need a pytest plugin for it: coverage.py calls one [unnecessary for most purposes](https://coverage.readthedocs.io/en/latest/). +Playwright was [created for end-to-end testing](https://playwright.dev/python/docs/intro) and runs Chromium, Firefox, and WebKit. Write your tests with its pytest plugin, which gives each test its own browser context. Playwright [waits for elements to be ready](https://playwright.dev/python/docs/actionability) before each action, so you don't add waits yourself. Find elements [by role, text, or test id](https://playwright.dev/python/docs/locators) rather than CSS or XPath, which break when the page changes. -When you mock with `unittest.mock`, [patch where an object is looked up](https://docs.python.org/3/library/unittest.mock.html#where-to-patch), not where it's defined. Pass `autospec=True` too, so a call with the wrong signature raises a `TypeError`. +Selenium drives real browsers through WebDriver, on your machine or on remote ones through Selenium Grid. Keep it for the WebDriver suites you already have. It [doesn't structure your test suite for you](https://www.selenium.dev/documentation/test_practices/), so run it under a test runner like pytest, and use [explicit waits](https://www.selenium.dev/documentation/webdriver/waits/) for the exact condition you need. SeleniumBase builds on Selenium's WebDriver APIs and [runs under pytest](https://github.com/seleniumbase/SeleniumBase), and its methods wait for elements that need time to load. + +For load tests, Selenium's docs [advise against using it](https://www.selenium.dev/documentation/test_practices/discouraged/performance_testing/); use Locust. You [write the tests in regular Python code](https://docs.locust.io/en/stable/what-is-locust.html): a `User` class with `@task` methods. When you need more load, [run one worker per CPU core](https://docs.locust.io/en/stable/running-distributed.html). + +Schemathesis generates property-based tests from your OpenAPI or GraphQL schema, using Hypothesis under the hood. Its docs [recommend the CLI for most users](https://schemathesis.readthedocs.io/en/stable/faq/), since the pytest integration has fewer features. + +unittest.mock ships with Python. [Patch where an object is looked up](https://docs.python.org/3/library/unittest.mock.html), not where it's defined, and add `autospec=True` so your tests fail when the real API changes. For code you own, pytest's docs suggest you [pass dependencies in](https://docs.pytest.org/en/stable/how-to/monkeypatch.html) rather than patch them. + +For HTTP, pick the mock that matches your client: responses for requests, and RESPX for HTTPX. Both raise an error on requests you didn't mock. VCR.py records real responses to a cassette file and replays them, with many clients including both. [Filter out credentials](https://vcrpy.readthedocs.io/en/latest/advanced.html) before you commit cassettes. For time, FreezeGun [freezes `datetime` and `time`](https://github.com/spulec/freezegun) at the moment you choose. + +factory_boy [replaces static fixtures with factories](https://factoryboy.readthedocs.io/en/stable/) that set only the fields a test cares about, and works with Django, SQLAlchemy, and MongoDB models. Polyfactory [builds objects from type hints](https://polyfactory.litestar.dev/latest/): dataclasses, TypedDicts, Pydantic models, and more. + +For the data itself, Faker [generates localized fake data](https://faker.readthedocs.io/en/master/) and comes with a pytest fixture; factory_boy uses it too. Mimesis is [fully typed and generates data from schemas](https://mimesis.name/latest/about.html), in many languages. + +Coverage.py measures which lines your tests run. Run pytest under it with [`coverage run -m pytest`](https://coverage.readthedocs.io/en/latest/), which its docs say is enough for most purposes, and include your tests in the measurement. It measures lines by default; add [`--branch`](https://coverage.readthedocs.io/en/latest/branch.html) to see which branches never ran. + +Write each test so it [runs in any order](https://www.selenium.dev/documentation/test_practices/discouraged/test_dependency/), without relying on other tests. A [flaky test](https://docs.pytest.org/en/stable/explanation/flaky.html) usually means state the test doesn't control, and random data is one such state: seed Faker, Mimesis, factory_boy, and Polyfactory so a [failing build reproduces](https://factoryboy.readthedocs.io/en/stable/). From c52f21f99d18b0e1e324f6391ec3ce542e364bb9 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:27:29 +0800 Subject: [PATCH 0894/1130] docs: rewrite CLI Development category intro Covers argparse for basic apps, Click or Typer beyond that, Rich for output, and TUI frameworks. Co-Authored-By: Claude --- .../data/category_intros/cli-development.md | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/website/data/category_intros/cli-development.md b/website/data/category_intros/cli-development.md index 018debf9..9c2009bc 100644 --- a/website/data/category_intros/cli-development.md +++ b/website/data/category_intros/cli-development.md @@ -1,26 +1,38 @@ -For a Python CLI framework, use Typer, or argparse when a script can't take dependencies. For a full-screen terminal app, use Textual. +Python's own argparse covers basic command-line apps. Beyond that, pick Click or Typer as your Python CLI library, and style the output with Rich. How to choose: -- A new CLI: Typer -- A script that runs on the standard library alone: argparse -- Decorators instead of type hints, or subcommands loaded lazily at runtime: Click -- A quick CLI over an existing function or class, for debugging or exploring code: Fire -- An interactive prompt with completion, history, and key bindings: prompt_toolkit -- Colored output, tables, and pretty logs: Rich -- A progress bar on a loop: tqdm, or alive-progress for animated bars -- A full-screen terminal UI: Textual, or urwid inside an event loop you already run, such as Twisted or Trio -- ASCII animations and effects: asciimatics -- ANSI colors on Windows consoles: colorama +- Basic command-line app with no dependencies: argparse +- Nested commands, or subcommands loaded lazily: Click +- Options declared once as type-hinted function parameters: Typer +- Interactive prompts and REPLs: prompt_toolkit +- CLI from existing code without writing a parser: Python Fire +- Progress bar for a loop: tqdm, or alive-progress for animated bars +- Colored text, tables, and logs: Rich, or colorama for ANSI colors on Windows +- Full-screen app in the terminal or a browser: Textual +- Terminal widgets on an event loop you already run: Urwid +- Full-screen forms and ASCII animations: asciimatics -With Typer, declare each parameter with `Annotated`. Its tutorial keeps repeating one line: ["Prefer to use the `Annotated` version if possible."](https://typer.tiangolo.com/tutorial/arguments/optional/) Create one `typer.Typer()` app and register your commands on it. Test them with `CliRunner`, which calls the app with a list of arguments and gives you back the exit code and output. +argparse is in the standard library. Python's docs call it [the recommended choice](https://docs.python.org/3/library/optparse.html#choosing-an-argument-parser) when you have no more specific needs, since it gives you the most out of the box for the least code. It [writes the help and usage messages](https://docs.python.org/3/library/argparse.html) and reports invalid arguments for you. For commands written as decorated functions, the same docs point to Click, and for a CLI that works with static type checking, to Typer. -Typer builds on Click, so reach for Click itself when you'd rather write decorators, or when your app has so many subcommands that they should [load lazily at runtime](https://click.palletsprojects.com/en/stable/). Group commands with `@click.group()`, and test them with `click.testing.CliRunner`. +Click builds a CLI from [commands declared with decorators](https://click.palletsprojects.com/en/stable/quickstart/). You can nest them to any depth, and Click can [load subcommands lazily](https://click.palletsprojects.com/en/stable/) at runtime. It also [reads option values from environment variables](https://click.palletsprojects.com/en/stable/why/) and comes with helpers for ANSI colors, terminal size, and launching editors. -Use argparse when the tool has to run without installing anything. Python's own docs call it [the recommended choice](https://docs.python.org/3/library/optparse.html#choosing-an-argument-parser) among the standard library's parsers, and `add_subparsers()` handles subcommands. +Typer builds a CLI from your function signatures: you [declare each argument and option once](https://typer.tiangolo.com/), as a type-hinted function parameter. Write those hints with `Annotated`, which its tutorial [recommends wherever possible](https://typer.tiangolo.com/tutorial/arguments/optional/). Its docs say you get [the best results by pairing it with Rich](https://typer.tiangolo.com/tutorial/printing/): Typer structures the commands and options, and Rich displays the output. -With Rich, create one `Console` and share it across the app: ["Most applications will require a single Console instance"](https://rich.readthedocs.io/en/stable/console.html). Add a second one with `stderr=True` for errors, and pass `RichHandler` to `logging.basicConfig()` so your logs get the same formatting. +prompt_toolkit is for interactive input. It can [replace GNU readline](https://python-prompt-toolkit.readthedocs.io/en/stable/) or build full-screen apps. For a REPL, use a [`PromptSession`](https://python-prompt-toolkit.readthedocs.io/en/stable/pages/asking_for_input.html), which keeps the history for the whole session. -With Textual, keep styles in a separate `.tcss` file and run `textual run --dev` to [live edit them](https://textual.textualize.io/guide/CSS/). For tests, `run_test()` starts the app headless and gives you a `Pilot` that presses keys and clicks widgets. +Python Fire turns any Python object into a CLI: [call `fire.Fire()`](https://github.com/google/python-fire/blob/master/docs/guide.md) at the end of your program. Its README pitches it for [developing and debugging your code](https://github.com/google/python-fire), exploring existing code, and turning other people's code into a CLI. It takes each argument's type from the value you pass, not from the function signature. -Whatever you pick, ship the CLI as a package, not a script. Click's docs put it plainly: ["It's recommended to write command line utilities as installable packages with entry points instead of telling users to run `python hello.py`."](https://click.palletsprojects.com/en/stable/entry-points/) Declare the command under `[project.scripts]` in `pyproject.toml`, and users can install it with pipx or `uv tool install`. +tqdm adds a progress bar to any loop: [wrap the iterable in `tqdm()`](https://github.com/tqdm/tqdm). Import it from `tqdm.auto`, which picks the console bar or the Jupyter widget for you. alive-progress wraps the loop in an [`alive_bar` context manager](https://github.com/rsalmei/alive-progress) instead, with a spinner that speeds up and slows down with your throughput. + +Rich writes colored text, tables, Markdown, and syntax-highlighted code to the terminal. Start with its [drop-in `print`](https://rich.readthedocs.io/en/stable/introduction.html), then create [one `Console` at module level](https://rich.readthedocs.io/en/stable/console.html) for the rest of your app. For colored logs, send the logging module's output through [`RichHandler`](https://rich.readthedocs.io/en/stable/logging.html). + +colorama makes ANSI escape codes work on Windows and does nothing on other platforms. If that's all you need, call [`just_fix_windows_console()`](https://github.com/tartley/colorama). + +Textual apps run in the terminal, and the `textual serve` command from textual-dev [serves them in a browser](https://textual.textualize.io/guide/devtools/). Build one by [subclassing `App`](https://textual.textualize.io/guide/app/), and style its widgets with [CSS](https://textual.textualize.io/guide/CSS/). + +Urwid is a [console widget construction set](https://urwid.org/manual/overview.html) rather than a finished UI library. It runs on [your choice of event loop](https://github.com/urwid/urwid): asyncio, or another one you already use. It's [licensed under the LGPL](https://github.com/urwid/urwid/blob/master/COPYING), while Textual and asciimatics use permissive licenses. + +asciimatics does [full-screen text UIs, from interactive forms to ASCII animations](https://github.com/peterbrittain/asciimatics): create a Screen, build a Scene from Effect objects, and let the Screen play it. + +Ship your CLI as an installable package with a `[project.scripts]` entry point, not a file users run with `python`. [Click recommends it](https://click.palletsprojects.com/en/stable/entry-points/), and shell completion in Click and [Typer](https://typer.tiangolo.com/tutorial/package/) works through that entry point. Test your commands in-process: Click and Typer both provide a [`CliRunner`](https://click.palletsprojects.com/en/stable/testing/), and Textual's [`run_test()`](https://textual.textualize.io/guide/testing/) drives your app as if you were using the keyboard and mouse. From e5088a6f5caf38ec1c9f8010e7fe71711c71b850 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:27:34 +0800 Subject: [PATCH 0895/1130] docs: rewrite Data Validation category intro Covers Pydantic for API input and config, Pandera for dataframes, and jsonschema for JSON Schema validation. Co-Authored-By: Claude --- .../data/category_intros/data-validation.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/website/data/category_intros/data-validation.md b/website/data/category_intros/data-validation.md index 3729ae2a..f5bd96ea 100644 --- a/website/data/category_intros/data-validation.md +++ b/website/data/category_intros/data-validation.md @@ -1,17 +1,15 @@ -For a Python validation library, use Pydantic for data coming into your app, jsonschema when a JSON Schema is the contract, and Pandera for dataframes. +Validate API input and config with Pydantic, the Python data validation library built on type hints. Use Pandera for dataframes, jsonschema for JSON Schema. How to choose: -- API payloads, config files, anything you can describe with type hints: Pydantic -- A JSON Schema shared with other languages or teams: jsonschema -- pandas, Polars, or PySpark dataframes: Pandera +- API input, forms, and config: Pydantic +- Data checked against a JSON Schema document: jsonschema +- pandas, polars, or PySpark dataframes: Pandera -With Pydantic, describe your data as a `BaseModel` with type hints. Parse JSON with `model_validate_json()`, not `model_validate(json.loads(...))`, as [its performance tips](https://pydantic.dev/docs/validation/latest/concepts/performance/) recommend. For a type that isn't a model, like `list[Item]`, create one `TypeAdapter` and reuse it. +Pydantic builds the schema from your [type hints](https://pydantic.dev/docs/validation/latest/get-started/why/) and guarantees the types of the [output, not the input](https://pydantic.dev/docs/validation/latest/concepts/models/): by default, a numeric string passed to an int field comes out as an int. Where a wrong type should raise an error instead, turn on [strict mode](https://pydantic.dev/docs/validation/latest/concepts/strict_mode/) per field or per model. [Validate incoming JSON directly](https://pydantic.dev/docs/validation/latest/concepts/performance/) instead of parsing it into a dict first. To load config from environment variables, use [Pydantic Settings](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/). -Pydantic is forgiving by default: it turns `"123"` into `123` and ignores fields your model doesn't declare. When that's too loose, turn on [strict mode](https://pydantic.dev/docs/validation/latest/concepts/strict_mode/) or set `extra='forbid'`. +jsonschema is an [implementation of the JSON Schema specification](https://python-jsonschema.readthedocs.io/en/stable/), so one schema can [work across different systems and platforms](https://json-schema.org/overview/what-is-jsonschema). When you validate many instances against one schema, [create a validator for your schema's draft once and call its `validate` method](https://python-jsonschema.readthedocs.io/en/stable/validate/). Use [`iter_errors()`](https://python-jsonschema.readthedocs.io/en/stable/errors/) to report every error, not only the first. To enforce `format` keywords such as dates or emails, [hook a format checker](https://python-jsonschema.readthedocs.io/en/stable/validate/#validating-formats) into the validator. -With jsonschema, `validate()` checks the schema itself on every call. To validate many documents against one schema, [create a validator once](https://python-jsonschema.readthedocs.io/en/stable/validate/) and reuse it, like `Draft202012Validator(schema)`. The `format` keyword checks nothing until you pass a `format_checker`, and [`default` doesn't fill in missing fields](https://python-jsonschema.readthedocs.io/en/stable/faq/). +Pandera validates [dataframe-like objects](https://pandera.readthedocs.io/en/stable/): define a schema once and use it on pandas, polars, PySpark, and other dataframe libraries. Write the schema as a DataFrameModel class, [much like a Pydantic model](https://pandera.readthedocs.io/en/stable/dataframe_models.html), and add the `check_types()` decorator to validate at run time. To check an existing pipeline, put [`check_input()` and `check_output()`](https://pandera.readthedocs.io/en/stable/decorators.html) on its functions. To see every failure in one run instead of only the first, validate with [`lazy=True`](https://pandera.readthedocs.io/en/stable/lazy_validation.html). -With Pandera, write a `DataFrameModel`, which [works like a Pydantic model](https://pandera.readthedocs.io/en/stable/dataframe_models.html) for your columns. Decorate pipeline functions with `@pa.check_types` to validate what goes in and what comes out. Pass `lazy=True` to [get every failure in one report](https://pandera.readthedocs.io/en/stable/lazy_validation.html), not just the first. - -Validate once, where untrusted data enters your program: a request body, a config file, a CSV upload. After that, [Pydantic guarantees](https://pydantic.dev/docs/validation/latest/concepts/models/) the fields match their types, so don't check them again deeper in. The same goes for schemas: if other teams need a JSON Schema, [generate it from your Pydantic models](https://pydantic.dev/docs/validation/latest/concepts/json_schema/) instead of writing it twice. +Pick by the shape of your data. Running a dataframe through a Pydantic model row by row [might not scale](https://pandera.readthedocs.io/en/stable/pydantic_integration.html) to larger datasets, so use Pandera there; a DataFrameModel can still be a field in a Pydantic model. Pydantic can [generate a JSON Schema](https://pydantic.dev/docs/validation/latest/concepts/json_schema/) from any model for tools that read the format. jsonschema works the other way: it validates data against a JSON Schema document you already have. From 62a4e7c4e5a50c4583bfa853dbab00a15dd67b10 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 00:20:56 +0800 Subject: [PATCH 0896/1130] fix: make tags link to category pages instead of filtering in place Clicking a category or group tag filtered the homepage table in place while rewriting the address bar to the category URL, so one URL rendered two different pages and readers never reached category pages with their intros and guides; category pages also showed a redundant self-referential filter bar. Co-Authored-By: Claude --- website/build.py | 8 --- website/static/main.js | 91 +++------------------------------ website/static/style.css | 49 +----------------- website/templates/category.html | 16 +----- website/templates/index.html | 16 +----- website/tests/test_build.py | 85 ++++-------------------------- 6 files changed, 18 insertions(+), 247 deletions(-) diff --git a/website/build.py b/website/build.py index 9164ca1a..70bbdc14 100644 --- a/website/build.py +++ b/website/build.py @@ -678,12 +678,7 @@ def build(repo_root: Path) -> None: filter_urls: dict[str, str] = dict(category_urls) for group in parsed_groups: filter_urls[group["name"]] = group_path(group["slug"]) - for entry in entries: - for sub in entry.get("subcategories", []): - filter_urls[sub["value"]] = sub["url"] builtin_entries = [e for e in entries if e.get("source_type") == BUILTIN_FILTER] - if builtin_entries: - filter_urls[BUILTIN_FILTER] = BUILTIN_PATH env = Environment( loader=FileSystemLoader(website / "templates"), @@ -696,7 +691,6 @@ def build(repo_root: Path) -> None: shutil.rmtree(site_dir) site_dir.mkdir(parents=True) - filter_urls_json = json.dumps(filter_urls, sort_keys=True, ensure_ascii=False).replace(" None: sponsors=sponsors, category_urls=category_urls, filter_urls=filter_urls, - filter_urls_json=filter_urls_json, homepage_json_ld=homepage_json_ld, ), encoding="utf-8", @@ -762,7 +755,6 @@ def build(repo_root: Path) -> None: category_urls=category_urls, current_path=current_path, filter_urls=filter_urls, - filter_urls_json=filter_urls_json, parent_category=parent_category, group_categories=group_categories, category_json_ld=category_json_ld, diff --git a/website/static/main.js b/website/static/main.js index 307e3f92..919aefd4 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -10,15 +10,10 @@ const defaultSort = table && table.dataset.defaultSort === "editorial" ? { col: "editorial", order: "asc" } : { col: "downloads", order: "desc" }; -let activeFilter = null; let activeSort = defaultSort; const searchInput = document.querySelector(".search"); -const filterBar = document.querySelector(".filter-bar"); -const filterValue = document.querySelector(".filter-value"); -const filterClear = document.querySelector(".filter-clear"); const noResults = document.querySelector(".no-results"); const rows = document.querySelectorAll(".table tbody tr.row"); -const tags = document.querySelectorAll(".tag"); const tbody = document.querySelector(".table tbody"); const groupRows = document.querySelectorAll(".table tbody tr.group-row"); @@ -145,7 +140,7 @@ function collapseAll() { function applyFilters() { const query = searchInput ? searchInput.value.toLowerCase().trim() : ""; - const descRowsVisible = !isIndexDocument || activeFilter !== null; + const descRowsVisible = !isIndexDocument; let visibleCount = 0; collapseAll(); @@ -153,12 +148,7 @@ function applyFilters() { rows.forEach(function (row) { let show = true; - if (activeFilter) { - const rowTags = row.dataset.tags; - show = rowTags ? rowTags.split("||").includes(activeFilter) : false; - } - - if (show && query) { + if (query) { if (!row._searchText) { let text = row.textContent.toLowerCase(); if (row._descRow) { @@ -200,35 +190,12 @@ function applyFilters() { if (noResults) noResults.hidden = visibleCount > 0; - tags.forEach(function (tag) { - tag.classList.toggle("active", activeFilter === tag.dataset.value); - }); - - if (filterBar) { - if (activeFilter) { - filterBar.classList.add("visible"); - if (filterValue) filterValue.textContent = activeFilter; - } else { - filterBar.classList.remove("visible"); - } - } - updateURL(); } -const filterUrlsScript = document.getElementById("filter-urls"); -const filterToUrl = filterUrlsScript - ? JSON.parse(filterUrlsScript.textContent) - : {}; - const isIndexDocument = location.pathname === "/" || location.pathname === "/index.html"; -const urlToFilter = {}; -Object.keys(filterToUrl).forEach(function (k) { - urlToFilter[filterToUrl[k]] = k; -}); - function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; @@ -243,9 +210,7 @@ function buildQueryString() { function updateURL() { if (!isIndexDocument) return; - const path = - activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/"; - history.replaceState(null, "", path + buildQueryString()); + history.replaceState(null, "", "/" + buildQueryString()); } function getSortValue(row, col) { @@ -340,8 +305,8 @@ function updateSortIndicators() { // 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; + // Don't toggle if clicking a link + if (e.target.closest("a")) return; let row = e.target.closest("tr.row"); if (!row) { @@ -370,36 +335,6 @@ if (tbody) { }); } -tags.forEach(function (tag) { - tag.addEventListener("click", function (e) { - e.preventDefault(); - const value = tag.dataset.value; - const url = tag.dataset.url; - if (isIndexDocument) { - activeFilter = activeFilter === value ? null : value; - if (activeFilter && url) { - history.pushState(null, "", url + buildQueryString()); - } else { - history.pushState(null, "", "/" + buildQueryString()); - } - applyFilters(); - } else if (url) { - window.location.href = url + "#library-index"; - } - }); -}); - -if (filterClear) { - filterClear.addEventListener("click", function () { - if (!isIndexDocument) { - window.location.href = "/#library-index"; - return; - } - activeFilter = null; - applyFilters(); - }); -} - const noResultsClear = document.querySelector(".no-results-clear"); if (noResultsClear) { noResultsClear.addEventListener("click", function () { @@ -408,7 +343,6 @@ if (noResultsClear) { return; } if (searchInput) searchInput.value = ""; - activeFilter = null; applyFilters(); }); } @@ -451,7 +385,6 @@ if (searchInput) { } if (e.key === "Escape" && document.activeElement === searchInput) { searchInput.value = ""; - activeFilter = null; applyFilters(); searchInput.blur(); } @@ -512,20 +445,8 @@ if (backToTop) { ) { activeSort = { col: sort, order: order }; } - const matched = urlToFilter[location.pathname]; - if (matched) activeFilter = matched; - if (q || activeFilter || sort) { + if (q || sort) { sortRows(); } - if (activeFilter) { - applyFilters(); - } updateSortIndicators(); })(); - -window.addEventListener("popstate", function () { - if (!isIndexDocument) return; - const matched = urlToFilter[location.pathname]; - activeFilter = matched || null; - applyFilters(); -}); diff --git a/website/static/style.css b/website/static/style.css index f7bddd67..4e067c06 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -265,8 +265,7 @@ kbd { .hero-topbar-link:active, .hero-action:active, -.tag:active, -.filter-clear:active { +.tag:active { transform: translateY(1px); } @@ -436,7 +435,6 @@ kbd { .hero-topbar-link:focus-visible, .hero-category-link:focus-visible, .search:focus-visible, -.filter-clear:focus-visible, .tag:focus-visible, .jump-link:focus-visible, .back-to-top:focus-visible, @@ -771,51 +769,6 @@ kbd { 0 1.6rem 3rem -2rem var(--search-focus-shadow); } -.filter-bar { - display: flex; - align-items: center; - gap: 0.75rem; - min-height: 2.3rem; - font-size: var(--text-sm); - color: var(--ink-soft); - opacity: 0; - transform: translateY(-0.4rem); - pointer-events: none; - transition: - opacity 180ms ease, - transform 180ms cubic-bezier(0.22, 1, 0.36, 1); -} - -.filter-bar.visible { - opacity: 1; - transform: translateY(0); - pointer-events: auto; -} - -.filter-bar strong { - color: var(--ink); -} - -.filter-clear { - border: 1px solid var(--line); - border-radius: 999px; - background: var(--bg-paper); - color: var(--ink-soft); - padding: 0.42rem 0.82rem; - cursor: pointer; - transition: - border-color 180ms ease, - color 180ms ease, - background-color 180ms ease, - transform 180ms ease; -} - -.filter-clear:hover { - color: var(--ink); - background: var(--accent-soft); - border-color: oklch(68% 0.08 58 / 0.5); -} - .table-wrap { width: 100%; border-top: 1px solid var(--line); diff --git a/website/templates/category.html b/website/templates/category.html index a9618aef..7528da00 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -78,7 +78,6 @@ {% macro entry_rows(entry, index) %}
    {% endmacro %} -
    {% if entry_groups %} @@ -255,12 +247,6 @@ aria-label="Search projects" />
    -
    - Filtering for - -

    Results

    diff --git a/website/templates/index.html b/website/templates/index.html index b5540624..e4d95d10 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -96,7 +96,6 @@
    {% endif %} -
    @@ -132,12 +131,6 @@ aria-label="Search projects" />
    -
    - Filtering for - -

    Results

    @@ -175,7 +168,6 @@ {% for entry in entries %}
    ' in html subcategory_html = (site / "widgets" / "small" / "index.html").read_text(encoding="utf-8") From ef00fe3adb9acfdc590fcade3ba60a9f5d690f30 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:04:41 +0800 Subject: [PATCH 0898/1130] fix: restore hidden group headings when navigating to a jump link After sorting by a column, or while a search hid a group, category page group headings stayed hidden, so the header's jump links (and any use-case heading link) pointed at a hidden element and did nothing. Co-Authored-By: Claude --- website/static/main.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/website/static/main.js b/website/static/main.js index 919aefd4..30871c9f 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -364,6 +364,24 @@ sortHeaders.forEach(function (th) { }); }); +// Group headings are hidden while sorted flat or filtered out by search, so a link to one must restore them first +document.addEventListener("click", function (e) { + const link = e.target.closest('a[href*="#"]'); + if (!link || link.pathname !== location.pathname) return; + const heading = document.getElementById(decodeURIComponent(link.hash.slice(1))); + const groupRow = heading ? heading.closest(".group-row") : null; + if (!groupRow) return; + if (activeSort.col !== "editorial") { + activeSort = defaultSort; + sortRows(); + updateSortIndicators(); + } + if (groupRow.hidden && searchInput) { + searchInput.value = ""; + applyFilters(); + } +}); + if (searchInput) { let searchTimer; searchInput.addEventListener("input", function () { From 77b1b70df5d88e1098a10cc23f3b8e9a33ab0efa Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:04:57 +0800 Subject: [PATCH 0899/1130] fix: hide use-case and page tags that repeat the group heading On section and subcategory pages, every row repeated its use case and current page as tags (and as the label under the name on phones), right under a heading that already said the same thing. They are now hidden in editorial order and shown again when a column sort flattens the groups. Co-Authored-By: Claude --- website/static/main.js | 1 + website/static/style.css | 5 +++++ website/templates/category.html | 9 ++++++--- website/tests/test_build.py | 2 ++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/website/static/main.js b/website/static/main.js index 30871c9f..93eac711 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -288,6 +288,7 @@ function sortRows() { const sortHeaders = document.querySelectorAll("th[data-sort]"); function updateSortIndicators() { + if (table) table.classList.toggle("sorted", activeSort.col !== "editorial"); sortHeaders.forEach(function (th) { th.classList.remove("sort-asc", "sort-desc"); if (th.dataset.sort === activeSort.col) { diff --git a/website/static/style.css b/website/static/style.css index 4e067c06..12ff6cdf 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -868,6 +868,11 @@ kbd { text-underline-offset: 0.2em; } +/* main.js adds .sorted once a column sort flattens the groups */ +.table:not(.sorted) .repeats-heading { + display: none; +} + .group-count { font-family: var(--font-body); font-size: var(--text-sm); diff --git a/website/templates/category.html b/website/templates/category.html index 7528da00..e8d05860 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -76,6 +76,9 @@ {% endblock %} {% block content %} {% macro entry_rows(entry, index) %} + {# On section and subcategory pages the group heading already names the row's use case #} + {% set grouped_page = entry_groups and not group_categories %} + {% set section_path = "/categories/" ~ parent_category.slug ~ "/" if parent_category else current_path %} {{ entry.name }} - {% if entry.subcategories %}{{ entry.subcategories[0].name }}{% else %}{{ category.name }}{% endif %} @@ -123,13 +126,13 @@ ' in html subcategory_html = (site / "widgets" / "small" / "index.html").read_text(encoding="utf-8") @@ -1033,6 +1034,7 @@ class TestBuild: dl_heading = html.index('Deep Learning') assert ml_heading < html.index(">ml1dl1ml1 Date: Sun, 27 Sep 2026 02:35:12 +0800 Subject: [PATCH 0900/1130] docs: add file format processing category intro The category page had no intro, giving readers no guidance for picking among the PDF, Office, Markdown, and config libraries listed. Co-Authored-By: Claude --- .../category_intros/file-format-processing.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 website/data/category_intros/file-format-processing.md diff --git a/website/data/category_intros/file-format-processing.md b/website/data/category_intros/file-format-processing.md new file mode 100644 index 00000000..069f0bec --- /dev/null +++ b/website/data/category_intros/file-format-processing.md @@ -0,0 +1,51 @@ +PDF, Word, and Excel files open with pypdf, python-docx, and openpyxl, a Python file format library for each. MarkItDown turns all three into Markdown. + +How to choose: + +- Splitting, merging, and reading PDFs: pypdf +- Word documents: python-docx +- Excel: openpyxl to read or edit, XlsxWriter for new reports +- Documents to Markdown for an LLM: MarkItDown +- ELF binaries and DWARF debug info: pyelftools +- One table exported to CSV, JSON, Excel, and more: Tablib +- Scanned PDFs, tables, and complex layouts: Docling +- PowerPoint decks: python-pptx +- New PDFs drawn from Python code: ReportLab +- PDF text with its position and font: pdfminer.six +- PDFs from HTML and CSS: WeasyPrint +- Markdown to HTML: markdown-it-py for CommonMark, Python-Markdown for its extensions, Mistune for speed +- Config files: tomllib for TOML, PyYAML for YAML + +pypdf works on PDFs that already exist: it [splits, merges, crops, and transforms pages](https://pypdf.readthedocs.io/en/stable/), adds passwords, and pulls out text and metadata. It's [pure Python with no C dependency](https://pypdf.readthedocs.io/en/stable/meta/comparisons.html), and it doesn't create PDFs. pypdf [isn't OCR software](https://pypdf.readthedocs.io/en/stable/user/extract-text.html), so run scanned pages through OCR instead. + +python-docx [only edits existing documents](https://python-docx.readthedocs.io/en/latest/user/documents.html): `Document()` opens a built-in template with no content. Start from your own .docx instead, so its styles, headers, and footers carry over. + +openpyxl reads and writes Excel files. For big workbooks, open them with `read_only=True` or create them with `write_only=True`, which [keep memory near constant](https://openpyxl.readthedocs.io/en/stable/optimized.html). A cell with a formula loads the formula; pass [`data_only=True`](https://openpyxl.readthedocs.io/en/stable/tutorial.html) to get the value Excel last calculated. + +XlsxWriter [only writes new files](https://xlsxwriter.readthedocs.io/introduction.html) and can't read or modify existing ones, but it supports more Excel features than the alternatives. Open the workbook [in a `with` block](https://xlsxwriter.readthedocs.io/workbook.html) so it gets closed and saved. For large files, turn on [`constant_memory`](https://xlsxwriter.readthedocs.io/working_with_memory.html) and write the rows in order. From pandas, pass [`engine='xlsxwriter'`](https://xlsxwriter.readthedocs.io/working_with_pandas.html) to `pd.ExcelWriter`. + +MarkItDown converts files to Markdown [for LLMs and text analysis](https://github.com/microsoft/markitdown), not for high-fidelity conversions that people read. Install `markitdown[all]`, or only the extras for your formats, like `markitdown[pdf, docx, pptx]`. + +Docling [understands PDF layout](https://docling-project.github.io/docling/): reading order, tables, and formulas. It also runs OCR on scanned pages. Its models [run locally and send no data out](https://docling-project.github.io/docling/usage/advanced_options/#using-remote-services) unless you turn remote services on. Each file becomes a DoclingDocument, which you export to Markdown or [split into chunks](https://docling-project.github.io/docling/concepts/chunking/) for an embedding model. + +python-pptx builds decks from data, like a database query or analytics output, and [doesn't need PowerPoint installed](https://python-pptx.readthedocs.io/en/latest/). It [only edits existing presentations](https://python-pptx.readthedocs.io/en/latest/user/presentations.html), so start from your own deck: its theme, slide master, and slide layouts set how the slides look. Add each slide from one of those layouts, picked by [its index in your deck](https://python-pptx.readthedocs.io/en/latest/user/slides.html). + +ReportLab draws new PDFs from Python code. Learn it on [`pdfgen`, its lowest-level interface](https://docs.reportlab.com/developerfaqs/), then build multi-page documents with [Platypus](https://docs.reportlab.com/reportlab/userguide/ch5_platypus/). Platypus lets you keep paragraph styles and page layouts in one shared file, so restyling takes a few lines. + +pdfminer.six [focuses on text](https://github.com/pdfminer/pdfminer.six): it gets each piece of text with its exact location, font, and color. Start with `extract_text()` from its [high-level API](https://pdfminersix.readthedocs.io/en/latest/tutorial/highlevel.html). A PDF [stores only characters and their positions](https://pdfminersix.readthedocs.io/en/latest/topic/converting_pdf_to_text.html), so pdfminer.six guesses words, lines, and paragraphs from the layout. Tune those guesses with `LAParams`. + +WeasyPrint turns HTML and CSS into PDFs, like [reports, invoices, and tickets](https://doc.courtbouillon.org/weasyprint/stable/). It runs [no JavaScript](https://doc.courtbouillon.org/weasyprint/stable/going_further.html). Set page size and margins [with the CSS `@page` rule](https://doc.courtbouillon.org/weasyprint/stable/common_use_cases.html). + +markdown-it-py [follows the CommonMark spec](https://markdown-it-py.readthedocs.io/en/latest/) and takes plugins for more syntax. For content your users submit, use the [`js-default` preset](https://markdown-it-py.readthedocs.io/en/latest/security.html), since the default settings aren't safe for it. + +Python-Markdown [isn't a CommonMark implementation](https://python-markdown.github.io/): it follows the original Markdown syntax and has an extension API. It [doesn't sanitize its HTML output](https://python-markdown.github.io/sanitization/), so sanitize it yourself when the input is untrusted. + +Mistune is [fast and has no dependencies](https://mistune.lepture.com/en/latest/). For untrusted input, build the parser with [`mistune.create_markdown()`](https://mistune.lepture.com/en/latest/guide.html), which escapes HTML tags, since `mistune.html()` doesn't. + +tomllib [only reads TOML](https://docs.python.org/3/library/tomllib.html), from a file opened in binary mode. + +Tablib holds one dataset and exports it to many formats; Excel, YAML, and pandas [are optional extras](https://tablib.readthedocs.io/en/stable/formats.html), like `tablib[xlsx]`. + +pyelftools is [pure Python with no dependencies](https://github.com/eliben/pyelftools); start from its [`ELFFile` class](https://github.com/eliben/pyelftools/blob/main/doc/user-guide.md) and stay on the high-level API. + +Treat every file you didn't create as untrusted. With PyYAML, call [`yaml.safe_load()`](https://pyyaml.org/wiki/PyYAMLDocumentation#loading-yaml), never `yaml.load()`, which can run any Python function. Install [defusedxml](https://openpyxl.readthedocs.io/en/stable/#security) next to openpyxl to guard against XML attacks like billion laughs. [Catch pypdf's exceptions](https://pypdf.readthedocs.io/en/stable/user/security.html) yourself, so a broken PDF can't crash your service. For MarkItDown, call [`convert_local()` or `convert_stream()`](https://github.com/microsoft/markitdown#security-considerations) instead of `convert()`, which also fetches remote URIs. Cap Docling's input with [`max_num_pages` and `max_file_size`](https://docling-project.github.io/docling/usage/advanced_options/#impose-limits-on-the-document-size). Run WeasyPrint on untrusted HTML [as a user with limited access](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#security), with a URL fetcher that blocks local files. With tomllib, [limit the size](https://docs.python.org/3/library/tomllib.html) of the data you parse. From fd40096421bb96edf39b9a9992fb90c012b6f63b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:35:36 +0800 Subject: [PATCH 0901/1130] docs: add text processing category intro The page had no intro, so readers got no pick among the encoding detection, fuzzy matching, parser, slug, and ID libraries. Co-Authored-By: Claude --- .../data/category_intros/text-processing.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 website/data/category_intros/text-processing.md diff --git a/website/data/category_intros/text-processing.md b/website/data/category_intros/text-processing.md new file mode 100644 index 00000000..70743995 --- /dev/null +++ b/website/data/category_intros/text-processing.md @@ -0,0 +1,42 @@ +Need a Python text processing library? charset-normalizer reads text in unknown encodings, RapidFuzz does fuzzy matching, and pyparsing builds parsers. + +How to choose: + +- Bytes in an unknown encoding: charset-normalizer +- Fuzzy matching against many strings: RapidFuzz +- Parsers for your own grammar: pyparsing, or parsy for small languages +- Text garbled by a wrong decode (mojibake): ftfy +- Diffs, or close matches without a dependency: difflib +- ASCII-art banners: pyfiglet +- Translations, plus localized dates and numbers: Babel +- Syntax highlighting: Pygments +- Splitting and formatting SQL: sqlparse +- Phone numbers: phonenumbers +- URL slugs: python-slugify, or Unidecode for ASCII transliteration alone +- Short public IDs: shortuuid, or Sqids to encode integer IDs + +Decode with the encoding you know before you guess one. ftfy's docs say to [assume UTF-8](https://ftfy.readthedocs.io/en/latest/avoid.html#assume-utf-8) until proven otherwise, and charset-normalizer's FAQ calls detection [the last resort](https://charset-normalizer.readthedocs.io/en/latest/community/faq.html#should-i-bother-using-detection). For bytes you have no clue about, charset-normalizer is the detector [requests installs](https://github.com/psf/requests/blob/main/pyproject.toml). Call `from_bytes()` or `from_path()`, then `.best()` to [get the most probable result](https://charset-normalizer.readthedocs.io/en/latest/user/advanced_search.html). Its `detect()` is [backward compatible with chardet's](https://charset-normalizer.readthedocs.io/en/latest/user/getstarted.html), so switching is one import. If you stay on chardet, read large files and streams with [`UniversalDetector`](https://chardet.readthedocs.io/en/latest/usage.html). + +ftfy fixes text that was already decoded wrong, and it [doesn't take bytes](https://ftfy.readthedocs.io/en/latest/detect.html), so it's no replacement for a detector. Call `ftfy.fix_text()`, [the function you'll use most](https://ftfy.readthedocs.io/en/latest/explain.html#ftfy.fix_text). Every fix is on by default, so [check which ones fit your text](https://ftfy.readthedocs.io/en/latest/config.html). + +RapidFuzz is a fast string matching library with a C++ core. To compare one string with a list, use the process module's `process.extractOne()` or `process.cdist()`, which is [faster than calling the scorers yourself](https://github.com/rapidfuzz/RapidFuzz). It doesn't lowercase or strip punctuation for you: pass `processor=utils.default_process` when case and punctuation shouldn't count. + +difflib ships with Python, so it works where you can't add a dependency. `get_close_matches()` returns the [good enough matches](https://docs.python.org/3/library/difflib.html#difflib.get_close_matches) for a word, and its diffs come out in unified, context, or HTML format. + +pyparsing lets you [build the grammar in Python code](https://github.com/pyparsing/pyparsing) instead of regular expressions. Since a grammar can accept invalid input, its docs say to use it on [input you assume is well-formatted](https://pyparsing-docs.readthedocs.io/en/latest/HowToUsePyparsing.html#usage-notes). Its [best practices](https://github.com/pyparsing/pyparsing/blob/master/pyparsing/ai/best_practices.md) say to write the grammar out in BNF first. When the grammar is recursive, call `enable_packrat()` right after the import, and give results names to the fields you read back. + +parsy combines small parsers into bigger ones. Its docs say it [excels at small languages](https://parsy.readthedocs.io/en/latest/overview.html) and is easy to read, but to look elsewhere when you need speed or good error messages. For more complex parsers, the [`@generate` decorator](https://parsy.readthedocs.io/en/latest/ref/generating.html) is both more readable and more powerful. + +Babel does two jobs: gettext message catalogs, and [CLDR locale data](https://babel.pocoo.org/en/latest/intro.html) for localized dates, numbers, and names. Run the catalog steps with the `pybabel` command: [extract, init, update, and compile](https://babel.pocoo.org/en/latest/cmdline.html). Keep times in UTC, and [convert to the user's time zone](https://babel.pocoo.org/en/latest/dates.html#time-zone-support) only for input and display. + +Pygments turns code into HTML, LaTeX, ANSI, and more, as a command-line tool or a library. For HTML, it writes CSS classes instead of inline styles, so [generate the stylesheet](https://pygments.org/docs/quickstart/#example) with `HtmlFormatter().get_style_defs()`. Pick the lexer by name or file name, and [guess it](https://pygments.org/docs/quickstart/#guessing-lexers) only when you don't know the language. Pygments [doesn't guarantee how long it runs](https://pygments.org/docs/security/), so on user input, run it with a short timeout and cap how many run at once. + +sqlparse is a [non-validating SQL parser](https://sqlparse.readthedocs.io/en/latest/): it splits scripts into statements, formats them, and walks their tokens, without assuming a SQL dialect. Use `split()`, `format()`, and `parse()`. On SQL from untrusted sources, [keep its grouping limits](https://sqlparse.readthedocs.io/en/latest/api.html#security-and-performance-considerations) as they are. + +phonenumbers is a Python port of Google's libphonenumber. Pass `parse()` the region the number was dialed from, unless it's in E.164 format. Then [check it's possible and valid](https://github.com/daviddrysdale/python-phonenumbers) with `is_possible_number()` and `is_valid_number()`. + +python-slugify makes URL slugs, and Unidecode turns Unicode text into ASCII. Their licenses differ. [Unidecode is GPL](https://github.com/avian2/unidecode). python-slugify's own code is MIT, and by default it runs on text-unidecode, which offers the Artistic license or GPL. But python-slugify [switches to Unidecode](https://github.com/un33k/python-slugify) whenever it's installed. + +shortuuid turns UUIDs into [short IDs for users to see](https://github.com/skorokithakis/shortuuid), and leaves out look-alike characters like l, 1, I, O, and 0. Sqids turns database keys and other integers into short IDs. Anyone can [decode them back into numbers](https://sqids.org/faq#not-recommended), so keep them away from sensitive data and user IDs. To check an ID is the canonical one, [re-encode the decoded numbers](https://sqids.org/faq#valid-ids) and compare. + +Store what these libraries generate, or pin their versions, since slugs and IDs can change between releases. python-slugify says to [pin the package and its backend](https://github.com/un33k/python-slugify), and Unidecode says to [store each slug once or lock the version](https://github.com/avian2/unidecode). Sqids says to [pass your own blocklist](https://sqids.org/faq#future-blocklist), even one identical to the default. From 9e8e38eb2eddce7b96417b355f6a572a624cde39 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:36:04 +0800 Subject: [PATCH 0902/1130] docs: add web scraping category intro The web scraping category page had no intro, leaving readers without guidance for picking among crawling frameworks, LLM-ready crawlers, AI browser agents, and content extractors. Co-Authored-By: Claude --- website/data/category_intros/web-scraping.md | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 website/data/category_intros/web-scraping.md diff --git a/website/data/category_intros/web-scraping.md b/website/data/category_intros/web-scraping.md new file mode 100644 index 00000000..0409e072 --- /dev/null +++ b/website/data/category_intros/web-scraping.md @@ -0,0 +1,28 @@ +To crawl a whole site, use Scrapy as your Python web scraping library; to feed pages to an LLM, Crawl4AI; to pull an article's main text, Trafilatura. + +How to choose: + +- Crawling a whole site, or many sites: Scrapy +- Web pages as clean Markdown for an LLM: Crawl4AI +- An article's main text and metadata: Trafilatura +- An AI agent that does a task in the browser for you: Browser Use +- Browser automation in your own code, with plain-language steps: Stagehand +- A browser agent that picks each action from a table of page elements: jev-ultrafast +- RSS, Atom, and JSON feeds: feedparser +- A whole HTML page as Markdown text: html2text + +Scrapy is a framework [for crawling websites and extracting structured data](https://docs.scrapy.org/en/latest/intro/overview.html) from them, and it sends requests asynchronously. Create a project with `scrapy startproject` and [write your spiders in it](https://docs.scrapy.org/en/latest/intro/tutorial.html). When a page loads its data with JavaScript, [find the request that returns the data](https://docs.scrapy.org/en/latest/topics/dynamic-content.html) and send it yourself. Use a headless browser only when that fails. To run your spiders in production, deploy them to [Scrapyd or Zyte Scrapy Cloud](https://docs.scrapy.org/en/latest/topics/deploy.html). + +Crawl4AI [turns websites into clean, LLM-ready Markdown](https://docs.crawl4ai.com/). It runs Chromium headless by default, so run `crawl4ai-setup` once to install the browser. Open an `AsyncWebCrawler` in an `async with` block and [call `arun()` for each URL](https://docs.crawl4ai.com/core/quickstart/). When pages share one layout, extract the data with CSS or XPath schemas: they [do exactly what you specify](https://docs.crawl4ai.com/extraction/no-llm-strategies/), while an LLM's output can vary. Keep [LLM extraction](https://docs.crawl4ai.com/extraction/llm-strategies/), which is slower and costlier, for content an AI has to interpret. + +Trafilatura [extracts a page's main text and metadata](https://trafilatura.readthedocs.io/en/latest/) and skips boilerplate like headers and footers. Call `extract()`, and pass `favor_precision=True` or `favor_recall=True` to [tune what it keeps](https://trafilatura.readthedocs.io/en/latest/usage-python.html). It works on raw HTML, so [render JavaScript pages first](https://trafilatura.readthedocs.io/en/latest/troubleshooting.html) with a browser and pass the result to `extract()`. It pairs with Scrapy: [Scrapy crawls, Trafilatura extracts](https://trafilatura.readthedocs.io/en/latest/faq.html#how-does-trafilatura-compare-to-beautifulsoup-or-scrapy). + +Browser Use is an AI browser agent: you [give it a task and an LLM](https://docs.browser-use.com/open-source/quickstart), and it runs the task in a browser. Its docs say to [be specific about the actions](https://docs.browser-use.com/open-source/customize/agent/prompting-guide) you want. Pass a Pydantic model as `output_model_schema` to get structured results. Don't take the agent's word for it: `is_successful()` is [only its own assessment](https://docs.browser-use.com/open-source/customize/agent/output-format), so check important results yourself, like whether a form really got submitted. Pass logins as `sensitive_data`, so the model [sees only placeholders](https://docs.browser-use.com/open-source/examples/templates/sensitive-data) in the text it reads. Set `use_vision=False` too, or the real values can leak through screenshots. + +Stagehand leaves the steps to your code: you [mix plain-language actions with regular page calls](https://docs.stagehand.dev/first-steps/introduction) in one script, and decide how much AI each step uses. Give each `act()` call [one focused action](https://docs.stagehand.dev/best-practices/prompting-best-practices), and pass credentials as variables, so their values never reach the model. To get data out, pass `extract()` a Pydantic model, and Stagehand [validates the result against it](https://docs.stagehand.dev/basics/extract). + +feedparser parses RSS, Atom, and JSON feeds with [one function, `parse()`](https://feedparser.readthedocs.io/en/latest/introduction/), which takes a URL, a file, or a string. When you poll a feed, send back the [ETag and Last-Modified values](https://feedparser.readthedocs.io/en/latest/http-etag/) from the last response. Otherwise you download unchanged feeds again, and the publisher may ban you. Content feedparser marks as `text/plain` [hasn't been sanitized](https://feedparser.readthedocs.io/en/latest/html-sanitization/), so escape it before you render it. + +html2text [converts a page of HTML into Markdown](https://github.com/Alir3z4/html2text), with options such as [`ignore_links`](https://github.com/Alir3z4/html2text/blob/master/docs/usage.md). It's GPL, while Trafilatura is [Apache](https://trafilatura.readthedocs.io/en/latest/). + +Whatever you pick, tell sites who you are and go easy on them. Scrapy's docs say to [set `USER_AGENT` to a value that identifies you](https://docs.scrapy.org/en/latest/topics/practices.html#avoiding-getting-banned) and space out your requests. The feedparser docs say to [set the User-Agent to your app's name and URL](https://feedparser.readthedocs.io/en/latest/http-useragent/), and Trafilatura's to [throttle per domain and follow robots.txt](https://trafilatura.readthedocs.io/en/latest/downloads.html). From 432a25e384bc573fff9174bbf914ff2b802d6e1b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:43:41 +0800 Subject: [PATCH 0903/1130] docs: add Game Development category intro The page had no guidance on which Python game library to pick. Recommends pygame-ce over upstream pygame, which fails to install on Python 3.14 (no cp314 wheel, open issues #4627/#4810). Co-Authored-By: Claude --- .../data/category_intros/game-development.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 website/data/category_intros/game-development.md diff --git a/website/data/category_intros/game-development.md b/website/data/category_intros/game-development.md new file mode 100644 index 00000000..60c4a8c2 --- /dev/null +++ b/website/data/category_intros/game-development.md @@ -0,0 +1,21 @@ +A 2D game needs a loop: write your own with pygame-ce, the Python game development library, or let Arcade run it. Panda3D does 3D, and Ren'Py visual novels. + +How to choose: + +- 2D game where you write the game loop: pygame-ce +- 2D game with a ready-made loop and physics engines: Arcade +- 3D game: Panda3D +- Visual novel or life simulation game: Ren'Py +- Windowing, OpenGL graphics, and sound with no other dependencies: pyglet + +pygame-ce is the community edition of pygame, a fork by its former core developers that aims for more frequent releases. Your code still says `import pygame`, so if pygame is already installed, [uninstall it first](https://github.com/pygame-community/pygame-ce/wiki/Installing-pygame%E2%80%90ce), then run `pip install pygame-ce` in a virtual environment. Its [quick start](https://pyga.me/docs/) gives you full control of the game loop: handle events, draw the frame, `flip()` the display, and cap the frame rate with `clock.tick()`. Convert each image once after you load it, with [`convert()`, or `convert_alpha()` if it has transparency](https://pyga.me/docs/ref/surface.html#pygame.Surface.convert), so it blits fast. It's under the LGPL, and its README says [closed-source and commercial games are fine](https://github.com/pygame-community/pygame-ce). + +Arcade is [an easy-to-learn library for 2D games](https://github.com/pythonarcade/arcade), built on pyglet and OpenGL, and meant for beginning programmers too. Start with the [Platformer Tutorial](https://api.arcade.academy/en/stable/tutorials/platform_tutorial/step_01.html): you subclass `arcade.Window`, draw in `on_draw()`, and `arcade.run()` runs the loop until the window closes. For movement and collisions, it comes with [physics engines](https://api.arcade.academy/en/stable/api_docs/api/physics_engines.html) for top-down and platformer games. Its code is MIT, and its [built-in assets need no attribution](https://api.arcade.academy/en/stable/), so you can ship them in a commercial game. + +Panda3D is [a 3D engine written in C++ with Python bindings](https://docs.panda3d.org/latest/python/introduction/index), and its manual says it's a tool for skilled programmers, not beginners. Install it with [`pip install panda3d`](https://github.com/panda3d/panda3d), subclass [`ShowBase`](https://docs.panda3d.org/latest/python/introduction/tutorial/starting-panda3d), and call `run()`, which holds the main loop. Its [`build_apps` tool](https://docs.panda3d.org/latest/python/distribution/index) builds self-contained executables for Windows, Linux, and macOS without needing each system. It's BSD, free for commercial games. + +Ren'Py is a [visual novel engine](https://www.renpy.org/) with its own script language, for stories that run on computers and mobile devices. It isn't a pip package: [download Ren'Py and run its launcher](https://www.renpy.org/doc/html/quickstart.html), create a project there, and write your story in `script.rpy`. Python works inside the scripts, and [third-party pure-Python packages](https://www.renpy.org/doc/html/python.html#first-and-third-party-python-modules-and-packages) go in `game/python-packages`. Ship with [Build Distributions](https://www.renpy.org/doc/html/build.html) in the launcher, which also builds a package for itch.io and Steam. Most of Ren'Py is MIT, but some parts are LGPL, so [distribute your game in a way that satisfies the LGPL](https://www.renpy.org/doc/html/license.html). + +pyglet is a [windowing and multimedia library with no external dependencies](https://pyglet.readthedocs.io/en/latest/), written in pure Python: windows, input, OpenGL graphics, images, video, and sound. Start with [Writing a pyglet application](https://pyglet.readthedocs.io/en/latest/programming_guide/quickstart.html), which attaches handlers with `@window.event` and calls `pyglet.app.run()`. Draw through a `Batch`, since the docs say [you always want batched rendering](https://pyglet.readthedocs.io/en/latest/programming_guide/shapes.html) for performance. It's under the BSD license. + +Move things by the time since the last frame, so your game runs at the same speed at any frame rate. pygame-ce's quick start gets it in seconds by [dividing `clock.tick()` by 1000](https://pyga.me/docs/), pyglet passes it as `dt` to [scheduled functions](https://pyglet.readthedocs.io/en/latest/programming_guide/time.html#sprite-movement-techniques), and Arcade passes it as `delta_time` to [`on_update()`](https://api.arcade.academy/en/stable/api_docs/api/window.html#arcade.Window.on_update). From e2308c087d349a39b20a93201adb4ed8f97b97ac Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:43:45 +0800 Subject: [PATCH 0904/1130] docs: add Date and Time category intro The page had no guidance on which Python date library to pick. Co-Authored-By: Claude --- website/data/category_intros/date-and-time.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 website/data/category_intros/date-and-time.md diff --git a/website/data/category_intros/date-and-time.md b/website/data/category_intros/date-and-time.md new file mode 100644 index 00000000..eeda6507 --- /dev/null +++ b/website/data/category_intros/date-and-time.md @@ -0,0 +1,21 @@ +Most code should handle time zones with zoneinfo and pick python-dateutil, the Python date library for parsing date strings and adding months. + +How to choose: + +- Time zones by IANA name, like Europe/Paris: zoneinfo +- Parsing date strings, adding months, and recurring dates: python-dateutil +- Dates as people write them, like "3 days ago", in many languages: dateparser +- An easier datetime API whose objects are still datetimes: pendulum +- Exact and local times as separate types, with DST-safe math: whenever + +zoneinfo brings the IANA time zone database to Python, and the datetime docs say [its usage is recommended](https://docs.python.org/3/library/datetime.html#tzinfo-objects). Attach a ZoneInfo to a datetime [through the constructor, `replace()`, or `astimezone()`](https://docs.python.org/3/library/zoneinfo.html#using-zoneinfo). Some systems, Windows among them, have no IANA database, so if your code runs across platforms, [declare a dependency on tzdata](https://docs.python.org/3/library/zoneinfo.html#data-sources). + +python-dateutil adds [extensions to the standard datetime module](https://dateutil.readthedocs.io/en/stable/); install it as `python-dateutil` and import it as `dateutil`. Its parser [reads most known formats](https://dateutil.readthedocs.io/en/stable/parser.html) and returns a datetime even for an ambiguous date. For input like 01/05/09, set `dayfirst` or `yearfirst` to match your data. For calendar math, `relativedelta` takes [plural arguments that add and singular ones that replace](https://dateutil.readthedocs.io/en/stable/relativedelta.html): `months=+1` moves a month ahead, and `day=1` jumps to the first. `rrule` builds [recurring dates from iCalendar rules](https://dateutil.readthedocs.io/en/stable/rrule.html). + +dateparser reads relative dates like "two weeks ago" and absolute ones in more than 200 language locales. Its docs say it [stands out](https://dateparser.readthedocs.io/en/latest/#common-use-cases) for scraped pages, logs, and other data from mixed sources, and for letting users type dates in their own words. Call `dateparser.parse()`, and [pass `languages` when you know them](https://dateparser.readthedocs.io/en/latest/#how-to-use), so it skips language detection. When you parse many dates from one source, [use `DateDataParser`](https://dateparser.readthedocs.io/en/latest/usage.html), which remembers the languages it has found. + +pendulum's classes are [drop-in replacements for the native ones](https://pendulum.eustace.io/docs/#introduction), since they inherit from datetime. Every instance is time zone aware and in UTC by default. Its docs call aware datetimes [the preferred and recommended way](https://pendulum.eustace.io/docs/#instantiation) to use it. For tests, install `pendulum[test]` and [travel in time](https://pendulum.eustace.io/docs/#testing). + +whenever puts exact time and local time in [separate types](https://whenever.readthedocs.io/en/latest/guide/choosing-a-type.html): an instant when only the moment matters, a zoned datetime when the local time matters too. Mixing up naive and aware [becomes a type error](https://whenever.readthedocs.io/en/latest/), and DST is handled in all arithmetic. A standard datetime [does no time zone adjustment](https://docs.python.org/3/library/datetime.html#datetime-objects) when you add a timedelta to it. In production, [turn whenever's DST warnings into errors](https://whenever.readthedocs.io/en/latest/faq.html#why-warnings-instead-of-errors) with Python's standard warnings filter. + +Decide whether you extend datetime or replace it. zoneinfo, python-dateutil, and dateparser all use standard datetime objects, so they work together. pendulum's objects are datetimes too, but code that checks the exact type, like sqlite3 and some database drivers, [needs an adapter registered](https://pendulum.eustace.io/docs/#limitations). whenever [doesn't subclass datetime at all](https://whenever.readthedocs.io/en/latest/faq.html#why-no-drop-in-replacement-for-datetime), so [convert to and from standard datetimes](https://whenever.readthedocs.io/en/latest/guide/stdlib-convert.html) where other code needs one. From 4d09bcb7aba35f9f519a75d12eb0779a2728818b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 02:43:48 +0800 Subject: [PATCH 0905/1130] docs: add Implementations category intro The page had no guidance on when to run CPython vs MicroPython, Pyodide, Cython, or PyPy. Co-Authored-By: Claude --- .../data/category_intros/implementations.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 website/data/category_intros/implementations.md diff --git a/website/data/category_intros/implementations.md b/website/data/category_intros/implementations.md new file mode 100644 index 00000000..1ea70ecd --- /dev/null +++ b/website/data/category_intros/implementations.md @@ -0,0 +1,21 @@ +Unless you need MicroPython on a microcontroller or Pyodide in the browser, CPython is the Python implementation to run. Cython compiles your slow code to C. + +How to choose: + +- Most projects: CPython +- Microcontrollers and other constrained devices: MicroPython +- Python in the browser or Node.js: Pyodide +- Slow code to compile, or a C library to wrap: Cython +- Long-running programs in pure Python: PyPy + +CPython is the [original and most-maintained implementation](https://docs.python.org/3/reference/introduction.html#alternate-implementations) of Python, written in C, and new language features generally show up there first. Give each app its own [virtual environment](https://docs.python.org/3/installing/index.html) from venv, and install packages into it with pip. + +MicroPython is a lean implementation of Python 3, [optimized to run on microcontrollers](https://micropython.org/) and in constrained environments. It includes a small subset of the standard library, plus modules like `machine` for the hardware. Flash your board with firmware from [the download page](https://micropython.org/download/), then manage it from your computer with [mpremote](https://docs.micropython.org/en/latest/reference/mpremote.html). `mpremote mip install` [installs packages](https://docs.micropython.org/en/latest/reference/packages.html#installing-packages-with-mpremote) from micropython-lib by default, not PyPI. When RAM runs short, [freeze the modules that rarely change](https://docs.micropython.org/en/latest/reference/packages.html#freezing-packages) into the firmware. + +Pyodide is [a port of CPython to WebAssembly](https://pyodide.org/en/stable/) that runs in the browser and Node.js. It installs any pure Python wheel from PyPI, and many packages with C extensions, like NumPy and pandas, have been ported for it. Install packages with [`micropip.install()`](https://pyodide.org/en/stable/usage/loading-packages.html#how-to-chose-between-micropip-install-and-pyodide-loadpackage), which its docs recommend for almost everything. In a web page, run Pyodide [in a web worker](https://pyodide.org/en/stable/usage/webworker.html), so long computations don't freeze your UI. + +Cython isn't a separate interpreter: it [translates Python code to C](https://cython.readthedocs.io/en/latest/src/quickstart/overview.html) that runs inside CPython, to speed up slow code or wrap C libraries. Write your code in [pure Python syntax](https://cython.readthedocs.io/en/latest/src/tutorial/pure.html) to keep the file runnable by the plain interpreter, and use `.pyx` files for what that syntax can't express. The annotation report from `cython -a` shows [where types help](https://cython.readthedocs.io/en/latest/src/quickstart/cythonize.html#determining-where-to-add-types). Build your package with [a build backend](https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compiling-with-a-build-backend), and ship [prebuilt wheels](https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compiling-with-pyximport) to your users. + +PyPy is a replacement for CPython, and speed is the reason to use it. It works best on [long-running programs that spend much of their time in Python code](https://pypy.org/features.html#speed), not on short scripts. Code built on C extension modules is a poor fit, since they [often run much slower on PyPy than on CPython](https://doc.pypy.org/faq.html#do-c-extension-modules-work-with-pypy). Packages installed for CPython aren't available to PyPy, so [install them for PyPy](https://doc.pypy.org/faq.html#module-xyz-does-not-work-with-pypy-importerror) in its own virtual environment with `pypy -m pip`. + +Before you switch implementations or compile anything for speed, [measure first](https://pypy.org/performance.html#profiling-vmprof) to confirm the slowdown is real. Then profile to find the slow parts, and only optimize those. From 80958eb516721cdefda3a07d56e50f1f8ecf8b65 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:02:51 +0800 Subject: [PATCH 0906/1130] docs: add deep learning category intro The Deep Learning page had no intro, leaving readers with no pick among PyTorch, Keras, JAX, Lightning, and the reinforcement learning libraries. Co-Authored-By: Claude --- website/data/category_intros/deep-learning.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 website/data/category_intros/deep-learning.md diff --git a/website/data/category_intros/deep-learning.md b/website/data/category_intros/deep-learning.md new file mode 100644 index 00000000..9201d434 --- /dev/null +++ b/website/data/category_intros/deep-learning.md @@ -0,0 +1,24 @@ +Three Python deep learning frameworks, three strengths: PyTorch for research and new architectures, Keras for a high-level API, JAX for compiled code on TPUs. + +How to choose: + +- Research and new model architectures: PyTorch +- High-level model building on JAX or PyTorch: Keras +- NumPy-style array code compiled for GPUs and TPUs: JAX +- PyTorch training without the loop boilerplate: PyTorch Lightning +- Reinforcement learning environments: Gymnasium +- Reinforcement learning algorithms: Stable-Baselines3 + +PyTorch models are `nn.Module` subclasses, and autograd [builds the computational graph as your code runs](https://docs.pytorch.org/docs/stable/user_guide/pytorch_main_components.html). Install it with [the command from its selector](https://pytorch.org/get-started/locally/), which matches your OS and GPU. To keep a model, [save its `state_dict`](https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html#save-load-state-dict-recommended) instead of pickling the whole module. Wrap your model in [`torch.compile`](https://docs.pytorch.org/tutorials/intermediate/torch_compile_tutorial.html) to speed it up with minimal code changes. To train on more than one GPU, use [DistributedDataParallel](https://docs.pytorch.org/docs/stable/notes/cuda.html#use-nn-parallel-distributeddataparallel-instead-of-multiprocessing-or-nn-dataparallel). + +Keras is a [multi-framework API](https://keras.io/getting_started/about/): a Keras model can run as a PyTorch Module or as a JAX function. Install a backend next to it and [set `KERAS_BACKEND`](https://keras.io/getting_started/#configuring-your-backend) before you import Keras. Build simple models as a Sequential stack of layers and anything more complex with the functional API. Save the whole model to [a `.keras` file](https://keras.io/getting_started/faq/#what-are-my-options-for-saving-models) with `model.save()` instead of pickling it. The file [reloads with any backend](https://keras.io/keras_3/). + +JAX does [accelerator-oriented array computation](https://docs.jax.dev/en/latest/) with a NumPy-style API and composable transformations: `jax.grad` for derivatives, `jax.jit` for compilation, and `jax.vmap` for batching. The transformations only work on [functionally pure functions](https://docs.jax.dev/en/latest/notebooks/Common_Gotchas_in_JAX.html#pure-functions), so pass all data in as arguments and return every result. JAX itself stays narrow: to train neural networks, use the [JAX AI Stack](https://docs.jaxstack.ai/en/latest/getting_started.html), with Flax NNX for models and Optax for optimizers. On NVIDIA GPUs, the JAX team strongly recommends [installing CUDA and cuDNN from pip wheels](https://docs.jax.dev/en/latest/installation.html#pip-installation-nvidia-gpu-cuda-installed-via-pip-easier). + +PyTorch Lightning [organizes PyTorch code to remove boilerplate](https://lightning.ai/docs/pytorch/stable/home/introduction): you write the model logic in a LightningModule, and the Trainer handles devices, precision, and distributed training. Install it as [the `lightning` package](https://lightning.ai/docs/pytorch/stable/home/installation). Its [style guide](https://lightning.ai/docs/pytorch/stable/reference/starter/style_guide) recommends keeping each LightningModule self-contained, the model separate from the system that trains it, and data loading in a LightningDataModule. To keep your own training loop, use [Lightning Fabric](https://lightning.ai/docs/fabric/stable), which scales a plain PyTorch script after you change a few lines. + +Gymnasium is [an API standard for reinforcement learning](https://gymnasium.farama.org/), with a collection of reference environments. It's the maintained fork of OpenAI's Gym, and many older tutorials still use Gym's old API, so follow its [migration guide](https://gymnasium.farama.org/introduction/migration_guide/) when you port one. [Register your own environment](https://gymnasium.farama.org/introduction/create_custom_env/#registering-and-making-the-environment) so `gymnasium.make()` creates it like a built-in one, and run [`check_env`](https://gymnasium.farama.org/introduction/create_custom_env/#check-environment-validity) on it to catch common issues. + +Stable-Baselines3 is a set of [reliable implementations of reinforcement learning algorithms in PyTorch](https://stable-baselines3.readthedocs.io/en/master/), and it trains on any environment that [follows the Gymnasium interface](https://stable-baselines3.readthedocs.io/en/master/guide/custom_env.html). It [assumes you know some reinforcement learning](https://github.com/DLR-RM/stable-baselines3). Its tips page recommends [starting from the RL Zoo's tuned hyperparameters](https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html#general-advice-when-using-reinforcement-learning) and normalizing the agent's input. Evaluate the agent on [a separate test environment](https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html#how-to-evaluate-an-rl-algorithm), since training adds exploration noise. [Pick an algorithm](https://stable-baselines3.readthedocs.io/en/master/guide/rl_tips.html#which-algorithm-should-i-use) by your action space first: DQN handles only discrete actions, and SAC only continuous ones. + +PyTorch's security policy says [running untrusted models is equivalent to running untrusted code](https://github.com/pytorch/pytorch/blob/main/SECURITY.md), so run untrusted ones in a sandbox. Load checkpoints with [`weights_only=True`](https://docs.pytorch.org/docs/stable/notes/serialization.html#weights-only-security) in `torch.load`, and leave [`safe_mode`](https://keras.io/api/models/model_saving_apis/model_saving_and_loading/) on when Keras loads a model. From 579ea6bf85b1c8d0179f558c5db85fa3bef993e0 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:03:12 +0800 Subject: [PATCH 0907/1130] docs: add machine learning category intro The page had no intro, so readers got no pick among scikit-learn, the boosting libraries, pgmpy, Feature-engine, and TimesFM. Co-Authored-By: Claude --- .../data/category_intros/machine-learning.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 website/data/category_intros/machine-learning.md diff --git a/website/data/category_intros/machine-learning.md b/website/data/category_intros/machine-learning.md new file mode 100644 index 00000000..586bbbc5 --- /dev/null +++ b/website/data/category_intros/machine-learning.md @@ -0,0 +1,29 @@ +Tabular data goes to scikit-learn, and boosted trees to LightGBM or CatBoost. Both fit in that Python machine learning library's Pipeline. + +How to choose: + +- Classification, regression, and clustering on tabular data: scikit-learn +- Boosted trees that train fast on large datasets: LightGBM +- Boosted trees on data with many categorical columns: CatBoost +- Bayesian networks and causal models: pgmpy +- Feature engineering on pandas dataframes: Feature-engine +- Boosted trees on a Spark, Dask, or Ray cluster: XGBoost +- Forecasting time series without training a model first: TimesFM + +scikit-learn covers [supervised and unsupervised learning](https://scikit-learn.org/stable/getting_started.html), plus preprocessing, model selection, and evaluation. Deep learning is [out of its scope](https://scikit-learn.org/stable/faq.html#why-is-there-no-support-for-deep-or-reinforcement-learning-will-there-be-such-support-in-the-future). To pick a model, follow its [Choosing the right estimator](https://scikit-learn.org/stable/machine_learning_map.html) chart. Split your data into train and test sets [before any preprocessing](https://scikit-learn.org/stable/common_pitfalls.html#how-to-avoid-data-leakage). Then put the preprocessing and the model in one Pipeline, so cross-validation and search never fit on the data they score. For gradient boosting without another dependency, its HistGradientBoostingClassifier and HistGradientBoostingRegressor [handle missing values and categorical data](https://scikit-learn.org/stable/modules/ensemble.html#gradient-boosted-trees) with no preprocessing. + +LightGBM aims at [faster training and lower memory use](https://lightgbm.readthedocs.io/en/latest/). It grows trees leaf-wise, so `num_leaves` is [the main parameter to tune](https://lightgbm.readthedocs.io/en/latest/Parameters-Tuning.html#tune-parameters-for-the-leaf-wise-best-first-tree): keep it below 2^(max_depth). To prevent overfitting, raise `min_data_in_leaf`. Instead of one-hot encoding, mark categorical columns with `categorical_feature`: it [often performs better](https://lightgbm.readthedocs.io/en/latest/Advanced-Topics.html#categorical-feature-support). With a validation set, use [early stopping](https://lightgbm.readthedocs.io/en/latest/Python-Intro.html#early-stopping) to find the number of boosting rounds. + +CatBoost takes [non-numeric features without preprocessing](https://catboost.ai/) and gives good results with its default parameters. Its docs say [not to one-hot encode](https://catboost.ai/docs/en/features/categorical-features) during preprocessing: list the categorical columns in `cat_features` instead. Before tuning anything else, [rule out underfitting and overfitting](https://catboost.ai/docs/en/concepts/parameter-tuning): set a large number of iterations, and turn on the overfitting detector and the use-best-model option. The learning rate is set from your data by default. + +pgmpy does [causal and probabilistic reasoning with graphical models](https://pgmpy.org/), from learning a graph from data to running inference on the fitted model. scikit-learn [leaves graphical models out](https://scikit-learn.org/stable/faq.html#will-you-add-graphical-models-or-sequence-prediction-to-scikit-learn), so use pgmpy for Bayesian networks. Every discovery algorithm [follows one pattern](https://pgmpy.org/guides/causal_discovery.html#api): instantiate, fit, and read the result. Switching algorithms means changing only the class. Pass what you already know about the domain as [required or forbidden edges](https://pgmpy.org/guides/causal_discovery.html#expert-knowledge) with `ExpertKnowledge`. For queries, Variable Elimination is [the default choice](https://pgmpy.org/guides/probabilistic_inference.html#exact-inference) while the model is small enough for exact inference. + +Feature-engine is for work where [pandas and scikit-learn are your main tools](https://feature-engine.trainindata.com/en/latest/#sitting-at-the-interface-of-pandas-and-scikit-learn). Each transformer takes the columns it changes in its `variables` argument, so it [applies steps to selected groups of variables](https://scikit-learn.org/stable/related_projects.html). Fit the transformers on the training set and transform both sets, as the [quick start](https://feature-engine.trainindata.com/en/latest/quickstart/index.html) does. Put them in a scikit-learn Pipeline, and your [whole feature engineering pipeline](https://feature-engine.trainindata.com/en/latest/quickstart/index.html#feature-engine-within-scikit-learn-s-pipeline) saves as one object. + +XGBoost is built to be [efficient, flexible, and portable](https://xgboost.readthedocs.io/en/stable/). The same code runs on distributed environments, and its docs cover training on Dask, Spark, and Ray. Use its scikit-learn interface, like `XGBClassifier`, so it [works with scikit-learn's tools](https://xgboost.readthedocs.io/en/stable/python/sklearn_estimator.html) such as cross-validation. For categorical columns, pass a dataframe with the `category` dtype and set [`enable_categorical`](https://xgboost.readthedocs.io/en/stable/tutorials/categorical.html#training-with-scikit-learn-interface). Its docs suggest you tune with cross-validation, then [retrain with the best parameters and early stopping](https://xgboost.readthedocs.io/en/stable/python/sklearn_estimator.html#early-stopping). To keep a model, [save it with `save_model`](https://xgboost.readthedocs.io/en/stable/tutorials/saving_model.html), since a pickle is a memory snapshot meant only for checkpoints. + +TimesFM is a forecasting model that Google Research pretrained on a large time-series corpus. It [does well zero-shot](https://research.google/blog/a-decoder-only-foundation-model-for-time-series-forecasting/) on benchmarks from many domains, so you can forecast without training a model first. Install it with the extra for your backend, and load a checkpoint from the Hugging Face Hub. The code is Apache licensed, but [the pretrained weights carry their own license](https://github.com/google-research/timesfm), so check the model card before commercial or production use. + +Every pick but TimesFM works with scikit-learn. XGBoost, LightGBM, and CatBoost ship scikit-learn estimators, Feature-engine's transformers go in a Pipeline, and pgmpy is [scikit-learn compatible where possible](https://pgmpy.org/). Independent benchmarks find no single winner among the three boosting libraries, so compare them on your own data in the same cross-validation. + +Treat a model file you didn't make like code. scikit-learn's docs say to [never load a pickle from an untrusted source](https://scikit-learn.org/stable/model_persistence.html#security-maintainability-limitations), and point to skops.io or ONNX instead. XGBoost's [security notes](https://xgboost.readthedocs.io/en/stable/security.html#use-of-python-pickle) say the same about pickles. From 74513330aa7b23ae19e456dc6a97ff6cac99e0b1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:03:31 +0800 Subject: [PATCH 0908/1130] docs: add natural language processing category intro The page had no intro, so readers got no pick among spaCy, NLTK, Stanza, and the Chinese text libraries. Co-Authored-By: Claude --- .../natural-language-processing.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 website/data/category_intros/natural-language-processing.md diff --git a/website/data/category_intros/natural-language-processing.md b/website/data/category_intros/natural-language-processing.md new file mode 100644 index 00000000..de0180fc --- /dev/null +++ b/website/data/category_intros/natural-language-processing.md @@ -0,0 +1,24 @@ +Whether you're building an app or learning the field, there's a Python NLP library for it: spaCy for apps, NLTK for learning. Stanza covers many languages. + +How to choose: + +- Production NLP pipelines: spaCy +- Learning NLP, or lexical resources like WordNet: NLTK +- Many languages, or CoreNLP from Python: Stanza +- Chinese word segmentation: jieba +- Chinese characters to pinyin: pypinyin +- Spaces between CJK text and letters or digits: pangu.py + +spaCy is [designed for production use](https://spacy.io/usage/facts-figures#comparison-usage): you build and train NLP pipelines, then package them to deploy. Its trained pipelines are Python packages. In an automated build, [install them with pip from a direct link](https://spacy.io/usage/models#download-pip) instead of spaCy's download command, and put that link in your requirements.txt. In a larger code base, [import the pipeline as a module](https://spacy.io/usage/models#models-loading), so a missing one raises an ImportError right away. Run your texts through `nlp.pipe` [in batches](https://spacy.io/usage/processing-pipelines#processing), and disable the components you don't need. To train your own pipeline, run [`spacy train` with one config file](https://spacy.io/usage/training#quickstart). + +NLTK comes with [corpora and lexical resources such as WordNet](https://www.nltk.org/), plus libraries to tokenize, tag, parse, and classify text. Its creators wrote a book that teaches NLP with it, and you can read it online. The book also says NLTK [isn't highly optimized for runtime performance](https://www.nltk.org/book/ch00.html#natural-language-toolkit-nltk), so use it to learn and experiment. The data ships separately: [get it with NLTK's data downloader](https://www.nltk.org/data.html), and set `NLTK_DATA` when you install it somewhere other than the standard locations. + +Stanza is [designed to work across many languages, using the Universal Dependencies formalism](https://stanfordnlp.github.io/stanza/#about). It's also the official Python interface to Stanford's Java CoreNLP, though its own pipeline doesn't need CoreNLP. You can [list the processors to load](https://stanfordnlp.github.io/stanza/getting_started.html#specifying-processors) with `processors=`. Pass all your documents to the pipeline [at once](https://stanfordnlp.github.io/stanza/getting_started.html#processing-multiple-documents), since a for loop over one sentence at a time is very slow. For a lot of text, [run it on a GPU](https://stanfordnlp.github.io/stanza/getting_started.html#controlling-devices). To keep the pipeline from downloading anything at runtime, [download the models ahead of time](https://stanfordnlp.github.io/stanza/getting_started.html#downloading-models-for-offline-usage). + +jieba [segments Chinese text into words](https://github.com/fxsjy/jieba): accurate mode suits text analysis, and search engine mode cuts long words into short ones for better recall. Add your own words with `jieba.load_userdict()` to get higher accuracy, and for Traditional Chinese, switch to its bigger dictionary with `jieba.set_dictionary()`. The other picks work with it: spaCy can [use jieba as its Chinese segmenter](https://spacy.io/usage/models#chinese), and Stanza [supports it as a tokenizer](https://stanfordnlp.github.io/stanza/pipeline.html). + +pypinyin [matches pinyin by whole words](https://github.com/mozillazg/python-pinyin), so it handles characters with more than one reading. It also writes zhuyin (Bopomofo) and Wade-Giles. When a wrong word split gives a wrong reading, [segment the text with jieba first](https://pypinyin.readthedocs.io/zh-cn/latest/faq.html) and pass in the list of words. For readings that are still wrong, [add your own](https://pypinyin.readthedocs.io/zh-cn/latest/usage.html#custom-dict) with `load_phrases_dict()` or `load_single_dict()`. + +pangu.py [inserts spaces between CJK characters and letters, digits, and symbols](https://github.com/vinta/pangu.py). Call `pangu.space_text()` on a string or `pangu.space_file()` on a file. From the command line, `pangu-py -c` prints the corrected text and exits with 1 when the spacing needed fixing. + +Check the license of the models and data, not only the library. spaCy is [MIT](https://github.com/explosion/spaCy/blob/master/LICENSE), but its [Spanish pipelines](https://spacy.io/models/es) are GPL and its [Italian ones](https://spacy.io/models/it) are for non-commercial use only. NLTK is [Apache](https://github.com/nltk/nltk/blob/develop/LICENSE.txt), and its corpora come [under various licenses](https://github.com/nltk/nltk/wiki/FAQ), each listed in its own README. From 3c05cd63d61d2a114a8619f7fac111478634467a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:26:34 +0800 Subject: [PATCH 0909/1130] docs: add web frameworks category intro The page had no intro, so readers got no pick among Django, Flask, and the other full-stack and async frameworks. Co-Authored-By: Claude --- .../data/category_intros/web-frameworks.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 website/data/category_intros/web-frameworks.md diff --git a/website/data/category_intros/web-frameworks.md b/website/data/category_intros/web-frameworks.md new file mode 100644 index 00000000..653d607d --- /dev/null +++ b/website/data/category_intros/web-frameworks.md @@ -0,0 +1,35 @@ +When you want an ORM, an admin, and auth built in, use Django. Flask, a smaller Python web framework, gives you a core you extend. + +How to choose: + +- ORM, admin, and auth built in: Django +- A small core you extend with your own picks: Flask +- One file with no dependencies: Bottle +- An app that starts small and may grow large: Pyramid +- HTML from the server with HTMX: FastHTML +- A minimal async toolkit to build on: Starlette +- Long polling, WebSockets, and other long-lived connections: Tornado +- Async with sessions, caching, and ORM integration built in: Litestar +- Frontend and backend in pure Python: Reflex + +Django comes with [a full stack for convenience](https://docs.djangoproject.com/en/stable/misc/design-philosophies/), and its pieces stay independent where possible. You describe your database layout as models in Python, and Django [builds an admin interface from them](https://docs.djangoproject.com/en/stable/intro/overview/). [User authentication](https://docs.djangoproject.com/en/stable/topics/auth/) with accounts, groups, and permissions is built in too. A project holds your settings and [one or more apps](https://docs.djangoproject.com/en/stable/intro/tutorial01/#creating-the-polls-app), and an app can move between projects. Before you deploy, run [`manage.py check --deploy`](https://docs.djangoproject.com/en/stable/howto/deployment/checklist/#run-manage-py-check-deploy) against your production settings. Async views work under WSGI, but for slow streaming and long polling, [deploy Django under ASGI](https://docs.djangoproject.com/en/stable/topics/async/#async-views). + +Flask keeps [the core simple but extensible](https://flask.palletsprojects.com/en/stable/design/#what-does-micro-mean): it doesn't pick your database or form library, and extensions add them. Create the app in an [application factory](https://flask.palletsprojects.com/en/stable/patterns/appfactories/) and bind each extension with `init_app`, so tests can build instances with their own settings. Flask runs each async view on a separate thread, not an event loop, which [costs performance compared with ASGI frameworks](https://flask.palletsprojects.com/en/stable/design/#async-await-and-asgi-support). For a mainly async codebase, its docs point you to an async-first framework. + +Bottle is [a single file module](https://bottlepy.org/docs/stable/) with no dependencies outside the standard library. Its FAQ pitches it for [prototyping, weekend projects, and small applications](https://bottlepy.org/docs/stable/faq.html#is-bottle-suitable-for-complex-applications), and suggests a full-stack framework like Django when you have tight deadlines. In production, have [Gunicorn or another WSGI server load your app](https://bottlepy.org/docs/stable/deployment.html) instead of calling `run()`. + +Pyramid is built so you [don't have to rewrite a small app in another framework](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/introduction.html#what-makes-pyramid-unique) when it gets too big. Its core maps URLs to code, handles security, and serves static assets, and it makes no assertions about which database or template system you use. Start from [its cookiecutter](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/project.html), which asks for your template language, persistence, and URL mapping. Deploy with the `production.ini` it generates, which turns off the interactive debugger. + +FastHTML is [designed to create hypermedia applications](https://www.fastht.ml/about/tech): it returns HTML from the server, the approach HTMX uses, and you often won't write any JavaScript at all. It's built on Starlette and Uvicorn. Its docs say [not to assume other frameworks' best practices apply](https://www.fastht.ml/docs/ref/best_practice.html): let the function name define each route, and use only GET and POST. + +Starlette is [a lightweight ASGI framework/toolkit](https://starlette.dev/#framework-or-toolkit): use it as a complete framework, or take any of its components on their own. Install an ASGI server such as Uvicorn next to it, and pick [any async database library](https://starlette.dev/database/) you like. Keep configuration [in environment variables or a `.env` file](https://starlette.dev/config/) that you don't commit. + +Tornado [isn't based on WSGI](https://www.tornadoweb.org/en/stable/#threads-and-wsgi) and typically runs one thread per process, with its own web framework and HTTP server used together. Its non-blocking I/O makes it a fit for [long polling, WebSockets, and other long-lived connections](https://www.tornadoweb.org/en/stable/). Hand blocking code to `run_in_executor`, and [run one process per CPU](https://www.tornadoweb.org/en/stable/guide/running.html#processes-and-ports). + +Litestar is [not a microframework](https://docs.litestar.dev/latest/#philosophy): it comes with ORM integration, client- and server-side sessions, and caching, though it will never have its own ORM. Class-based controllers sit at its core. Set dependencies, guards, and middleware on [any layer](https://docs.litestar.dev/latest/onboarding/flask.html), from the app down to one handler, and the setting closest to the handler wins. + +Reflex builds the [frontend, backend, and database in pure Python](https://reflex.dev/docs/getting-started/introduction/). It [compiles your UI to a React frontend](https://reflex.dev/docs/advanced-onboarding/how-reflex-works/), runs your state handlers on the server, and syncs the two over WebSockets. Change state only through event handlers on your `State` class. In production, the Reflex team runs Redis as the state manager. Keep auth data and other sensitive state in [backend-only vars](https://reflex.dev/docs/vars/base-vars/#backend-only-vars). + +Don't deploy on the development server: [Flask](https://flask.palletsprojects.com/en/stable/deploying/) and [Django](https://docs.djangoproject.com/en/stable/howto/deployment/checklist/#switch-away-from-manage-py-runserver) both tell you to switch to a production server, and to turn debug mode off. + +For a site with forms and logins, turn on CSRF protection. Django's [CSRF middleware is on by default](https://docs.djangoproject.com/en/stable/howto/csrf/). Tornado, Pyramid, and Litestar have it as a setting you turn on. Flask [leaves it to a form library](https://flask.palletsprojects.com/en/stable/web-security/#cross-site-request-forgery-csrf), and Starlette to third-party middleware. From d45070592679adf7a970c051200a093fb2bc9065 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:26:57 +0800 Subject: [PATCH 0910/1130] docs: add web APIs category intro The Web APIs category page had no intro, leaving readers without a pick among Django REST framework, Django Ninja, FastAPI, and the GraphQL and RPC libraries. Co-Authored-By: Claude --- website/data/category_intros/web-apis.md | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 website/data/category_intros/web-apis.md diff --git a/website/data/category_intros/web-apis.md b/website/data/category_intros/web-apis.md new file mode 100644 index 00000000..87bc81c7 --- /dev/null +++ b/website/data/category_intros/web-apis.md @@ -0,0 +1,33 @@ +For a Django project, pick Django REST framework or Django Ninja. Outside Django, build on FastAPI, a Python API framework based on type hints. + +How to choose: + +- Django, with CRUD endpoints over your models: Django REST framework +- Django, with endpoints declared by type hints: Django Ninja +- A new service outside Django: FastAPI +- GraphQL over Django models: Strawberry GraphQL Django +- Django, with msgspec, attrs, or dataclass schemas: django-modern-rest +- A Flask app: APIFlask +- An OpenAPI spec written before the code: Connexion +- GraphQL on FastAPI, Flask, or another framework: Strawberry +- RPC between services in any language: gRPC + +Django REST framework adds serializers and a [browsable API](https://www.django-rest-framework.org/) to Django. For CRUD over your models, [group the views in a `ModelViewSet` and register it with a router](https://www.django-rest-framework.org/tutorial/quickstart/). For an OpenAPI schema, its docs [recommend drf-spectacular](https://www.django-rest-framework.org/topics/documenting-your-api/#drf-spectacular). By default, the API [allows unrestricted access](https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy), so set a policy in `DEFAULT_PERMISSION_CLASSES`. + +Django Ninja is [heavily inspired by FastAPI](https://django-ninja.dev/motivation/) and works with Django's ORM, URLs, views, and auth. You write each endpoint as a function with type hints, and it [generates the OpenAPI docs](https://django-ninja.dev/) from them. Run async views on [an ASGI server](https://django-ninja.dev/guides/async-support/). If you use Django's cookie-based auth, [keep Django's CSRF protection on](https://django-ninja.dev/reference/csrf/#use-djangos-built-in-csrf-protection). + +FastAPI builds [request validation and OpenAPI docs from standard type hints](https://fastapi.tiangolo.com/), on top of Pydantic and Starlette. Install `fastapi[standard]`, which brings Uvicorn and the `fastapi` command: run `fastapi dev` while you work and [`fastapi run` in production](https://fastapi.tiangolo.com/fastapi-cli/). + +Strawberry GraphQL Django [builds types from your Django models](https://strawberry.rocks/docs/django), while Strawberry alone [only provides a GraphQL view](https://strawberry.rocks/docs/integrations/django) for Django. Add its [query optimizer](https://strawberry.rocks/docs/django/guide/optimizer), which calls `select_related()` and `prefetch_related()` for you to avoid N+1 queries. + +django-modern-rest [drops into an existing Django app](https://django-modern-rest.readthedocs.io/en/latest/) and validates both requests and responses against your schemas: msgspec, Pydantic, attrs, dataclasses, and more. Its docs [recommend always installing msgspec](https://django-modern-rest.readthedocs.io/en/latest/pages/getting-started.html#installation) to parse JSON, even when your schemas are Pydantic models. + +APIFlask is a [thin wrapper on Flask](https://apiflask.com/migrations/flask/): swap `Flask` for `APIFlask` and `Blueprint` for `APIBlueprint`, and your [Flask extensions keep working](https://apiflask.com/comparison/#apiflask-vs-fastapi). Declare each endpoint's input and output with `@app.input()` and `@app.output()`, as [marshmallow schemas or Pydantic models](https://apiflask.com/), and APIFlask generates the OpenAPI docs from them. + +Connexion is spec-first: you write the OpenAPI spec, and Connexion [routes and validates requests against it](https://connexion.readthedocs.io/en/latest/#why-connexion), so server and client can be built in parallel. FastAPI goes the other way, and its maintainer says it's [not meant for writing the schema first](https://github.com/fastapi/fastapi/discussions/6169). Start a new project on [`AsyncApp`](https://connexion.readthedocs.io/en/latest/quickstart.html#creating-your-application), or on `FlaskApp` to keep the Flask ecosystem. + +Strawberry builds a GraphQL schema from [dataclasses and type hints](https://strawberry.rocks/docs), and FastAPI's docs [recommend it for GraphQL](https://fastapi.tiangolo.com/how-to/graphql/#graphql-with-strawberry). Before you deploy, [turn off GraphiQL and introspection](https://strawberry.rocks/docs/operations/deployment), and add the [security extensions](https://strawberry.rocks/docs/operations/deployment#security-extensions) that limit query depth, aliases, and tokens. + +gRPC has you [define a service once in a `.proto` file](https://grpc.io/docs/languages/python/basics/) and generate its clients and servers in any language gRPC supports. Install `grpcio` and `grpcio-tools`, and [compile the `.proto` file into Python code with `grpc_tools.protoc`](https://grpc.io/docs/languages/python/quickstart/). [Use TLS](https://grpc.io/docs/guides/auth/) to authenticate the server and encrypt the traffic. + +Keep separate models for what an endpoint takes in and what it sends back. FastAPI [filters the response through the output model](https://fastapi.tiangolo.com/tutorial/response-model/#add-an-output-model), so fields the output model leaves out, like a password, never reach the client. APIFlask [recommends separate input and output schemas](https://apiflask.com/schema/#marshmallow) too. From 10a7f12861ce64ef0567c85ff77e08e45da15f7a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 03:27:35 +0800 Subject: [PATCH 0911/1130] docs: add web servers category intro The Web Servers page had no intro, so readers got no pick among Gunicorn, Uvicorn, Waitress, Granian, and Hypercorn. Co-Authored-By: Claude --- website/data/category_intros/web-servers.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 website/data/category_intros/web-servers.md diff --git a/website/data/category_intros/web-servers.md b/website/data/category_intros/web-servers.md new file mode 100644 index 00000000..68fa6400 --- /dev/null +++ b/website/data/category_intros/web-servers.md @@ -0,0 +1,21 @@ +In production, Gunicorn serves WSGI apps like Django, and Uvicorn serves ASGI apps like FastAPI. Waitress, a pure-Python web server, runs WSGI apps on Windows. + +How to choose: + +- A WSGI app (Flask, Django) on Linux or macOS: Gunicorn +- An ASGI app (FastAPI, Starlette): Uvicorn +- A WSGI app on Windows, or a server in pure Python: Waitress +- One server for both ASGI and WSGI apps, or throughput above all: Granian +- An app on Trio, or HTTP/3 from the Python server: Hypercorn + +Gunicorn is a [pre-fork server](https://gunicorn.org/design/#server-model): one process manages a pool of worker processes. It's [built for Unix](https://gunicorn.org/). With its default sync workers, your proxy must [buffer slow clients](https://gunicorn.org/deploy/#nginx-configuration), or Gunicorn is open to denial-of-service attacks. Start with (2 × CPU cores) + 1 workers and [adjust under load](https://gunicorn.org/design/#how-many-workers). For long blocking calls, streaming, or WebSockets, switch to [async workers](https://gunicorn.org/design/#when-to-use-async-workers) like gevent. + +Uvicorn is an [ASGI web server](https://uvicorn.dev/). In production, run it under a [process manager](https://uvicorn.dev/deployment/#using-a-process-manager). Its [built-in one](https://uvicorn.dev/deployment/#built-in) starts workers with `--workers` and restarts any that die, and its docs also cover [running Uvicorn workers under Gunicorn](https://uvicorn.dev/deployment/#gunicorn). In a container, run a single Uvicorn process and [let your orchestrator scale](https://uvicorn.dev/deployment/docker/) the number of containers. + +Granian is a [Rust HTTP server](https://github.com/emmett-framework/granian) that serves ASGI, WSGI, and RSGI apps from one package. Its README [suggests it](https://github.com/emmett-framework/granian#rationale) when you care about throughput above all, and not when you want pure Python or your app relies on Trio or gevent. Pass [`--interface asgi` or `--interface wsgi`](https://github.com/emmett-framework/granian#options), since the default is RSGI. Start with [one worker per CPU core](https://github.com/emmett-framework/granian#workers-and-threads), or one per container on Docker or Kubernetes, rather than numbers suggested for other servers. + +Hypercorn is an ASGI server [inspired by Gunicorn](https://hypercorn.readthedocs.io/en/latest/). It speaks HTTP/1 and HTTP/2, with WebSockets over both, and runs on [Trio](https://hypercorn.readthedocs.io/en/latest/discussion/workers.html#trio) as well as asyncio. For HTTP/3, install its [`h3` extra](https://github.com/pgjones/hypercorn). Its docs recommend setting [`server_names`](https://hypercorn.readthedocs.io/en/latest/how_to_guides/server_names.html#dns-rebinding-attacks) to the hosts you serve, to block DNS rebinding attacks. + +Waitress is a [pure-Python WSGI server](https://docs.pylonsproject.org/projects/waitress/en/latest/index.html) with no dependencies outside the standard library, and it runs on both Unix and Windows. Waitress [doesn't support TLS](https://docs.pylonsproject.org/projects/waitress/en/latest/reverse-proxy.html) itself, so put a reverse proxy in front of it for HTTPS. + +Put a proxy like Nginx in front: Gunicorn's docs [strongly recommend it](https://gunicorn.org/deploy/), and Uvicorn's recommend it [for resilience](https://uvicorn.dev/deployment/#running-behind-nginx). Behind a proxy, tell your server which proxies to trust for `X-Forwarded-*` headers, since any client can set them. [Uvicorn](https://uvicorn.dev/deployment/#proxies-and-forwarded-headers) and Gunicorn take `--forwarded-allow-ips`, Granian [`trusted_hosts`](https://github.com/emmett-framework/granian#proxies-and-forwarded-headers), Hypercorn [`ProxyFixMiddleware`](https://hypercorn.readthedocs.io/en/latest/how_to_guides/proxy_fix.html), and Waitress [`trusted_proxy`](https://docs.pylonsproject.org/projects/waitress/en/latest/reverse-proxy.html#passing-the-proxy-headers-to-setup-the-wsgi-environment). In Uvicorn, Gunicorn, Granian, and Waitress, trust every address with `*` only when [no client can reach the server directly](https://gunicorn.org/deploy/#nginx-configuration). From cabd05ec7e193f0b2b9c4dd035ea0ae6f88f730e Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 06:26:18 +0800 Subject: [PATCH 0912/1130] docs: add WebSocket category intro The page had no intro, so readers got no pick among Channels, Flask-SocketIO, websockets, and Autobahn|Python. Co-Authored-By: Claude --- website/data/category_intros/websocket.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 website/data/category_intros/websocket.md diff --git a/website/data/category_intros/websocket.md b/website/data/category_intros/websocket.md new file mode 100644 index 00000000..163e4eb7 --- /dev/null +++ b/website/data/category_intros/websocket.md @@ -0,0 +1,21 @@ +With Django, Channels is the pick; with Flask and Socket.IO clients, Flask-SocketIO. Standalone apps run on websockets, a Python WebSocket library. + +How to choose: + +- A Django project: Channels +- A Flask app with Socket.IO clients: Flask-SocketIO +- Plain WebSocket servers and clients: websockets +- A few real-time features next to a Django project: websockets as a separate server +- RPC and pub/sub over WAMP, or a Twisted app: Autobahn|Python + +Channels [extends Django beyond HTTP](https://channels.readthedocs.io/en/latest/) to handle WebSockets, and it [integrates with Django's auth and sessions](https://channels.readthedocs.io/en/latest/introduction.html). Write [sync consumers by default](https://channels.readthedocs.io/en/latest/topics/consumers.html#basic-layout). Switch to async ones only when async handling helps and every library you call is async-native. Serve everything with Daphne, or [keep HTTP on your WSGI server](https://channels.readthedocs.io/en/latest/deploying.html#http-and-websocket) and send only WebSockets to Daphne. + +Flask-SocketIO speaks Socket.IO, which is [not a WebSocket implementation](https://socket.io/docs/v4/#what-socketio-is-not): a plain WebSocket client can't connect to it. Pick it when your clients use [a Socket.IO client library](https://flask-socketio.readthedocs.io/en/latest/intro.html#requirements). In return, you get events, [rooms, and broadcasting](https://flask-socketio.readthedocs.io/en/latest/getting_started.html#rooms). Start the server with `socketio.run()`, since `flask run` [lacks WebSocket support](https://flask-socketio.readthedocs.io/en/latest/getting_started.html#initialization). + +websockets runs on asyncio by default, which is [ideal for servers with many connections](https://websockets.readthedocs.io/en/stable/). For clients, its threading implementation is a good alternative. It [isn't an HTTP server](https://websockets.readthedocs.io/en/stable/faq/server.html#how-do-i-run-http-and-websocket-servers-on-the-same-port), so [run it as its own program](https://websockets.readthedocs.io/en/stable/deploy/index.html#how-do-i-start-a-process) that calls `serve()`, not under a WSGI or ASGI server. For a few real-time features in a Django project, like notifications, its docs find [a separate websockets server next to Django](https://websockets.readthedocs.io/en/stable/howto/django.html) well suited, where Channels means switching to a new deployment architecture. + +Autobahn|Python implements [both WebSocket and WAMP, on Twisted or asyncio](https://autobahn.readthedocs.io/en/latest/). WAMP adds [RPC and pub/sub over WebSocket](https://github.com/crossbario/autobahn-python), and every WAMP client [needs a WAMP router to talk to](https://autobahn.readthedocs.io/en/latest/wamp/programming.html#wamp-programming-1). Write components with functions and decorators, [the recommended approach](https://autobahn.readthedocs.io/en/latest/wamp/programming.html#creating-components-1). + +Always [secure WebSocket connections with TLS](https://websockets.readthedocs.io/en/stable/topics/security.html#encryption) in production. Any site can open a WebSocket to yours, with your users' cookies attached. If you serve private data, [restrict the allowed origins](https://channels.readthedocs.io/en/latest/topics/security.html#websockets): Channels has `AllowedHostsOriginValidator`, websockets has the [`origins` argument](https://websockets.readthedocs.io/en/stable/reference/asyncio/server.html#websockets.asyncio.server.serve), and Flask-SocketIO [allows only the same origin by default](https://flask-socketio.readthedocs.io/en/latest/deployment.html#cross-origin-controls). + +Broadcasting across processes needs a message broker such as Redis. Channels' [production channel layer](https://channels.readthedocs.io/en/latest/topics/channel_layers.html#redis-channel-layer) runs on it, Flask-SocketIO [takes it as a message queue](https://flask-socketio.readthedocs.io/en/latest/deployment.html#using-multiple-workers), with sticky sessions at the load balancer, and websockets' docs [suggest it for pub/sub](https://websockets.readthedocs.io/en/stable/faq/server.html#how-do-i-send-a-message-to-a-channel-a-topic-or-some-users). From d7c709371fdbe4e16f022ba18afc586ade336cfd Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 06:26:22 +0800 Subject: [PATCH 0913/1130] docs: add template engines category intro The page had no intro, so readers got no pick between Jinja and Mako. Co-Authored-By: Claude --- website/data/category_intros/template-engines.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 website/data/category_intros/template-engines.md diff --git a/website/data/category_intros/template-engines.md b/website/data/category_intros/template-engines.md new file mode 100644 index 00000000..b59f345d --- /dev/null +++ b/website/data/category_intros/template-engines.md @@ -0,0 +1,15 @@ +If your templates should hold no Python code, Jinja fits: a Python template engine whose sandbox also renders untrusted templates. Mako embeds plain Python. + +How to choose: + +- Templates without embedded Python: Jinja +- Templates your users write: Jinja, in its sandbox +- Plain Python inside your templates: Mako + +Jinja [doesn't allow arbitrary Python code in templates](https://jinja.palletsprojects.com/en/stable/faq/): you get blocks, filters, and function calls, and the rest of your logic stays in Python. Outside a framework, [create one `Environment`](https://jinja.palletsprojects.com/en/stable/api/) when your app starts and load templates through a `PackageLoader`. Put the layout your pages share in a base template, and let each page [extend it and override its blocks](https://jinja.palletsprojects.com/en/stable/templates/#template-inheritance). Flask [sets up Jinja for you](https://flask.palletsprojects.com/en/stable/templating/#jinja-setup), Django has a [built-in Jinja2 backend](https://docs.djangoproject.com/en/stable/topics/templates/#django.template.backends.jinja2.Jinja2), and FastAPI [supports it with `Jinja2Templates`](https://fastapi.tiangolo.com/advanced/templates/). + +When your users write templates, like a report layout, render them in Jinja's [sandbox](https://jinja.palletsprojects.com/en/stable/sandbox/#sandbox), which can block attribute access, method calls, and other operations. Its docs say the sandbox alone [is not a solution for perfect security](https://jinja.palletsprojects.com/en/stable/sandbox/#security-considerations): catch errors when a template renders, limit CPU and memory, and pass the template only the data it needs. + +Mako is [an embedded Python language](https://www.makotemplates.org/): inside `<% %>` tags you [write regular Python](https://docs.makotemplates.org/en/latest/syntax.html#python-blocks). A real application [loads its templates from a `TemplateLookup`](https://docs.makotemplates.org/en/latest/usage.html#using-templatelookup) with a `module_directory`, which caches each compiled template on disk as a Python module. + +Jinja leaves HTML escaping [off by default](https://jinja.palletsprojects.com/en/stable/faq/#why-is-html-escaping-not-the-default), since it also renders plain text, emails, and config files. When you create the `Environment` yourself, turn it on with [`select_autoescape()`](https://jinja.palletsprojects.com/en/stable/api/#jinja2.select_autoescape); Flask turns it on for HTML templates, and Django's Jinja2 backend turns it on for all. Mako escapes HTML only through [the `h` filter](https://docs.makotemplates.org/en/latest/filtering.html#expression-filtering): add it to `default_filters` on your `TemplateLookup` to escape every expression. From 8b04838903f45132885c30dc439ca4c8a9157350 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 06:26:25 +0800 Subject: [PATCH 0914/1130] docs: add web asset management category intro The page had no intro, so readers got no pick between django-storages and Django Compressor. Co-Authored-By: Claude --- website/data/category_intros/web-asset-management.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 website/data/category_intros/web-asset-management.md diff --git a/website/data/category_intros/web-asset-management.md b/website/data/category_intros/web-asset-management.md new file mode 100644 index 00000000..03733638 --- /dev/null +++ b/website/data/category_intros/web-asset-management.md @@ -0,0 +1,12 @@ +Django serves static files only in development, but django-storages puts them and user uploads on S3 or other clouds. Django Compressor bundles CSS and JS. + +How to choose: + +- Static files and user uploads on S3, Google Cloud Storage, or Azure: django-storages +- CSS and JavaScript combined and minified from your templates: Django Compressor + +django-storages is [a collection of storage backends for Django](https://django-storages.readthedocs.io/en/latest/): Amazon S3, Google Cloud Storage, Azure Storage, and more. Install the extra for your backend, like [`django-storages[s3]`](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#installation). Configure it through Django's [`STORAGES` setting](https://docs.djangoproject.com/en/stable/ref/settings/#storages), with the backend's settings under `OPTIONS`. The `default` key stores user uploads, and a `staticfiles` key has `collectstatic` [put your static files on S3](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#configuration-settings) too. Django's security docs say to [serve user uploads from a separate domain](https://docs.djangoproject.com/en/stable/topics/security/#user-uploaded-content), not a subdomain of your site. + +Django Compressor [processes, combines, and minifies](https://github.com/django-compressor/django-compressor) the CSS and JavaScript in your Django templates into cacheable static files. It also supports compilers like Sass and LESS. Its maintainer sees it as [an alternative for people who don't want to keep up](https://github.com/django-compressor/django-compressor/discussions/1095) with JavaScript build tools and want one easy setup for development and production. Add `compressor` to `INSTALLED_APPS` and [its finder](https://django-compressor.readthedocs.io/en/stable/quickstart.html) to `STATICFILES_FINDERS`. Then wrap your `` and `", start)]) + collection = next(node for node in data["@graph"] if node["@type"] == "CollectionPage") + items = collection["mainEntity"]["itemListElement"] + assert [(item["position"], item["name"]) for item in items] == [(1, "zeta"), (2, "alpha")] + def test_group_page_falls_back_to_default_description_in_json_ld(self, tmp_path): readme = textwrap.dedent("""\ # T From 0abd19a99c6c4378fcf9a297668a16d699590a9d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 14:47:06 +0800 Subject: [PATCH 0976/1130] fix: number rows in sorted order after a column sort applyFilters walked the static load-order NodeList, so after sorting by a column header, row numbers kept each row's original load-order position instead of counting 1..n down the sorted table. Co-Authored-By: Claude --- website/static/main.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/static/main.js b/website/static/main.js index 93eac711..5314aca9 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -145,7 +145,9 @@ function applyFilters() { collapseAll(); - rows.forEach(function (row) { + // Number rows in their sorted DOM order, not the load order `rows` keeps + const orderedRows = tbody ? tbody.querySelectorAll("tr.row") : rows; + orderedRows.forEach(function (row) { let show = true; if (query) { From ec818ebcf8dcb9b18b9e6101ccef6bc5caa53cb3 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 27 Sep 2026 14:47:10 +0800 Subject: [PATCH 0977/1130] fix: drop redundant aria-hidden on homepage description rows Homepage description rows carried aria-hidden="true" on top of hidden, unlike category pages (fixed in 528f9ed); the hidden attribute alone already keeps them from assistive tech while hidden. Co-Authored-By: Claude --- website/templates/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/templates/index.html b/website/templates/index.html index e4d95d10..4cb99d4c 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -241,7 +241,7 @@ {% if entry.description %} - +
    + + + {% if entry.downloads is not none %}{{ + "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + +
    {{ entry.description | safe }}
    +
    {% if entry.also_see %}
    @@ -249,6 +256,10 @@ >{% endif %} {% if entry.downloads is not none %}/{{ + "{:,}".format(entry.downloads) }} downloads/month{% endif %}
    diff --git a/website/templates/index.html b/website/templates/index.html index e7f1c9ea..dc20049f 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -171,6 +171,9 @@
    + + + {% if entry.downloads is not none %}{{ + "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + +
    {{ entry.description | safe }}
    +
    {% if entry.description %}
    {{ entry.description | safe }}
    @@ -298,6 +305,10 @@ >{% endif %} {% if entry.downloads is not none %}/{{ + "{:,}".format(entry.downloads) }} downloads/month{% endif %}
    diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 0af6c0de..3b417b24 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -17,6 +17,7 @@ from build import ( detect_source_type, extract_entries, extract_github_repo, + load_downloads, load_stars, sort_entries, subcategory_path, @@ -429,6 +430,40 @@ class TestBuild: # Expand content present assert "expand-content" in html + def test_build_with_downloads_renders_column(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + ## Projects + + ## Stuff + + - [My-Lib](https://github.com/org/mylib) - On PyPI. + - [no-pypi](https://example.com/none) - Not on PyPI. + - [asyncio](https://docs.python.org/3/library/asyncio.html) - Built-in. + + # Contributing + + Done. + """) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + self._copy_real_templates(tmp_path) + + data_dir = tmp_path / "website" / "data" + data_dir.mkdir(parents=True) + # Keyed by normalized README display name, like fetch_pypi_downloads_via_clickpy.py writes it + (data_dir / "pypi_downloads.tsv").write_text( + "name\tpackage\tdownloads\tfetched_at\nasyncio\tasyncio\t26305454\t2026-08-16\nmy-lib\tmy-lib\t1234567\t2026-08-16\n", + encoding="utf-8", + ) + + build(tmp_path) + + html = (tmp_path / "website" / "output" / "index.html").read_text(encoding="utf-8") + assert "1,234,567" in html + # Built-in entries never show PyPI counts: the asyncio row is the backport package + assert "26,305,454" not in html + def test_build_fails_when_group_and_category_slug_collide(self, tmp_path): readme = textwrap.dedent("""\ # T @@ -974,6 +1009,7 @@ def _template_entry(name: str, stars: int | None, source_type: str | None = None groups=[], subcategories=[], stars=stars, + downloads=None, owner=None, last_commit_at=None, source_type=source_type, @@ -1199,3 +1235,16 @@ class TestAnnotateEntriesWithStars: markdown = "- [foo](https://github.com/owner/foo) - A foo." stars = {"owner/foo": {"stars": 5, "owner": "owner"}} assert annotate_entries_with_stars(markdown, stars) == ("- [foo](https://github.com/owner/foo) - A foo. (5 GitHub stars)") + + +class TestLoadDownloads: + def test_parses_tsv_and_skips_not_found(self, tmp_path): + tsv = tmp_path / "pypi_downloads.tsv" + tsv.write_text( + "name\tpackage\tdownloads\tfetched_at\naiohttp\taiohttp\t649105404\t2026-08-16\npytorch\ttorch\t50000000\t2026-08-16\ndead-pkg\t-\tNOT_FOUND\t2026-08-16\n", + encoding="utf-8", + ) + assert load_downloads(tsv) == {"aiohttp": 649105404, "pytorch": 50000000} + + def test_missing_file_returns_empty(self, tmp_path): + assert load_downloads(tmp_path / "nope.tsv") == {} From f3ff733fd7ac97e888df5e48ceb2e288140995df Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 18:54:58 +0800 Subject: [PATCH 0818/1130] fix: add three more PyPI name remaps for NOT_FOUND triage autobahn-python publishes as autobahn (7.1M/mo), pangu-py as pangu, and strawberry-django as strawberry-graphql-django (1.5M/mo). httpx.URL is left unmapped deliberately since it's a class within the httpx package, not a package of its own. Co-Authored-By: Claude --- website/data/pypi_name_overrides.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/data/pypi_name_overrides.json b/website/data/pypi_name_overrides.json index 47a400a0..78e691c1 100644 --- a/website/data/pypi_name_overrides.json +++ b/website/data/pypi_name_overrides.json @@ -1,4 +1,5 @@ { + "autobahn-python": "autobahn", "cpython": null, "devpi": "devpi-server", "django-rest-framework": "djangorestframework", @@ -6,11 +7,13 @@ "fasthtml": "python-fasthtml", "jinja": "jinja2", "mem0": "mem0ai", + "pangu-py": "pangu", "playwright-python": "playwright", "pyenv": null, "python-patterns": null, "pytorch": "torch", "renpy": null, "strawberry": "strawberry-graphql", + "strawberry-django": "strawberry-graphql-django", "winpython": null } From fcfc65df00d88c2264c6a2106bcf4400e8c1ff7b Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 18:57:13 +0800 Subject: [PATCH 0819/1130] data: record all remaining NOT_FOUND names as explicit null overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every queried name now resolves 447/447. Adds 23 explicit null overrides so squatters can never silently attach a PyPI number to these names later: stdlib-named entries (concurrent-futures, difflib, mimetypes, sqlite3, tkinter, tomllib, zoneinfo), interpreters (micropython, pypy), monorepo umbrellas (azure-sdk-for-python, google-cloud-python), self-hosted or distro-installed projects (odoo, cloud-init, warehouse), GitHub-only projects (thealgorithms, geodjango, django-db-models, django-ai-plugins, graphify, sentry-skills, social-engineer-toolkit, trailofbits-skills), and httpx-url (a class within httpx, not a package). Caveat: graphify and django-ai-plugins are young projects that may legitimately publish to PyPI later — flip their null to a remap during a future audit if they do. Co-Authored-By: Claude --- website/data/pypi_name_overrides.json | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/website/data/pypi_name_overrides.json b/website/data/pypi_name_overrides.json index 78e691c1..5ec7958a 100644 --- a/website/data/pypi_name_overrides.json +++ b/website/data/pypi_name_overrides.json @@ -1,19 +1,42 @@ { "autobahn-python": "autobahn", + "azure-sdk-for-python": null, + "cloud-init": null, + "concurrent-futures": null, "cpython": null, "devpi": "devpi-server", + "difflib": null, + "django-ai-plugins": null, + "django-db-models": null, "django-rest-framework": "djangorestframework", "django-rules": "rules", "fasthtml": "python-fasthtml", + "geodjango": null, + "google-cloud-python": null, + "graphify": null, + "httpx-url": null, "jinja": "jinja2", "mem0": "mem0ai", + "micropython": null, + "mimetypes": null, + "odoo": null, "pangu-py": "pangu", "playwright-python": "playwright", "pyenv": null, + "pypy": null, "python-patterns": null, "pytorch": "torch", "renpy": null, + "sentry-skills": null, + "social-engineer-toolkit": null, + "sqlite3": null, "strawberry": "strawberry-graphql", "strawberry-django": "strawberry-graphql-django", - "winpython": null + "thealgorithms": null, + "tkinter": null, + "tomllib": null, + "trailofbits-skills": null, + "warehouse": null, + "winpython": null, + "zoneinfo": null } From b841db68ba4c032a4896a3477905951d5b6d8613 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 19:08:55 +0800 Subject: [PATCH 0820/1130] refactor: make pypi_name_overrides.json entries self-documenting Every entry is now {"package": str|null, "reason": str|null} instead of a bare string/null. Reasons are required for null packages, explaining why the name must never be queried (squatted name, stdlib module, monorepo umbrella, GitHub-only project, and so on). Reasons are optional for remaps and kept only on the six non-obvious ones: pytorch (squatter), jinja (jinja is Jinja1), strawberry (unrelated bookmarking service), django-rules (abandoned fork), django-rest-framework (dead alias), and devpi (deprecated metapackage); plain publishes-as-X remaps get a null reason. load_overrides() in the clickpy fetcher now extracts the package field from each entry; resolve() and the pepy/bigquery cross-check scripts are unchanged since they consume load_overrides()'s output. Co-Authored-By: Claude --- website/data/pypi_name_overrides.json | 80 ++++++++++----------- website/fetch_pypi_downloads_via_clickpy.py | 15 ++-- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/website/data/pypi_name_overrides.json b/website/data/pypi_name_overrides.json index 5ec7958a..8c8a5f95 100644 --- a/website/data/pypi_name_overrides.json +++ b/website/data/pypi_name_overrides.json @@ -1,42 +1,42 @@ { - "autobahn-python": "autobahn", - "azure-sdk-for-python": null, - "cloud-init": null, - "concurrent-futures": null, - "cpython": null, - "devpi": "devpi-server", - "difflib": null, - "django-ai-plugins": null, - "django-db-models": null, - "django-rest-framework": "djangorestframework", - "django-rules": "rules", - "fasthtml": "python-fasthtml", - "geodjango": null, - "google-cloud-python": null, - "graphify": null, - "httpx-url": null, - "jinja": "jinja2", - "mem0": "mem0ai", - "micropython": null, - "mimetypes": null, - "odoo": null, - "pangu-py": "pangu", - "playwright-python": "playwright", - "pyenv": null, - "pypy": null, - "python-patterns": null, - "pytorch": "torch", - "renpy": null, - "sentry-skills": null, - "social-engineer-toolkit": null, - "sqlite3": null, - "strawberry": "strawberry-graphql", - "strawberry-django": "strawberry-graphql-django", - "thealgorithms": null, - "tkinter": null, - "tomllib": null, - "trailofbits-skills": null, - "warehouse": null, - "winpython": null, - "zoneinfo": null + "autobahn-python": { "package": "autobahn", "reason": null }, + "azure-sdk-for-python": { "package": null, "reason": "monorepo umbrella; ships as many azure-* packages, no single package represents it" }, + "cloud-init": { "package": null, "reason": "installed via distro images, not pip" }, + "concurrent-futures": { "package": null, "reason": "stdlib module" }, + "cpython": { "package": null, "reason": "not pip-installable; the cpython PyPI name was squatted" }, + "devpi": { "package": "devpi-server", "reason": "devpi is a deprecated metapackage; the server package carries real usage" }, + "difflib": { "package": null, "reason": "stdlib module" }, + "django-ai-plugins": { "package": null, "reason": "GitHub-only so far; young project, remap if it publishes" }, + "django-db-models": { "package": null, "reason": "part of Django, not a package" }, + "django-rest-framework": { "package": "djangorestframework", "reason": "django-rest-framework is a dead alias package" }, + "django-rules": { "package": "rules", "reason": "PyPI django-rules is an abandoned unrelated fork; dfunckt's project publishes as rules" }, + "fasthtml": { "package": "python-fasthtml", "reason": null }, + "geodjango": { "package": null, "reason": "part of Django (django.contrib.gis), not a package" }, + "google-cloud-python": { "package": null, "reason": "monorepo umbrella; ships as many google-cloud-* packages" }, + "graphify": { "package": null, "reason": "GitHub-only so far; young project, remap if it publishes" }, + "httpx-url": { "package": null, "reason": "class in the httpx package, not a package" }, + "jinja": { "package": "jinja2", "reason": "PyPI jinja is Jinja1; current releases publish as jinja2" }, + "mem0": { "package": "mem0ai", "reason": null }, + "micropython": { "package": null, "reason": "interpreter distributed as firmware builds, not pip" }, + "mimetypes": { "package": null, "reason": "stdlib module" }, + "odoo": { "package": null, "reason": "self-hosted suite installed from odoo.com, not pip" }, + "pangu-py": { "package": "pangu", "reason": null }, + "playwright-python": { "package": "playwright", "reason": null }, + "pyenv": { "package": null, "reason": "installed via git/homebrew; PyPI pyenv is a defunct shim" }, + "pypy": { "package": null, "reason": "interpreter distributed via pypy.org, not pip" }, + "python-patterns": { "package": null, "reason": "reference repo, not a package; PyPI name held by unrelated 2012 project" }, + "pytorch": { "package": "torch", "reason": "PyPI pytorch is a squatter; real package is torch" }, + "renpy": { "package": null, "reason": "distributed via renpy.org; PyPI name was squatted, now 404" }, + "sentry-skills": { "package": null, "reason": "agent skills repo, not a package" }, + "social-engineer-toolkit": { "package": null, "reason": "GitHub-only, installed from source" }, + "sqlite3": { "package": null, "reason": "stdlib module" }, + "strawberry": { "package": "strawberry-graphql", "reason": "PyPI strawberry is an unrelated bookmarking service" }, + "strawberry-django": { "package": "strawberry-graphql-django", "reason": null }, + "thealgorithms": { "package": null, "reason": "educational monorepo, not a package" }, + "tkinter": { "package": null, "reason": "stdlib module" }, + "tomllib": { "package": null, "reason": "stdlib module" }, + "trailofbits-skills": { "package": null, "reason": "agent skills repo, not a package" }, + "warehouse": { "package": null, "reason": "the PyPI server itself; deployed, not pip-installed" }, + "winpython": { "package": null, "reason": "distribution with its own installer; PyPI name is a 2012 relic" }, + "zoneinfo": { "package": null, "reason": "stdlib module" } } diff --git a/website/fetch_pypi_downloads_via_clickpy.py b/website/fetch_pypi_downloads_via_clickpy.py index 6934dc01..c7c979f2 100644 --- a/website/fetch_pypi_downloads_via_clickpy.py +++ b/website/fetch_pypi_downloads_via_clickpy.py @@ -16,9 +16,10 @@ naturally; names with no PyPI rows are written as NOT_FOUND. Counts are looked up by README display name; when the display name differs from the canonical PyPI package (a squatter or a dead predecessor would be measured otherwise), add the mapping to the curated -data/pypi_name_overrides.json — normalized README name to the real -package, or null for projects that are not pip-installable so their -row is never queried. The file starts with a header row (name, package, +data/pypi_name_overrides.json — normalized README name to +{"package": ..., "reason": ...}, where package is the real PyPI name +or null for projects that are not pip-installable so their row is +never queried, and reason records why. The file starts with a header row (name, package, downloads, fetched_at) — package is the PyPI package the row actually measured ("-" for null overrides) — and every row carries the sweep date: a cache fetched within the last 7 days is current enough for @@ -53,7 +54,13 @@ def normalize(name: str) -> str: def load_overrides() -> dict[str, str | None]: - return json.loads(OVERRIDES_FILE.read_text()) + """Load curated overrides: normalized README name -> PyPI package to measure (None = never query). + + Each file entry is {"package": str | null, "reason": str | null}; the reason is + documentation only — required for null packages, optional for remaps. + """ + raw = json.loads(OVERRIDES_FILE.read_text()) + return {name: entry["package"] for name, entry in raw.items()} def resolve(name: str) -> str | None: From d3fec1f7fade7bc05e60f38b9a5fcda98d67c9df Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 19:15:21 +0800 Subject: [PATCH 0821/1130] feat: default-sort the website table by downloads per month Entries with a download count now sort first (descending), with stars, then Built-in, then name as fallback tiers for entries that lack a count. main.js mirrors this in its default activeSort, clean URL check, and third-click reset target. Sorting by stars remains one header click away. Co-Authored-By: Claude --- website/build.py | 14 +++++++++----- website/static/main.js | 6 +++--- website/tests/test_build.py | 2 ++ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/website/build.py b/website/build.py index cba524fa..6ca467ab 100644 --- a/website/build.py +++ b/website/build.py @@ -115,20 +115,24 @@ def load_downloads(path: Path) -> dict[str, int]: def sort_entries(entries: Sequence[TemplateEntry]) -> list[TemplateEntry]: - """Sort entries by stars descending, then name ascending. + """Sort entries by downloads descending, then name ascending. - Three tiers: starred entries first, stdlib second, other non-starred last. + Four tiers: entries with download counts first, then starred entries by + stars descending, then stdlib, then the rest. """ def sort_key(entry: TemplateEntry) -> tuple[int, int, int, str]: + downloads = entry["downloads"] stars = entry["stars"] name = entry["name"].lower() + if downloads is not None: + return (0, -downloads, 0, name) if stars is not None: builtin = 1 if entry.get("source_type") == "Built-in" else 0 - return (0, -stars, builtin, name) + return (1, -stars, builtin, name) if entry.get("source_type") == "Built-in": - return (1, 0, 0, name) - return (2, 0, 0, name) + return (2, 0, 0, name) + return (3, 0, 0, name) return sorted(entries, key=sort_key) diff --git a/website/static/main.js b/website/static/main.js index 073bb6c4..11d43861 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -5,7 +5,7 @@ function getScrollBehavior() { } let activeFilter = null; -let activeSort = { col: "stars", order: "desc" }; +let activeSort = { col: "downloads", order: "desc" }; const searchInput = document.querySelector(".search"); const filterBar = document.querySelector(".filter-bar"); const filterValue = document.querySelector(".filter-value"); @@ -211,7 +211,7 @@ function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; if (query) params.set("q", query); - if (activeSort.col !== "stars" || activeSort.order !== "desc") { + if (activeSort.col !== "downloads" || activeSort.order !== "desc") { params.set("sort", activeSort.col); params.set("order", activeSort.order); } @@ -392,7 +392,7 @@ sortHeaders.forEach(function (th) { if (activeSort.col === col) { if (activeSort.order === defaultOrder) activeSort = { col: col, order: altOrder }; - else activeSort = { col: "stars", order: "desc" }; + else activeSort = { col: "downloads", order: "desc" }; } else { activeSort = { col: col, order: defaultOrder }; } diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 3b417b24..50627e27 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -463,6 +463,8 @@ class TestBuild: assert "1,234,567" in html # Built-in entries never show PyPI counts: the asyncio row is the backport package assert "26,305,454" not in html + # Default sort: entries with download counts come first + assert html.index("My-Lib") < html.index("no-pypi") def test_build_fails_when_group_and_category_slug_collide(self, tmp_path): readme = textwrap.dedent("""\ From da491ff8ccacb383a0e68b6b7ddc105fcdfd7be2 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 19:16:03 +0800 Subject: [PATCH 0822/1130] docs: rename Downloads/Month column header to PyPI Downloads The header no longer carries the per-month unit; expand-row text keeps its 'downloads/month' wording. Co-Authored-By: Claude --- website/templates/category.html | 2 +- website/templates/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/templates/category.html b/website/templates/category.html index 51542767..ab95552b 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -121,7 +121,7 @@
    - + diff --git a/website/templates/index.html b/website/templates/index.html index dc20049f..efb69834 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -172,7 +172,7 @@ - + From f5f7a1c50c3c88777a1f5d91d9426f210cc26091 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 19:19:42 +0800 Subject: [PATCH 0823/1130] refactor: swap PyPI Downloads and GitHub Stars column order Downloads is now the default sort, so it sits directly after the project name in both the index and category table templates. The source-type badge stays in the stars cell. Co-Authored-By: Claude --- website/templates/category.html | 14 +++++++------- website/templates/index.html | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/website/templates/category.html b/website/templates/category.html index ab95552b..4077db61 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -117,12 +117,12 @@ - - + + + {% if entry.downloads is not none %}{{ + "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% elif entry.source_type %}{{ entry.source_type }}{% else %}—{% endif %} - {% if entry.downloads is not none %}{{ - "{:,}".format(entry.downloads) }}{% else %}—{% endif %} - - - - + + + {% if entry.downloads is not none %}{{ + "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% elif entry.source_type %}{{ entry.source_type }}{% else %}—{% endif %} - {% if entry.downloads is not none %}{{ - "{:,}".format(entry.downloads) }}{% else %}—{% endif %} - Date: Sun, 16 Aug 2026 19:20:55 +0800 Subject: [PATCH 0824/1130] feat: show "Not on PyPI" badge for missing download counts Replaces the em dash in the PyPI Downloads column with a source-badge pill labeled "Not on PyPI", reusing the existing badge style used by the stars column for visual consistency. Sorting is unaffected since non-numeric cells already parse as missing. Co-Authored-By: Claude --- website/templates/category.html | 5 ++++- website/templates/index.html | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/website/templates/category.html b/website/templates/category.html index 4077db61..e29e2f17 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -155,7 +155,10 @@ {% if entry.downloads is not none %}{{ - "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + "{:,}".format(entry.downloads) }}{% else %}Not on PyPI{% endif %} {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% diff --git a/website/templates/index.html b/website/templates/index.html index ac243c9b..0bac1446 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -206,7 +206,10 @@ {% if entry.downloads is not none %}{{ - "{:,}".format(entry.downloads) }}{% else %}—{% endif %} + "{:,}".format(entry.downloads) }}{% else %}Not on PyPI{% endif %} {% if entry.stars is not none %}{{ "{:,}".format(entry.stars) }}{% From 30c9f18bc2d6d3f23e072d27f3fc91123fe1f250 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 19:43:52 +0800 Subject: [PATCH 0825/1130] feat: annotate llms.txt entries with PyPI download counts Entries now end with "(PyPI downloads/month: N, GitHub stars: M)" where known, replacing the stars-only note, since download counts are the list's stated primary evidence signal. annotate_entries_with_stars is renamed to annotate_entries_with_stats and looks downloads up by the first link's display name, skipping category-index bullets (which link into the site itself) and Built-in entries (which would otherwise hit same-named PyPI backports like logging or asyncio). The intro now mirrors the README subtitle verbatim with the project/category totals on their own line below, and the "opinionated catalog" wording is gone since the shortlist ADR is literally titled "shortlist, not a catalog". Co-Authored-By: Claude --- website/build.py | 59 ++++++++++++++++++++++--------------- website/templates/llms.txt | 6 ++-- website/tests/test_build.py | 42 +++++++++++++++++--------- 3 files changed, 68 insertions(+), 39 deletions(-) diff --git a/website/build.py b/website/build.py index 6ca467ab..f434e85c 100644 --- a/website/build.py +++ b/website/build.py @@ -16,7 +16,7 @@ from jinja2 import Environment, FileSystemLoader from readme_parser import AlsoSee, ParsedGroup, ParsedSection, parse_readme, parse_sponsors, slugify GITHUB_REPO_URL_RE = re.compile(r"^https?://github\.com/([^/]+/[^/]+?)(?:\.git)?/?$") -MARKDOWN_LINK_RE = re.compile(r"\[[^\]]+\]\(([^)\s]+)\)") +MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)\s]+)\)") BULLET_LINE_RE = re.compile(r"^\s*-\s") SITE_URL = "https://awesome-python.com/" SITEMAP_URL = f"{SITE_URL}sitemap.xml" @@ -377,7 +377,7 @@ def link_llms_category_index_to_canonical_pages(markdown: str, categories: Seque out: list[str] = [] def replace_link(match: re.Match[str]) -> str: - target = match.group(1) + target = match.group(2) url = category_urls.get(target) if url is None: return match.group(0) @@ -393,21 +393,24 @@ def build_llms_txt( template_text: str, *, readme_text: str, + subtitle: str, stars_data: dict[str, dict], + downloads_data: dict[str, int], categories: Sequence[ParsedSection], total_entries: int, ) -> str: - """Render the llms.txt entry point with the curated category catalog.""" - categories_md = annotate_entries_with_stars( + """Render the llms.txt entry point with the curated category shortlist.""" + categories_md = annotate_entries_with_stats( link_llms_category_index_to_canonical_pages( extract_categories_body(readme_text).rstrip(), categories, ), stars_data, - format_stars=lambda n: f"GitHub stars: {n}", + downloads_data, ) text_env = Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True) rendered = text_env.from_string(template_text).render( + subtitle=subtitle, site_url=SITE_URL, github_repo_url="https://github.com/vinta/awesome-python", contributing_url="https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md", @@ -420,38 +423,46 @@ def build_llms_txt( return rendered.rstrip() + "\n" -def annotate_entries_with_stars( +def annotate_entries_with_stats( markdown: str, stars_data: dict[str, dict], - *, - format_stars=None, + downloads_data: dict[str, int], ) -> str: - """Append the star count to bullet entry lines whose first GitHub link has known star data. + """Append download and star counts to bullet entry lines. - `format_stars` controls the parenthesized text. Defaults to "{N} GitHub stars". - Pass `str` for a bare number. + Downloads are looked up by the first link's display name (the same key the + TSV cache uses); stars by the first GitHub link with known star data. """ - if format_stars is None: - format_stars = lambda n: f"{n} GitHub stars" # noqa: E731 lambda-assignment lines = markdown.splitlines(keepends=True) out: list[str] = [] for line in lines: if not BULLET_LINE_RE.match(line): out.append(line) continue - annotated = line - for match in MARKDOWN_LINK_RE.finditer(line): - repo_key = extract_github_repo(match.group(1)) + links = MARKDOWN_LINK_RE.findall(line) + parts: list[str] = [] + if links: + name, url = links[0] + # Category-index bullets link into the site itself, and Built-in entries + # would hit same-named PyPI backports (e.g. logging) — no counts for either. + if not url.startswith((SITE_URL, "#")) and detect_source_type(url) != "Built-in": + downloads = downloads_data.get(normalize(name)) + if downloads is not None: + parts.append(f"PyPI downloads/month: {downloads}") + for _, url in links: + repo_key = extract_github_repo(url) if not repo_key: continue entry = stars_data.get(repo_key) - if not entry or "stars" not in entry: - continue - stripped = line.rstrip("\n") - ending = line[len(stripped) :] - annotated = f"{stripped} ({format_stars(entry['stars'])}){ending}" - break - out.append(annotated) + if entry and "stars" in entry: + parts.append(f"GitHub stars: {entry['stars']}") + break + if not parts: + out.append(line) + continue + stripped = line.rstrip("\n") + ending = line[len(stripped) :] + out.append(f"{stripped} ({', '.join(parts)}){ending}") return "".join(out) @@ -751,7 +762,9 @@ def build(repo_root: Path) -> None: llms_txt = build_llms_txt( llms_template, readme_text=readme_text, + subtitle=subtitle, stars_data=stars_data, + downloads_data=downloads_data, categories=categories, total_entries=total_entries, ) diff --git a/website/templates/llms.txt b/website/templates/llms.txt index 401a1d38..e199bd27 100644 --- a/website/templates/llms.txt +++ b/website/templates/llms.txt @@ -1,8 +1,10 @@ # Awesome Python -Awesome Python is an opinionated catalog of {{ total_entries }} Python frameworks, libraries, tools, and resources across {{ total_categories }} {% if total_categories == 1 %}category{% else %}categories{% endif %}. +{{ subtitle }} -Scan the category index, then jump to the matching section for direct project links and short descriptions. GitHub entries with known star data end with a `GitHub stars: N` note in parentheses; treat it as popularity context, not a quality guarantee. Use the homepage for project context outside the catalog. +{{ total_entries }} projects across {{ total_categories }} {% if total_categories == 1 %}category{% else %}categories{% endif %}. + +Scan the category index, then jump to the matching section for direct project links and short descriptions. Entries end with `PyPI downloads/month: N` (last 30 days, mirrors and CI included) and `GitHub stars: N` notes in parentheses where known; treat both as popularity context, not a quality guarantee. Use the homepage for project context outside the shortlist. ## Primary Links diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 50627e27..587f16a6 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -12,7 +12,7 @@ from pathlib import Path import pytest from build import ( TemplateEntry, - annotate_entries_with_stars, + annotate_entries_with_stats, build, detect_source_type, extract_entries, @@ -334,6 +334,10 @@ class TestBuild: "owner/w2": {"stars": 42, "owner": "owner", "fetched_at": "2026-01-01T00:00:00+00:00"}, } (data_dir / "github_stars.json").write_text(json.dumps(stars), encoding="utf-8") + (data_dir / "pypi_downloads.tsv").write_text( + "name\tpackage\tdownloads\tfetched_at\nw1\tw1\t777\t2026-08-16\n", + encoding="utf-8", + ) build(tmp_path) @@ -343,7 +347,8 @@ class TestBuild: assert '' in index_html - assert llms_txt.startswith("# Awesome Python\n") + assert llms_txt.startswith("# Awesome Python\n\nIntro.\n") + assert "2 projects across 1 category." in llms_txt assert "Scan the category index" in llms_txt assert "Homepage: https://awesome-python.com/" in llms_txt assert "Markdown homepage" not in llms_txt @@ -357,7 +362,7 @@ class TestBuild: assert "- [Widgets](https://awesome-python.com/categories/widgets/)" in llms_txt assert "- [Widgets](#widgets)" not in llms_txt assert "### Widgets" in llms_txt - assert "- [w1](https://example.com) - A widget." in llms_txt + assert "- [w1](https://example.com) - A widget. (PyPI downloads/month: 777)" in llms_txt assert "- [w2](https://github.com/owner/w2) - A starred widget. (GitHub stars: 42)" in llms_txt assert llms_txt != readme assert "# Contributing" not in llms_txt @@ -1196,15 +1201,24 @@ class TestExtractEntries: # --------------------------------------------------------------------------- -# annotate_entries_with_stars +# annotate_entries_with_stats # --------------------------------------------------------------------------- -class TestAnnotateEntriesWithStars: +class TestAnnotateEntriesWithStats: def test_appends_star_count_to_bullet(self): markdown = "- [foo](https://github.com/owner/foo) - A foo.\n" stars = {"owner/foo": {"stars": 123, "owner": "owner"}} - assert annotate_entries_with_stars(markdown, stars) == ("- [foo](https://github.com/owner/foo) - A foo. (123 GitHub stars)\n") + assert annotate_entries_with_stats(markdown, stars, {}) == ("- [foo](https://github.com/owner/foo) - A foo. (GitHub stars: 123)\n") + + def test_appends_downloads_by_display_name(self): + markdown = "- [Foo.py](https://example.com) - A foo.\n" + assert annotate_entries_with_stats(markdown, {}, {"foo-py": 777}) == ("- [Foo.py](https://example.com) - A foo. (PyPI downloads/month: 777)\n") + + def test_appends_downloads_and_stars_together(self): + markdown = "- [foo](https://github.com/owner/foo) - A foo.\n" + stars = {"owner/foo": {"stars": 123, "owner": "owner"}} + assert annotate_entries_with_stats(markdown, stars, {"foo": 777}) == ("- [foo](https://github.com/owner/foo) - A foo. (PyPI downloads/month: 777, GitHub stars: 123)\n") def test_uses_first_github_link(self): markdown = "- [foo](https://github.com/owner/foo) - A foo. Also [bar](https://github.com/owner/bar).\n" @@ -1212,31 +1226,31 @@ class TestAnnotateEntriesWithStars: "owner/foo": {"stars": 10, "owner": "owner"}, "owner/bar": {"stars": 99, "owner": "owner"}, } - assert annotate_entries_with_stars(markdown, stars) == ("- [foo](https://github.com/owner/foo) - A foo. Also [bar](https://github.com/owner/bar). (10 GitHub stars)\n") + assert annotate_entries_with_stats(markdown, stars, {}) == ("- [foo](https://github.com/owner/foo) - A foo. Also [bar](https://github.com/owner/bar). (GitHub stars: 10)\n") - def test_skips_entries_without_star_data(self): + def test_skips_entries_without_data(self): markdown = "- [foo](https://github.com/owner/foo) - A foo.\n" - assert annotate_entries_with_stars(markdown, {}) == markdown + assert annotate_entries_with_stats(markdown, {}, {}) == markdown - def test_skips_non_github_links(self): + def test_skips_non_github_links_for_stars(self): markdown = "- [foo](https://example.com) - A foo.\n" stars = {"owner/foo": {"stars": 1, "owner": "owner"}} - assert annotate_entries_with_stars(markdown, stars) == markdown + assert annotate_entries_with_stats(markdown, stars, {}) == markdown def test_skips_non_bullet_lines(self): markdown = "See [foo](https://github.com/owner/foo) for details.\n" stars = {"owner/foo": {"stars": 1, "owner": "owner"}} - assert annotate_entries_with_stars(markdown, stars) == markdown + assert annotate_entries_with_stats(markdown, stars, {"foo": 5}) == markdown def test_handles_indented_bullets(self): markdown = " - [foo](https://github.com/owner/foo)\n" stars = {"owner/foo": {"stars": 7, "owner": "owner"}} - assert annotate_entries_with_stars(markdown, stars) == (" - [foo](https://github.com/owner/foo) (7 GitHub stars)\n") + assert annotate_entries_with_stats(markdown, stars, {}) == (" - [foo](https://github.com/owner/foo) (GitHub stars: 7)\n") def test_preserves_lines_without_trailing_newline(self): markdown = "- [foo](https://github.com/owner/foo) - A foo." stars = {"owner/foo": {"stars": 5, "owner": "owner"}} - assert annotate_entries_with_stars(markdown, stars) == ("- [foo](https://github.com/owner/foo) - A foo. (5 GitHub stars)") + assert annotate_entries_with_stats(markdown, stars, {}) == ("- [foo](https://github.com/owner/foo) - A foo. (GitHub stars: 5)") class TestLoadDownloads: From ee8089d0d5c50cd22171ec039144e90489a4cc50 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:16:05 +0800 Subject: [PATCH 0826/1130] add build date to llms.txt --- website/build.py | 3 +++ website/templates/llms.txt | 2 +- website/tests/test_build.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/website/build.py b/website/build.py index f434e85c..611c3952 100644 --- a/website/build.py +++ b/website/build.py @@ -394,6 +394,7 @@ def build_llms_txt( *, readme_text: str, subtitle: str, + build_date: str, stars_data: dict[str, dict], downloads_data: dict[str, int], categories: Sequence[ParsedSection], @@ -411,6 +412,7 @@ def build_llms_txt( text_env = Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True) rendered = text_env.from_string(template_text).render( subtitle=subtitle, + build_date=build_date, site_url=SITE_URL, github_repo_url="https://github.com/vinta/awesome-python", contributing_url="https://github.com/vinta/awesome-python/blob/master/CONTRIBUTING.md", @@ -763,6 +765,7 @@ def build(repo_root: Path) -> None: llms_template, readme_text=readme_text, subtitle=subtitle, + build_date=build_date.strftime("%Y/%m/%d"), stars_data=stars_data, downloads_data=downloads_data, categories=categories, diff --git a/website/templates/llms.txt b/website/templates/llms.txt index e199bd27..c29782d8 100644 --- a/website/templates/llms.txt +++ b/website/templates/llms.txt @@ -2,7 +2,7 @@ {{ subtitle }} -{{ total_entries }} projects across {{ total_categories }} {% if total_categories == 1 %}category{% else %}categories{% endif %}. +{{ total_entries }} projects across {{ total_categories }} {% if total_categories == 1 %}category{% else %}categories{% endif %}, updated on {{ build_date }}. Scan the category index, then jump to the matching section for direct project links and short descriptions. Entries end with `PyPI downloads/month: N` (last 30 days, mirrors and CI included) and `GitHub stars: N` notes in parentheses where known; treat both as popularity context, not a quality guarantee. Use the homepage for project context outside the shortlist. diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 587f16a6..e93ca3ce 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -348,7 +348,7 @@ class TestBuild: assert '' in index_html assert llms_txt.startswith("# Awesome Python\n\nIntro.\n") - assert "2 projects across 1 category." in llms_txt + assert "2 projects across 1 category, updated on " in llms_txt assert "Scan the category index" in llms_txt assert "Homepage: https://awesome-python.com/" in llms_txt assert "Markdown homepage" not in llms_txt From e63de264164f9030c0b4c7c0ac2fcdde0935b7e8 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:19:01 +0800 Subject: [PATCH 0827/1130] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f510f538..cd6719db 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Awesome Python](https://awesome-python.com/) -The definitive list that answers "I want to do X in Python, which tool should I use?" - An opinionated guide to the best Python frameworks, libraries, tools, and projects. +An opinionated guide to the best Python frameworks, libraries, and tools. **Visit the [website](https://awesome-python.com/) to search and filter projects more easily.** From 8b14b4fd1e46835fea4f80131fc91cf6ff983863 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:25:19 +0800 Subject: [PATCH 0828/1130] update styles --- website/static/style.css | 15 +++++++++++++-- website/templates/index.html | 28 +++++++--------------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/website/static/style.css b/website/static/style.css index 1cd3a9f9..2313c010 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -306,7 +306,7 @@ kbd { } .hero-sub { - margin-top: 1.3rem; + margin-top: 2rem; color: var(--hero-muted); font-size: clamp(1rem, 2vw, 1.18rem); text-wrap: pretty; @@ -331,12 +331,23 @@ kbd { } .hero-proof { - margin-top: 1.5rem; + margin-top: 0.5rem; color: var(--hero-proof); font-size: var(--text-sm); letter-spacing: 0.02em; } +.hero-proof a { + color: var(--hero-text); + text-decoration: underline; + text-decoration-color: oklch(100% 0 0 / 0.25); + text-underline-offset: 0.2em; +} + +.hero-proof a:hover { + text-decoration-color: oklch(100% 0 0 / 0.6); +} + .hero-category-nav { display: grid; grid-template-columns: minmax(10rem, 14rem) minmax(0, 1fr); diff --git a/website/templates/index.html b/website/templates/index.html index 0bac1446..35329f43 100644 --- a/website/templates/index.html +++ b/website/templates/index.html @@ -24,22 +24,16 @@
    -

    The field guide to the Python ecosystem

    +

    The definitive list that answers "I want to do X in Python, which tool should I use?"

    Awesome Python

    -

    - {{ subtitle }}
    Maintained by - @vinta +

    {{ subtitle }}

    +

    + {% if repo_stars %}{{ repo_stars }}+ stars on GitHub{% endif %} {% if repo_stars and build_date %}/{% endif %} {% if build_date %}Updated {{ build_date }}{% endif %} {% if repo_stars or + build_date %}/{% endif %} Maintained by + @vinta and - @JinyangWang27. + @JinyangWang27

    - - - {% if repo_stars or build_date %} -

    - {% if repo_stars %}{{ repo_stars }}+ stars on GitHub{% endif %} {% if - repo_stars and build_date %}/{% endif %} {% if build_date %}Updated {{ - build_date }}{% endif %} -

    - {% endif %}
    From 4fc35351b2309b3b04af53bd17fe822b479d0507 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:31:39 +0800 Subject: [PATCH 0829/1130] docs: update og-image wordings to match current site copy Kicker now mirrors the hero kicker ("The definitive list that answers..."), replacing the old "field guide" line changed in 8b14b4f. Subtitle now matches the current tagline ("An opinionated guide to the best Python frameworks, libraries, and tools.") on one line. PNG regenerated from the SVG. Co-Authored-By: Claude --- website/static/og-image.png | Bin 149905 -> 157094 bytes website/static/og-image.svg | 5 ++--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/website/static/og-image.png b/website/static/og-image.png index d41e78098d4dd4f4da20c887fa8439c4b7c2f263..954b5157dae4559c557b062617a1bc7275d69e15 100644 GIT binary patch delta 123788 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�Pxs< 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�jj-LffsH|;&dz}WP3<^4IOb_5kYy}@7&bm&znmXck` z;OxwXT`8xNocyGz)&ar}xQo1#@(RDBG5_i5Gp*%rD**2$F~`%RDLf7ioZ_)}V1?Yo zZ!}Jzj+q8G^LDrwwyao~|0-D~p`hb*$b$%gkXbme76R}2`6T-k0V4B^|f}-QQe#dAWi+P7~nH&&N zL}8E`_(a!tcaAF!EMtuv(s+Sy56}-@S50Lbk0jU*r`K@J8ox$Yp0Y zFqPNq*xj9bAgoE}awyH&`f4;w={E+e^BnqB0d0!Z!{}*7%Z=dvH9|EXK^X3WjY0ouwpj6i{4-2q|b)d z&8i`s#+(Md9EZERdEl@9PCMu7%I)~|)6Pik&W<4^Mc2;u&zfo{t)@BZ_)qi(rh55b zdgW5M9S_Z+Tk8DxX->A~Zf&RI8MHdc9d;Y3uG!l1d(=^{(!co(Ai})@OLi9Z^!4Nw zqN&_lu1L*!J^y_jEN9hP=I_EZDb&4b4@-8kSjjE%iMW;xmAkxtt7&tG={M%L>h$+H z-(1VweCQ~>PAr-N_!SZsbwmDh$-OgaYHG7`hr#pjkJlhEb?IWu#agnJ)smR4)?apk zTbE|esgiQVX#!d)(?Y2Ya6@lU3fax|e#HY^3E$BgQ^qbYZlm8~j9&MilQ`Fq7MTRIV<@xBQmBHLW^M zS2RGKbNPjC=XZVjuj$J5?t6Ry*X%-#os);0)r-L(Zl5a}2U`KCz@Wp^n%jT^q=9=! z-cXWfq^_XgTe9mL#v7i_=XQ9TSbCf`5d3d{KpyBDk{~3(cR6{A;ILTsC~u~vXQ!lQ zo_n1fk^1VtGrSw#qfm6}A8~hjs)39&#mr)GxL-oz*XYn!T6*dwoJ{_Txw^W#!U0TC z^JFPH)S&72U={ZD>(e`A3+9ZTyY_$x==*bfyiGioZk{xym6Ar{BQco?O-fKbxxMZ3 z4!bQkP$Y?DX8O#$uIIKmZ27!Rc0t&9Sj5 zr|gF3=l38rzcrqWTRu8U-7@HorhwqQma;mO=z!>-5wC2ya$hrZT(`JldWGg zBq;2zi{6Zq0!GN({O~$;y~X;eS|vN1BHgUH&SX%w-h$71btgFZ9Qcz}>Gl*zYOx5` z)Lyx3SjrkgpRu8sx};6}ar0Z-m4&3>=zp&wHVcx!6Ju?p;-y3HGOJ+g=iu4ooy)uf zs~OB%*3m;iKp>26`=c6!nZ+VD@kt2oo;F5P`5aKu-M7P&&uuGVbZ^oEoK{zx>~6eg zkdIur)gb>hKc9G%x@>WD8IQ;O1LR0d@_%iTi0*xKwpmjd!H_0prxi^R3R_iFwR8-g z6XQRBs;TQ&=qE^=MR~@Cy?)3^;6ffKp|n>EP#yf3TfHZzz)ED!d@u3-yQH;nxtPme zEBjv&40~>2=d_1%-^msEzELi$pZK0#E3a?Ro;RaU}WRS~H zJ)hnp>u+EI*0;N7#Etu;x97wL2L7>I-Ia#ZGez(GJ;AkcDxE?rSZ?RK>jw?RyK@3&!dFH%2FMBXLkttt|{*E`Pr%sl!x&^#2BH;PdXaqT$ zeyRv!K~^?4cPN#B^Ma_{p3dHt7i(Ss>JnyXkQ){n-h6ir8b-_>+}r`W;~i?y-2
      NIYn#A>fes=51bz@y0BB~l+ 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%0D!S<{kgx*$NSDpbhUq>^0w6|F1wS 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 - THE FIELD GUIDE TO THE PYTHON ECOSYSTEM + The definitive list that answers "I want to do X in Python, which tool should I use?" Awesome Python - An opinionated list of awesome Python frameworks, - libraries, tools, and resources. + An opinionated guide to the best Python frameworks, libraries, and tools. awesome-python.com From 9ce2a21d45d8fe8cb16e2e6705ef9c00321c4a04 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:41:20 +0800 Subject: [PATCH 0830/1130] feat: add review-prs skill as workflow-only PR triage The old review-pending-prs command carried a stale copy of CONTRIBUTING.md's rejection rules (100-star bar, "too niche") that contradicted the current shortlist model. This skill screens from the diff only, delegates admission judgment to the audit-the-list skill, then acts on GitHub: merge with section reconcile, AskUserQuestion-gated closes. Co-Authored-By: Claude --- .claude/skills/review-prs/SKILL.md | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .claude/skills/review-prs/SKILL.md diff --git a/.claude/skills/review-prs/SKILL.md b/.claude/skills/review-prs/SKILL.md new file mode 100644 index 00000000..4f0b05d0 --- /dev/null +++ b/.claude/skills/review-prs/SKILL.md @@ -0,0 +1,33 @@ +--- +name: review-prs +description: Triage open PRs — screen from the diff, delegate the admission judgment to audit-the-list, then merge or close on GitHub. Fire when the maintainer asks to review PRs, process the PR queue, or judge whether a specific PR should be merged. +argument-hint: [PR numbers] +--- + +# Review PRs + +A PR review turns the open queue into terminal states: merged, closed, or explicitly parked. The rules live in CONTRIBUTING.md (Quality Requirements, Admission, Review Process, Automatic Rejection) — this skill is the workflow that applies them, not a second copy. + +## 1. Fetch + +Arguments name specific PRs; otherwise take the queue: `gh pr list --repo vinta/awesome-python --limit 10 --search '-label:"claude reviewed"' --json number,title,author,url,body,files,mergeable,mergeStateStatus`. Fetch all diffs in parallel: `gh pr diff --repo vinta/awesome-python`. + +Sort the batch: entry additions continue; anything else (typo fixes, website changes, docs) is out of scope — report it under "needs human" and touch nothing, so it resurfaces every run until a human acts. Done when every PR is sorted and has its diff. + +## 2. Screen + +Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment, plus merge conflicts (`mergeable`/`mergeStateStatus`). The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "" --limit 10`. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason. Done when every surviving PR names its target use case. + +## 3. Judge + +Group survivors by target use case — PRs proposing entries for the same use case compete for the same slots, so they ride one invocation. Per group, invoke the audit-the-list skill with arguments in this shape: "Judge proposed entries for the
      use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." The verdict for each PR is merge or close, grounded in the fetched evidence; when the use case is at cap, a merge verdict names the entry that leaves. Done when every survivor holds a verdict with a reason. + +## 4. Act + +- **Close**: per closing PR, AskUserQuestion presenting the draft closing comment — the comment states the reason and links CONTRIBUTING.md. Arms: close with this comment, close without a comment, keep open. Then `gh pr close --repo vinta/awesome-python --comment ""`, or a plain close. +- **Merge**: `gh pr merge --repo vinta/awesome-python --merge`, then reconcile the section locally: `git pull`, remove the entry the verdict displaced, put the new entry at its Entry Ordering position, `UV_PYTHON=3.13 make test`, commit, push. An add-only diff that displaces is the normal case — the removal is this step's job, not the contributor's. +- **Park**: any reviewed PR left open gets `gh pr edit --repo vinta/awesome-python --add-label "claude reviewed"` so the next fetch skips it. + +## 5. Report + +Summary table: PR, verdict, action taken, plus the needs-human list. Done when every fetched PR ends in exactly one state — merged with its section reconciled, closed, labeled and open, or flagged needs-human. From b3222c4fcfc6bf1ac55ce6be3dc3225d2695bb75 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 20:41:52 +0800 Subject: [PATCH 0831/1130] chore: remove stale review-pending-prs command file Superseded by the rewritten .claude/skills/review-prs/SKILL.md committed just before (9ce2a21); leaving it in place would keep a stale second copy of the PR-review instructions. Co-Authored-By: Claude --- .claude/commands/review-pending-prs.md | 70 -------------------------- .claude/skills/review-prs/SKILL.md | 2 +- 2 files changed, 1 insertion(+), 71 deletions(-) delete mode 100644 .claude/commands/review-pending-prs.md diff --git a/.claude/commands/review-pending-prs.md b/.claude/commands/review-pending-prs.md deleted file mode 100644 index a026ce92..00000000 --- a/.claude/commands/review-pending-prs.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -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 --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// --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 --repo vinta/awesome-python --comment "" -``` - -## Mark as Passed - -```bash -gh pr edit --repo vinta/awesome-python --add-label "claude reviewed" -``` - -## Extra Instructions (If Provided) - -$ARGUMENTS diff --git a/.claude/skills/review-prs/SKILL.md b/.claude/skills/review-prs/SKILL.md index 4f0b05d0..a84c6c1c 100644 --- a/.claude/skills/review-prs/SKILL.md +++ b/.claude/skills/review-prs/SKILL.md @@ -25,7 +25,7 @@ Group survivors by target use case — PRs proposing entries for the same use ca ## 4. Act - **Close**: per closing PR, AskUserQuestion presenting the draft closing comment — the comment states the reason and links CONTRIBUTING.md. Arms: close with this comment, close without a comment, keep open. Then `gh pr close --repo vinta/awesome-python --comment ""`, or a plain close. -- **Merge**: `gh pr merge --repo vinta/awesome-python --merge`, then reconcile the section locally: `git pull`, remove the entry the verdict displaced, put the new entry at its Entry Ordering position, `UV_PYTHON=3.13 make test`, commit, push. An add-only diff that displaces is the normal case — the removal is this step's job, not the contributor's. +- **Merge**: `gh pr merge --repo vinta/awesome-python --merge`, then reconcile the section locally: `git pull`, remove the entry the verdict displaced, put the new entry at its Entry Ordering position, `make test`, commit, push. An add-only diff that displaces is the normal case — the removal is this step's job, not the contributor's. - **Park**: any reviewed PR left open gets `gh pr edit --repo vinta/awesome-python --add-label "claude reviewed"` so the next fetch skips it. ## 5. Report From de4a7e6a36469f883c49e2bb03be4244c4e152d6 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 21:18:30 +0800 Subject: [PATCH 0832/1130] docs: fold review-prs fixes from live runs into SKILL.md Two live review-prs runs surfaced fixes: - Merge conflicts move from the screen-out rules to a local-merge path in the Merge arm; 7/10 PRs conflicted only from the reform's stale bases, and one such PR had already been merged this way. - Target use case is now resolved from the current README instead of stale diff context, since most PR bases no longer match live sections. - Judge gets a third verdict: no fitting use case is a structure question, carried to Act for the maintainer to decide. - Act's Close step gets a batching and checklist note, and the whole step gets an explicit completion criterion after a verdict nearly fell out of the question batches. - Drop the "claude reviewed" label mechanism entirely: the fetch filter, the Park arm, and the labeled-open terminal state. Co-Authored-By: Claude --- .claude/skills/review-prs/SKILL.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.claude/skills/review-prs/SKILL.md b/.claude/skills/review-prs/SKILL.md index a84c6c1c..6555ac69 100644 --- a/.claude/skills/review-prs/SKILL.md +++ b/.claude/skills/review-prs/SKILL.md @@ -10,24 +10,26 @@ A PR review turns the open queue into terminal states: merged, closed, or explic ## 1. Fetch -Arguments name specific PRs; otherwise take the queue: `gh pr list --repo vinta/awesome-python --limit 10 --search '-label:"claude reviewed"' --json number,title,author,url,body,files,mergeable,mergeStateStatus`. Fetch all diffs in parallel: `gh pr diff --repo vinta/awesome-python`. +Arguments name specific PRs; otherwise take the queue: `gh pr list --repo vinta/awesome-python --limit 10 --json number,title,author,url,body,files,mergeable,mergeStateStatus`. Fetch all diffs in parallel: `gh pr diff --repo vinta/awesome-python`. Sort the batch: entry additions continue; anything else (typo fixes, website changes, docs) is out of scope — report it under "needs human" and touch nothing, so it resurfaces every run until a human acts. Done when every PR is sorted and has its diff. ## 2. Screen -Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment, plus merge conflicts (`mergeable`/`mergeStateStatus`). The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "" --limit 10`. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason. Done when every surviving PR names its target use case. +Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment. The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "" --limit 10`. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason. + +Resolve each survivor's target use case against the current README, since most diffs sit on stale bases and their context lines show sections that no longer exist. A merge conflict is a symptom of that stale base — it rides to the Merge arm, which absorbs it. Done when every surviving PR names its target use case. ## 3. Judge -Group survivors by target use case — PRs proposing entries for the same use case compete for the same slots, so they ride one invocation. Per group, invoke the audit-the-list skill with arguments in this shape: "Judge proposed entries for the
      use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." The verdict for each PR is merge or close, grounded in the fetched evidence; when the use case is at cap, a merge verdict names the entry that leaves. Done when every survivor holds a verdict with a reason. +Group survivors by target use case — PRs proposing entries for the same use case compete for the same slots, so they ride one invocation. Per group, invoke the audit-the-list skill with arguments in this shape: "Judge proposed entries for the
      use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." The verdict for each PR is merge or close, grounded in the fetched evidence; when the use case is at cap, a merge verdict names the entry that leaves. An entry no current use case fits is a third outcome — a structure question: carry it to Act with evidence attached, and the maintainer decides there (mint the subcategory and merge, close, or leave open). Done when every survivor holds a verdict with a reason. ## 4. Act -- **Close**: per closing PR, AskUserQuestion presenting the draft closing comment — the comment states the reason and links CONTRIBUTING.md. Arms: close with this comment, close without a comment, keep open. Then `gh pr close --repo vinta/awesome-python --comment ""`, or a plain close. -- **Merge**: `gh pr merge --repo vinta/awesome-python --merge`, then reconcile the section locally: `git pull`, remove the entry the verdict displaced, put the new entry at its Entry Ordering position, `make test`, commit, push. An add-only diff that displaces is the normal case — the removal is this step's job, not the contributor's. -- **Park**: any reviewed PR left open gets `gh pr edit --repo vinta/awesome-python --add-label "claude reviewed"` so the next fetch skips it. +- **Close**: per closing PR, AskUserQuestion presenting the draft closing comment — the comment states the reason and links CONTRIBUTING.md. Batch up to 4 PRs per call and keep a checklist of which verdicts have been asked; answers often arrive as custom text, and that text is the decision. Arms: close with this comment, close without a comment, keep open. Then `gh pr close --repo vinta/awesome-python --comment ""`, or a plain close. +- **Merge**: a clean PR merges with `gh pr merge --repo vinta/awesome-python --merge`. A conflicted one merges locally: `git fetch origin pull//head`, `git merge FETCH_HEAD` with the standard `Merge pull request # from /` message, resolving the conflict by placing the entry correctly — GitHub still marks the PR merged and the contributor keeps credit. Either way, reconcile the section per CONTRIBUTING before pushing: remove the entry the verdict displaced, fix the new entry's display name and Entry Ordering position, `make test`, commit. An add-only diff that displaces is the normal case — the removal is this step's job, not the contributor's. +Done when every verdict has been adjudicated by the maintainer and its action executed. A kept-open PR resurfaces next run — that is its point. ## 5. Report -Summary table: PR, verdict, action taken, plus the needs-human list. Done when every fetched PR ends in exactly one state — merged with its section reconciled, closed, labeled and open, or flagged needs-human. +Summary table: PR, verdict, action taken, plus the needs-human list. Done when every fetched PR ends in exactly one state — merged with its section reconciled, closed, or left open (kept open, needs-human, or structure question pending). From e9d6ea711515d12f0016ec879d580784fe6372b4 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 21:21:21 +0800 Subject: [PATCH 0833/1130] docs: reword review-prs stale-base rationale to be rate-free The "most diffs sit on stale bases" claim was a one-time artifact of the shortlist reform, not a durable fact about future PRs. Reworded to state that diff context lines show the base the PR was written on, which may have changed since, while keeping conflict-to-Merge-arm routing. Co-Authored-By: Claude --- .claude/skills/review-prs/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/review-prs/SKILL.md b/.claude/skills/review-prs/SKILL.md index 6555ac69..e3d2cdda 100644 --- a/.claude/skills/review-prs/SKILL.md +++ b/.claude/skills/review-prs/SKILL.md @@ -18,7 +18,7 @@ Sort the batch: entry additions continue; anything else (typo fixes, website cha Apply the Automatic Rejection rules that the diff and PR metadata answer without judgment. The recently-closed-duplicate rule needs a lookup: `gh pr list --repo vinta/awesome-python --state closed --search "" --limit 10`. A screened-out PR goes straight to step 4 as a close, with the rule it broke as its reason. -Resolve each survivor's target use case against the current README, since most diffs sit on stale bases and their context lines show sections that no longer exist. A merge conflict is a symptom of that stale base — it rides to the Merge arm, which absorbs it. Done when every surviving PR names its target use case. +Resolve each survivor's target use case against the current README — a diff's context lines show the base the PR was written on, which may have changed since. A merge conflict rides to the Merge arm, which absorbs it. Done when every surviving PR names its target use case. ## 3. Judge From 9ffb5abf59c9cb4010d2c197c241396919b25d00 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 21:22:00 +0800 Subject: [PATCH 0834/1130] docs: drop UV_PYTHON=3.13 pin from audit-the-list commands Project now requires Python >=3.14; the pin fails live (verified during PR review), and plain uv run / make resolves correctly without it. Co-Authored-By: Claude --- .claude/skills/audit-the-list/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/audit-the-list/SKILL.md b/.claude/skills/audit-the-list/SKILL.md index 4c2b576c..a7a7cc7e 100644 --- a/.claude/skills/audit-the-list/SKILL.md +++ b/.claude/skills/audit-the-list/SKILL.md @@ -16,7 +16,7 @@ Resolve the scope from the arguments. Named sections mean exactly those, whether Fetch live evidence for every entry in scope before judging anything (CLAUDE.md verification rule): -- **Downloads/month**: `cd website && UV_PYTHON=3.13 uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run; header row, every row stamped with its `fetched_at` date). A cache whose `fetched_at` is within the last 7 days is current enough for verdicts — skip the sweep; older than that, re-run it (costs ~1s). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. +- **Downloads/month**: `cd website && uv run python fetch_pypi_downloads_via_clickpy.py` — free keyless ClickPy sweep of the full README, sole writer of `data/pypi_downloads.tsv` (rewritten from scratch each run; header row, every row stamped with its `fetched_at` date). A cache whose `fetched_at` is within the last 7 days is current enough for verdicts — skip the sweep; older than that, re-run it (costs ~1s). Cross-checks print to stdout, take explicit names, and never touch the cache: `fetch_pypi_downloads_via_bigquery.py ...` (canonical source, maintainer's own GCP account, `--dry-run` first — the docstring carries the cost constraints; full-README sweeps exceed the free tier, keep name lists small), `fetch_pypi_downloads_via_pepy.py ...` (needs `PEPY_TECH_API_KEY` in repo-root `.env`, throttled to 5 requests/minute), or `https://pypistats.org/api/packages/{name}/recent` paced 8s or slower. pypistats excludes mirror/CI traffic; ClickPy, BigQuery, and pepy include it — never mix sources within one comparison. - **Repo state**: archived flag, last push, created date, stars, description — `gh api repos/{owner}/{repo}`, GitLab API for GitLab-hosted projects. - **PyPI metadata** (`https://pypi.org/pypi/{name}/json`) wherever a name might not be the canonical package — ownership collisions and wrong display names surface here. Wrong-package cache rows are common: the sweep looks up README display names, so when the display name differs from the canonical package, the row silently measures an unrelated squatter or a dead predecessor. When a famous entry shows a count that looks off (too small, or absurdly round), verify identity before citing it, and fetch the canonical package's count via pepy. @@ -32,7 +32,7 @@ Run the `preview-verdicts` skill: it generates the interactive review page and d ## 5. Execute -One commit per section: body lists each removal with its reason and downloads figure; restructures, tier moves, and reorders ride the same commit. Format-only outcomes (no removals) are a single style commit. `UV_PYTHON=3.13 make test` before every commit, `UV_PYTHON=3.13 make build` after the last one. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes. +One commit per section: body lists each removal with its reason and downloads figure; restructures, tier moves, and reorders ride the same commit. Format-only outcomes (no removals) are a single style commit. `make test` before every commit, `make build` after the last one. Generic commit helpers tend to split a section audit into structural and per-subcategory commits — if that happens, squash back to one commit per section. Done when the tree is clean, tests passed before each commit, and the build count reconciles with the adjudicated changes. ## 6. Record From bf11114ce8d5ee45e8ddf9dc7b4f13704f22877f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 21:25:16 +0800 Subject: [PATCH 0835/1130] update skill --- .claude/skills/review-prs/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/skills/review-prs/SKILL.md b/.claude/skills/review-prs/SKILL.md index e3d2cdda..bcc854e0 100644 --- a/.claude/skills/review-prs/SKILL.md +++ b/.claude/skills/review-prs/SKILL.md @@ -22,7 +22,7 @@ Resolve each survivor's target use case against the current README — a diff's ## 3. Judge -Group survivors by target use case — PRs proposing entries for the same use case compete for the same slots, so they ride one invocation. Per group, invoke the audit-the-list skill with arguments in this shape: "Judge proposed entries for the
      use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." The verdict for each PR is merge or close, grounded in the fetched evidence; when the use case is at cap, a merge verdict names the entry that leaves. An entry no current use case fits is a third outcome — a structure question: carry it to Act with evidence attached, and the maintainer decides there (mint the subcategory and merge, close, or leave open). Done when every survivor holds a verdict with a reason. +Group survivors by target use case — PRs proposing entries for the same use case compete for the same slots, so they ride one invocation. Per group, invoke the `audit-the-list` skill with arguments in this shape: "Judge proposed entries for the
      use case. Evidence and Verdicts steps only; report the verdicts back. No preview page, no README changes, no commits." The verdict for each PR is merge or close, grounded in the fetched evidence; when the use case is at cap, a merge verdict names the entry that leaves. An entry no current use case fits is a third outcome — a structure question: carry it to Act with evidence attached, and the maintainer decides there (mint the subcategory and merge, close, or leave open). Done when every survivor holds a verdict with a reason. ## 4. Act From d4fd148ba8e8b1756f8951b774f13340699d37ee Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 16 Aug 2026 21:31:40 +0800 Subject: [PATCH 0836/1130] update wordings --- AGENTS.md | 9 +++++---- CLAUDE.md | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 31f45e60..49023b5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,13 +1,14 @@ # AGENTS.md -An opinionated guide to the best Python frameworks, libraries, tools, and resources. +An opinionated guide to the best Python frameworks, libraries, and tools. -[README.md](README.md) is the single source of truth for catalog entries and README sponsor placements; `website/` renders it into the static site: [awesome-python.com](https://awesome-python.com/). +[README.md](README.md) is the single source of content truth; `website/` renders it into the static site: [awesome-python.com](https://awesome-python.com/). ## Entry Rules [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. - Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. -- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. -- Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). `website/templates/sponsorship.html` separately defines the sponsorship content on the published website page. +- One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. Cross-section re-homes ride the originating audit's commit (both sides of the move in one diff). +- Resources sections are not project entries: out of audit scope, and the website never parses them. +- Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). diff --git a/CLAUDE.md b/CLAUDE.md index 5da7ad70..16e15f9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md -An opinionated guide to the best Python frameworks, libraries, tools, and resources. +An opinionated guide to the best Python frameworks, libraries, and tools. [README.md](README.md) is the single source of content truth; `website/` renders it into the static site: [awesome-python.com](https://awesome-python.com/). diff --git a/pyproject.toml b/pyproject.toml index 30ce8fc5..6f825068 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "awesome-python" version = "0.1.0" -description = "An opinionated list of awesome Python frameworks, libraries, software and resources." +description = "An opinionated guide to the best Python frameworks, libraries, and tools." authors = [{ name = "Vinta Chen", email = "vinta.chen@gmail.com" }] readme = "README.md" license = "MIT" From 49c2488244cb7e76d1dce34dade53197c9d2b613 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Fri, 21 Aug 2026 21:42:59 +0800 Subject: [PATCH 0837/1130] docs: add httpx2 as challenger to HTTP Clients Pydantic Services took over stewardship of the stalling httpx under the httpx2 name, Starlette already switched its TestClient, and it hit 144M downloads/month (pepy) within 3 months of first release at Production/Stable v2.12.0. Placed last in the challenger tier behind urllib3 per the downloads-descending ordering rule. Fork format and a rewritten description distinguish it from httpx, whose PyPI summary is identical. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70b1e445..e3942056 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,7 @@ _Libraries for working with HTTP._ - [httpx](https://github.com/encode/httpx) - A next generation HTTP client for Python. - [aiohttp](https://github.com/aio-libs/aiohttp) - Asynchronous HTTP client/server framework for asyncio and Python. - [urllib3](https://github.com/urllib3/urllib3) - A HTTP library with thread-safe connection pooling, file post support, sanity friendly. + - [httpx2](https://github.com/pydantic/httpx2) - HTTP/1.1 and HTTP/2 client with sync and async APIs, maintained by Pydantic ([httpx](https://github.com/encode/httpx) fork). - URL Manipulation - [yarl](https://github.com/aio-libs/yarl) - Yet another URL library. - [httpx.URL](https://www.python-httpx.org/api/) - The immutable URL class bundled with HTTPX. From 6ff59a63c6db5f23ec808381994050bbf324801d Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Fri, 21 Aug 2026 22:25:12 +0800 Subject: [PATCH 0838/1130] chore: bump actions/checkout from v6 to v7 v7 blocks checking out fork PR heads under pull_request_target and workflow_run. Neither workflow here uses those triggers, so the only effect is staying current with the other repos. --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy-website.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02898b64..6d6165bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml index 0c23761c..89c13a12 100644 --- a/.github/workflows/deploy-website.yml +++ b/.github/workflows/deploy-website.yml @@ -23,7 +23,7 @@ jobs: name: github-pages url: https://awesome-python.com/ steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install uv uses: astral-sh/setup-uv@v7 From 62a7b5a275a251c782b005fa7fcacd503abb7089 Mon Sep 17 00:00:00 2001 From: Kar Petrosyan Date: Sat, 22 Aug 2026 00:37:36 +0400 Subject: [PATCH 0839/1130] docs: add hishel to Caching --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e3942056..16c55b89 100644 --- a/README.md +++ b/README.md @@ -453,6 +453,7 @@ _Libraries for caching data._ - [cachetools](https://github.com/tkem/cachetools) - Extensible memoizing collections and decorators. - [diskcache](https://github.com/grantjenks/python-diskcache) - SQLite and file backed cache backend with faster lookups than memcached and redis. +- [hishel](https://github.com/karpetrosyan/hishel) - RFC 9111 compliant HTTP caching for httpx and requests, with sync and async support. - [dogpile.cache](https://github.com/sqlalchemy/dogpile.cache) - dogpile.cache is a next generation replacement for Beaker made by the same authors. - [django-cacheops](https://github.com/Suor/django-cacheops) - A slick ORM cache with automatic granular event-driven invalidation. From 149cdd63bb7299958b357dde43a65b0941dcdbd8 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:46:57 +0800 Subject: [PATCH 0840/1130] feat: add python-statemachine as a challenger to transitions Audit of proposed additions from a YouTube video roundup. transitions has not been pushed since 2025-09-11 and crosses the 12-month activity line on 2026-09-11, while python-statemachine is actively developed (pushed 2026-08-17, version 3.2.1 released 2026-08-01) and covers strictly more (SCXML-compliant statecharts, compound and parallel states, history, sync and async). Downloads 1,422,332/month vs transitions 3,137,416/month (pepy.tech, 2026-08-23). Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e3942056..81f09598 100644 --- a/README.md +++ b/README.md @@ -585,6 +585,7 @@ _Python implementation of data structures, algorithms and design patterns. Also - Design Patterns - [transitions](https://github.com/pytransitions/transitions) - A lightweight, object-oriented finite state machine implementation. - [python-patterns](https://github.com/faif/python-patterns) - A collection of design patterns in Python. + - [python-statemachine](https://github.com/fgmacedo/python-statemachine) - Expressive statecharts and finite state machines with a declarative API, in sync and async codebases. ### Interactive Interpreter From 7d07309b23e50313d5ad6e92b736ad67c154df99 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:47:22 +0800 Subject: [PATCH 0841/1130] feat: add complexipy to Code Analysis as a challenger Audit of proposed additions from a YouTube video roundup. Admitted as a challenger: 761,507 downloads/month (pepy.tech, 2026-08-23) already outranks the incumbent prospector (497,880), the repo is active (pushed 2026-08-21, version 7.0.1, Production/Stable) and it has reached 794 stars since being created in January 2024. It fills a real gap: ruff's PLR0912 measures cyclomatic complexity while complexipy measures cognitive complexity, and the two are complementary. Adoption-trajectory evidence is thin, so the challenger tier is a judgment call. Placed last in the subcategory since position marks tier and challengers follow obvious choices. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 81f09598..f52dda6f 100644 --- a/README.md +++ b/README.md @@ -605,6 +605,7 @@ _Tools of static analysis, linters and code quality checkers. Also see [awesome- - [vulture](https://github.com/jendrikseipp/vulture) - A tool for finding and analyzing dead Python code. - [prospector](https://github.com/prospector-dev/prospector) - A tool to analyze Python code. - [repowise](https://github.com/repowise-dev/repowise) - Codebase intelligence that indexes repos into dependency graphs, git history, and auto-generated docs with dead code detection. + - [complexipy](https://github.com/rohaquinlop/complexipy) - Cognitive complexity analysis for Python code, written in Rust. - Git Hooks - [pre-commit](https://github.com/pre-commit/pre-commit) - A framework for managing and maintaining multi-language pre-commit hooks. - Linters and Formatters From 6bf9e8c5bf4a8c74746370d7d57fb8461cbf6411 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:47:49 +0800 Subject: [PATCH 0842/1130] feat: add pint to Science > Physics and Engineering Obvious choice for physical units and dimensional analysis in Python: 8,647,557 downloads/month (pepy.tech, 2026-08-23), more than twice astropy (3,713,879) and thirty-six times obspy (237,352), 2,781 stars, created 2012, pushed 2026-08-05. PyPI classifier still reads Beta at v0.25.3, treated as stale given fourteen years of history and download scale (judgment call). Listed under Physics and Engineering per maintainer choice over minting a Units and Quantities subcategory. Co-Authored-By: Claude --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f52dda6f..be8f3647 100644 --- a/README.md +++ b/README.md @@ -549,6 +549,7 @@ _Libraries for scientific computing. Also see [Python-for-Scientists](https://gi - [biopython](https://github.com/biopython/biopython) - Biopython is a set of freely available tools for biological computation. - [rdkit](https://github.com/rdkit/rdkit) - Cheminformatics and Machine Learning Software. - Physics and Engineering + - [pint](https://github.com/hgrecco/pint) - Operate and manipulate physical quantities with units and dimensional analysis. - [astropy](https://github.com/astropy/astropy) - A community Python library for Astronomy. - [obspy](https://github.com/obspy/obspy) - A Python toolbox for seismology. - Simulation and Modeling From a2a0fee72a25405d08f78680ed485aee4d168766 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:48:25 +0800 Subject: [PATCH 0843/1130] docs: replace mkdocs with zensical in Documentation section Material for MkDocs went into maintenance mode on 2025-11-05 and its own team called upstream mkdocs unmaintained since 2024-08 and a supply chain risk; the mkdocs repo was last pushed 2025-10-20. Since mkdocs-material hard-depends on mkdocs, mkdocs' download count is almost entirely mkdocs-material pulling it in (18,360,677/month vs mkdocs-material's 18,167,775/month, ~1% delta), so dropping the redundant direct entry costs little. zensical is a clean replacement with no mkdocs dependency, built by the same Material for MkDocs team, at 1,585,786 downloads/month and 5,540 stars, pushed 2026-08-21. Added as a challenger, placed last below pdoc, keeping the section at 5 of 5. Approved via verdict preview. Co-Authored-By: Claude --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be8f3647..2d92f12b 100644 --- a/README.md +++ b/README.md @@ -696,9 +696,9 @@ _Libraries for generating project documentation._ - [sphinx](https://github.com/sphinx-doc/sphinx/) - Python Documentation generator. - [awesome-sphinxdoc](https://github.com/ygzgxyz/awesome-sphinxdoc) - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - A documentation framework and Material Design theme built on MkDocs. -- [mkdocs](https://github.com/mkdocs/mkdocs/) - Markdown friendly documentation generator. - [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. - [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. +- [zensical](https://github.com/zensical/zensical) - A modern static site generator for technical documentation, built by the Material for MkDocs team. **DevOps** From 66ac5fc775c7ac4ceb1bd62ae0bf0a1d4babf432 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:50:17 +0800 Subject: [PATCH 0844/1130] docs: record 2026-08-23 audit sitting outcomes Add a Stability Exceptions section to docs/audit-logs.md documenting the zensical override (admitted pre-1.0 to displace a dying mkdocs upstream). Add a matching evidence bullet to CLAUDE.md and AGENTS.md warning that a download count is not automatically independent demand when one listed entry depends on another, using mkdocs / mkdocs-material as the worked example. Both facts were discovered during this sitting and need to persist so the next audit doesn't rediscover them. Co-Authored-By: Claude --- AGENTS.md | 1 + CLAUDE.md | 1 + docs/audit-logs.md | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 49023b5d..9103b0f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,7 @@ An opinionated guide to the best Python frameworks, libraries, and tools. [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. - Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- A download count is not automatically independent demand. When one listed entry depends on another, check `requires_dist` on PyPI before citing the depended-on entry's count: mkdocs-material hard-depends on mkdocs, so mkdocs' figure exceeded mkdocs-material's by only about one percent and nearly all of it was mkdocs-material pulling it in. - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. Cross-section re-homes ride the originating audit's commit (both sides of the move in one diff). - Resources sections are not project entries: out of audit scope, and the website never parses them. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). diff --git a/CLAUDE.md b/CLAUDE.md index 16e15f9b..2a599c49 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ An opinionated guide to the best Python frameworks, libraries, and tools. [CONTRIBUTING.md](CONTRIBUTING.md) holds the admission rules, quality requirements, rejection rules, entry format, and ordering. Apply it whenever adding or removing an entry — direct commits included, not only PR reviews. - Every keep/drop reason must be verified against current online data at decision time — download counts, repo activity and archived status, PyPI metadata, project docs. Judging tiers — obvious choice vs challenger — also requires WebSearch evidence (adoption trajectory, community sentiment), not download counts alone. Training-data recollections are not evidence; label anything unverifiable as a judgment call. +- A download count is not automatically independent demand. When one listed entry depends on another, check `requires_dist` on PyPI before citing the depended-on entry's count: mkdocs-material hard-depends on mkdocs, so mkdocs' figure exceeded mkdocs-material's by only about one percent and nearly all of it was mkdocs-material pulling it in. - One entry per commit when adding or deleting entries. Exceptions: a prune sweep is one commit per section, its body listing each removal with its reason; format, wording, or categorization changes may be bundled. Cross-section re-homes ride the originating audit's commit (both sides of the move in one diff). - Resources sections are not project entries: out of audit scope, and the website never parses them. - Sponsor placement never influences which projects get listed — see [SPONSORSHIP.md](SPONSORSHIP.md). diff --git a/docs/audit-logs.md b/docs/audit-logs.md index 80407a2f..cd069570 100644 --- a/docs/audit-logs.md +++ b/docs/audit-logs.md @@ -22,6 +22,12 @@ Display names follow the canonical PyPI package name. These entries keep a diffe - strawberry -- `strawberry-graphql`. - strawberry-django -- `strawberry-graphql-django`. +### Stability Exceptions + +Entries admitted despite the Stable quality requirement, by maintainer decision: + +- zensical -- admitted 2026-08-23 at version 0.0.57, PyPI classifier `Development Status :: 3 - Alpha`, with no 1.0 target announced. Admitted as a challenger displacing mkdocs because the Documentation section was carrying a dying upstream: the Material for MkDocs team's announcement of 2025-11-05 put Material into maintenance mode for twelve months and called MkDocs itself unmaintained since 2024-08 and a supply chain risk. Re-check when 1.0 lands. + ### Mature-stable Keeps These entries sit past the 12-month activity requirement without an override. Each one is kept by editorial judgment: mature, stable, and no successor exists. From b1f473319cc8006a9eb9016ae249ebd0f4ef855f Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 00:52:55 +0800 Subject: [PATCH 0845/1130] clean up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d92f12b..e056ad84 100644 --- a/README.md +++ b/README.md @@ -698,7 +698,7 @@ _Libraries for generating project documentation._ - [mkdocs-material](https://github.com/squidfunk/mkdocs-material) - A documentation framework and Material Design theme built on MkDocs. - [diagrams](https://github.com/mingrammer/diagrams) - Diagram as Code. - [pdoc](https://github.com/mitmproxy/pdoc) - Epydoc replacement to auto generate API documentation for Python libraries. -- [zensical](https://github.com/zensical/zensical) - A modern static site generator for technical documentation, built by the Material for MkDocs team. +- [zensical](https://github.com/zensical/zensical) - A modern static site generator for technical documentation. **DevOps** From 2fc0edd565afedd2883d31ad07c8cf2f4806f200 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sun, 23 Aug 2026 01:01:17 +0800 Subject: [PATCH 0846/1130] feat: rename Built-in source type to Stdlib and show it in downloads column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standard-library entries rendered "Not on PyPI" in the PyPI Downloads column, which read like missing data rather than a deliberate category — the build already forces downloads to None for them so they never pick up a same-named PyPI backport. They now render a "Stdlib" badge instead, while genuine non-PyPI entries keep "Not on PyPI". The filter tag is renamed to match, so the source-type value, the row filter tag, and the synthetic category heading all read Stdlib now. The literal "Built-in" strings scattered through build.py are routed through the existing BUILTIN_FILTER constant so the label lives in one place. The category page slug stays "built-in" so the public URL /categories/built-in/ does not break. Co-Authored-By: Claude --- website/build.py | 18 +++++++++--------- website/templates/category.html | 11 ++++++----- website/templates/index.html | 11 ++++++----- website/tests/test_build.py | 10 +++++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/website/build.py b/website/build.py index 611c3952..8912281f 100644 --- a/website/build.py +++ b/website/build.py @@ -22,7 +22,7 @@ SITE_URL = "https://awesome-python.com/" SITEMAP_URL = f"{SITE_URL}sitemap.xml" SITEMAP_NS = "http://www.sitemaps.org/schemas/sitemap/0.9" -BUILTIN_FILTER = "Built-in" +BUILTIN_FILTER = "Stdlib" BUILTIN_SLUG = "built-in" BUILTIN_PATH = f"/categories/{BUILTIN_SLUG}/" BUILTIN_PUBLIC_URL = f"{SITE_URL}categories/{BUILTIN_SLUG}/" @@ -32,7 +32,7 @@ SPONSORSHIP_PUBLIC_URL = f"{SITE_URL}sponsorship/" SPONSORSHIP_DESCRIPTION = "Sponsorship for awesome-python: tiers, audience, and how to get your product in front of professional Python developers evaluating tools for production use." SOURCE_TYPE_DOMAINS = { - "docs.python.org": "Built-in", + "docs.python.org": BUILTIN_FILTER, "gitlab.com": "GitLab", "bitbucket.org": "Bitbucket", } @@ -128,9 +128,9 @@ def sort_entries(entries: Sequence[TemplateEntry]) -> list[TemplateEntry]: if downloads is not None: return (0, -downloads, 0, name) if stars is not None: - builtin = 1 if entry.get("source_type") == "Built-in" else 0 + builtin = 1 if entry.get("source_type") == BUILTIN_FILTER else 0 return (1, -stars, builtin, name) - if entry.get("source_type") == "Built-in": + if entry.get("source_type") == BUILTIN_FILTER: return (2, 0, 0, name) return (3, 0, 0, name) @@ -445,9 +445,9 @@ def annotate_entries_with_stats( parts: list[str] = [] if links: name, url = links[0] - # Category-index bullets link into the site itself, and Built-in entries + # Category-index bullets link into the site itself, and stdlib entries # would hit same-named PyPI backports (e.g. logging) — no counts for either. - if not url.startswith((SITE_URL, "#")) and detect_source_type(url) != "Built-in": + if not url.startswith((SITE_URL, "#")) and detect_source_type(url) != BUILTIN_FILTER: downloads = downloads_data.get(normalize(name)) if downloads is not None: parts.append(f"PyPI downloads/month: {downloads}") @@ -581,15 +581,15 @@ def build(repo_root: Path) -> None: for entry in entries: repo_key = extract_github_repo(entry["url"]) - if not repo_key and entry.get("source_type") == "Built-in": + if not repo_key and entry.get("source_type") == BUILTIN_FILTER: repo_key = "python/cpython" 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", "") - # Built-in entries would hit same-named PyPI backports (e.g. asyncio), not the stdlib. - if entry.get("source_type") != "Built-in": + # Stdlib entries would hit same-named PyPI backports (e.g. asyncio), not the stdlib. + if entry.get("source_type") != BUILTIN_FILTER: entry["downloads"] = downloads_data.get(normalize(entry["name"])) entries = sort_entries(entries) diff --git a/website/templates/category.html b/website/templates/category.html index e29e2f17..6e8c247c 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -138,7 +138,7 @@ {% for entry in entries %}
    {{ entry.description | safe }}
    From 2b9469c0ae1c8f9fd3fb3f47d5fb4e7f2db3c0f1 Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Sat, 26 Sep 2026 23:33:57 +0800 Subject: [PATCH 0886/1130] feat: group category page rows by use case, restructure intro Category pages sorted rows by downloads, which buried editorial leads (the Django ORM showed as row 7 of 7, tkinter as 14 of 14) against CONTRIBUTING.md's "position is the marker"; the full intro in the hero pushed the table 2.5 screens down on a phone; and every page repeated the same "Search every project in one place" H2. Co-Authored-By: Claude --- website/build.py | 61 ++++++++++++++-- website/static/main.js | 35 ++++++++- website/static/style.css | 118 +++++++++++++++++++++++++++++-- website/templates/category.html | 54 +++++++++++++- website/tests/test_build.py | 121 +++++++++++++++++++++++++++++++- 5 files changed, 370 insertions(+), 19 deletions(-) diff --git a/website/build.py b/website/build.py index fefc2f77..9164ca1a 100644 --- a/website/build.py +++ b/website/build.py @@ -66,6 +66,13 @@ class TemplateEntry(TypedDict): also_see: list[AlsoSee] +class EntryGroup(TypedDict): + name: str # empty for a page with a single unnamed group + slug: str + url: str # links the group heading to its own page, empty when it has none + entries: list[TemplateEntry] + + class SyntheticCategory(TypedDict): name: str slug: str @@ -237,13 +244,16 @@ def category_meta_description(name: str, entry_count: int, description: str, par return f"{count_sentence} Part of the Awesome Python catalog." -def load_category_intro(path: Path) -> tuple[str, str]: - """Render a category intro file to HTML, plus its first paragraph as plain text for the meta description. +def load_category_intro(path: Path) -> tuple[str, str, str]: + """Render a category intro file to HTML, split at the end of its "How to choose:" list. - Returns empty strings if the category has no intro file. + Returns the part shown above the table, the guide shown below it, and the + first paragraph as plain text for the meta description. A file without the + list keeps everything above the table. Returns empty strings if the category + has no intro file. """ if not path.exists(): - return "", "" + return "", "", "" md = MarkdownIt("commonmark") tokens = md.parse(path.read_text(encoding="utf-8")) for token in tokens: @@ -252,7 +262,38 @@ def load_category_intro(path: Path) -> tuple[str, str]: child.attrSet("target", "_blank") child.attrSet("rel", "noopener") lead = next(node for node in SyntaxTreeNode(tokens).children if node.type == "paragraph") - return md.renderer.render(tokens, md.options, {}), render_inline_text(lead.children[0].children) + split_at = len(tokens) + for i, token in enumerate(tokens): + if token.type == "inline" and token.level == 1 and token.content == "How to choose:" and i + 2 < len(tokens) and tokens[i + 2].type == "bullet_list_open": + split_at = next(j for j in range(i + 3, len(tokens)) if tokens[j].type == "bullet_list_close" and tokens[j].level == 0) + 1 + break + render = md.renderer.render + return render(tokens[:split_at], md.options, {}), render(tokens[split_at:], md.options, {}), render_inline_text(lead.children[0].children) + + +def group_section_entries(section: ParsedSection, entries_by_key: dict[tuple[str, str], TemplateEntry]) -> list[EntryGroup]: + """Group a section's entries by use case (subcategory), both in README order.""" + groups: dict[str, EntryGroup] = {} + for parsed in section["entries"]: + name = parsed["subcategory"] + group = groups.setdefault(name, EntryGroup(name=name, slug=slugify(name) if name else "", url="", entries=[])) + group["entries"].append(entries_by_key[(parsed["url"], parsed["name"])]) + return list(groups.values()) + + +def group_entries_by_section(sections: Sequence[ParsedSection], entries_by_key: dict[tuple[str, str], TemplateEntry]) -> list[EntryGroup]: + """Group a thematic group's entries by section, both in README order, listing each entry once.""" + placed: set[tuple[str, str]] = set() + groups: list[EntryGroup] = [] + for section in sections: + entries: list[TemplateEntry] = [] + for parsed in section["entries"]: + key = (parsed["url"], parsed["name"]) + if key not in placed: + placed.add(key) + entries.append(entries_by_key[key]) + groups.append(EntryGroup(name=section["name"], slug=section["slug"], url=category_path(section), entries=entries)) + return groups def build_breadcrumb_json_ld(items: Sequence[tuple[str, str]]) -> dict: @@ -692,11 +733,12 @@ def build(repo_root: Path) -> None: page_dir: Path, parent_category: ParsedSection | None = None, group_categories: Sequence[ParsedSection] | None = None, + entry_groups: Sequence[EntryGroup] = (), ) -> None: page_dir.mkdir(parents=True, exist_ok=True) parent_name = parent_category["name"] if parent_category else None category_title = category_meta_title(category["name"], parent_name) - intro_html, intro_lead = load_category_intro(website / "data" / "category_intros" / f"{current_path.removeprefix('/categories/').strip('/')}.md") + intro_html, guide_html, intro_lead = load_category_intro(website / "data" / "category_intros" / f"{current_path.removeprefix('/categories/').strip('/')}.md") category_description = intro_lead or category_meta_description(category["name"], len(entries), category["description"], parent_name) breadcrumbs = [("Awesome Python", SITE_URL)] if parent_category: @@ -713,7 +755,9 @@ def build(repo_root: Path) -> None: category_url=category_url, category_description=category_description, intro_html=intro_html, + guide_html=guide_html, entries=entries, + entry_groups=entry_groups, total_categories=len(categories), category_urls=category_urls, current_path=current_path, @@ -726,6 +770,8 @@ def build(repo_root: Path) -> None: encoding="utf-8", ) + entries_by_key = {(e["url"], e["name"]): e for e in entries} + section_groups = {category["name"]: group_section_entries(category, entries_by_key) for category in categories} for category in categories: render_category( category, @@ -733,6 +779,7 @@ def build(repo_root: Path) -> None: entries=[e for e in entries if category["name"] in e["categories"]], current_path=category_path(category), page_dir=categories_dir / category["slug"], + entry_groups=section_groups[category["name"]], ) for group in parsed_groups: @@ -743,6 +790,7 @@ def build(repo_root: Path) -> None: current_path=group_path(group["slug"]), page_dir=categories_dir / group["slug"], group_categories=group["categories"], + entry_groups=group_entries_by_section(group["categories"], entries_by_key), ) if builtin_entries: @@ -792,6 +840,7 @@ def build(repo_root: Path) -> None: current_path=subcategory_path(cat_slug, sub_slug), page_dir=categories_dir / cat_slug / sub_slug, parent_category=cat_by_slug[cat_slug], + entry_groups=[EntryGroup(name="", slug="", url="", entries=group["entries"]) for group in section_groups[cat_by_slug[cat_slug]["name"]] if group["name"] == sub_name], ) redirects_file = website / "data" / "redirects.json" diff --git a/website/static/main.js b/website/static/main.js index 11d43861..307e3f92 100644 --- a/website/static/main.js +++ b/website/static/main.js @@ -4,8 +4,14 @@ function getScrollBehavior() { return reducedMotion.matches ? "auto" : "smooth"; } +const table = document.querySelector(".table"); +// Category pages list rows in editorial (README) order until a column is sorted +const defaultSort = + table && table.dataset.defaultSort === "editorial" + ? { col: "editorial", order: "asc" } + : { col: "downloads", order: "desc" }; let activeFilter = null; -let activeSort = { col: "downloads", order: "desc" }; +let activeSort = defaultSort; const searchInput = document.querySelector(".search"); const filterBar = document.querySelector(".filter-bar"); const filterValue = document.querySelector(".filter-value"); @@ -14,6 +20,7 @@ const noResults = document.querySelector(".no-results"); const rows = document.querySelectorAll(".table tbody tr.row"); const tags = document.querySelectorAll(".tag"); const tbody = document.querySelector(".table tbody"); +const groupRows = document.querySelectorAll(".table tbody tr.group-row"); function initRevealSections() { const sections = document.querySelectorAll("[data-reveal]"); @@ -111,6 +118,12 @@ document time.textContent = relativeTime(time.getAttribute("datetime")); }); +let currentGroupRow = null; +Array.prototype.forEach.call(tbody ? tbody.rows : [], function (tr) { + if (tr.classList.contains("group-row")) currentGroupRow = tr; + else if (tr.classList.contains("row")) tr._groupRow = currentGroupRow; +}); + rows.forEach(function (row, i) { row._origIndex = i; let next = row.nextElementSibling; @@ -176,6 +189,15 @@ function applyFilters() { } }); + groupRows.forEach(function (groupRow) { + groupRow.hidden = true; + }); + if (activeSort.col === "editorial") { + rows.forEach(function (row) { + if (!row.hidden && row._groupRow) row._groupRow.hidden = false; + }); + } + if (noResults) noResults.hidden = visibleCount > 0; tags.forEach(function (tag) { @@ -211,7 +233,7 @@ function buildQueryString() { const params = new URLSearchParams(); const query = searchInput ? searchInput.value.trim() : ""; if (query) params.set("q", query); - if (activeSort.col !== "downloads" || activeSort.order !== "desc") { + if (activeSort.col !== defaultSort.col || activeSort.order !== defaultSort.order) { params.set("sort", activeSort.col); params.set("order", activeSort.order); } @@ -227,6 +249,8 @@ function updateURL() { } function getSortValue(row, col) { + // +1 keeps the first row above the "no value" cutoff in sortRows + if (col === "editorial") return row._origIndex + 1; if (col === "name") { return row.querySelector(".col-name a").textContent.trim().toLowerCase(); } @@ -282,7 +306,12 @@ function sortRows() { }); const frag = document.createDocumentFragment(); + let lastGroupRow = null; arr.forEach(function (row) { + if (col === "editorial" && row._groupRow && row._groupRow !== lastGroupRow) { + frag.appendChild(row._groupRow); + lastGroupRow = row._groupRow; + } frag.appendChild(row); if (row._descRow) frag.appendChild(row._descRow); if (row._expandRow) frag.appendChild(row._expandRow); @@ -392,7 +421,7 @@ sortHeaders.forEach(function (th) { if (activeSort.col === col) { if (activeSort.order === defaultOrder) activeSort = { col: col, order: altOrder }; - else activeSort = { col: "downloads", order: "desc" }; + else activeSort = defaultSort; } else { activeSort = { col: col, order: defaultOrder }; } diff --git a/website/static/style.css b/website/static/style.css index bdad3835..f7bddd67 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -438,6 +438,7 @@ kbd { .search:focus-visible, .filter-clear:focus-visible, .tag:focus-visible, +.jump-link:focus-visible, .back-to-top:focus-visible, .no-results-clear:focus-visible, .table a:focus-visible, @@ -459,9 +460,9 @@ kbd { z-index: 1; width: min(100%, calc(var(--shell-max) + (var(--shell-pad) * 2))); margin: 0 auto; - padding: 1.25rem var(--shell-pad) clamp(3.75rem, 8vw, 6.75rem); + padding: 1.25rem var(--shell-pad) clamp(2.25rem, 5vw, 3.5rem); display: grid; - gap: clamp(3rem, 8vw, 5.5rem); + gap: clamp(2rem, 5vw, 3rem); } .category-hero h1 { @@ -497,7 +498,9 @@ kbd { } .category-subtitle a, -.category-intro a { +.category-intro a, +.guide-body a, +.jump-link { color: var(--hero-text); text-decoration: underline; text-decoration-color: oklch(100% 0 0 / 0.32); @@ -505,12 +508,14 @@ kbd { } .category-subtitle a:hover, -.category-intro a:hover { +.category-intro a:hover, +.guide-body a:hover, +.jump-link:hover { text-decoration-color: oklch(100% 0 0 / 0.7); } .category-intro { - margin-top: 1.75rem; + margin-top: 1.25rem; display: grid; gap: 0.9rem; color: var(--hero-muted); @@ -530,10 +535,65 @@ kbd { padding-left: 1.25rem; } -.category-intro code { +.category-intro code, +.guide-body code { font-size: 0.9em; } +.jump-links { + display: flex; + flex-wrap: wrap; + gap: 0.45rem 1.6rem; + margin-top: 1.5rem; +} + +.jump-link { + font-size: var(--text-base); + font-weight: 600; +} + +/* The arrow marks an in-page jump; inline-block keeps it out of the underline */ +.jump-link::after { + content: "\2193"; + display: inline-block; + margin-left: 0.3rem; + color: var(--hero-kicker); +} + +.guide-band { + padding-block: clamp(3rem, 7vw, 5.5rem); + background: linear-gradient(140deg, var(--hero-bg-start) 0%, var(--hero-bg-mid) 58%, var(--hero-bg-end) 100%); + color: var(--hero-text); +} + +.guide-section { + display: grid; + gap: 1.5rem; +} + +.guide-section h2 { + font-family: var(--font-display); + font-size: clamp(2.2rem, 4vw, 3.3rem); + font-weight: 600; + line-height: 0.94; + letter-spacing: -0.03em; +} + +.guide-body { + display: grid; + gap: 1rem; + color: var(--hero-muted); + font-size: var(--text-lg); + line-height: 1.7; + text-wrap: pretty; +} + +.guide-body ul { + display: grid; + gap: 0.35rem; + padding-left: 1.25rem; +} + .sponsor-band { padding-block: clamp(2.5rem, 5.5vw, 4rem); background: @@ -651,6 +711,11 @@ kbd { padding-bottom: 1.75rem; } +.order-note { + color: var(--ink-soft); + font-size: var(--text-base); +} + .results-note { color: var(--ink-soft); font-size: var(--text-sm); @@ -819,6 +884,44 @@ kbd { cursor: pointer; } +.group-row th { + padding-top: 2.25rem; + padding-bottom: 0.85rem; + padding-left: max(var(--shell-pad), calc(50vw - (var(--shell-max) / 2) + var(--shell-pad))); + text-align: left; + border-bottom: 1px solid var(--line-strong); + background: var(--bg-paper); +} + +.group-row h2 { + display: flex; + align-items: baseline; + gap: 0.75rem; + font-family: var(--font-display); + font-size: clamp(1.7rem, 2.8vw, 2.2rem); + font-weight: 600; + line-height: 1; + scroll-margin-top: 4.5rem; +} + +.group-row h2 a { + color: var(--ink); +} + +.group-row h2 a:hover { + color: var(--accent-deep); + text-decoration: underline; + text-decoration-color: var(--accent-underline); + text-underline-offset: 0.2em; +} + +.group-count { + font-family: var(--font-body); + font-size: var(--text-sm); + font-weight: 600; + color: var(--ink-muted); +} + .row:not(.open):hover td { background: var(--row-hover); } @@ -1735,7 +1838,8 @@ th[data-sort].sort-asc::after { } .table thead th:first-child, - .table tbody td:first-child { + .table tbody td:first-child, + .group-row th { padding-left: 0.8rem; } diff --git a/website/templates/category.html b/website/templates/category.html index 9fca1b26..a9618aef 100644 --- a/website/templates/category.html +++ b/website/templates/category.html @@ -40,6 +40,19 @@ {% if intro_html %}
    {{ intro_html | safe }}
    {% endif %} + {% set named_groups = entry_groups | selectattr("name") | list %} + {% if (named_groups and not group_categories) or guide_html %} + + {% endif %} {% if group_categories %} @@ -201,9 +214,17 @@
    + {% if entry_groups %} + {% set named_groups = entry_groups | selectattr("name") | list %} +

    + {% if group_categories %}Listed by section, in editorial order.{% elif named_groups %}Listed in editorial order, grouped by use case.{% else %}Listed in editorial order.{% endif %} + Click a column to re-sort the whole list. +

    + {% else %}
    -

    Search every project in one place

    +

    {{ entries | length }} {{ category.name }} projects

    + {% endif %}

    Press / to search. Tap a tag to filter. Click any row for details. @@ -249,7 +270,7 @@ role="region" aria-label="Libraries table" > - +
    @@ -274,9 +295,29 @@ + {% if entry_groups %} + {% set row = namespace(index=0) %} + {% for group in entry_groups %} + {% if group.name %} + + + + {% endif %} + {% for entry in group.entries %} + {% set row.index = row.index + 1 %} + {{ entry_rows(entry, row.index) }} + {% endfor %} + {% endfor %} + {% else %} {% for entry in entries %} {{ entry_rows(entry, loop.index) }} {% endfor %} + {% endif %}
    Row number
    +

    + {% if group.url %}{{ group.name }}{% else %}{{ group.name }}{% endif %} + {{ group.entries | length }} project{{ "s" if group.entries | length != 1 }} +

    +

    @@ -290,6 +331,15 @@
    +{% if guide_html %} +
    +
    +

    {{ category.name }} guide

    +
    {{ guide_html | safe }}
    +
    +
    +{% endif %} +
    diff --git a/website/tests/test_build.py b/website/tests/test_build.py index d32486b3..cfebc616 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -17,6 +17,7 @@ from build import ( detect_source_type, extract_entries, extract_github_repo, + load_category_intro, load_downloads, load_pypi_badges, load_stars, @@ -291,7 +292,7 @@ class TestBuild: assert 'href="https://example.com/w1"' in category_html assert "A widget." in category_html assert 'href="https://github.com/owner/w2"' in category_html - assert '' in category_html + assert '
    ' in category_html assert "42" in category_html assert "2026-01-01T00:00:00+00:00" in category_html @@ -1007,6 +1008,99 @@ class TestBuild: assert "
  • Small apps: w1
  • " in category_html assert 'the docs' in category_html + def test_build_renders_category_guide_below_table(self, tmp_path): + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(self._REDIRECT_README, encoding="utf-8") + intros_dir = tmp_path / "website" / "data" / "category_intros" + intros_dir.mkdir(parents=True) + (intros_dir / "widgets.md").write_text("Use w1.\n\nHow to choose:\n\n- Small apps: w1\n\nSet up w1 once per process.\n", encoding="utf-8") + build(tmp_path) + + category_html = (tmp_path / "website" / "output" / "categories" / "widgets" / "index.html").read_text(encoding="utf-8") + intro_html = category_html.split('
    ', 1)[1].split("
    ", 1)[0] + assert "
  • Small apps: w1
  • " in intro_html + assert "Set up w1" not in intro_html + guide_html = category_html.split('
    ', 1)[1] + assert "

    Widgets guide

    " in guide_html + assert "

    Set up w1 once per process.

    " in guide_html + assert category_html.index('id="guide"') > category_html.index("
    ") + assert 'Widgets guide' in category_html + + def test_section_page_groups_rows_by_use_case_in_readme_order(self, tmp_path): + readme = textwrap.dedent("""\ + # T + + ## Projects + + **Tools** + + ### Widgets + + - Small + - [w2](https://example.com/w2) - Second. + - [w1](https://example.com/w1) - First. + - Large + - [w3](https://example.com/w3) - Third. + - [sqlite3](https://docs.python.org/3/library/sqlite3.html) - Stdlib. + + # Contributing + + Done. + """) + self._copy_real_templates(tmp_path) + (tmp_path / "README.md").write_text(readme, encoding="utf-8") + build(tmp_path) + + site = tmp_path / "website" / "output" / "categories" + html = (site / "widgets" / "index.html").read_text(encoding="utf-8") + assert 'data-default-sort="editorial"' in html + positions = [html.index(marker) for marker in ('

    ', ">w2w1', ">w3Small' in html + assert '

    {% for subcat in entry.subcategories %} - + {{ subcat.name }} {% endfor %} {% for cat in entry.categories %} {{ cat }} diff --git a/website/tests/test_build.py b/website/tests/test_build.py index a322f1c2..78495fa8 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -991,6 +991,7 @@ class TestBuild: assert positions == sorted(positions) assert 'Small' in html assert 'Small' in html + assert '' in html assert '
    →