Summary
import openscm_runner.adapters fails on Python 3.12 and later unless setuptools happens to be installed, because the CICERO-SCM adapter imports distutils, which was removed from the standard library in 3.12 (PEP 632).
Cause
adapters/__init__.py eagerly imports every adapter:
from .ciceroscm_adapter import CICEROSCM
and adapters/ciceroscm_adapter/ciceroscm_wrapper.py:10 does:
from distutils import dir_util
So importing the package at all pulls in distutils, regardless of which climate model the caller intends to use. On 3.12+ that only resolves through the _distutils_hack shim that setuptools installs.
Reproducing
Verified against v0.13.0 (current release) on Python 3.13:
>>> import openscm_runner.adapters
ModuleNotFoundError: No module named 'distutils'
File ".../openscm_runner/adapters/__init__.py", line 6, in <module>
from .ciceroscm_adapter import CICEROSCM
File ".../ciceroscm_adapter/ciceroscm_wrapper.py", line 10, in <module>
from distutils import dir_util
Why this bites in practice
setuptools is present in most environments by accident rather than by declaration, so the failure is environment-dependent and surfaces late. Tools such as uv do not install it into a project environment at all, so the same lockfile works on one machine and not another. In our case it made a downstream pipeline unable to run its climate stage on one checkout while an otherwise identical one worked, with nothing in either lockfile to explain the difference.
Possible fixes
dir_util.copy_tree is the only usage as far as I can see, and shutil.copytree(..., dirs_exist_ok=True) covers it on all supported versions. shutil is already imported in that module.
- Alternatively, import the adapters lazily in
adapters/__init__.py, so a distutils dependency in one adapter does not break users of the others.
Happy to open a PR for either if you have a preference.
Summary
import openscm_runner.adaptersfails on Python 3.12 and later unlesssetuptoolshappens to be installed, because the CICERO-SCM adapter importsdistutils, which was removed from the standard library in 3.12 (PEP 632).Cause
adapters/__init__.pyeagerly imports every adapter:and
adapters/ciceroscm_adapter/ciceroscm_wrapper.py:10does:So importing the package at all pulls in
distutils, regardless of which climate model the caller intends to use. On 3.12+ that only resolves through the_distutils_hackshim thatsetuptoolsinstalls.Reproducing
Verified against v0.13.0 (current release) on Python 3.13:
Why this bites in practice
setuptoolsis present in most environments by accident rather than by declaration, so the failure is environment-dependent and surfaces late. Tools such asuvdo not install it into a project environment at all, so the same lockfile works on one machine and not another. In our case it made a downstream pipeline unable to run its climate stage on one checkout while an otherwise identical one worked, with nothing in either lockfile to explain the difference.Possible fixes
dir_util.copy_treeis the only usage as far as I can see, andshutil.copytree(..., dirs_exist_ok=True)covers it on all supported versions.shutilis already imported in that module.adapters/__init__.py, so adistutilsdependency in one adapter does not break users of the others.Happy to open a PR for either if you have a preference.