eric7/DebugClients/Python/ModuleLoader.py

branch
eric7
changeset 8312
800c432b34c8
parent 8207
d359172d11be
child 8314
e3642a6a1e71
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eric7/DebugClients/Python/ModuleLoader.py	Sat May 15 18:45:04 2021 +0200
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an import hook patching modules to support debugging.
+"""
+
+import sys
+import importlib
+
+from QProcessExtension import patchQProcess
+from SubprocessExtension import patchSubprocess
+from MultiprocessingExtension import patchMultiprocessing
+
+
+class ModuleLoader:
+    """
+    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
+        
+        # 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',
+            'subprocess',
+            'multiprocessing',
+            'PyQt5.QtCore',
+            'PyQt6.QtCore',
+            'PySide2.QtCore',
+            'PySide6.QtCore',
+        )
+        
+        sys.meta_path.insert(0, self)
+    
+    def __loadModule(self, fullname):
+        """
+        Private 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 subprocess.Popen()
+        elif (
+            fullname == 'subprocess' and
+            not hasattr(module, 'eric6_patched')
+        ):
+            module.eric6_patched = True
+            patchSubprocess(module, self.__dbgClient)
+        
+        ## Add hook for multiprocessing.Process
+        elif (
+            fullname == 'multiprocessing' and
+            not hasattr(module, 'eric6_patched')
+        ):
+            module.eric6_patched = True
+            patchMultiprocessing(module, self.__dbgClient)
+        
+        ## Add hook for *.QThread and *.QProcess
+        elif (
+            fullname in ('PyQt5.QtCore', 'PyQt6.QtCore',
+                         'PySide2.QtCore', 'PySide6.QtCore') and
+            not hasattr(module, 'eric6_patched')
+        ):
+            module.eric6_patched = True
+            self.__dbgClient.patchQThread(module)
+            patchQProcess(module, self.__dbgClient)
+        
+        self.__enableImportHooks = True
+        return module
+    
+    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
+        @rtype 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

eric ide

mercurial