From 45d02859cb4ee96357b4cca1abfb56d72692459e Mon Sep 17 00:00:00 2001 From: Dean Li Date: Wed, 1 Dec 2021 20:06:38 +0800 Subject: [PATCH] import_helper: refactor `frozen_modules` `frozen_modules` requires non-trivial changes in import (`imp.rs`) and from the doc it is deprecated since 3.3 which makes this feacture unlikely to be support on this project. So instead of implementing this feature, I make `frozen_modules` to raise exception if `frozen` is required. Other than that, all implementation is just like the upstream version (except one TODO comment to remind us there is something not fully supported) --- Lib/test/support/import_helper.py | 222 +++++++++--------------------- 1 file changed, 63 insertions(+), 159 deletions(-) diff --git a/Lib/test/support/import_helper.py b/Lib/test/support/import_helper.py index e2f1def9a1..6681cad36e 100644 --- a/Lib/test/support/import_helper.py +++ b/Lib/test/support/import_helper.py @@ -91,204 +91,105 @@ def _save_and_remove_modules(names): return orig_modules -# XXX RUSTPYTHON: need _imp._override_frozen_modules_for_tests -# @contextlib.contextmanager -# def frozen_modules(enabled=True): -# """Force frozen modules to be used (or not). +# TODO RUSTPYTHON: need _imp._override_frozen_modules_for_tests +# The following implementation is NOT correct and only raise +# exception when it needs enabled=True +@contextlib.contextmanager +def frozen_modules(enabled=True): + """Force frozen modules to be used (or not). -# This only applies to modules that haven't been imported yet. -# Also, some essential modules will always be imported frozen. -# """ -# _imp._override_frozen_modules_for_tests(1 if enabled else -1) -# try: -# yield -# finally: -# _imp._override_frozen_modules_for_tests(0) - - -# XXX RUSTPYTHON: new implementation needs fronzen_modules -# def import_fresh_module(name, fresh=(), blocked=(), *, -# deprecated=False, -# usefrozen=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. If one of these -# modules can't be imported, None is returned. - -# *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. - -# If "usefrozen" is False (the default) then the frozen importer is -# disabled (except for essential modules like importlib._bootstrap). -# """ -# # 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 -# fresh = list(fresh) -# blocked = list(blocked) -# names = {name, *fresh, *blocked} -# orig_modules = _save_and_remove_modules(names) -# for modname in blocked: -# sys.modules[modname] = None - -# try: -# with frozen_modules(usefrozen): -# # Return None when one of the "fresh" modules can not be imported. -# try: -# for modname in fresh: -# __import__(modname) -# except ImportError: -# return None -# return importlib.import_module(name) -# finally: -# _save_and_remove_modules(names) -# sys.modules.update(orig_modules) - - -# TODO RUSTPYTHON: old implementation -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. + This only applies to modules that haven't been imported yet. + Also, some essential modules will always be imported frozen. """ - # 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] + if enabled: + raise NotImplemented("frozen_modules is not implemented on RustPython") + + yield + + # TODO: original implementation + # _imp._override_frozen_modules_for_tests(1 if enabled else -1) + # try: + # yield + # finally: + # _imp._override_frozen_modules_for_tests(0) -# TODO RUSTPYTHON: old implementation -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 - - -# TODO RUSTPYTHON: old implementation -def import_fresh_module(name, fresh=(), blocked=(), deprecated=False): +# TODO: `frozen_modules` is not supported +def import_fresh_module(name, fresh=(), blocked=(), *, + deprecated=False, + usefrozen=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. + from the sys.modules cache before doing the import. If one of these + modules can't be imported, None is returned. + *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. + + If "usefrozen" is False (the default) then the frozen importer is + disabled (except for essential modules like importlib._bootstrap). """ # 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) + fresh = list(fresh) + blocked = list(blocked) + names = {name, *fresh, *blocked} + orig_modules = _save_and_remove_modules(names) + for modname in blocked: + sys.modules[modname] = None + 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 + with frozen_modules(usefrozen): + # Return None when one of the "fresh" modules can not be imported. + try: + for modname in fresh: + __import__(modname) + except ImportError: + return None + return importlib.import_module(name) 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 - -# TODO RUSTPYTHON: new implementation needs fronzen_modules -# 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 - -# If "usefrozen" is False (the default) then the frozen importer is -# disabled (except for essential modules like importlib._bootstrap). -# """ - -# def __init__(self, *module_names, usefrozen=False): -# 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] -# self._frozen_modules = frozen_modules(usefrozen) - -# def __enter__(self): -# self._frozen_modules.__enter__() -# return self - -# def __exit__(self, *ignore_exc): -# sys.modules.update(self.original_modules) -# self._frozen_modules.__exit__(*ignore_exc) + _save_and_remove_modules(names) + sys.modules.update(orig_modules) -# TODO RUSTPYTHON: old implementation +# TODO: `frozen_modules` is not supported 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 + + If "usefrozen" is False (the default) then the frozen importer is + disabled (except for essential modules like importlib._bootstrap). """ - def __init__(self, *module_names): + def __init__(self, *module_names, usefrozen=False): self.original_modules = sys.modules.copy() for module_name in module_names: if module_name in sys.modules: @@ -300,12 +201,15 @@ class CleanImport(object): if module.__name__ != module_name: del sys.modules[module.__name__] del sys.modules[module_name] + self._frozen_modules = frozen_modules(usefrozen) def __enter__(self): + self._frozen_modules.__enter__() return self def __exit__(self, *ignore_exc): sys.modules.update(self.original_modules) + self._frozen_modules.__exit__(*ignore_exc) class DirsOnSysPath(object):