Newer
Older
# This file is part of the mantidqt package
#
# Copyright (C) 2017 mantidproject
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, unicode_literals)
# std imports
# local imports
from mantidqt.utils.async import AsyncTask
class PythonCodeExecution(object):
"""Provides the ability to execute arbitrary
strings of Python code. It supports
reporting progress updates in asynchronous execution
"""
on_success = None
on_error = None
on_progress = None
def __init__(self,success_cb=None, error_cb=None,
progress_cb=None):
:param success_cb: A callback of the form f() called on success
:param error_cb: A callback of the form f(exc) called on error,
providing an AsyncTaskFailure object
:param progress_cb: A callback for progress updates as the code executes
"""
# AsyncTask's callbacks have a slightly different form
if success_cb:
def success_cb_wrap(_): pass
self.on_success = success_cb_wrap
self.on_error = error_cb
self.on_progress_update = progress_cb
def execute_async(self, code_str, user_globals,
"""
Execute the given code string on a separate thread. This function
returns as soon as the new thread starts
:param code_str: A string containing code to execute
:param user_globals: A mutable mapping type to store global variables
:param user_locals: A mutable mapping type to store local variables
:returns: The created async task
"""
# Stack is chopped on error to avoid the AsyncTask.run->_do_exec->exec calls appearing
# as these are not useful in this context
t = AsyncTask(self.execute, args=(code_str, user_globals, user_locals),
success_cb=self.on_success, error_cb=self.on_error)
t.start()
return t
def execute(self, code_str, user_globals,
user_locals):
"""Execute the given code on the calling thread
within the provided context.
:param code_str: A string containing code to execute
:param user_globals: A mutable mapping type to store global variables
:param user_locals: A mutable mapping type to store local variables
:raises: Any error that the code generates
"""
# execute whole string if no reporting is required
if self.on_progress_update is None:
self._do_exec(code_str, user_globals, user_locals)
else:
self._execute_as_blocks(code_str, user_globals, user_locals,
self.on_progress_update)
def _execute_as_blocks(self, code_str, user_globals, user_locals,
progress_cb):
"""Execute the code in the supplied context and report the progress
using the supplied callback"""
# will raise a SyntaxError if any of the code is invalid
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
compile(code_str, "<string>", mode='exec')
for block in code_blocks(code_str):
progress_cb(block.lineno)
self._do_exec(block.code_obj, user_globals, user_locals)
def _do_exec(self, code, user_globals, user_locals):
exec (code, user_globals, user_locals)
class CodeBlock(object):
"""Holds an executable code object. It also stores the line number
of the first line within a larger group of code blocks"""
def __init__(self, code_obj, lineno):
self.code_obj = code_obj
self.lineno = lineno
def code_blocks(code_str):
"""Generator to produce blocks of executable code
from the given code string.
"""
lineno = 1
lines = code_str.splitlines()
cur_block = []
for line in lines:
cur_block.append(line)
code_block = "\n".join(cur_block)
try:
code_obj = compile(code_block, "<string>", mode='exec')
yield CodeBlock(code_obj, lineno)
lineno += len(cur_block)
cur_block = []
except (SyntaxError, TypeError):
# assume we don't have a full block yet
continue