Source code for daq_queuing_service.plugins.converter
import importlib
from blueapi.service.model import TaskRequest
from blueapi.worker.event import TaskError, TaskResult
from daq_queuing_service.external_interaction.blueapi.blueapi_call import (
BlueapiCall,
BlueapiCallResponse,
)
from daq_queuing_service.task_queue.task import (
Experiment,
Task,
TaskWithPosition,
)
class ConverterError(Exception):
def __init__(self, original: Exception):
super().__init__(f"{type(original).__name__}: {original}")
self.original = original
class ValidateError(Exception): ...
[docs]
class Converter:
def __init__(self): ...
[docs]
def validate(self, experiments: list[TaskRequest | Experiment]) -> None:
"""This gets run on new experiments added to the queue through the api endpoint.
Any error raised will be handled and cause the experiments to not be added to
the queue.
Args:
experiments (list[TaskRequest | Experiment]): List of experiments or plans
added to the queue.
Raises:
Exception: Any exception raised due to experiments failing validation
"""
...
[docs]
def pre_process(
self,
current_task: TaskWithPosition | None,
queue: list[Task],
history: list[TaskWithPosition],
call_history: list[BlueapiCallResponse],
) -> list[Task]:
"""This gets run whenever something changes in the queue, before
'construct_blueapi_calls'. It is an opportunity to automatically modify the
queue based on it's current state and history.
For example, it could be used to add calibration scans before any experiments
that require them. It could also be used to query another service such as ulims
and add experiments to the queue.
If not overridden, this method returns the queue as is with no modification.
Args:
current_task (TaskWithPosition | None): The current running task, if one
exists.
queue (list[Task]): List of tasks currently in the queue
history (list[TaskWithPosition]): List of completed tasks
call_history (list[BlueapiCallResponse]): List of completed blueapi calls
Returns:
list[Task]: The new list of tasks in the queue
"""
return queue
[docs]
def construct_blueapi_calls(
self,
queue: list[TaskWithPosition],
history: list[TaskWithPosition],
call_history: list[BlueapiCallResponse],
) -> list[BlueapiCall]:
"""Converts the list of queued tasks into a list of blueapi calls. This is
needed if the queue contains ulims experiments, as these must be mapped onto
bluesky plans. If the queue contains TaskRequests, they can just be
wrapped into BlueapiCall objects with no conversion, as this default
implementation does.
Args:
queue (list[TaskWithPosition]): List of tasks in the queue
history (list[TaskWithPosition]): List of completed tasks
call_history (list[BlueapiCallResponse]): List of completed blueapi calls
Returns:
list[BlueapiCall]: List of blueapi calls to execute.
"""
call_list = [
BlueapiCall(
parent_task_id=task.id,
task_request=self._construct_blueapi_task_request(task.experiment),
)
for task in queue
]
return call_list
[docs]
def pause_on_complete_blueapi_call(
self, call: BlueapiCall, result: TaskResult
) -> bool:
"""Decide whether to pause the queue after a blueapi call completes successfully
Args:
call (BlueapiCall): The completed call
result (TaskResult): The result of the call from blueapi
Returns:
bool: True to pause the queue, False to leave it running.
"""
return False
[docs]
def pause_on_fail_blueapi_call(
self, call: BlueapiCall, errors: list[str | TaskError] | None
) -> bool:
"""Decide whether to pause the queue after a blueapi call fails
Args:
call (BlueapiCall): The failed call
errors (list[str | TaskError] | None): The errors that occurred
Returns:
bool: True to pause the queue, False to leave it running.
"""
return True
def _construct_blueapi_task_request(
self,
experiment: Experiment | TaskRequest,
) -> TaskRequest:
match experiment:
case TaskRequest():
return experiment
case Experiment() as experiment:
raise NotImplementedError(
f"No conversion implemented for {type(experiment)}. "
+ "Try using a different converter"
)
def get_converter(path: str, name: str) -> Converter:
"""Instantiates a converter based on a path and class name
Args:
path (str): Path to converter class. For example:
"daq_queuing_service.plugins.converter"
converter_name (str): Name of the converter class. For example:
"Converter"
Returns:
Converter: Converter instance
"""
module = importlib.import_module(path)
converter_cls = getattr(module, name)
converter = converter_cls()
if not isinstance(converter, Converter):
raise TypeError(f"Converter is not of type Converter, it is {type(converter)}")
return converter