Source info missing from python classes loaded with module_from_spec

699 views Asked by At

I am trying to use Python's introspection module inspect to retrieve the source code for live objects which have been loaded into the scope using the module_from_spec function of importlib.util. Attempts to use inspect.getsource() on either the spec_file itself or any function in the spec file successfully return the desired source code. However, the same method used to retrieve the source code for class types in the spec file throws a TypeError: None is a built-in class.

from importlib.util import spec_from_file_location, module_from_spec
spec_file = spec_from_file_location("file_module", 'test_file.py')
spec_module = module_from_spec(spec_file)
spec_file.loader.exec_module(spec_module)

# spec module
import inspect
assert isinstance(inspect.getsource(spec_module), str)

# function in spec module
func_obj = getattr(spec_module, 'test_function')
assert isinstance(inspect.getsource(func_obj), str)

# class in spec module
class_obj = getattr(spec_module, 'testClass')
try:
    inspect.getsource(class_obj)
except TypeError:
    print('where did the source code data go?')

The culprit seems to be the inspect.getfile() call in the traceback chain where function objects return object.__code__ while class objects attempt to load their module in order to retrieve the module.__file__. Why do functions have the __code__ method, while classes do not? Is this a side-effect of how Python handles types which inadvertently breaks introspection for dynamically loaded classes?

1

There are 1 answers

0
Nikolas Stevenson-Molnar On

It looks like the loaded module must be added to sys.modules in order for the source and module path to be correctly reflected in classes (though I am unable to find reference to this in the documentation). So, if you import sys and add your module to sys.modules, your example should work (I tested a similar example with Python 3.5):

import sys

from importlib.util import spec_from_file_location, module_from_spec
spec_file = spec_from_file_location("file_module", 'test_file.py')
spec_module = module_from_spec(spec_file)
spec_file.loader.exec_module(spec_module)

sys.modules['file_module'] = spec_module

# spec module
import inspect
assert isinstance(inspect.getsource(spec_module), str)

# function in spec module
func_obj = getattr(spec_module, 'test_function')
assert isinstance(inspect.getsource(func_obj), str)

# class in spec module
class_obj = getattr(spec_module, 'testClass')
try:
    inspect.getsource(class_obj)  # This should work now
except TypeError:
    print('where did the source code data go?')