Compare commits

...

6 Commits

Author SHA1 Message Date
semantic-release 8ad0e46d98 3.8.1
Automatically generated by python-semantic-release
2026-05-11 09:36:33 +00:00
wakonig_k 9d92f8b53a fix(web_links): update documentation links in BECWebLinksMixin 2026-05-11 11:35:46 +02:00
semantic-release c1d5069a48 3.8.0
Automatically generated by python-semantic-release
2026-05-01 15:16:03 +00:00
wyzula_j 0b1f0b4c26 fix(dock_area): change to show_dialo=False for CLI profile baseline restore 2026-05-01 17:15:03 +02:00
wyzula_j cc825972c2 fix(dock_area): cli call load_profile has restore_baseline kwarg 2026-05-01 17:15:03 +02:00
wyzula_j 17865a2c33 feat(dock_area): add CLI restore current profile from baseline with optional confirmation dialog 2026-05-01 17:15:03 +02:00
9 changed files with 156 additions and 34 deletions
+24
View File
@@ -1,6 +1,30 @@
# CHANGELOG # CHANGELOG
## v3.8.1 (2026-05-11)
### Bug Fixes
- **web_links**: Update documentation links in BECWebLinksMixin
([`9d92f8b`](https://github.com/bec-project/bec_widgets/commit/9d92f8b53a6ffe57a9dffad797580228023bf6e1))
## v3.8.0 (2026-05-01)
### Bug Fixes
- **dock_area**: Change to show_dialo=False for CLI profile baseline restore
([`0b1f0b4`](https://github.com/bec-project/bec_widgets/commit/0b1f0b4c262ff31469b7114b9f00bf0a7b85e8f2))
- **dock_area**: Cli call load_profile has restore_baseline kwarg
([`cc82597`](https://github.com/bec-project/bec_widgets/commit/cc825972c202cd9ded32f8b2d1ce5f822c2ebdba))
### Features
- **dock_area**: Add CLI restore current profile from baseline with optional confirmation dialog
([`17865a2`](https://github.com/bec-project/bec_widgets/commit/17865a2c338a4a1f944659dde4ec05c25a8dd963))
## v3.7.3 (2026-05-01) ## v3.7.3 (2026-05-01)
### Bug Fixes ### Bug Fixes
+36 -4
View File
@@ -358,15 +358,31 @@ class BECDockArea(RPCBase):
@rpc_timeout(None) @rpc_timeout(None)
@rpc_call @rpc_call
def load_profile(self, name: "str | None" = None): def load_profile(self, name: "str | None" = None, restore_baseline: "bool" = False):
""" """
Load a workspace profile. Load a workspace profile.
Before switching, persist the current profile to the runtime copy. Before switching, persist the current profile to the runtime copy.
Prefer loading the runtime copy; fall back to the baseline copy. Prefer loading the runtime copy; fall back to the baseline copy. When
``restore_baseline`` is True, first overwrite the runtime copy with the
baseline profile and then load it.
Args: Args:
name (str | None): The name of the profile to load. If None, prompts the user. name (str | None): The name of the profile to load. If None, prompts the user.
restore_baseline (bool): If True, restore the runtime copy from the
baseline before loading. Defaults to False.
"""
@rpc_timeout(None)
@rpc_call
def restore_baseline_profile(self, name: "str | None" = None, show_dialog: "bool" = False):
"""
Overwrite the runtime copy of *name* with the baseline.
If *name* is None, target the currently active profile.
Args:
name (str | None): The name of the profile to restore. If None, uses the current profile.
show_dialog (bool): If True, ask for confirmation before restoring.
""" """
@rpc_call @rpc_call
@@ -1366,15 +1382,31 @@ class DockAreaView(RPCBase):
@rpc_timeout(None) @rpc_timeout(None)
@rpc_call @rpc_call
def load_profile(self, name: "str | None" = None): def load_profile(self, name: "str | None" = None, restore_baseline: "bool" = False):
""" """
Load a workspace profile. Load a workspace profile.
Before switching, persist the current profile to the runtime copy. Before switching, persist the current profile to the runtime copy.
Prefer loading the runtime copy; fall back to the baseline copy. Prefer loading the runtime copy; fall back to the baseline copy. When
``restore_baseline`` is True, first overwrite the runtime copy with the
baseline profile and then load it.
Args: Args:
name (str | None): The name of the profile to load. If None, prompts the user. name (str | None): The name of the profile to load. If None, prompts the user.
restore_baseline (bool): If True, restore the runtime copy from the
baseline before loading. Defaults to False.
"""
@rpc_timeout(None)
@rpc_call
def restore_baseline_profile(self, name: "str | None" = None, show_dialog: "bool" = False):
"""
Overwrite the runtime copy of *name* with the baseline.
If *name* is None, target the currently active profile.
Args:
name (str | None): The name of the profile to restore. If None, uses the current profile.
show_dialog (bool): If True, ask for confirmation before restoring.
""" """
@rpc_call @rpc_call
@@ -108,6 +108,7 @@ class BECDockArea(DockAreaWidget):
"list_profiles", "list_profiles",
"save_profile", "save_profile",
"load_profile", "load_profile",
"restore_baseline_profile",
"delete_profile", "delete_profile",
] ]
@@ -819,16 +820,21 @@ class BECDockArea(DockAreaWidget):
@SafeSlot() @SafeSlot()
@SafeSlot(str) @SafeSlot(str)
@SafeSlot(str, bool)
@rpc_timeout(None) @rpc_timeout(None)
def load_profile(self, name: str | None = None): def load_profile(self, name: str | None = None, restore_baseline: bool = False):
""" """
Load a workspace profile. Load a workspace profile.
Before switching, persist the current profile to the runtime copy. Before switching, persist the current profile to the runtime copy.
Prefer loading the runtime copy; fall back to the baseline copy. Prefer loading the runtime copy; fall back to the baseline copy. When
``restore_baseline`` is True, first overwrite the runtime copy with the
baseline profile and then load it.
Args: Args:
name (str | None): The name of the profile to load. If None, prompts the user. name (str | None): The name of the profile to load. If None, prompts the user.
restore_baseline (bool): If True, restore the runtime copy from the
baseline before loading. Defaults to False.
""" """
if name == "": if name == "":
return return
@@ -850,6 +856,9 @@ class BECDockArea(DockAreaWidget):
us_prev = open_runtime_settings(prev_name, namespace=namespace) us_prev = open_runtime_settings(prev_name, namespace=namespace)
self._write_snapshot_to_settings(us_prev, save_preview=True) self._write_snapshot_to_settings(us_prev, save_preview=True)
if restore_baseline:
restore_runtime_from_baseline(name, namespace=namespace)
settings = None settings = None
if any(os.path.exists(path) for path in runtime_profile_candidates(name, namespace)): if any(os.path.exists(path) for path in runtime_profile_candidates(name, namespace)):
settings = open_runtime_settings(name, namespace=namespace) settings = open_runtime_settings(name, namespace=namespace)
@@ -896,30 +905,34 @@ class BECDockArea(DockAreaWidget):
@SafeSlot() @SafeSlot()
@SafeSlot(str) @SafeSlot(str)
def restore_runtime_profile_from_baseline(self, name: str | None = None): @SafeSlot(str, bool)
@rpc_timeout(None)
def restore_baseline_profile(self, name: str | None = None, show_dialog: bool = False):
""" """
Overwrite the runtime copy of *name* with the baseline. Overwrite the runtime copy of *name* with the baseline.
If *name* is None, target the currently active profile. If *name* is None, target the currently active profile.
Args: Args:
name (str | None): The name of the profile to restore. If None, uses the current profile. name (str | None): The name of the profile to restore. If None, uses the current profile.
show_dialog (bool): If True, ask for confirmation before restoring.
""" """
target = name or getattr(self, "_current_profile_name", None) target = name or getattr(self, "_current_profile_name", None)
if not target: if not target:
return return
namespace = self.profile_namespace namespace = self.profile_namespace
current_pixmap = None if show_dialog:
if self.isVisible(): current_pixmap = None
current_pixmap = QPixmap() if self.isVisible():
ba = bytes(self.screenshot_bytes()) current_pixmap = QPixmap()
current_pixmap.loadFromData(ba) ba = bytes(self.screenshot_bytes())
if current_pixmap is None or current_pixmap.isNull(): current_pixmap.loadFromData(ba)
current_pixmap = load_runtime_profile_screenshot(target, namespace=namespace) if current_pixmap is None or current_pixmap.isNull():
baseline_pixmap = load_baseline_profile_screenshot(target, namespace=namespace) current_pixmap = load_runtime_profile_screenshot(target, namespace=namespace)
baseline_pixmap = load_baseline_profile_screenshot(target, namespace=namespace)
if not RestoreProfileDialog.confirm(self, current_pixmap, baseline_pixmap): if not RestoreProfileDialog.confirm(self, current_pixmap, baseline_pixmap):
return return
restore_runtime_from_baseline(target, namespace=namespace) restore_runtime_from_baseline(target, namespace=namespace)
self.delete_all() self.delete_all()
@@ -235,4 +235,4 @@ class WorkspaceConnection(BundleConnection):
""" """
Refreshes the current workspace. Refreshes the current workspace.
""" """
self.target_widget.restore_runtime_profile_from_baseline() self.target_widget.restore_baseline_profile(show_dialog=True)
@@ -4,11 +4,7 @@ import webbrowser
class BECWebLinksMixin: class BECWebLinksMixin:
@staticmethod @staticmethod
def open_bec_docs(): def open_bec_docs():
webbrowser.open("https://beamline-experiment-control.readthedocs.io/en/latest/") webbrowser.open("https://bec-project.github.io/bec_docs/")
@staticmethod
def open_bec_widgets_docs():
webbrowser.open("https://bec.readthedocs.io/projects/bec-widgets/en/latest/")
@staticmethod @staticmethod
def open_bec_bug_report(): def open_bec_bug_report():
@@ -355,17 +355,13 @@ class BECMainWindow(BECWidget, QMainWindow):
bec_docs = QAction("BEC Docs", self) bec_docs = QAction("BEC Docs", self)
bec_docs.setIcon(help_icon) bec_docs.setIcon(help_icon)
widgets_docs = QAction("BEC Widgets Docs", self)
widgets_docs.setIcon(help_icon)
bug_report = QAction("Bug Report", self) bug_report = QAction("Bug Report", self)
bug_report.setIcon(bug_icon) bug_report.setIcon(bug_icon)
bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs) bec_docs.triggered.connect(BECWebLinksMixin.open_bec_docs)
widgets_docs.triggered.connect(BECWebLinksMixin.open_bec_widgets_docs)
bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report) bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report)
help_menu.addAction(bec_docs) help_menu.addAction(bec_docs)
help_menu.addAction(widgets_docs)
help_menu.addAction(bug_report) help_menu.addAction(bug_report)
################################################################################ ################################################################################
+2 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "bec_widgets" name = "bec_widgets"
version = "3.7.3" version = "3.8.1"
description = "BEC Widgets" description = "BEC Widgets"
requires-python = ">=3.11" requires-python = ">=3.11"
classifiers = [ classifiers = [
@@ -58,6 +58,7 @@ dev = [
] ]
qtermwidget = ["pyside6_qtermwidget"] qtermwidget = ["pyside6_qtermwidget"]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
+65 -3
View File
@@ -1421,7 +1421,7 @@ class TestAdvancedDockAreaRestoreAndDialogs:
patch.object(advanced_dock_area, "delete_all") as mock_delete_all, patch.object(advanced_dock_area, "delete_all") as mock_delete_all,
patch.object(advanced_dock_area, "load_profile") as mock_load_profile, patch.object(advanced_dock_area, "load_profile") as mock_load_profile,
): ):
advanced_dock_area.restore_runtime_profile_from_baseline() advanced_dock_area.restore_baseline_profile(show_dialog=True)
assert mock_restore.call_count == 1 assert mock_restore.call_count == 1
args, kwargs = mock_restore.call_args args, kwargs = mock_restore.call_args
@@ -1455,16 +1455,41 @@ class TestAdvancedDockAreaRestoreAndDialogs:
with patch( with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline" "bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
) as mock_restore: ) as mock_restore:
advanced_dock_area.restore_runtime_profile_from_baseline() advanced_dock_area.restore_baseline_profile(show_dialog=True)
mock_restore.assert_not_called() mock_restore.assert_not_called()
def test_restore_runtime_profile_from_baseline_without_dialog(self, advanced_dock_area):
profile_name = "alignment_scan"
helper = profile_helper(advanced_dock_area)
helper.open_baseline(profile_name).sync()
helper.open_runtime(profile_name).sync()
with (
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
) as mock_confirm,
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
) as mock_restore,
patch.object(advanced_dock_area, "delete_all") as mock_delete_all,
patch.object(advanced_dock_area, "load_profile") as mock_load_profile,
):
advanced_dock_area.restore_baseline_profile(profile_name, show_dialog=False)
mock_confirm.assert_not_called()
mock_restore.assert_called_once_with(
profile_name, namespace=advanced_dock_area.profile_namespace
)
mock_delete_all.assert_called_once()
mock_load_profile.assert_called_once_with(profile_name)
def test_restore_runtime_profile_from_baseline_no_target(self, advanced_dock_area, monkeypatch): def test_restore_runtime_profile_from_baseline_no_target(self, advanced_dock_area, monkeypatch):
advanced_dock_area._current_profile_name = None advanced_dock_area._current_profile_name = None
with patch( with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm" "bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
) as mock_confirm: ) as mock_confirm:
advanced_dock_area.restore_runtime_profile_from_baseline() advanced_dock_area.restore_baseline_profile(show_dialog=True)
mock_confirm.assert_not_called() mock_confirm.assert_not_called()
def test_refresh_workspace_list_with_refresh_profiles(self, advanced_dock_area): def test_refresh_workspace_list_with_refresh_profiles(self, advanced_dock_area):
@@ -1862,6 +1887,43 @@ class TestWorkspaceProfileOperations:
widget_map = advanced_dock_area.widget_map() widget_map = advanced_dock_area.widget_map()
assert "test_widget" in widget_map assert "test_widget" in widget_map
def test_load_profile_default_does_not_restore_baseline(self, advanced_dock_area):
"""Regular profile loading should not restore the runtime copy."""
profile_name = "load_without_baseline_restore"
helper = profile_helper(advanced_dock_area)
helper.open_runtime(profile_name).sync()
with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
) as mock_restore:
advanced_dock_area.load_profile(profile_name)
mock_restore.assert_not_called()
assert advanced_dock_area._current_profile_name == profile_name
def test_load_profile_restores_baseline_without_dialog(self, advanced_dock_area):
"""CLI loading can restore the runtime copy from baseline without confirmation."""
profile_name = "alignment_scan"
helper = profile_helper(advanced_dock_area)
helper.open_baseline(profile_name).sync()
helper.open_runtime(profile_name).sync()
with (
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.RestoreProfileDialog.confirm"
) as mock_confirm,
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.restore_runtime_from_baseline"
) as mock_restore,
):
advanced_dock_area.load_profile(profile_name, restore_baseline=True)
mock_confirm.assert_not_called()
mock_restore.assert_called_once_with(
profile_name, namespace=advanced_dock_area.profile_namespace
)
assert advanced_dock_area._current_profile_name == profile_name
def test_load_profile_materializes_runtime_namespace_fallback(self, advanced_dock_area): def test_load_profile_materializes_runtime_namespace_fallback(self, advanced_dock_area):
"""Loading a runtime fallback copies it into the active namespace before opening.""" """Loading a runtime fallback copies it into the active namespace before opening."""
profile_name = "load_runtime_namespace_fallback" profile_name = "load_runtime_namespace_fallback"
+1 -3
View File
@@ -247,12 +247,10 @@ def test_bec_weblinks(monkeypatch):
monkeypatch.setattr(webbrowser, "open", fake_open) monkeypatch.setattr(webbrowser, "open", fake_open)
BECWebLinksMixin.open_bec_docs() BECWebLinksMixin.open_bec_docs()
BECWebLinksMixin.open_bec_widgets_docs()
BECWebLinksMixin.open_bec_bug_report() BECWebLinksMixin.open_bec_bug_report()
assert opened_urls == [ assert opened_urls == [
"https://beamline-experiment-control.readthedocs.io/en/latest/", "https://bec-project.github.io/bec_docs/",
"https://bec.readthedocs.io/projects/bec-widgets/en/latest/",
"https://github.com/bec-project/bec_widgets/issues", "https://github.com/bec-project/bec_widgets/issues",
] ]