--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric6/DebugClients/Python/ModuleLoader.py Sat Feb 08 17:02:40 2020 +0100 @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an import hook patching modules to support debugging. +""" + +import sys +import importlib + + +class ModuleLoader(object): + """ + Class implementing an import hook patching modules to support debugging. + """ + def __init__(self, debugClient): + """ + Constructor + + @param debugClient reference to the debug client object + @type DebugClient + """ + self.__dbgClient = debugClient + + self.__enableImportHooks = True + + # TODO: check if needed + if sys.version_info[0] == 2: + self.threadModName = 'thread' + else: + self.threadModName = '_thread' + + # reset already imported thread module to apply hooks at next import + for moduleName in ("thread", "_thread", "threading"): + if moduleName in sys.modules: + del sys.modules[moduleName] + + self.__modulesToPatch = ( + 'thread', '_thread', 'threading', + 'greenlet', + 'PyQt4.QtCore', 'PyQt5.QtCore', + 'PySide.QtCore', 'PySide2.QtCore', + ) + + sys.meta_path.insert(0, self) + + def __loadModule(self, fullname): + """ + Public method to load a module. + + @param fullname name of the module to be loaded + @type str + @return reference to the loaded module + @rtype module + """ + module = importlib.import_module(fullname) + sys.modules[fullname] = module + + ## Add hook for _thread.start_new_thread + if ( + fullname in ('thread', '_thread') and + not hasattr(module, 'eric6_patched') + ): + module.eric6_patched = True + self.__dbgClient.patchPyThread(module) + + ## Add hook for threading.run() + elif ( + fullname == "threading" and + not hasattr(module, 'eric6_patched') + ): + module.eric6_patched = True + self.__dbgClient.patchPyThreading(module) + + ## greenlet support + elif ( + fullname == 'greenlet' and + not hasattr(module, 'eric6_patched') + ): + if self.__dbgClient.patchGreenlet(module): + module.eric6_patched = True + + ## Add hook for *.QThread + elif ( + fullname in ('PyQt4.QtCore', 'PyQt5.QtCore', + 'PySide.QtCore', 'PySide2.QtCore') and + not hasattr(module, 'eric6_patched') + ): + module.eric6_patched = True + self.__dbgClient.patchQThread(module) + + self.__enableImportHooks = True + return module + + if sys.version_info >= (3, 4): + def find_spec(self, fullname, path, target=None): + """ + Public method returning the module spec. + + @param fullname name of the module to be loaded + @type str + @param path path to resolve the module name + @type str + @param target module object to use for a more educated guess + about what spec to return + @type module + @return module spec object pointing to the module loader + @type ModuleSpec + """ + if fullname in sys.modules or not self.__dbgClient.debugging: + return None + + if ( + fullname in self.__modulesToPatch and + self.__enableImportHooks + ): + # Disable hook to be able to import original module + self.__enableImportHooks = False + return importlib.machinery.ModuleSpec(fullname, self) + + return None + + def create_module(self, spec): + """ + Public method to create a module based on the passed in spec. + + @param spec module spec object for loading the module + @type ModuleSpec + @return created and patched module + @rtype module + """ + return self.__loadModule(spec.name) + + def exec_module(self, module): + """ + Public method to execute the created module + + @param module module to be executed + @type module + """ + pass + + else: + def find_module(self, fullname, path=None): + """ + Public method returning the module loader. + + @param fullname name of the module to be loaded + @type str + @param path path to resolve the module name + @type str + @return module loader object + @rtype object + """ + if fullname in sys.modules or not self.__dbgClient.debugging: + return None + + if ( + fullname in self.__modulesToPatch and + self.__enableImportHooks + ): + # Disable hook to be able to import original module + self.__enableImportHooks = False + return self + + return None + + def load_module(self, fullname): + """ + Public method to load a module. + + @param fullname name of the module to be loaded + @type str + @return reference to the loaded module + @rtype module + """ + return self.__loadModule(fullname)