Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ADSObserver to MSlice #1041

Merged
merged 18 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -36,6 +36,10 @@ def get_visible_workspace_names():
return [key for key in iterkeys(_loaded_workspaces) 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, string_types):
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 @@ -4,13 +4,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 @@ -38,6 +40,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 @@ -228,3 +231,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
Loading