Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
mantid
Manage
Activity
Members
Labels
Plan
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Locked files
Deploy
Releases
Model registry
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Code review analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
mantidproject
mantid
Commits
45118765
Commit
45118765
authored
7 years ago
by
Martyn Gigg
Browse files
Options
Downloads
Patches
Plain Diff
Add support for progress updates during code execution.
Refs #21251
parent
570a0c48
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
qt/python/mantidqt/widgets/codeeditor/execution.py
+53
-5
53 additions, 5 deletions
qt/python/mantidqt/widgets/codeeditor/execution.py
qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
+98
-1
98 additions, 1 deletion
qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
with
151 additions
and
6 deletions
qt/python/mantidqt/widgets/codeeditor/execution.py
+
53
−
5
View file @
45118765
...
...
@@ -54,8 +54,10 @@ class PythonCodeExecution(object):
def
error_cb_wrapped
(
_
):
pass
self
.
on_error
=
error_cb_wrapped
self
.
on_progress_update
=
progress_cb
def
execute_async
(
self
,
code_str
,
user_globals
,
user_locals
,
):
user_locals
):
"""
Execute the given code string on a separate thread. This function
returns as soon as the new thread starts
...
...
@@ -65,7 +67,6 @@ class PythonCodeExecution(object):
:param user_locals: A mutable mapping type to store local variables
:returns: The created async task
"""
t
=
AsyncTask
(
self
.
execute
,
args
=
(
code_str
,
user_globals
,
user_locals
),
success_cb
=
self
.
on_success
,
error_cb
=
self
.
on_error
)
t
.
start
()
...
...
@@ -74,12 +75,59 @@ class PythonCodeExecution(object):
def
execute
(
self
,
code_str
,
user_globals
,
user_locals
):
"""
Execute the given code on the calling thread
within the provided context. All exceptions are caught
and stored with the returned result
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
"""
exec
(
code_str
,
user_globals
,
user_locals
)
# 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 all of the code is invalid
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
This diff is collapsed.
Click to expand it.
qt/python/mantidqt/widgets/codeeditor/test/test_execution.py
+
98
−
1
View file @
45118765
...
...
@@ -30,13 +30,28 @@ class PythonCodeExecutionTest(unittest.TestCase):
error_cb_called
=
False
task_exc
=
None
def
on_success
(
self
,
task_result
):
def
on_success
(
self
):
self
.
success_cb_called
=
True
def
on_error
(
self
,
exc
):
self
.
error_cb_called
=
True
self
.
task_exc
=
exc
class
ReceiverWithProgress
(
Receiver
):
def
__init__
(
self
):
self
.
lines_received
=
[]
def
on_progess_update
(
self
,
lineno
):
self
.
lines_received
.
append
(
lineno
)
def
on_error
(
self
,
exc
):
self
.
error_cb_called
=
True
self
.
task_exc
=
exc
# ---------------------------------------------------------------------------
# Successful execution tests
# ---------------------------------------------------------------------------
def
test_execute_places_output_in_provided_mapping_object
(
self
):
code
=
"
_local=100
"
namespace
=
{}
...
...
@@ -60,6 +75,9 @@ class PythonCodeExecutionTest(unittest.TestCase):
task
=
executor
.
execute_async
(
code
,
{},
{})
task
.
join
()
# ---------------------------------------------------------------------------
# Error execution tests
# ---------------------------------------------------------------------------
def
test_execute_raises_syntax_error_on_bad_code
(
self
):
code
=
"
if:
"
self
.
_verify_failed_serial_execute
(
SyntaxError
,
code
,
{},
{})
...
...
@@ -80,6 +98,85 @@ class PythonCodeExecutionTest(unittest.TestCase):
code
=
"
x = _local + 1
"
self
.
_verify_failed_serial_execute
(
NameError
,
code
,
{},
{})
# ---------------------------------------------------------------------------
# Progress tests
# ---------------------------------------------------------------------------
def
test_progress_cb_is_not_called_for_empty_string
(
self
):
code
=
""
recv
=
PythonCodeExecutionTest
.
ReceiverWithProgress
()
executor
=
PythonCodeExecution
(
success_cb
=
recv
.
on_success
,
error_cb
=
recv
.
on_error
,
progress_cb
=
recv
.
on_progess_update
)
task
=
executor
.
execute_async
(
code
,
{},
{})
task
.
join
()
self
.
assertEqual
(
0
,
len
(
recv
.
lines_received
))
def
test_progress_cb_is_not_called_for_code_with_syntax_errors
(
self
):
code
=
"""
x = 1
y =
"""
recv
=
PythonCodeExecutionTest
.
ReceiverWithProgress
()
executor
=
PythonCodeExecution
(
success_cb
=
recv
.
on_success
,
error_cb
=
recv
.
on_error
,
progress_cb
=
recv
.
on_progess_update
)
task
=
executor
.
execute_async
(
code
,
{},
{})
task
.
join
()
self
.
assertFalse
(
recv
.
success_cb_called
)
self
.
assertTrue
(
recv
.
error_cb_called
)
self
.
assertEqual
(
0
,
len
(
recv
.
lines_received
))
def
test_progress_cb_is_called_for_single_line
(
self
):
code
=
"
x = 1
"
recv
=
PythonCodeExecutionTest
.
ReceiverWithProgress
()
executor
=
PythonCodeExecution
(
success_cb
=
recv
.
on_success
,
error_cb
=
recv
.
on_error
,
progress_cb
=
recv
.
on_progess_update
)
task
=
executor
.
execute_async
(
code
,
{},
{})
task
.
join
()
if
not
recv
.
success_cb_called
:
self
.
assertTrue
(
recv
.
error_cb_called
)
self
.
fail
(
"
Execution failed with error:
\n
"
+
str
(
recv
.
task_exc
))
self
.
assertEqual
([
1
],
recv
.
lines_received
)
def
test_progress_cb_is_called_for_multiple_single_lines
(
self
):
code
=
"""
x = 1
y = 2
"""
recv
=
PythonCodeExecutionTest
.
ReceiverWithProgress
()
executor
=
PythonCodeExecution
(
success_cb
=
recv
.
on_success
,
error_cb
=
recv
.
on_error
,
progress_cb
=
recv
.
on_progess_update
)
task
=
executor
.
execute_async
(
code
,
{},
{})
task
.
join
()
if
not
recv
.
success_cb_called
:
self
.
assertTrue
(
recv
.
error_cb_called
)
self
.
fail
(
"
Execution failed with error:
\n
"
+
str
(
recv
.
task_exc
))
self
.
assertEqual
([
1
,
2
],
recv
.
lines_received
)
def
test_progress_cb_is_called_for_mix_single_lines_and_blocks
(
self
):
code
=
"""
x = 1
# comment line
sum = 0
for i in range(10):
if i %2 == 0:
sum += i
squared = sum*sum
"""
recv
=
PythonCodeExecutionTest
.
ReceiverWithProgress
()
executor
=
PythonCodeExecution
(
success_cb
=
recv
.
on_success
,
error_cb
=
recv
.
on_error
,
progress_cb
=
recv
.
on_progess_update
)
context
=
{}
task
=
executor
.
execute_async
(
code
,
context
,
context
)
task
.
join
()
if
not
recv
.
success_cb_called
:
self
.
assertTrue
(
recv
.
error_cb_called
)
self
.
fail
(
"
Execution failed with error:
\n
"
+
str
(
recv
.
task_exc
))
self
.
assertEqual
(
20
,
context
[
'
sum
'
])
self
.
assertEqual
(
20
*
20
,
context
[
'
squared
'
])
self
.
assertEqual
(
1
,
context
[
'
x
'
])
self
.
assertEqual
([
1
,
2
,
3
,
4
,
5
,
8
,
9
],
recv
.
lines_received
)
# -------------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------------
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment