Skip to content

Commit

Permalink
Merge branch 'main' into 511_check_undulator_gap_before_collection
Browse files Browse the repository at this point in the history
  • Loading branch information
shihab-dls authored Jan 8, 2025
2 parents 0e9be45 + 79d8aac commit 9bb55eb
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 57 deletions.
10 changes: 9 additions & 1 deletion src/mx_bluesky/common/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from collections.abc import Callable, Generator
from typing import TypeVar

Expand All @@ -22,7 +23,14 @@ class ISPyBDepositionNotMade(Exception):
class SampleException(WarningException):
"""An exception which identifies an issue relating to the sample."""

pass
def __str__(self):
class_name = type(self).__name__
return f"[{class_name}]: {super().__str__()}"

@classmethod
def type_and_message_from_reason(cls, reason: str) -> tuple[str, str]:
match = re.match(r"\[(\S*)?]: (.*)", reason)
return (match.group(1), match.group(2)) if match else (None, None)


T = TypeVar("T")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
RotationScanComposite,
multi_rotation_scan,
)
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
sample_handling_callback_decorator,
)
from mx_bluesky.hyperion.parameters.constants import CONST
from mx_bluesky.hyperion.parameters.load_centre_collect import LoadCentreCollect
from mx_bluesky.hyperion.utils.context import device_composite_from_context
Expand Down Expand Up @@ -62,7 +59,6 @@ def load_centre_collect_full(
"activate_callbacks": ["SampleHandlingCallback"],
}
)
@sample_handling_callback_decorator()
def plan_with_callback_subs():
flyscan_event_handler = XRayCentreEventHandler()
yield from subs_wrapper(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import dataclasses
from collections.abc import Generator
from functools import partial
from typing import Any
from event_model import RunStart, RunStop

import bluesky.plan_stubs as bps
from bluesky.preprocessors import contingency_wrapper
from bluesky.utils import Msg, make_decorator
from event_model import Event, EventDescriptor, RunStart

from mx_bluesky.common.external_interaction.callbacks.common.abstract_event import (
AbstractEvent,
)
from mx_bluesky.common.external_interaction.callbacks.common.plan_reactive_callback import (
PlanReactiveCallback,
)
Expand All @@ -20,26 +9,6 @@
)
from mx_bluesky.common.utils.exceptions import CrystalNotFoundException, SampleException
from mx_bluesky.common.utils.log import ISPYB_ZOCALO_CALLBACK_LOGGER
from mx_bluesky.hyperion.parameters.constants import CONST

# TODO remove this event-raising shenanigans once
# https://github.com/bluesky/bluesky/issues/1829 is addressed


@dataclasses.dataclass(frozen=True)
class _ExceptionEvent(AbstractEvent):
exception_type: str


def _exception_interceptor(exception: Exception) -> Generator[Msg, Any, Any]:
yield from bps.create(CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION)
yield from bps.read(_ExceptionEvent(type(exception).__name__))
yield from bps.save()


sample_handling_callback_decorator = make_decorator(
partial(contingency_wrapper, except_plan=_exception_interceptor)
)


class SampleHandlingCallback(PlanReactiveCallback):
Expand All @@ -57,16 +26,13 @@ def activity_gated_start(self, doc: RunStart):
self.log.info(f"Recording sample ID at run start {sample_id}")
self._sample_id = sample_id

def activity_gated_descriptor(self, doc: EventDescriptor) -> EventDescriptor | None:
if doc.get("name") == CONST.DESCRIPTORS.SAMPLE_HANDLING_EXCEPTION:
self._descriptor = doc["uid"]
return super().activity_gated_descriptor(doc)

def activity_gated_event(self, doc: Event) -> Event | None:
if doc["descriptor"] == self._descriptor:
exception_type = doc["data"]["exception_type"]
def activity_gated_stop(self, doc: RunStop) -> RunStop:
if doc["exit_status"] != "success":
exception_type, message = SampleException.type_and_message_from_reason(
doc.get("reason", "")
)
self.log.info(
f"Sample handling callback intercepted exception of type {exception_type}"
f"Sample handling callback intercepted exception of type {exception_type}: {message}"
)
self._record_exception(exception_type)
return doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
)
from mx_bluesky.hyperion.external_interaction.callbacks.sample_handling.sample_handling_callback import (
SampleHandlingCallback,
sample_handling_callback_decorator,
)

TEST_SAMPLE_ID = 123456
Expand All @@ -23,10 +22,9 @@
"activate_callbacks": ["SampleHandlingCallback"],
}
)
@sample_handling_callback_decorator()
def plan_with_general_exception(exception_type: type):
def plan_with_general_exception(exception_type: type, msg: str):
yield from []
raise exception_type("Test failure")
raise exception_type(msg)


@run_decorator(
Expand All @@ -35,21 +33,24 @@ def plan_with_general_exception(exception_type: type):
"activate_callbacks": ["SampleHandlingCallback"],
}
)
@sample_handling_callback_decorator()
def plan_with_normal_completion():
yield from []


@pytest.mark.parametrize(
"exception_type, expected_sample_status",
"exception_type, expected_sample_status, message",
[
[AssertionError, BLSampleStatus.ERROR_BEAMLINE],
[SampleException, BLSampleStatus.ERROR_SAMPLE],
[CrystalNotFoundException, BLSampleStatus.ERROR_SAMPLE],
[AssertionError, BLSampleStatus.ERROR_BEAMLINE, "Test failure"],
[SampleException, BLSampleStatus.ERROR_SAMPLE, "Test failure"],
[CrystalNotFoundException, BLSampleStatus.ERROR_SAMPLE, "Test failure"],
[AssertionError, BLSampleStatus.ERROR_BEAMLINE, None],
],
)
def test_sample_handling_callback_intercepts_general_exception(
RE: RunEngine, exception_type: type, expected_sample_status: BLSampleStatus
RE: RunEngine,
exception_type: type,
expected_sample_status: BLSampleStatus,
message: str,
):
callback = SampleHandlingCallback()
RE.subscribe(callback)
Expand All @@ -63,7 +64,7 @@ def test_sample_handling_callback_intercepts_general_exception(
),
pytest.raises(exception_type),
):
RE(plan_with_general_exception(exception_type))
RE(plan_with_general_exception(exception_type, message))
mock_expeye.update_sample_status.assert_called_once_with(
TEST_SAMPLE_ID, expected_sample_status
)
Expand Down

0 comments on commit 9bb55eb

Please sign in to comment.