Fix Python console command execution without a __main__ module
Account for sys.modules not to have a __main__ module, various other minor improvements too. - Use a context manager to temporarily override the __main__ module. - Use a context manager to override the stdin when auto-completing. - Remove `_BPY_MAIN_OWN` constant as the __main__ module is needed.
This commit is contained in:
@@ -7,9 +7,29 @@ import bpy
|
||||
|
||||
language_id = "python"
|
||||
|
||||
# store our own __main__ module, not 100% needed
|
||||
# but python expects this in some places
|
||||
_BPY_MAIN_OWN = True
|
||||
|
||||
class _TempModuleOverride:
|
||||
__slots__ = (
|
||||
"module_name",
|
||||
"module",
|
||||
"module_override",
|
||||
)
|
||||
|
||||
def __init__(self, module_name, module_override):
|
||||
self.module_name = module_name
|
||||
self.module = None
|
||||
self.module_override = module_override
|
||||
|
||||
def __enter__(self):
|
||||
self.module = sys.modules.get(self.module_name)
|
||||
sys.modules[self.module_name] = self.module_override
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
if self.module is None:
|
||||
# Account for removal of `module_override` (albeit unlikely).
|
||||
sys.modules.pop(self.module_name, None)
|
||||
else:
|
||||
sys.modules[self.module_name] = self.module
|
||||
|
||||
|
||||
def add_scrollback(text, text_type):
|
||||
@@ -72,12 +92,9 @@ def get_console(console_id):
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
else:
|
||||
if _BPY_MAIN_OWN:
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
else:
|
||||
namespace = {}
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
|
||||
namespace["__builtins__"] = sys.modules["builtins"]
|
||||
namespace["bpy"] = bpy
|
||||
@@ -94,8 +111,7 @@ def get_console(console_id):
|
||||
console.push("from mathutils import *")
|
||||
console.push("from math import *")
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
@@ -121,25 +137,23 @@ def execute(context, is_interactive):
|
||||
|
||||
console, stdout, stderr = get_console(hash(context.region))
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
# redirect output
|
||||
from contextlib import (
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
|
||||
# not included with Python
|
||||
# Not included with Python.
|
||||
class redirect_stdin(redirect_stdout.__base__):
|
||||
_stream = "stdin"
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
with redirect_stdout(stdout), \
|
||||
redirect_stderr(stderr), \
|
||||
redirect_stdin(None):
|
||||
|
||||
with (
|
||||
redirect_stdout(stdout),
|
||||
redirect_stderr(stderr),
|
||||
# Don't allow the `stdin` to be used because it can lock Blender.
|
||||
redirect_stdin(None),
|
||||
_TempModuleOverride("__main__", console._bpy_main_mod),
|
||||
):
|
||||
# in case exception happens
|
||||
line = "" # in case of encoding error
|
||||
is_multiline = False
|
||||
@@ -156,9 +170,6 @@ def execute(context, is_interactive):
|
||||
import traceback
|
||||
stderr.write(traceback.format_exc())
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
output = stdout.getvalue()
|
||||
output_err = stderr.getvalue()
|
||||
|
||||
@@ -220,46 +231,48 @@ def autocomplete(context):
|
||||
if not console:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
# note: unlikely stdin would be used for autocomplete. but its possible.
|
||||
stdin_backup = sys.stdin
|
||||
sys.stdin = None
|
||||
|
||||
scrollback = ""
|
||||
scrollback_error = ""
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
# Don't allow the `stdin` to be used, can lock blender.
|
||||
# note: unlikely `stdin` would be used for auto-complete - but it's possible.
|
||||
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
# This function isn't aware of the text editor or being an operator
|
||||
# just does the autocomplete then copy its results back
|
||||
result = intellisense.expand(
|
||||
line=line,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug_python)
|
||||
# Not included with Python.
|
||||
class redirect_stdin(redirect_stdout.__base__):
|
||||
_stream = "stdin"
|
||||
|
||||
line_new = result[0]
|
||||
current_line.body, current_line.current_character, scrollback = result
|
||||
del result
|
||||
with (
|
||||
# Don't allow the `stdin` to be used because it can lock Blender.
|
||||
redirect_stdin(None),
|
||||
_TempModuleOverride("__main__", console._bpy_main_mod),
|
||||
):
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
|
||||
# update selection. setting body should really do this!
|
||||
ofs = len(line_new) - len(line)
|
||||
sc.select_start += ofs
|
||||
sc.select_end += ofs
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the api attribute access itself causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
# This function isn't aware of the text editor or being an operator
|
||||
# just does the autocomplete then copy its results back
|
||||
result = intellisense.expand(
|
||||
line=line,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug_python)
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
line_new = result[0]
|
||||
current_line.body, current_line.current_character, scrollback = result
|
||||
del result
|
||||
|
||||
# update selection. setting body should really do this!
|
||||
ofs = len(line_new) - len(line)
|
||||
sc.select_start += ofs
|
||||
sc.select_end += ofs
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the API attribute access itself causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
|
||||
# Separate autocomplete output by command prompts
|
||||
if scrollback != '':
|
||||
@@ -275,9 +288,6 @@ def autocomplete(context):
|
||||
if scrollback_error:
|
||||
add_scrollback(scrollback_error, 'ERROR')
|
||||
|
||||
# restore the stdin
|
||||
sys.stdin = stdin_backup
|
||||
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
Reference in New Issue
Block a user