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

ExecutionLoader.
This commit is contained in:
Brett Cannon 2013-05-27 21:11:04 -04:00
parent acfa291af9
commit 3b62ca88e4
3 changed files with 121 additions and 17 deletions

View File

@ -318,16 +318,20 @@ ABC hierarchy::
.. 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)
@ -410,7 +414,7 @@ ABC hierarchy::
.. 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

View File

@ -147,14 +147,18 @@ class InspectLoader(Loader):
"""
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):
@ -194,6 +198,22 @@ class ExecutionLoader(InspectLoader):
"""
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):

View File

@ -9,6 +9,7 @@ import marshal
import os
import sys
import unittest
from unittest import mock
from . import util
@ -166,9 +167,6 @@ class InspectLoaderSubclass(LoaderSubclass, abc.InspectLoader):
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)
@ -181,10 +179,6 @@ class InspectLoaderDefaultsTests(unittest.TestCase):
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')
@ -206,7 +200,7 @@ class ExecutionLoaderDefaultsTests(unittest.TestCase):
##### 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."""
@ -248,6 +242,92 @@ class InspectLoaderConcreteMethodTests(unittest.TestCase):
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):