1import contextlib 2import importlib 3import importlib.util 4import os 5import shutil 6import sys 7import unittest 8import warnings 9 10from .os_helper import unlink 11 12 13@contextlib.contextmanager 14def _ignore_deprecated_imports(ignore=True): 15 """Context manager to suppress package and module deprecation 16 warnings when importing them. 17 18 If ignore is False, this context manager has no effect. 19 """ 20 if ignore: 21 with warnings.catch_warnings(): 22 warnings.filterwarnings("ignore", ".+ (module|package)", 23 DeprecationWarning) 24 yield 25 else: 26 yield 27 28 29def unload(name): 30 try: 31 del sys.modules[name] 32 except KeyError: 33 pass 34 35 36def forget(modname): 37 """'Forget' a module was ever imported. 38 39 This removes the module from sys.modules and deletes any PEP 3147/488 or 40 legacy .pyc files. 41 """ 42 unload(modname) 43 for dirname in sys.path: 44 source = os.path.join(dirname, modname + '.py') 45 # It doesn't matter if they exist or not, unlink all possible 46 # combinations of PEP 3147/488 and legacy pyc files. 47 unlink(source + 'c') 48 for opt in ('', 1, 2): 49 unlink(importlib.util.cache_from_source(source, optimization=opt)) 50 51 52def make_legacy_pyc(source): 53 """Move a PEP 3147/488 pyc file to its legacy pyc location. 54 55 :param source: The file system path to the source file. The source file 56 does not need to exist, however the PEP 3147/488 pyc file must exist. 57 :return: The file system path to the legacy pyc file. 58 """ 59 pyc_file = importlib.util.cache_from_source(source) 60 up_one = os.path.dirname(os.path.abspath(source)) 61 legacy_pyc = os.path.join(up_one, source + 'c') 62 shutil.move(pyc_file, legacy_pyc) 63 return legacy_pyc 64 65 66def import_module(name, deprecated=False, *, required_on=()): 67 """Import and return the module to be tested, raising SkipTest if 68 it is not available. 69 70 If deprecated is True, any module or package deprecation messages 71 will be suppressed. If a module is required on a platform but optional for 72 others, set required_on to an iterable of platform prefixes which will be 73 compared against sys.platform. 74 """ 75 with _ignore_deprecated_imports(deprecated): 76 try: 77 return importlib.import_module(name) 78 except ImportError as msg: 79 if sys.platform.startswith(tuple(required_on)): 80 raise 81 raise unittest.SkipTest(str(msg)) 82 83 84def _save_and_remove_modules(names): 85 orig_modules = {} 86 prefixes = tuple(name + '.' for name in names) 87 for modname in list(sys.modules): 88 if modname in names or modname.startswith(prefixes): 89 orig_modules[modname] = sys.modules.pop(modname) 90 return orig_modules 91 92 93def import_fresh_module(name, fresh=(), blocked=(), deprecated=False): 94 """Import and return a module, deliberately bypassing sys.modules. 95 96 This function imports and returns a fresh copy of the named Python module 97 by removing the named module from sys.modules before doing the import. 98 Note that unlike reload, the original module is not affected by 99 this operation. 100 101 *fresh* is an iterable of additional module names that are also removed 102 from the sys.modules cache before doing the import. If one of these 103 modules can't be imported, None is returned. 104 105 *blocked* is an iterable of module names that are replaced with None 106 in the module cache during the import to ensure that attempts to import 107 them raise ImportError. 108 109 The named module and any modules named in the *fresh* and *blocked* 110 parameters are saved before starting the import and then reinserted into 111 sys.modules when the fresh import is complete. 112 113 Module and package deprecation messages are suppressed during this import 114 if *deprecated* is True. 115 116 This function will raise ImportError if the named module cannot be 117 imported. 118 """ 119 # NOTE: test_heapq, test_json and test_warnings include extra sanity checks 120 # to make sure that this utility function is working as expected 121 with _ignore_deprecated_imports(deprecated): 122 # Keep track of modules saved for later restoration as well 123 # as those which just need a blocking entry removed 124 fresh = list(fresh) 125 blocked = list(blocked) 126 names = {name, *fresh, *blocked} 127 orig_modules = _save_and_remove_modules(names) 128 for modname in blocked: 129 sys.modules[modname] = None 130 131 try: 132 # Return None when one of the "fresh" modules can not be imported. 133 try: 134 for modname in fresh: 135 __import__(modname) 136 except ImportError: 137 return None 138 return importlib.import_module(name) 139 finally: 140 _save_and_remove_modules(names) 141 sys.modules.update(orig_modules) 142 143 144class CleanImport(object): 145 """Context manager to force import to return a new module reference. 146 147 This is useful for testing module-level behaviours, such as 148 the emission of a DeprecationWarning on import. 149 150 Use like this: 151 152 with CleanImport("foo"): 153 importlib.import_module("foo") # new reference 154 """ 155 156 def __init__(self, *module_names): 157 self.original_modules = sys.modules.copy() 158 for module_name in module_names: 159 if module_name in sys.modules: 160 module = sys.modules[module_name] 161 # It is possible that module_name is just an alias for 162 # another module (e.g. stub for modules renamed in 3.x). 163 # In that case, we also need delete the real module to clear 164 # the import cache. 165 if module.__name__ != module_name: 166 del sys.modules[module.__name__] 167 del sys.modules[module_name] 168 169 def __enter__(self): 170 return self 171 172 def __exit__(self, *ignore_exc): 173 sys.modules.update(self.original_modules) 174 175 176class DirsOnSysPath(object): 177 """Context manager to temporarily add directories to sys.path. 178 179 This makes a copy of sys.path, appends any directories given 180 as positional arguments, then reverts sys.path to the copied 181 settings when the context ends. 182 183 Note that *all* sys.path modifications in the body of the 184 context manager, including replacement of the object, 185 will be reverted at the end of the block. 186 """ 187 188 def __init__(self, *paths): 189 self.original_value = sys.path[:] 190 self.original_object = sys.path 191 sys.path.extend(paths) 192 193 def __enter__(self): 194 return self 195 196 def __exit__(self, *ignore_exc): 197 sys.path = self.original_object 198 sys.path[:] = self.original_value 199 200 201def modules_setup(): 202 return sys.modules.copy(), 203 204 205def modules_cleanup(oldmodules): 206 # Encoders/decoders are registered permanently within the internal 207 # codec cache. If we destroy the corresponding modules their 208 # globals will be set to None which will trip up the cached functions. 209 encodings = [(k, v) for k, v in sys.modules.items() 210 if k.startswith('encodings.')] 211 sys.modules.clear() 212 sys.modules.update(encodings) 213 # XXX: This kind of problem can affect more than just encodings. 214 # In particular extension modules (such as _ssl) don't cope 215 # with reloading properly. Really, test modules should be cleaning 216 # out the test specific modules they know they added (ala test_runpy) 217 # rather than relying on this function (as test_importhooks and test_pkg 218 # do currently). Implicitly imported *real* modules should be left alone 219 # (see issue 10556). 220 sys.modules.update(oldmodules) 221