mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 15:41:43 +00:00 
			
		
		
		
	
		
			
	
	
		
			239 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			239 lines
		
	
	
	
		
			8.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import contextlib
							 | 
						||
| 
								 | 
							
								import importlib
							 | 
						||
| 
								 | 
							
								import importlib.util
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import unittest
							 | 
						||
| 
								 | 
							
								import warnings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .os_helper import unlink
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@contextlib.contextmanager
							 | 
						||
| 
								 | 
							
								def _ignore_deprecated_imports(ignore=True):
							 | 
						||
| 
								 | 
							
								    """Context manager to suppress package and module deprecation
							 | 
						||
| 
								 | 
							
								    warnings when importing them.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    If ignore is False, this context manager has no effect.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    if ignore:
							 | 
						||
| 
								 | 
							
								        with warnings.catch_warnings():
							 | 
						||
| 
								 | 
							
								            warnings.filterwarnings("ignore", ".+ (module|package)",
							 | 
						||
| 
								 | 
							
								                                    DeprecationWarning)
							 | 
						||
| 
								 | 
							
								            yield
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								        yield
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def unload(name):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        del sys.modules[name]
							 | 
						||
| 
								 | 
							
								    except KeyError:
							 | 
						||
| 
								 | 
							
								        pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def forget(modname):
							 | 
						||
| 
								 | 
							
								    """'Forget' a module was ever imported.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This removes the module from sys.modules and deletes any PEP 3147/488 or
							 | 
						||
| 
								 | 
							
								    legacy .pyc files.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    unload(modname)
							 | 
						||
| 
								 | 
							
								    for dirname in sys.path:
							 | 
						||
| 
								 | 
							
								        source = os.path.join(dirname, modname + '.py')
							 | 
						||
| 
								 | 
							
								        # It doesn't matter if they exist or not, unlink all possible
							 | 
						||
| 
								 | 
							
								        # combinations of PEP 3147/488 and legacy pyc files.
							 | 
						||
| 
								 | 
							
								        unlink(source + 'c')
							 | 
						||
| 
								 | 
							
								        for opt in ('', 1, 2):
							 | 
						||
| 
								 | 
							
								            unlink(importlib.util.cache_from_source(source, optimization=opt))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def make_legacy_pyc(source):
							 | 
						||
| 
								 | 
							
								    """Move a PEP 3147/488 pyc file to its legacy pyc location.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    :param source: The file system path to the source file.  The source file
							 | 
						||
| 
								 | 
							
								        does not need to exist, however the PEP 3147/488 pyc file must exist.
							 | 
						||
| 
								 | 
							
								    :return: The file system path to the legacy pyc file.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    pyc_file = importlib.util.cache_from_source(source)
							 | 
						||
| 
								 | 
							
								    up_one = os.path.dirname(os.path.abspath(source))
							 | 
						||
| 
								 | 
							
								    legacy_pyc = os.path.join(up_one, source + 'c')
							 | 
						||
| 
								 | 
							
								    os.rename(pyc_file, legacy_pyc)
							 | 
						||
| 
								 | 
							
								    return legacy_pyc
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def import_module(name, deprecated=False, *, required_on=()):
							 | 
						||
| 
								 | 
							
								    """Import and return the module to be tested, raising SkipTest if
							 | 
						||
| 
								 | 
							
								    it is not available.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    If deprecated is True, any module or package deprecation messages
							 | 
						||
| 
								 | 
							
								    will be suppressed. If a module is required on a platform but optional for
							 | 
						||
| 
								 | 
							
								    others, set required_on to an iterable of platform prefixes which will be
							 | 
						||
| 
								 | 
							
								    compared against sys.platform.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    with _ignore_deprecated_imports(deprecated):
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            return importlib.import_module(name)
							 | 
						||
| 
								 | 
							
								        except ImportError as msg:
							 | 
						||
| 
								 | 
							
								            if sys.platform.startswith(tuple(required_on)):
							 | 
						||
| 
								 | 
							
								                raise
							 | 
						||
| 
								 | 
							
								            raise unittest.SkipTest(str(msg))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _save_and_remove_module(name, orig_modules):
							 | 
						||
| 
								 | 
							
								    """Helper function to save and remove a module from sys.modules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Raise ImportError if the module can't be imported.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # try to import the module and raise an error if it can't be imported
							 | 
						||
| 
								 | 
							
								    if name not in sys.modules:
							 | 
						||
| 
								 | 
							
								        __import__(name)
							 | 
						||
| 
								 | 
							
								        del sys.modules[name]
							 | 
						||
| 
								 | 
							
								    for modname in list(sys.modules):
							 | 
						||
| 
								 | 
							
								        if modname == name or modname.startswith(name + '.'):
							 | 
						||
| 
								 | 
							
								            orig_modules[modname] = sys.modules[modname]
							 | 
						||
| 
								 | 
							
								            del sys.modules[modname]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _save_and_block_module(name, orig_modules):
							 | 
						||
| 
								 | 
							
								    """Helper function to save and block a module in sys.modules
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Return True if the module was in sys.modules, False otherwise.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    saved = True
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        orig_modules[name] = sys.modules[name]
							 | 
						||
| 
								 | 
							
								    except KeyError:
							 | 
						||
| 
								 | 
							
								        saved = False
							 | 
						||
| 
								 | 
							
								    sys.modules[name] = None
							 | 
						||
| 
								 | 
							
								    return saved
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
							 | 
						||
| 
								 | 
							
								    """Import and return a module, deliberately bypassing sys.modules.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function imports and returns a fresh copy of the named Python module
							 | 
						||
| 
								 | 
							
								    by removing the named module from sys.modules before doing the import.
							 | 
						||
| 
								 | 
							
								    Note that unlike reload, the original module is not affected by
							 | 
						||
| 
								 | 
							
								    this operation.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    *fresh* is an iterable of additional module names that are also removed
							 | 
						||
| 
								 | 
							
								    from the sys.modules cache before doing the import.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    *blocked* is an iterable of module names that are replaced with None
							 | 
						||
| 
								 | 
							
								    in the module cache during the import to ensure that attempts to import
							 | 
						||
| 
								 | 
							
								    them raise ImportError.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    The named module and any modules named in the *fresh* and *blocked*
							 | 
						||
| 
								 | 
							
								    parameters are saved before starting the import and then reinserted into
							 | 
						||
| 
								 | 
							
								    sys.modules when the fresh import is complete.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Module and package deprecation messages are suppressed during this import
							 | 
						||
| 
								 | 
							
								    if *deprecated* is True.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This function will raise ImportError if the named module cannot be
							 | 
						||
| 
								 | 
							
								    imported.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    # NOTE: test_heapq, test_json and test_warnings include extra sanity checks
							 | 
						||
| 
								 | 
							
								    # to make sure that this utility function is working as expected
							 | 
						||
| 
								 | 
							
								    with _ignore_deprecated_imports(deprecated):
							 | 
						||
| 
								 | 
							
								        # Keep track of modules saved for later restoration as well
							 | 
						||
| 
								 | 
							
								        # as those which just need a blocking entry removed
							 | 
						||
| 
								 | 
							
								        orig_modules = {}
							 | 
						||
| 
								 | 
							
								        names_to_remove = []
							 | 
						||
| 
								 | 
							
								        _save_and_remove_module(name, orig_modules)
							 | 
						||
| 
								 | 
							
								        try:
							 | 
						||
| 
								 | 
							
								            for fresh_name in fresh:
							 | 
						||
| 
								 | 
							
								                _save_and_remove_module(fresh_name, orig_modules)
							 | 
						||
| 
								 | 
							
								            for blocked_name in blocked:
							 | 
						||
| 
								 | 
							
								                if not _save_and_block_module(blocked_name, orig_modules):
							 | 
						||
| 
								 | 
							
								                    names_to_remove.append(blocked_name)
							 | 
						||
| 
								 | 
							
								            fresh_module = importlib.import_module(name)
							 | 
						||
| 
								 | 
							
								        except ImportError:
							 | 
						||
| 
								 | 
							
								            fresh_module = None
							 | 
						||
| 
								 | 
							
								        finally:
							 | 
						||
| 
								 | 
							
								            for orig_name, module in orig_modules.items():
							 | 
						||
| 
								 | 
							
								                sys.modules[orig_name] = module
							 | 
						||
| 
								 | 
							
								            for name_to_remove in names_to_remove:
							 | 
						||
| 
								 | 
							
								                del sys.modules[name_to_remove]
							 | 
						||
| 
								 | 
							
								        return fresh_module
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class CleanImport(object):
							 | 
						||
| 
								 | 
							
								    """Context manager to force import to return a new module reference.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This is useful for testing module-level behaviours, such as
							 | 
						||
| 
								 | 
							
								    the emission of a DeprecationWarning on import.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Use like this:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        with CleanImport("foo"):
							 | 
						||
| 
								 | 
							
								            importlib.import_module("foo") # new reference
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *module_names):
							 | 
						||
| 
								 | 
							
								        self.original_modules = sys.modules.copy()
							 | 
						||
| 
								 | 
							
								        for module_name in module_names:
							 | 
						||
| 
								 | 
							
								            if module_name in sys.modules:
							 | 
						||
| 
								 | 
							
								                module = sys.modules[module_name]
							 | 
						||
| 
								 | 
							
								                # It is possible that module_name is just an alias for
							 | 
						||
| 
								 | 
							
								                # another module (e.g. stub for modules renamed in 3.x).
							 | 
						||
| 
								 | 
							
								                # In that case, we also need delete the real module to clear
							 | 
						||
| 
								 | 
							
								                # the import cache.
							 | 
						||
| 
								 | 
							
								                if module.__name__ != module_name:
							 | 
						||
| 
								 | 
							
								                    del sys.modules[module.__name__]
							 | 
						||
| 
								 | 
							
								                del sys.modules[module_name]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __enter__(self):
							 | 
						||
| 
								 | 
							
								        return self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __exit__(self, *ignore_exc):
							 | 
						||
| 
								 | 
							
								        sys.modules.update(self.original_modules)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class DirsOnSysPath(object):
							 | 
						||
| 
								 | 
							
								    """Context manager to temporarily add directories to sys.path.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This makes a copy of sys.path, appends any directories given
							 | 
						||
| 
								 | 
							
								    as positional arguments, then reverts sys.path to the copied
							 | 
						||
| 
								 | 
							
								    settings when the context ends.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Note that *all* sys.path modifications in the body of the
							 | 
						||
| 
								 | 
							
								    context manager, including replacement of the object,
							 | 
						||
| 
								 | 
							
								    will be reverted at the end of the block.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, *paths):
							 | 
						||
| 
								 | 
							
								        self.original_value = sys.path[:]
							 | 
						||
| 
								 | 
							
								        self.original_object = sys.path
							 | 
						||
| 
								 | 
							
								        sys.path.extend(paths)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __enter__(self):
							 | 
						||
| 
								 | 
							
								        return self
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __exit__(self, *ignore_exc):
							 | 
						||
| 
								 | 
							
								        sys.path = self.original_object
							 | 
						||
| 
								 | 
							
								        sys.path[:] = self.original_value
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def modules_setup():
							 | 
						||
| 
								 | 
							
								    return sys.modules.copy(),
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def modules_cleanup(oldmodules):
							 | 
						||
| 
								 | 
							
								    # Encoders/decoders are registered permanently within the internal
							 | 
						||
| 
								 | 
							
								    # codec cache. If we destroy the corresponding modules their
							 | 
						||
| 
								 | 
							
								    # globals will be set to None which will trip up the cached functions.
							 | 
						||
| 
								 | 
							
								    encodings = [(k, v) for k, v in sys.modules.items()
							 | 
						||
| 
								 | 
							
								                 if k.startswith('encodings.')]
							 | 
						||
| 
								 | 
							
								    sys.modules.clear()
							 | 
						||
| 
								 | 
							
								    sys.modules.update(encodings)
							 | 
						||
| 
								 | 
							
								    # XXX: This kind of problem can affect more than just encodings.
							 | 
						||
| 
								 | 
							
								    # In particular extension modules (such as _ssl) don't cope
							 | 
						||
| 
								 | 
							
								    # with reloading properly. Really, test modules should be cleaning
							 | 
						||
| 
								 | 
							
								    # out the test specific modules they know they added (ala test_runpy)
							 | 
						||
| 
								 | 
							
								    # rather than relying on this function (as test_importhooks and test_pkg
							 | 
						||
| 
								 | 
							
								    # do currently). Implicitly imported *real* modules should be left alone
							 | 
						||
| 
								 | 
							
								    # (see issue 10556).
							 | 
						||
| 
								 | 
							
								    sys.modules.update(oldmodules)
							 |