Commits

Brett Cannon  committed 11510db

Issue #18072: Implement get_code() for importlib.abc.InspectLoader and
ExecutionLoader.

  • Participants
  • Parent commits 789f8bb

Comments (0)

Files changed (3)

File Doc/library/importlib.rst

 
     .. method:: get_code(fullname)
 
-        An abstract method to return the :class:`code` object for a module.
-        ``None`` is returned if the module does not have a code object
+        Return the code object for a module.
+        ``None`` should be returned if the module does not have a code object
         (e.g. built-in module).  :exc:`ImportError` is raised if loader cannot
         find the requested module.
 
+        .. note::
+           While the method has a default implementation, it is suggested that
+           it be overridden if possible for performance.
+
         .. index::
            single: universal newlines; importlib.abc.InspectLoader.get_source method
 
         .. versionchanged:: 3.4
-           Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.
+           No longer abstract and a concrete implementation is provided.
 
     .. method:: get_source(fullname)
 
 
    .. method:: get_data(path)
 
-      Returns the open, binary file for *path*.
+      Reads *path* as a binary file and returns the bytes from it.
 
 
 .. class:: SourceLoader

File Lib/importlib/abc.py

         """
         raise ImportError
 
-    @abc.abstractmethod
     def get_code(self, fullname):
-        """Abstract method which when implemented should return the code object
-        for the module.  The fullname is a str.  Returns a types.CodeType.
+        """Method which returns the code object for the module.
 
-        Raises ImportError if the module cannot be found.
+        The fullname is a str.  Returns a types.CodeType if possible, else
+        returns None if a code object does not make sense
+        (e.g. built-in module). Raises ImportError if the module cannot be
+        found.
         """
-        raise ImportError
+        source = self.get_source(fullname)
+        if source is None:
+            return None
+        return self.source_to_code(source)
 
     @abc.abstractmethod
     def get_source(self, fullname):
         """
         raise ImportError
 
+    def get_code(self, fullname):
+        """Method to return the code object for fullname.
+
+        Should return None if not applicable (e.g. built-in module).
+        Raise ImportError if the module cannot be found.
+        """
+        source = self.get_source(fullname)
+        if source is None:
+            return None
+        try:
+            path = self.get_filename(fullname)
+        except ImportError:
+            return self.source_to_code(source)
+        else:
+            return self.source_to_code(source, path)
+
 
 class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
 

File Lib/test/test_importlib/test_abc.py

 import os
 import sys
 import unittest
+from unittest import mock
 
 from . import util
 
     def is_package(self, fullname):
         return super().is_package(fullname)
 
-    def get_code(self, fullname):
-        return super().get_code(fullname)
-
     def get_source(self, fullname):
         return super().get_source(fullname)
 
         with self.assertRaises(ImportError):
             self.ins.is_package('blah')
 
-    def test_get_code(self):
-        with self.assertRaises(ImportError):
-            self.ins.get_code('blah')
-
     def test_get_source(self):
         with self.assertRaises(ImportError):
             self.ins.get_source('blah')
 
 
 ##### InspectLoader concrete methods ###########################################
-class InspectLoaderConcreteMethodTests(unittest.TestCase):
+class InspectLoaderSourceToCodeTests(unittest.TestCase):
 
     def source_to_module(self, data, path=None):
         """Help with source_to_code() tests."""
         self.assertEqual(code.co_filename, '<string>')
 
 
+class InspectLoaderGetCodeTests(unittest.TestCase):
+
+    def test_get_code(self):
+        # Test success.
+        module = imp.new_module('blah')
+        with mock.patch.object(InspectLoaderSubclass, 'get_source') as mocked:
+            mocked.return_value = 'attr = 42'
+            loader = InspectLoaderSubclass()
+            code = loader.get_code('blah')
+        exec(code, module.__dict__)
+        self.assertEqual(module.attr, 42)
+
+    def test_get_code_source_is_None(self):
+        # If get_source() is None then this should be None.
+        with mock.patch.object(InspectLoaderSubclass, 'get_source') as mocked:
+            mocked.return_value = None
+            loader = InspectLoaderSubclass()
+            code = loader.get_code('blah')
+        self.assertIsNone(code)
+
+    def test_get_code_source_not_found(self):
+        # If there is no source then there is no code object.
+        loader = InspectLoaderSubclass()
+        with self.assertRaises(ImportError):
+            loader.get_code('blah')
+
+
+##### ExecutionLoader concrete methods #########################################
+class ExecutionLoaderGetCodeTests(unittest.TestCase):
+
+    def mock_methods(self, *, get_source=False, get_filename=False):
+        source_mock_context, filename_mock_context = None, None
+        if get_source:
+            source_mock_context = mock.patch.object(ExecutionLoaderSubclass,
+                                                    'get_source')
+        if get_filename:
+            filename_mock_context = mock.patch.object(ExecutionLoaderSubclass,
+                                                      'get_filename')
+        return source_mock_context, filename_mock_context
+
+    def test_get_code(self):
+        path = 'blah.py'
+        source_mock_context, filename_mock_context = self.mock_methods(
+                get_source=True, get_filename=True)
+        with source_mock_context as source_mock, filename_mock_context as name_mock:
+            source_mock.return_value = 'attr = 42'
+            name_mock.return_value = path
+            loader = ExecutionLoaderSubclass()
+            code = loader.get_code('blah')
+        self.assertEqual(code.co_filename, path)
+        module = imp.new_module('blah')
+        exec(code, module.__dict__)
+        self.assertEqual(module.attr, 42)
+
+    def test_get_code_source_is_None(self):
+        # If get_source() is None then this should be None.
+        source_mock_context, _ = self.mock_methods(get_source=True)
+        with source_mock_context as mocked:
+            mocked.return_value = None
+            loader = ExecutionLoaderSubclass()
+            code = loader.get_code('blah')
+        self.assertIsNone(code)
+
+    def test_get_code_source_not_found(self):
+        # If there is no source then there is no code object.
+        loader = ExecutionLoaderSubclass()
+        with self.assertRaises(ImportError):
+            loader.get_code('blah')
+
+    def test_get_code_no_path(self):
+        # If get_filename() raises ImportError then simply skip setting the path
+        # on the code object.
+        source_mock_context, filename_mock_context = self.mock_methods(
+                get_source=True, get_filename=True)
+        with source_mock_context as source_mock, filename_mock_context as name_mock:
+            source_mock.return_value = 'attr = 42'
+            name_mock.side_effect = ImportError
+            loader = ExecutionLoaderSubclass()
+            code = loader.get_code('blah')
+        self.assertEqual(code.co_filename, '<string>')
+        module = imp.new_module('blah')
+        exec(code, module.__dict__)
+        self.assertEqual(module.attr, 42)
+
+
+
 ##### SourceLoader concrete methods ############################################
 class SourceOnlyLoaderMock(abc.SourceLoader):