# Python 3.15 Is Out. I Checked the Top 500 PyPI Packages: 97% Install From a Wheel, 12 Still Run Build Scripts

> I classified the latest release of the 500 top PyPI packages for Python 3.15. 485 install from a wheel; 12 fall back to running build code, led by PyYAML.

- URL: https://www.kielltampubolon.id/writing/python-3-15-is-out-i-checked-the-top-500-pypi-packages/
- Author: Kiell Tampubolon
- Published: 2026-10-25
- Last updated: 2026-10-25
- Language: en
- Tags: Engineering Practice, Security Engineering

## Key takeaways

- 485 of the top 500 PyPI packages (97.0%) install from a wheel on Python 3.15, and 12 of the other 15 download a source archive and run its build code.
- PyYAML is the biggest of those 12, with 910,851,659 downloads in 30 days.
- Only 102 of the 500 ship compiled code, and of those 84 (82.4%) have a compiled wheel that works on 3.15 while 18 (17.6%) do not.
- Only 60 of the 102 compiled packages (58.8%) have a free-threaded wheel for python3.15t.
- With --only-binary :all:, 14 of 19 problem packages refused to install, 4 fell back to pure Python, and snowflake-connector-python silently resolved to 1.9.1 instead of 5.1.0.

Python 3.15.0 shipped on October 9, and 3.15.1 followed on October 21. Before any upgrade I ask one question: what will `pip install` actually do on the new interpreter?

I pulled the 500 most-downloaded packages on PyPI and classified every file in each one's latest release. **485 of 500 (97.0%) install from a wheel on 3.15.** The other 15 matter more than the percentage suggests. **12 of them will download a source archive and run its build code on your machine**, and the biggest one is PyYAML, with 910,851,659 downloads in 30 days.

## TL;DR (for non-technical readers)

> - Python 3.15 is the new version of Python. Most popular add-on packages (97% of the top 500) are ready and install as finished, pre-built files.
> - 12 popular packages are not ready yet. Installing them on 3.15 means downloading their source code and running their build scripts on your computer, which needs a compiler and trusts that code completely.
> - On Windows the picture is almost identical (96.8% ready), but a typical Windows laptop has no C compiler, so most of those 12 installs would fail there after starting to run code.
> - There is a one-line setting that refuses to run build scripts. It is worth turning on, but it can also quietly install a much older version, so check what you get.

## What changed in 3.15 (verified)

I only list what I confirmed on python.org and docs.python.org today:

- **Explicit lazy imports (PEP 810).** New syntax, `lazy import json` and `lazy from pathlib import Path`, defers loading a module until the name is first used. There is also a global switch, `-X lazy_imports` or `PYTHON_LAZY_IMPORTS`.
- **UTF-8 by default (PEP 686).** `open()` without an `encoding` argument now uses UTF-8 regardless of the system locale. You can opt out with `PYTHONUTF8=0` or `-X utf8=0`.
- **Free-threading.** The no-GIL build is still a separate interpreter (`python3.15t`). New in 3.15 is a stable ABI for it, PEP 803 (`abi3t`), so one extension build can serve many free-threaded versions. The docs put single-thread overhead at about 1% on macOS aarch64 up to 8% on x86-64 Linux.
- Also new: a built-in `frozendict` (PEP 814) and a sampling profiler in the new `profiling` package (PEP 799).

On the tooling side, uv 0.13.0 (October 9) added CPython 3.15.0 and made 3.15 its default. I used uv 0.13.5 with CPython 3.15.1.

## Method

On October 25, 2026, on a Windows 11 laptop:

1. Took the top 500 rows of Hugo van Kemenade's 30-day [top-pypi-packages](https://hugovk.github.io/top-pypi-packages/) list (snapshot dated October 1).
2. Fetched `https://pypi.org/pypi/<name>/json` for each, 20 at a time, cached to disk, and parsed every wheel tag of the latest release with `packaging`. Yanked files were excluded.
3. Put each package in the first matching bucket: `cp315` wheel, `abi3` wheel built for 3.X ≤ 3.15, pure Python `py3-none-any`, a Python-agnostic binary wheel such as `py3-none-win_amd64`, sdist only, or nothing installable.
4. For 30 packages (all 19 problem packages plus 11 random controls), ran `uv pip install --dry-run --only-binary :all:` against a real 3.15.1 venv, and the same resolution for Linux with `uv pip compile --python-platform x86_64-manylinux_2_28`.

I did **not** let any sdist build run. That is deliberate: the question is whether you must execute someone else's build code, so I read each sdist's `pyproject.toml` and file list statically.

## Results

![How the top 500 PyPI packages install on Python 3.15](https://www.kielltampubolon.id/writing/python-3-15-is-out-i-checked-the-top-500-pypi-packages/img-1.png)

| How it installs on 3.15 | Packages | Share | Share of downloads |
|---|---|---|---|
| Pure Python wheel (`py3-none-any`) | 395 | 79.0% | 77.8% |
| `cp315` wheel | 65 | 13.0% | 15.9% |
| `abi3` wheel | 19 | 3.8% | 3.8% |
| `py3-none-<platform>` binary | 6 | 1.2% | 0.7% |
| Has wheels, none for 3.15: **builds from sdist** | 11 | 2.2% | 1.6% |
| No wheels at all: **always builds** | 1 | 0.2% | 0.0% |
| No 3.15 wheel and no sdist | 3 | 0.6% | 0.2% |

Pure Python flatters the headline. Only 102 of the 500 ship compiled code, and that is where 3.15 support actually costs maintainers work. Of those 102, **84 (82.4%) have a compiled wheel that works on 3.15**. **18 (17.6%) do not.**

Free-threading is further behind. 57 packages ship a `cp315t` wheel and only 3 ship the new `abi3t` (cryptography, ast-serialize, hypothesis). Counting pure Python, 464 of 500 (92.8%) install from a wheel on `python3.15t`, but only 60 of the 102 compiled packages (58.8%) have a free-threaded wheel.

## The biggest blockers by download count

![No compiled 3.15 wheel yet, by 30-day downloads](https://www.kielltampubolon.id/writing/python-3-15-is-out-i-checked-the-top-500-pypi-packages/img-2.png)

Ten of the 19, covering each failure type (the chart has all):

| Rank | Package | 30-day downloads | Newest wheel | On 3.15 |
|---|---|---|---|---|
| 13 | pyyaml | 910,851,659 | cp314 | sdist build |
| 61 | frozenlist | 417,051,290 | cp314 | pure-Python fallback |
| 177 | zstandard | 157,498,843 | cp314 | sdist build |
| 248 | black | 114,824,778 | cp314 | pure-Python fallback |
| 261 | snowflake-connector-python | 103,373,898 | cp314 | sdist build |
| 294 | watchdog | 87,261,951 | cp313 | py3-none fallback |
| 318 | lz4 | 75,441,790 | cp314 | sdist build |
| 334 | onnxruntime | 70,379,534 | cp314 | no install |
| 366 | torch | 61,685,076 | cp314 | no install |
| 428 | pyspark | 48,293,347 | none | sdist only |

The "fallback" rows are a quiet trap: frozenlist, black, watchdog and pytokens install on 3.15, but you get the slower pure-Python code instead of the compiled build you had on 3.14, with no warning.

torch, onnxruntime and triton have no 3.15 wheels and no sdist, so they simply fail to resolve. Loud is the good kind of failure.

## The supply-chain angle: build scripts at install time

A wheel is unpacked. An sdist is executed. When pip or uv has to build an sdist, it runs the package's build backend, and in 10 of the 11 sdists I inspected that includes a `setup.py`. Nine use setuptools, PyYAML uses a custom in-tree backend, and fastuuid uses maturin, which needs a Rust toolchain. 10 of the 11 contain C, C++, Cython or Rust source. Every one of those builds is arbitrary code with your user's permissions, pulled from whichever release resolved, including its build dependencies.

pyspark is the odd one: it ships no wheels at all, so its `setup.py` runs on every Python version, not just 3.15.

You can refuse this. Both flags below are verified against current docs and `uv pip install --help`:

```bash
# pip: wheels only, for every package
pip install --only-binary :all: -r requirements.txt

# uv pip: same flag, or the uv-native switch
uv pip install --only-binary :all: -r requirements.txt
uv pip install --no-build -r requirements.txt
```

For uv projects, put it in `pyproject.toml`, optionally with a per-package list:

```toml
[tool.uv]
no-build = true
# or, narrower:
# no-build-package = ["pyyaml", "zstandard"]
```

Here is what that did in my sample:

![What --only-binary :all: did on 3.15](https://www.kielltampubolon.id/writing/python-3-15-is-out-i-checked-the-top-500-pypi-packages/img-3.png)

- All 11 controls resolved.
- 14 of 19 problem packages refused to install on Windows rather than build. That refusal is the point.
- 4 quietly fell back to the pure-Python wheel.
- **1 silently downgraded.** `snowflake-connector-python` resolved to **1.9.1** instead of 5.1.0, because that old release, which still supports Python 2.7, ships a pure-Python `py2.py3-none-any` wheel. On Linux, `ddtrace` went from 4.16.0 to 4.13.1 the same way.

So "wheels only" is a security control, not a correctness guarantee. Pin versions or check the resolved set after you turn it on.

## Windows notes

On `win_amd64`, 484 of 500 (96.8%) install from a wheel and 82 compiled packages (80.4% of 102) have a 3.15 wheel. The differences are small: pyzmq and ast-serialize ship `cp315` elsewhere but `abi3` on Windows, caio falls back to pure Python, and uvloop has no Windows wheels for any version.

The real Windows difference is the toolchain. My test laptop has no MSVC (`cl.exe` is not on the PATH), which is normal for a developer machine that never needed one. With 10 of the 11 sdists containing native source, most of those builds would fail anyway, after they have already started running code.

The other Windows change is PEP 686. On Windows, `open("file.txt")` used the ANSI code page (often cp1252) before; on 3.15 it is UTF-8. Code that wrote files without `encoding=` on 3.14 and reads them on 3.15 can see mojibake or a `UnicodeDecodeError`. Run your tests with `-X warn_default_encoding` to find every call that relies on the default, then pass `encoding=` explicitly (or `encoding="locale"` if you really want the old behavior).

Lazy imports help on Windows too. With `-X lazy_imports=all`, a script that imports `requests` and touches `requests.Session` dropped from 993.8 ms to 466.9 ms (53% less, median of 21 runs). httpx dropped 37% and `rich.console` 26%. Eager 3.15 was about the same as 3.14 (993.8 ms vs 992.7 ms for requests). This laptop is slow in absolute terms (an empty `pass` takes about 170 ms), so trust the ratios.

## A practical upgrade checklist

1. Install the interpreter: `uv python install 3.15`.
2. Resolve your lock file wheels-only: `uv pip compile requirements.in --python-version 3.15 --only-binary :all:`. Every failure is a package that would have run build code.
3. Diff the resolved versions against your 3.14 lock. Look for downgrades like snowflake-connector-python 5.1.0 to 1.9.1.
4. Check for silent pure-Python fallbacks (frozenlist, black, pytokens, watchdog) if you care about their speed.
5. Set `no-build = true` (or `no-build-package`) in CI so a missing wheel fails the build instead of executing an sdist.
6. On Windows, run tests with `-X warn_default_encoding` and fix every implicit encoding.
7. If you want `python3.15t`, check your compiled dependencies separately; only 58.8% have a free-threaded wheel.
8. Measure your CLI with `-X lazy_imports=all` before rewriting imports.

## Caveats

- This is the **latest release only**. Pinned older versions are a different, usually worse, story.
- The ranking snapshot is from October 1; new 3.15 wheels may appear any day. Re-run before you quote these numbers.
- Wheel tags say "compatible", not "works". I did not run test suites.
- Some blockers exclude 3.15 on purpose via `requires-python`: backports-zstd (`<3.14`) is not needed on 3.15 at all, and ddtrace and triton declare `<3.15`.
- The 30-package resolution test is a sample; the classification covers all 500.
- I never executed an sdist build, so "would fail without MSVC" is inferred from the source files, not observed. Some packages, PyYAML among them, can fall back to pure Python when the extension does not compile, but their `setup.py` still runs first.

**Do you already run CI with `--only-binary :all:` or `no-build = true`, and if not, what is stopping you: a specific sdist-only dependency, or just never having thought about install-time code?**

Related: [uv check, type checking for single-file Python scripts](https://www.kielltampubolon.id/writing/uv-check-finally-type-checking-for-single-file-python-scripts/) and [a 1-day cooldown would have blocked 70% of 2026's hijacked npm releases](https://www.kielltampubolon.id/writing/a-1-day-cooldown-would-have-blocked-70-of-2026s-hijacked-npm-releases/).

## FAQ

### Does PyYAML have wheels for Python 3.15?

Not yet. Its newest wheel is cp314, so on 3.15 it builds from an sdist, which runs its custom in-tree build backend and a setup.py on your machine. PyYAML had 910,851,659 downloads in 30 days.

### How do I stop pip or uv from building sdists?

With pip use pip install --only-binary :all: -r requirements.txt. With uv pip use --only-binary :all: or --no-build. In a uv project set no-build = true under [tool.uv], or narrow it with no-build-package = ["pyyaml", "zstandard"].

### Is it safe to turn on wheels-only installs?

It is a security control, not a correctness guarantee. In the sample, snowflake-connector-python resolved to 1.9.1 instead of 5.1.0 because that old release ships a pure-Python py2.py3-none-any wheel, and on Linux ddtrace went from 4.16.0 to 4.13.1. Pin versions or check the resolved set.

### How many of the top 500 PyPI packages install from a wheel on Python 3.15?

485 of 500 (97.0%). Of the other 15, 12 build from an sdist and 3 have neither a 3.15 wheel nor an sdist, including torch, onnxruntime and triton, which fail to resolve. On win_amd64 the figure is 484 of 500 (96.8%).
