Skip to content

Commit

Permalink
Merge pull request #1041 from mantidproject/839_add_ads_observer
Browse files Browse the repository at this point in the history
Added ADSObserver to MSlice
  • Loading branch information
SilkeSchomann authored Dec 13, 2024
2 parents d56a367 + 62d18fd commit b54db7f
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 12 deletions.
59 changes: 59 additions & 0 deletions src/mslice/models/mslice_ads_observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from functools import wraps
import sys

from mantid.api import AnalysisDataServiceObserver


def _catch_exceptions(func):
"""
Catch all exceptions in method and print a traceback to stderr
"""

@wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception:
sys.stderr.write("Error occurred in handler:\n")
import traceback

traceback.print_exc()

return wrapper


class MSliceADSObserver(AnalysisDataServiceObserver):
def __init__(self, delete_callback, clear_callback, rename_callback):
super(MSliceADSObserver, self).__init__()
self.delete_callback = delete_callback
self.clear_callback = clear_callback
self.rename_callback = rename_callback

self.observeDelete(True)
self.observeRename(True)
self.observeClear(True)

@_catch_exceptions
def deleteHandle(self, workspace_name, workspace):
"""
Called when the ADS deletes a workspace, removes it from the dict of tracked workspaces.
:param workspace_name: name of the workspace
:param workspace: reference to the workspace (not used)
"""
self.delete_callback(workspace_name)

@_catch_exceptions
def renameHandle(self, old_workspace_name, new_workspace_name):
"""
Called when the ADS renames a workspace, updates the dict with the new name.
:param old_workspace_name: original name of the workspace
:param new_workspace_name: new name for the workspace
"""
self.rename_callback(old_workspace_name, new_workspace_name)

@_catch_exceptions
def clearHandle(self):
"""
Called when the ADS has been cleared, removes all data.
"""
self.clear_callback()
4 changes: 4 additions & 0 deletions src/mslice/models/workspacemanager/workspace_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def get_visible_workspace_names():
return [key for key in _loaded_workspaces.keys() if key[:2] != '__']


def get_workspace_names():
return [key for key in _loaded_workspaces.keys()]


def get_workspace_name(workspace):
"""Returns the name of a workspace given the workspace handle"""
if isinstance(workspace, str):
Expand Down
7 changes: 4 additions & 3 deletions src/mslice/plotting/plot_window/interactive_cut.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def flip_axis(self):
self.plot_cut(*self.rect.extents)

def window_closing(self):
self.slice_plot.toggle_interactive_cuts()
self.slice_plot.toggle_interactive_cuts(False)
self.slice_plot.plot_window.action_interactive_cuts.setChecked(False)

def refresh_rect_selector(self, ax):
Expand All @@ -125,8 +125,9 @@ def refresh_rect_selector(self, ax):
self.rect.extents = extents
self.slice_plot.set_cross_cursor()

def store_icut_cut_upon_toggle_and_reset(self):
self._cut_plotter_presenter.store_icut_cut()
def store_icut_cut_upon_toggle_and_reset(self, store=True):
if store:
self._cut_plotter_presenter.store_icut_cut()
self._cut_plotter_presenter.set_icut_cut(None)

def set_icut_intensity_category(self, intensity_type):
Expand Down
8 changes: 4 additions & 4 deletions src/mslice/plotting/plot_window/slice_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,11 +403,11 @@ def _update_lines(self):
self.update_legend()
self._canvas.draw()

def toggle_interactive_cuts(self):
self.toggle_icut_button()
def toggle_interactive_cuts(self, store=True):
self.toggle_icut_button(store)
self.toggle_icut()

def toggle_icut_button(self):
def toggle_icut_button(self, store=True):
if not self.icut:
self.manager.picking_connected(False)
if self.plot_window.action_zoom_in.isChecked():
Expand All @@ -428,7 +428,7 @@ def toggle_icut_button(self):
self.plot_window.action_flip_axis.setVisible(False)
self._canvas.setCursor(Qt.ArrowCursor)
self.icut.set_icut_intensity_category(self.intensity_type)
self.icut.store_icut_cut_upon_toggle_and_reset()
self.icut.store_icut_cut_upon_toggle_and_reset(store)
self.plot_window.menu_intensity.setDisabled(False)

def toggle_icut(self):
Expand Down
21 changes: 20 additions & 1 deletion src/mslice/presenters/workspace_manager_presenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
from .busy import show_busy
from mslice.widgets.workspacemanager.command import Command
from mslice.widgets.workspacemanager import TAB_2D, TAB_NONPSD
from mslice.models.mslice_ads_observer import MSliceADSObserver
from mslice.models.workspacemanager.file_io import get_save_directory
from mslice.models.workspacemanager.workspace_algorithms import (save_workspaces, export_workspace_to_ads, subtract,
is_pixel_workspace, combine_workspace,
add_workspace_runs, scale_workspaces,
remove_workspace_from_ads)
from mslice.models.workspacemanager.workspace_provider import (get_workspace_handle, get_visible_workspace_names,
get_workspace_name, delete_workspace, rename_workspace)
get_workspace_names, get_workspace_name,
delete_workspace, rename_workspace)
from .interfaces.workspace_manager_presenter import WorkspaceManagerPresenterInterface
from .interfaces.main_presenter import MainPresenterInterface
from .validation_decorators import require_main_presenter
Expand Down Expand Up @@ -37,6 +39,7 @@ def __init__(self, workspace_view):
lambda: self._workspace_manager_view._display_error('Please select a Compose action from the dropdown menu'),
Command.Scale: self._scale_workspace,
Command.Bose: lambda: self._scale_workspace(is_bose=True)}
self._ads_observer = MSliceADSObserver(self.delete_handle, self.clear_handle, self.rename_handle)

def register_master(self, main_presenter):
assert (isinstance(main_presenter, MainPresenterInterface))
Expand Down Expand Up @@ -227,3 +230,19 @@ def update_displayed_workspaces(self):

def _clear_displayed_error(self):
self._workspace_manager_view.clear_displayed_error()

def delete_handle(self, workspace):
delete_workspace(workspace)
self.update_displayed_workspaces()

def clear_handle(self):
for workspace in get_workspace_names():
delete_workspace(workspace)
self.update_displayed_workspaces()

def rename_handle(self, workspace, new_name):
if new_name is None:
return
if workspace in get_visible_workspace_names():
rename_workspace(workspace, new_name)
self.update_displayed_workspaces()
5 changes: 4 additions & 1 deletion src/mslice/widgets/workspacemanager/workspacemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,15 @@ def add_workspace(self, workspace):
raise TypeError("Loaded file is not a valid workspace")

def display_loaded_workspaces(self, workspaces):
to_be_removed = []
for workspace in workspaces:
if workspace not in self.onscreen_workspaces:
self.add_workspace(workspace)
for workspace in self.onscreen_workspaces:
if workspace not in workspaces:
self.remove_workspace(workspace)
to_be_removed.append(workspace)
for workspace in to_be_removed:
self.remove_workspace(workspace)

def remove_workspace(self, workspace):
"""Remove workspace from list.
Expand Down
56 changes: 56 additions & 0 deletions tests/workspacemanager_presenter_ads_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import (absolute_import, division, print_function)
import unittest

from mock import MagicMock

from mantid.api import AnalysisDataService
from mantid.simpleapi import CreateSampleWorkspace, RenameWorkspace

from mslice.models.mslice_ads_observer import MSliceADSObserver
from mslice.presenters.workspace_manager_presenter import WorkspaceManagerPresenter


class WorkspaceManagerPresenterTest(unittest.TestCase):

def test_ensure_that_the_ads_observer_calls_delete_handle(self):
presenter = WorkspaceManagerPresenter(MagicMock())
presenter.delete_handle = MagicMock()
self.assertTrue(isinstance(presenter._ads_observer, MSliceADSObserver))
presenter._ads_observer = MSliceADSObserver(
presenter.delete_handle, presenter.clear_handle, presenter.rename_handle
)

CreateSampleWorkspace(OutputWorkspace="ws", StoreInADS=True)
AnalysisDataService.remove("ws")

presenter.delete_handle.assert_called_once_with("ws")

def test_ensure_that_the_ads_observer_calls_rename_handle(self):
presenter = WorkspaceManagerPresenter(MagicMock())
presenter.rename_handle = MagicMock()
self.assertTrue(isinstance(presenter._ads_observer, MSliceADSObserver))
presenter._ads_observer = MSliceADSObserver(
presenter.delete_handle, presenter.clear_handle, presenter.rename_handle
)

CreateSampleWorkspace(OutputWorkspace="ws", StoreInADS=True)
RenameWorkspace(InputWorkspace="ws", OutputWorkspace="ws1")

presenter.rename_handle.assert_called_once_with("ws", "ws1")

def test_ensure_that_the_ads_observer_calls_clear_handle(self):
presenter = WorkspaceManagerPresenter(MagicMock())
presenter.clear_handle = MagicMock()
self.assertTrue(isinstance(presenter._ads_observer, MSliceADSObserver))
presenter._ads_observer = MSliceADSObserver(
presenter.delete_handle, presenter.clear_handle, presenter.rename_handle
)

CreateSampleWorkspace(OutputWorkspace="ws", StoreInADS=True)
AnalysisDataService.clear(True)

presenter.clear_handle.assert_called_once()


if __name__ == '__main__':
unittest.main()
7 changes: 4 additions & 3 deletions tests/workspacemanager_presenter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def setUp(self):
self.mainview = mock.create_autospec(MainView)
self.main_presenter = mock.create_autospec(MainPresenterInterface)
self.mainview.get_presenter = mock.Mock(return_value=self.main_presenter)
self._ads_observer = mock.Mock()

def test_register_master_success(self):
workspace_presenter = WorkspaceManagerPresenter(self.view)
Expand Down Expand Up @@ -168,12 +169,12 @@ def test_remove_workspace(self, delete_ws_mock):
# Create a workspace that reports a single selected workspace on calls to get_workspace_selected
workspace_to_be_removed = CloneWorkspace(self.m_workspace.raw_ws, OutputWorkspace='file1')
self.view.get_workspace_selected = mock.Mock(return_value=[workspace_to_be_removed])
self.view.display_loaded_workspaces = mock.Mock()

self.presenter.notify(Command.RemoveSelectedWorkspaces)
self.view.get_workspace_selected.assert_called_once_with()
delete_ws_mock.assert_called_once_with(workspace_to_be_removed)
self.view.display_loaded_workspaces.assert_called_once()
delete_calls = [call(workspace_to_be_removed)]
delete_ws_mock.assert_has_calls(delete_calls)
self.assertTrue(self.view.display_loaded_workspaces.called)

@patch('mslice.presenters.workspace_manager_presenter.delete_workspace')
def test_remove_multiple_workspaces(self, delete_ws_mock):
Expand Down

0 comments on commit b54db7f

Please sign in to comment.