diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py index 75bc4463bb..0307734d88 100644 --- a/_distutils_hack/__init__.py +++ b/_distutils_hack/__init__.py @@ -1,19 +1,11 @@ +# don't import any costly modules import sys import os -import re -import importlib -import warnings -import contextlib is_pypy = '__pypy__' in sys.builtin_module_names -warnings.filterwarnings('ignore', - r'.+ distutils\b.+ deprecated', - DeprecationWarning) - - def warn_distutils_present(): if 'distutils' not in sys.modules: return @@ -21,6 +13,7 @@ def warn_distutils_present(): # PyPy for 3.6 unconditionally imports distutils, so bypass the warning # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 return + import warnings warnings.warn( "Distutils was imported before Setuptools, but importing Setuptools " "also replaces the `distutils` module in `sys.modules`. This may lead " @@ -33,8 +26,12 @@ def warn_distutils_present(): def clear_distutils(): if 'distutils' not in sys.modules: return + import warnings warnings.warn("Setuptools is replacing distutils.") - mods = [name for name in sys.modules if re.match(r'distutils\b', name)] + mods = [ + name for name in sys.modules + if name == "distutils" or name.startswith("distutils.") + ] for name in mods: del sys.modules[name] @@ -48,6 +45,7 @@ def enabled(): def ensure_local_distutils(): + import importlib clear_distutils() # With the DistutilsMetaFinder in place, @@ -73,15 +71,12 @@ def do_override(): ensure_local_distutils() -class suppress(contextlib.suppress, contextlib.ContextDecorator): - """ - A version of contextlib.suppress with decorator support. +class _TrivialRe: + def __init__(self, *patterns): + self._patterns = patterns - >>> @suppress(KeyError) - ... def key_error(): - ... {}[''] - >>> key_error() - """ + def match(self, string): + return all(pat in string for pat in self._patterns) class DistutilsMetaFinder: @@ -94,8 +89,20 @@ def find_spec(self, fullname, path, target=None): return method() def spec_for_distutils(self): + import importlib import importlib.abc import importlib.util + import warnings + + # warnings.filterwarnings() imports the re module + warnings._add_filter( + 'ignore', + _TrivialRe("distutils", "deprecated"), + DeprecationWarning, + None, + 0, + append=True + ) try: mod = importlib.import_module('setuptools._distutils') @@ -144,13 +151,15 @@ def pip_imported_during_build(cls): ) @classmethod - @suppress(AttributeError) def is_get_pip(cls): """ Detect if get-pip is being invoked. Ref #2993. """ - import __main__ - return os.path.basename(__main__.__file__) == 'get-pip.py' + try: + import __main__ + return os.path.basename(__main__.__file__) == 'get-pip.py' + except AttributeError: + pass @staticmethod def frame_file_is_setup(frame): @@ -168,12 +177,11 @@ def add_shim(): DISTUTILS_FINDER in sys.meta_path or insert_shim() -@contextlib.contextmanager -def shim(): - insert_shim() - try: - yield - finally: +class shim: + def __enter__(self): + insert_shim() + + def __exit__(self, exc, value, tb): remove_shim() diff --git a/changelog.d/3006.change.rst b/changelog.d/3006.change.rst new file mode 100644 index 0000000000..d5f5d99d02 --- /dev/null +++ b/changelog.d/3006.change.rst @@ -0,0 +1,2 @@ +Fixed startup performance issue of Python interpreter due to imports of +costly modules in ``_distutils_hack`` -- by :user:`tiran` diff --git a/pytest.ini b/pytest.ini index 5c0ad039a4..f522a45e22 100644 --- a/pytest.ini +++ b/pytest.ini @@ -38,6 +38,7 @@ filterwarnings= # SETUPTOOLS_USE_DISTUTILS=stdlib but for # https://github.com/pytest-dev/pytest/discussions/9296 ignore:The distutils.sysconfig module is deprecated, use sysconfig instead + ignore:The distutils package is deprecated.* # Workaround for pypa/setuptools#2868 # ideally would apply to PyPy only but for