eric6/DebugClients/Python/ModuleLoader.py

branch
multi_processing
changeset 7404
663f1c3d6f53
child 7407
a0b6acee2c20
--- /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)

eric ide

mercurial