PyAPI: bpy.utils.execfile temporarily overrides the __main__ module

This is needed to support Python 3.10's `typing.get_type_hints`,
to access the name-space used when creating the class.

Also added a docstring for execfile.
This commit is contained in:
Campbell Barton 2021-02-19 11:13:35 +11:00
parent 4604350eef
commit eecb90d8d2

View File

@ -82,14 +82,39 @@ _is_factory_startup = _bpy.app.factory_startup
def execfile(filepath, mod=None):
# module name isn't used or added to 'sys.modules'.
# passing in 'mod' allows re-execution without having to reload.
"""
Execute a file path as a Python script.
:arg filepath: Path of the script to execute.
:type filepath: string
:arg mod: Optional cached module, the result of a previous execution.
:type mod: Module or None
:return: The module which can be passed back in as ``mod``.
:rtype: ModuleType
"""
import importlib.util
mod_spec = importlib.util.spec_from_file_location("__main__", filepath)
mod_name = "__main__"
mod_spec = importlib.util.spec_from_file_location(mod_name, filepath)
if mod is None:
mod = importlib.util.module_from_spec(mod_spec)
mod_spec.loader.exec_module(mod)
# While the module name is not added to `sys.modules`, it's important to temporarily
# include this so statements such as `sys.modules[cls.__module__].__dict__` behave as expected.
# See: https://bugs.python.org/issue9499 for details.
modules = _sys.modules
mod_orig = modules.get(mod_name, None)
modules[mod_name] = mod
# No error supression, just ensure `sys.modules[mod_name]` is properly restored in the case of an error.
try:
mod_spec.loader.exec_module(mod)
finally:
if mod_orig is None:
modules.pop(mod_name, None)
else:
modules[mod_name] = mod_orig
return mod