Skip to content
Nilay Kabariya

Entry No.070·Fixes·Updated ·3 min read

No module named 'distutils' on Python 3.12+ (node-gyp, setuptools)

Python 3.12 removed distutils. setuptools brings it back for Python code; npm installs need a newer npm (node-gyp 10+); msvccompiler needs setuptools below 74.

The error, verbatim

Jump to the fix ↓
ModuleNotFoundError: No module named 'distutils'
✓ Reproduced on Windows 11 Pro, Ubuntu (GitHub Actions)

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
  1. 1. Your code or a Python package01
  2. 2. npm install: node-gyp02
  3. 3. distutils.msvccompiler03
  4. How this was tested04

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 setuptools

That’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@latest

If you can’t update npm, installing setuptools into the Python that node-gyp uses also works:

python -m pip install setuptools

3. 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

Useful? Pass it on:Post on XFollow @EmotionalMatter

Related entries

  1. No.063

    Python 3.15 on Windows: which packages install (tested)

    Most of the stack is ready: numpy, pandas, scipy, scikit-learn, polars, pydantic, FastAPI, Django and the OpenAI and Anthropic SDKs all install on 3.15, and so do PyTorch (since torch 2.15.0) and sentencepiece (since 0.2.3). What fails on Windows: TensorFlow (no 3.15 builds at all), PyYAML and everything that needs it, such as transformers (no 3.15 wheel, so pip tries to compile it), jupyterlab, and langchain (Rust and C dependencies with no 3.15 wheels).

    TESTED10 minWindows
  2. No.093

    Fix: bigint Failed to load bindings, pure JS will be used

    bigint: Failed to load bindings, pure JS will be used. Why npm 12 shows it on every install, whether it matters, and how to get the native build back.

    > bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)

    FIXED3 minWindows
  3. No.091

    Fix: npm error code EALLOWREMOTE (and EALLOWGIT), tested

    npm error code EALLOWREMOTE: Fetching packages of type remote have been disabled. Every case reproduced on npm 12, from npx to npm ci, with each fix.

    > npm error code EALLOWREMOTE

    FIXED4 min
  4. No.086

    npm error code EACCES: permission denied, 3 causes tested

    npm error code EACCES with syscall mkdir or open: a global install, a cache that sudo left behind, or Docker. Each reproduced, with the fix that lasts.

    > npm error code EACCES

    FIXED2 min

Post card · Every Sunday

What broke this week.

One email on Sundays: the Node, TypeScript, Python and AI SDK releases that broke builds that week, each one reproduced, with a link to the fix that worked. Never shared, and you can leave any time.

— Nilay

or follow by RSSor on X

By subscribing you agree to the privacy note. One click to leave.

tip: paste the exact error text