• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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