mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2025-06-24 11:41:09 +02:00
feat(psi device base): stoppable status objects
Add methods to PSIDeviceBase to register status object that should be cancelled when the device is stopped or destroyed.
This commit is contained in:
@ -64,6 +64,7 @@ class PSIDeviceBase(Device):
|
|||||||
else:
|
else:
|
||||||
super().__init__(prefix=prefix, name=name, **kwargs)
|
super().__init__(prefix=prefix, name=name, **kwargs)
|
||||||
self._stopped = False
|
self._stopped = False
|
||||||
|
self._stoppable_status_objects: list[StatusBase] = []
|
||||||
self.task_handler = TaskHandler(parent=self)
|
self.task_handler = TaskHandler(parent=self)
|
||||||
self.file_utils = FileHandler()
|
self.file_utils = FileHandler()
|
||||||
if scan_info is None:
|
if scan_info is None:
|
||||||
@ -113,6 +114,7 @@ class PSIDeviceBase(Device):
|
|||||||
"""Unstage the device."""
|
"""Unstage the device."""
|
||||||
super_unstage = super().unstage()
|
super_unstage = super().unstage()
|
||||||
status = self.on_unstage() # pylint: disable=assignment-from-no-return
|
status = self.on_unstage() # pylint: disable=assignment-from-no-return
|
||||||
|
self._stop_stoppable_status_objects()
|
||||||
if isinstance(status, StatusBase):
|
if isinstance(status, StatusBase):
|
||||||
return status
|
return status
|
||||||
return super_unstage
|
return super_unstage
|
||||||
@ -154,13 +156,52 @@ class PSIDeviceBase(Device):
|
|||||||
"""
|
"""
|
||||||
self.on_stop()
|
self.on_stop()
|
||||||
self.stopped = True # Set stopped flag to True, in case a custom stop method listens to stopped property
|
self.stopped = True # Set stopped flag to True, in case a custom stop method listens to stopped property
|
||||||
|
# Stop all stoppable status objects
|
||||||
|
self._stop_stoppable_status_objects()
|
||||||
super().stop(success=success)
|
super().stop(success=success)
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
"""Destroy the device."""
|
"""Destroy the device."""
|
||||||
self.on_destroy() # Call the on_destroy method
|
self.on_destroy() # Call the on_destroy method
|
||||||
|
self._stop_stoppable_status_objects()
|
||||||
return super().destroy()
|
return super().destroy()
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# Stoppable Status Objects Management #
|
||||||
|
########################################
|
||||||
|
|
||||||
|
def cancel_on_stop(self, status: StatusBase) -> None:
|
||||||
|
"""
|
||||||
|
Register a status object to be cancelled when the device is stopped.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (StatusBase): The status object to be cancelled.
|
||||||
|
"""
|
||||||
|
if not isinstance(status, StatusBase):
|
||||||
|
raise TypeError("status must be an instance of StatusBase")
|
||||||
|
self._stoppable_status_objects.append(status)
|
||||||
|
|
||||||
|
def _clear_stoppable_status_objects(self) -> None:
|
||||||
|
"""
|
||||||
|
Clear all registered stoppable status objects.
|
||||||
|
|
||||||
|
This is useful to reset the list of status objects that should be cancelled
|
||||||
|
when the device is stopped.
|
||||||
|
"""
|
||||||
|
self._stoppable_status_objects = []
|
||||||
|
|
||||||
|
def _stop_stoppable_status_objects(self) -> None:
|
||||||
|
"""
|
||||||
|
Stop all registered stoppable status objects.
|
||||||
|
|
||||||
|
This method will cancel all status objects that have been registered
|
||||||
|
to be stopped when the device is stopped.
|
||||||
|
"""
|
||||||
|
for status in self._stoppable_status_objects:
|
||||||
|
if not status.done:
|
||||||
|
status.set_exception(DeviceStoppedError(f"Device {self.name} has been stopped"))
|
||||||
|
self._clear_stoppable_status_objects()
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# Utility Method to wait for signals #
|
# Utility Method to wait for signals #
|
||||||
########################################
|
########################################
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""Module for testing the PSIDeviceBase class."""
|
"""Module for testing the PSIDeviceBase class."""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -146,3 +148,31 @@ def test_on_stop_hook(device):
|
|||||||
with mock.patch.object(device, "on_stop") as mock_on_stop:
|
with mock.patch.object(device, "on_stop") as mock_on_stop:
|
||||||
device.stop()
|
device.stop()
|
||||||
mock_on_stop.assert_called_once()
|
mock_on_stop.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_stoppable_status(device):
|
||||||
|
"""Test stoppable status"""
|
||||||
|
status = StatusBase()
|
||||||
|
device.cancel_on_stop(status)
|
||||||
|
device.stop()
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_stoppable_status_not_done(device):
|
||||||
|
"""Test stoppable status not done"""
|
||||||
|
|
||||||
|
def stop_after_delay():
|
||||||
|
time.sleep(5)
|
||||||
|
device.stop()
|
||||||
|
|
||||||
|
status = StatusBase()
|
||||||
|
device.cancel_on_stop(status)
|
||||||
|
thread = threading.Thread(target=stop_after_delay)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
with pytest.raises(DeviceStoppedError, match="Device device has been stopped"):
|
||||||
|
status.wait()
|
||||||
|
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is False
|
||||||
|
Reference in New Issue
Block a user