The error, verbatim
Jump to the fix ↓ModuleNotFoundError: No module named 'distutils'
Tested on
- Python
- 3.14.5 (Windows), 3.14.8 (GitHub Actions)
- setuptools
- 70.0.0 to 84.0.0
- node-gyp
- 9.4.1, 10.0.0, 11.5.0
- OS
- Windows 11 Pro, Ubuntu (GitHub Actions)
✓ Last re-run on GitHub Actions · see the run · all repros
Contents
Three different things import distutils, and each needs its own fix. In my tests, node-gyp 9.4.1 (bundled with npm 9.9.4 and 10.0.0) failed on it while node-gyp 10 and 11 built fine, so for npm install errors the cleanest fix is a newer npm (installing setuptools into node-gyp’s Python works too). Python 3.12 removed distutils from the standard library, and every Python from 3.12 to 3.15 fails with the same line, whichever of these it comes from:
| Where it comes from | Fix |
|---|---|
| Your code or a Python package | Install setuptools |
npm install building a native module (node-gyp) |
Update npm / node-gyp |
from distutils import msvccompiler |
setuptools older than 74 |
1. Your code or a Python package
On Python 3.14.5, every one of these failed with ModuleNotFoundError: No module named 'distutils':
import distutils
from distutils.util import strtobool
from distutils.version import LooseVersion
setuptools ships its own copy of distutils and makes import distutils find it:
pip install setuptoolsThat’s a bridge, not a fix. In your own code, move to the replacements: packaging.version.Version instead of LooseVersion, shutil.which instead of distutils.spawn.find_executable, sysconfig instead of distutils.sysconfig.
2. npm install: node-gyp
Packages with native code (bcrypt, sqlite3, canvas and others) build with node-gyp when there’s no prebuilt binary for your system, and node-gyp runs Python. Older node-gyp versions import distutils. I configured the same tiny native addon with Python 3.14 and three node-gyp versions on GitHub’s Ubuntu runner:
| node-gyp | Result |
|---|---|
| 9.4.1 | ModuleNotFoundError: No module named 'distutils', then gyp ERR! stack Error: `gyp` failed with exit code: 1 |
| 10.0.0 | gyp info ok |
| 11.5.0 | gyp info ok |
Which node-gyp you get depends on your npm: npm 9.9.4 and even npm 10.0.0 bundle node-gyp 9.4, npm 10.9.2 bundles node-gyp 11, and npm 11.9.0 bundles node-gyp 12.
Update npm, so its bundled node-gyp no longer needs distutils:
npm install -g npm@latestIf you can’t update npm, installing setuptools into the Python that node-gyp uses also works:
python -m pip install setuptools3. distutils.msvccompiler
This one fails even with setuptools installed:
ImportError: cannot import name 'msvccompiler' from 'distutils'
setuptools removed it. I checked versions one by one on Python 3.14: 73.0.1 still has distutils.msvccompiler, 74.0.0 and everything after it (75.0.0, 80.0.0, 84.0.0) don’t.
If an old build script needs it, pin setuptools below 74 in that environment:
pip install "setuptools<74"The longer-term fix is updating whatever imports it; current build tools don’t.
How this was tested
Python 3.14.5 on Windows 11 Pro, October 17, 2026, each case in a fresh virtual environment with setuptools 70.0.0, 71.1.0, 73.0.1, 74.0.0, 74.1.0, 75.0.0, 80.0.0 and 84.0.0. The node-gyp cases configured a one-file N-API addon with Python 3.14.8 and Node 24.14.0 on GitHub Actions’ ubuntu-latest and windows-latest; the workflow is public. The bundled node-gyp versions come from each npm release’s package.json. Every message above is the real output.
— N.K., end of entry No.070