Skip to content

bpo-42403: Simplify importlib external bootstrap #23397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib/importlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import _frozen_importlib_external as _bootstrap_external
except ImportError:
from . import _bootstrap_external
_bootstrap_external._setup(_bootstrap)
_bootstrap_external._set_bootstrap_module(_bootstrap)
_bootstrap._bootstrap_external = _bootstrap_external
else:
_bootstrap_external.__name__ = 'importlib._bootstrap_external'
Expand Down
7 changes: 7 additions & 0 deletions Lib/importlib/_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,15 @@

# Bootstrap-related code ######################################################

# Modules injected manually by _setup()
_thread = None
_warnings = None
_weakref = None

# Import done by _install_external_importers()
_bootstrap_external = None


def _wrap(new, old):
"""Simple substitute for functools.update_wrapper."""
for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
Expand Down
111 changes: 47 additions & 64 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@
# reference any injected objects! This includes not only global code but also
# anything specified at the class level.

# Module injected manually by _set_bootstrap_module()
_bootstrap = None

# Import builtin modules
import _imp
import _io
import sys
import _warnings
import marshal


_MS_WINDOWS = (sys.platform == 'win32')
if _MS_WINDOWS:
import nt as _os
import winreg
else:
import posix as _os


if _MS_WINDOWS:
path_separators = ['\\', '/']
else:
path_separators = ['/']
# Assumption made in _path_join()
assert all(len(sep) == 1 for sep in path_separators)
path_sep = path_separators[0]
path_separators = ''.join(path_separators)
_pathseps_with_colon = {f':{s}' for s in path_separators}


# Bootstrap-related code ######################################################
_CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win',
_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin'
Expand All @@ -42,6 +72,8 @@ def _relax_case():
return False
return _relax_case

_relax_case = _make_relax_case()


def _pack_uint32(x):
"""Convert a 32-bit integer to little-endian."""
Expand Down Expand Up @@ -294,7 +326,11 @@ def _write_atomic(path, data, mode=0o666):
_PYCACHE = '__pycache__'
_OPT = 'opt-'

SOURCE_SUFFIXES = ['.py'] # _setup() adds .pyw as needed.
SOURCE_SUFFIXES = ['.py']
if _MS_WINDOWS:
SOURCE_SUFFIXES.append('.pyw')

EXTENSION_SUFFIXES = _imp.extension_suffixes()

BYTECODE_SUFFIXES = ['.pyc']
# Deprecated.
Expand Down Expand Up @@ -469,15 +505,18 @@ def _check_name_wrapper(self, name=None, *args, **kwargs):
raise ImportError('loader for %s cannot handle %s' %
(self.name, name), name=name)
return method(self, name, *args, **kwargs)
try:

# FIXME: @_check_name is used to define class methods before the
# _bootstrap module is set by _set_bootstrap_module().
if _bootstrap is not None:
_wrap = _bootstrap._wrap
except NameError:
# XXX yuck
else:
def _wrap(new, old):
for replace in ['__module__', '__name__', '__qualname__', '__doc__']:
if hasattr(old, replace):
setattr(new, replace, getattr(old, replace))
new.__dict__.update(old.__dict__)

_wrap(_check_name_wrapper, method)
return _check_name_wrapper

Expand Down Expand Up @@ -713,7 +752,7 @@ class WindowsRegistryFinder:
REGISTRY_KEY_DEBUG = (
'Software\\Python\\PythonCore\\{sys_version}'
'\\Modules\\{fullname}\\Debug')
DEBUG_BUILD = False # Changed in _setup()
DEBUG_BUILD = (_MS_WINDOWS and '_d.pyd' in EXTENSION_SUFFIXES)

@classmethod
def _open_registry(cls, key):
Expand Down Expand Up @@ -1060,10 +1099,6 @@ def get_source(self, fullname):
return None


# Filled in by _setup().
EXTENSION_SUFFIXES = []


class ExtensionFileLoader(FileLoader, _LoaderBasics):

"""Loader for extension modules.
Expand Down Expand Up @@ -1552,66 +1587,14 @@ def _get_supported_file_loaders():
return [extensions, source, bytecode]


def _setup(_bootstrap_module):
"""Setup the path-based importers for importlib by importing needed
built-in modules and injecting them into the global namespace.

Other components are extracted from the core bootstrap module.

"""
global sys, _imp, _bootstrap
def _set_bootstrap_module(_bootstrap_module):
global _bootstrap
_bootstrap = _bootstrap_module
sys = _bootstrap.sys
_imp = _bootstrap._imp

self_module = sys.modules[__name__]

# Directly load the os module (needed during bootstrap).
os_details = ('posix', ['/']), ('nt', ['\\', '/'])
for builtin_os, path_separators in os_details:
# Assumption made in _path_join()
assert all(len(sep) == 1 for sep in path_separators)
path_sep = path_separators[0]
if builtin_os in sys.modules:
os_module = sys.modules[builtin_os]
break
else:
try:
os_module = _bootstrap._builtin_from_name(builtin_os)
break
except ImportError:
continue
else:
raise ImportError('importlib requires posix or nt')

setattr(self_module, '_os', os_module)
setattr(self_module, 'path_sep', path_sep)
setattr(self_module, 'path_separators', ''.join(path_separators))
setattr(self_module, '_pathseps_with_colon', {f':{s}' for s in path_separators})

# Directly load built-in modules needed during bootstrap.
builtin_names = ['_io', '_warnings', 'marshal']
if builtin_os == 'nt':
builtin_names.append('winreg')
for builtin_name in builtin_names:
if builtin_name not in sys.modules:
builtin_module = _bootstrap._builtin_from_name(builtin_name)
else:
builtin_module = sys.modules[builtin_name]
setattr(self_module, builtin_name, builtin_module)

# Constants
setattr(self_module, '_relax_case', _make_relax_case())
EXTENSION_SUFFIXES.extend(_imp.extension_suffixes())
if builtin_os == 'nt':
SOURCE_SUFFIXES.append('.pyw')
if '_d.pyd' in EXTENSION_SUFFIXES:
WindowsRegistryFinder.DEBUG_BUILD = True


def _install(_bootstrap_module):
"""Install the path-based import components."""
_setup(_bootstrap_module)
_set_bootstrap_module(_bootstrap_module)
supported_loaders = _get_supported_file_loaders()
sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)])
sys.meta_path.append(PathFinder)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Simplify the :mod:`importlib` external bootstrap code:
``importlib._bootstrap_external`` now uses regular imports to import builtin
modules. When it is imported, the builtin :func:`__import__()` function is
already fully working and so can be used to import builtin modules like
:mod:`sys`. Patch by Victor Stinner.
Loading