Sitelet https://github.com/python/cpython/commit/3b62ca88e4217679e4291126162252e75396fb10
Skip to content

Commit 3b62ca8

Browse files
committed
Issue #18072: Implement get_code() for importlib.abc.InspectLoader and
ExecutionLoader.
1 parent acfa291 commit 3b62ca8

3 files changed

Lines changed: 121 additions & 17 deletions

File tree

‎Doc/library/importlib.rst‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,20 @@ ABC hierarchy::
318318

319319
.. method:: get_code(fullname)
320320

321-
An abstract method to return the :class:`code` object for a module.
322-
``None`` is returned if the module does not have a code object
321+
Return the code object for a module.
322+
``None`` should be returned if the module does not have a code object
323323
(e.g. built-in module). :exc:`ImportError` is raised if loader cannot
324324
find the requested module.
325325

326+
.. note::
327+
While the method has a default implementation, it is suggested that
328+
it be overridden if possible for performance.
329+
326330
.. index::
327331
single: universal newlines; importlib.abc.InspectLoader.get_source method
328332

329333
.. versionchanged:: 3.4
330-
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.
334+
No longer abstract and a concrete implementation is provided.
331335

332336
.. method:: get_source(fullname)
333337

@@ -410,7 +414,7 @@ ABC hierarchy::
410414

411415
.. method:: get_data(path)
412416

413-
Returns the open, binary file for *path*.
417+
Reads *path* as a binary file and returns the bytes from it.
414418

415419

416420
.. class:: SourceLoader

‎Lib/importlib/abc.py‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,18 @@ def is_package(self, fullname):
147147
"""
148148
raise ImportError
149149

150-
@abc.abstractmethod
151150
def get_code(self, fullname):
152-
"""Abstract method which when implemented should return the code object
153-
for the module. The fullname is a str. Returns a types.CodeType.
151+
"""Method which returns the code object for the module.
154152
155-
Raises ImportError if the module cannot be found.
153+
The fullname is a str. Returns a types.CodeType if possible, else
154+
returns None if a code object does not make sense
155+
(e.g. built-in module). Raises ImportError if the module cannot be
156+
found.
156157
"""
157-
raise ImportError
158+
source = self.get_source(fullname)
159+
if source is None:
160+
return None
161+
return self.source_to_code(source)
158162

159163
@abc.abstractmethod
160164
def get_source(self, fullname):
@@ -194,6 +198,22 @@ def get_filename(self, fullname):
194198
"""
195199
raise ImportError
196200

201+
def get_code(self, fullname):
202+
"""Method to return the code object for fullname.
203+
204+
Should return None if not applicable (e.g. built-in module).
205+
Raise ImportError if the module cannot be found.
206+
"""
207+
source = self.get_source(fullname)
208+
if source is None:
209+
return None
210+
try:
211+
path = self.get_filename(fullname)
212+
except ImportError:
213+
return self.source_to_code(source)
214+
else:
215+
return self.source_to_code(source, path)
216+
197217

198218
class FileLoader(_bootstrap.FileLoader, ResourceLoader, ExecutionLoader):
199219

‎Lib/test/test_importlib/test_abc.py‎

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import os
1010
import sys
1111
import unittest
12+
from unittest import mock
1213

1314
from . import util
1415

@@ -166,9 +167,6 @@ class InspectLoaderSubclass(LoaderSubclass, abc.InspectLoader):
166167
def is_package(self, fullname):
167168
return super().is_package(fullname)
168169

169-
def get_code(self, fullname):
170-
return super().get_code(fullname)
171-
172170
def get_source(self, fullname):
173171
return super().get_source(fullname)
174172

@@ -181,10 +179,6 @@ def test_is_package(self):
181179
with self.assertRaises(ImportError):
182180
self.ins.is_package('blah')
183181

184-
def test_get_code(self):
185-
with self.assertRaises(ImportError):
186-
self.ins.get_code('blah')
187-
188182
def test_get_source(self):
189183
with self.assertRaises(ImportError):
190184
self.ins.get_source('blah')
@@ -206,7 +200,7 @@ def test_get_filename(self):
206200

207201

208202
##### InspectLoader concrete methods ###########################################
209-
class InspectLoaderConcreteMethodTests(unittest.TestCase):
203+
class InspectLoaderSourceToCodeTests(unittest.TestCase):
210204

211205
def source_to_module(self, data, path=None):
212206
"""Help with source_to_code() tests."""
@@ -248,6 +242,92 @@ def test_source_to_code_no_path(self):
248242
self.assertEqual(code.co_filename, '<string>')
249243

250244

245+
class InspectLoaderGetCodeTests(unittest.TestCase):
246+
247+
def test_get_code(self):
248+
# Test success.
249+
module = imp.new_module('blah')
250+
with mock.patch.object(InspectLoaderSubclass, 'get_source') as mocked:
251+
mocked.return_value = 'attr = 42'
252+
loader = InspectLoaderSubclass()
253+
code = loader.get_code('blah')
254+
exec(code, module.__dict__)
255+
self.assertEqual(module.attr, 42)
256+
257+
def test_get_code_source_is_None(self):
258+
# If get_source() is None then this should be None.
259+
with mock.patch.object(InspectLoaderSubclass, 'get_source') as mocked:
260+
mocked.return_value = None
261+
loader = InspectLoaderSubclass()
262+
code = loader.get_code('blah')
263+
self.assertIsNone(code)
264+
265+
def test_get_code_source_not_found(self):
266+
# If there is no source then there is no code object.
267+
loader = InspectLoaderSubclass()
268+
with self.assertRaises(ImportError):
269+
loader.get_code('blah')
270+
271+
272+
##### ExecutionLoader concrete methods #########################################
273+
class ExecutionLoaderGetCodeTests(unittest.TestCase):
274+
275+
def mock_methods(self, *, get_source=False, get_filename=False):
276+
source_mock_context, filename_mock_context = None, None
277+
if get_source:
278+
source_mock_context = mock.patch.object(ExecutionLoaderSubclass,
279+
'get_source')
280+
if get_filename:
281+
filename_mock_context = mock.patch.object(ExecutionLoaderSubclass,
282+
'get_filename')
283+
return source_mock_context, filename_mock_context
284+
285+
def test_get_code(self):
286+
path = 'blah.py'
287+
source_mock_context, filename_mock_context = self.mock_methods(
288+
get_source=True, get_filename=True)
289+
with source_mock_context as source_mock, filename_mock_context as name_mock:
290+
source_mock.return_value = 'attr = 42'
291+
name_mock.return_value = path
292+
loader = ExecutionLoaderSubclass()
293+
code = loader.get_code('blah')
294+
self.assertEqual(code.co_filename, path)
295+
module = imp.new_module('blah')
296+
exec(code, module.__dict__)
297+
self.assertEqual(module.attr, 42)
298+
299+
def test_get_code_source_is_None(self):
300+
# If get_source() is None then this should be None.
301+
source_mock_context, _ = self.mock_methods(get_source=True)
302+
with source_mock_context as mocked:
303+
mocked.return_value = None
304+
loader = ExecutionLoaderSubclass()
305+
code = loader.get_code('blah')
306+
self.assertIsNone(code)
307+
308+
def test_get_code_source_not_found(self):
309+
# If there is no source then there is no code object.
310+
loader = ExecutionLoaderSubclass()
311+
with self.assertRaises(ImportError):
312+
loader.get_code('blah')
313+
314+
def test_get_code_no_path(self):
315+
# If get_filename() raises ImportError then simply skip setting the path
316+
# on the code object.
317+
source_mock_context, filename_mock_context = self.mock_methods(
318+
get_source=True, get_filename=True)
319+
with source_mock_context as source_mock, filename_mock_context as name_mock:
320+
source_mock.return_value = 'attr = 42'
321+
name_mock.side_effect = ImportError
322+
loader = ExecutionLoaderSubclass()
323+
code = loader.get_code('blah')
324+
self.assertEqual(code.co_filename, '<string>')
325+
module = imp.new_module('blah')
326+
exec(code, module.__dict__)
327+
self.assertEqual(module.attr, 42)
328+
329+
330+
251331
##### SourceLoader concrete methods ############################################
252332
class SourceOnlyLoaderMock(abc.SourceLoader):
253333

0 commit comments

Comments
 (0)