Repository: bluesky/bluesky (python)

Issue:
bps.wait move_on kwarg is undocumented
This behaviour, added as part of #1641, is undocumented. According to @coretl it is to enable a `wait` that times out to not be an exception. In my opinion, it's not the best name as "wait" and "move on" are antonyms. It should be documented or self-documenting what the argument does.
If the kwarg name was amended to "allow_timeout" or similar, it might be clearer what is meant.

https://github.com/bluesky/bluesky/blob/3bce96b85558993499174be7eebcd09f270a9271/src/bluesky/plan_stubs.py#L636

Pull request description:
1824 bpswait move on kwarg is undocumented
<!--- Provide a general summary of your changes in the Title above -->

## Description

This change updates the bps.wait plan stub by replacing the `move_on=False` argument with `error_on_timeout=True`. This aligns the parameter with its intended behaviour and makes the functionality clearer and more intuitive for users.

## Motivation and Context
The move_on keyword argument in the bps.wait plan stub was undocumented, leading to confusion about its purpose and usage. By renaming it to error_on_timeout and clarifying its behaviour, this update improves the usability and discoverability of this feature. close #1824 

## How Has This Been Tested?
The changes were tested using the `ophyd-async` branch from PR [#691](https://github.com/bluesky/ophyd-async/pull/691) in a Linux environment with Python 3.11.3 and the provided dependency stack. The fly_and_collect plan was executed via a `RunEngine`, incorporating devices like `HDFPanda` and `AravisDetector`. The plan staged devices, ran a collection process, and produced documents, which were verified to match expected behaviour without errors. Compatibility with `bluesky` and `ophyd_async` was confirmed.
Testing Environment
Python Version: Python 3.11.3 (packaged by conda-forge) ,GCC 11.3.0
Operating System: Linux
Package Dependencies:
    
```
accessible-pygments==0.0.5
aioca==1.8.1
alabaster==0.7.16
annotated-types==0.7.0
anyio==4.6.2.post1
asttokens==3.0.0
attrs==24.2.0
autodoc_pydantic==2.2.0
babel==2.16.0
beautifulsoup4==4.12.3
bluesky @ git+https://github.com/bluesky/bluesky@7132526b86741acaa80173a703ea84d1a04040b8
certifi==2024.8.30
cfgv==3.4.0
charset-normalizer==3.4.0
click==8.1.7
colorama==0.4.6
colorlog==6.9.0
comm==0.2.2
compress-pickle==2.1.0
contourpy==1.3.1
coverage==7.6.8
cycler==0.12.1
decorator==5.1.1
Deprecated==1.2.15
distlib==0.3.9
docutils==0.21.2
epicscorelibs==7.0.7.99.1.1
event-model==1.22.1
executing==2.1.0
filelock==3.16.1
flexcache==0.3
flexparser==0.4
fonttools==4.55.1
grimp==3.5
h11==0.14.0
h5py==3.12.1
HeapDict==1.0.1
historydict==1.2.6
identify==2.6.3
idna==3.10
imagesize==1.4.1
import-linter==2.1
importlib_metadata==8.5.0
importlib_resources==6.4.5
inflection==0.5.1
iniconfig==2.0.0
ipython==8.30.0
ipywidgets==8.1.5
jedi==0.19.2
Jinja2==3.1.4
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyterlab_widgets==3.0.13
kiwisolver==1.4.7
lz4==4.3.3
markdown-it-py==3.0.0
MarkupSafe==3.0.2
matplotlib==3.9.3
matplotlib-inline==0.1.7
mdit-py-plugins==0.4.2
mdurl==0.1.2
msgpack==1.1.0
msgpack-numpy==0.4.8
myst-parser==4.0.0
networkx==3.4.2
nodeenv==1.9.1
nose2==0.15.1
numpy==2.1.3
numpydoc==1.8.0
opentelemetry-api==1.28.2
ophyd==1.10.0
-e git+ssh://git@github.com/bluesky/ophyd-async.git@6b3d95a9ce82e2f4682bf9fc0b189843a2d02200#egg=ophyd_async
p4p==4.2.0
packaging==24.2
parso==0.8.4
pathlib2==2.3.7.post1
pexpect==4.9.0
pickleshare==0.7.5
pillow==11.0.0
Pint==0.24.4
pipdeptree==2.24.0
platformdirs==4.3.6
pluggy==1.5.0
ply==3.11
pre_commit==4.0.1
prompt_toolkit==3.0.48
psutil==6.1.0
ptyprocess==0.7.0
pure_eval==0.2.3
pvxslibs==1.3.2
py==1.11.0
pydantic==2.10.3
pydantic-settings==2.6.1
pydantic_core==2.27.1
pydantic_numpy==7.0.0
pydata-sphinx-theme==0.16.0
pyepics==3.5.7
Pygments==2.18.0
pyparsing==3.2.0
pyright==1.1.390
PySide6==6.7.0
PySide6_Addons==6.7.0
PySide6_Essentials==6.7.0
pytango==10.0.0
pytest==8.3.4
pytest-asyncio==0.24.0
pytest-cov==6.0.0
pytest-faulthandler==2.0.1
pytest-forked==1.6.0
pytest-rerunfailures==15.0
pytest-timeout==2.3.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
PyYAML==6.0.2
referencing==0.35.1
requests==2.32.3
rpds-py==0.22.1
ruamel.yaml==0.18.6
ruamel.yaml.clib==0.2.12
ruff==0.8.1
semver==3.0.2
setuptools-dso==2.11
shiboken6==6.7.0
six==1.16.0
sniffio==1.3.1
snowballstemmer==2.2.0
soupsieve==2.6
Sphinx==7.3.7
sphinx-autobuild==2024.10.3
sphinx-copybutton==0.5.2
sphinx_design==0.6.1
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-mermaid==1.0.0
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
stack-data==0.6.3
starlette==0.41.3
super-state-machine==2.0.2
tabulate==0.9.0
toolz==1.0.0
tox==3.28.0
tox-direct==0.4
tqdm==4.67.1
traitlets==5.14.3
types-mock==5.1.0.20240425
types-PyYAML==6.0.12.20240917
typing_extensions==4.12.2
urllib3==2.2.3
uvicorn==0.32.1
virtualenv==20.28.0
watchfiles==1.0.0
wcwidth==0.2.13
websockets==14.1
widgetsnbextension==4.0.13
wrapt==1.17.0
zict==2.2.0
zipp==3.21.0
```

Plan used to test the functionality:- 
```
from pathlib import Path
from pprint import pprint

import bluesky.preprocessors as bpp
from bluesky.plan_stubs import close_run, open_run, prepare, stage_all, unstage_all
from bluesky.run_engine import RunEngine
import ophyd_async.plan_stubs as ops
from ophyd_async.core import (
    DeviceCollector,
    StandardFlyer,
    StaticPathProvider,
)
from ophyd_async.core._providers import StaticFilenameProvider
from ophyd_async.epics.adaravis import AravisDetector
from ophyd_async.epics.motor import Motor
from ophyd_async.fastcs.panda import (
    HDFPanda,
    StaticSeqTableTriggerLogic,
)
from ophyd_async.plan_stubs import fly_and_collect
from ophyd_async.plan_stubs._fly import (
    prepare_static_seq_table_flyer_and_detectors_with_same_trigger,
)

RE = RunEngine()
fp = StaticFilenameProvider("ophyd_async_tests")
path_provider = StaticPathProvider(fp, Path("/data"))

with DeviceCollector():
    panda = HDFPanda(
        "BL46P-MO-PANDA-01:",
        path_provider=path_provider,
    )

    diff = AravisDetector(
        "BL46P-EA-DET-01:",
        path_provider=path_provider,
        drv_suffix="DET:",
        hdf_suffix="HDF5:",
    )
    prefix = "BL46P-MO-MAP-01:STAGE:"
    motor_x = Motor(prefix+"X")
    motor_a = Motor(prefix+"A")

trigger_logic = StaticSeqTableTriggerLogic(panda.seq[1])
flyer = StandardFlyer(
    trigger_logic,
    name="flyer",
)


def plan():
    @bpp.stage_decorator(devices=[diff, panda, flyer])
    @bpp.run_decorator()
    def inner():
        yield from prepare_static_seq_table_flyer_and_detectors_with_same_trigger(
            flyer, [diff], number_of_frames=15, exposure=0.1, shutter_time=0.05
        )
        yield from fly_and_collect(
            stream_name="primary",
            flyer=flyer,
            detectors=[diff],
        )

    yield from inner()



RE(plan(), lambda name, doc: pprint({"name": name, "doc": doc}))
```

Output from the plan:- 
```
{
    "doc": {
        "plan_name": "plan",
        "plan_type": "generator",
        "scan_id": 1,
        "time": 1733325369.2203283,
        "uid": "21a54e55-51fb-4467-b3b9-076bb66a10bf",
        "versions": {
            "bluesky": "1.14.dev63+g7132526b",
            "ophyd": "1.10.0"
        }
    },
    "name": "start"
}
{
    "doc": {
        "configuration": {
            "diff": {
                "data": {
                    "diff-drv-acquire_time": 0.099988
                },
                "data_keys": {
                    "diff-drv-acquire_time": {
                        "dtype": "number",
                        "dtype_numpy": "<f8",
                        "limits": {
                            "control": {
                                "high": 0.0,
                                "low": 0.0
                            },
                            "display": {
                                "high": 0.0,
                                "low": 0.0
                            }
                        },
                        "precision": 3,
                        "shape": [],
                        "source": "ca: //BL46P-EA-DET-01:DET:AcquireTime_RBV",
                        "units": ""
                    }
                },
                "timestamps": {
                    "diff-drv-acquire_time": 1733325369.258608
                }
            }
        },
        "data_keys": {
            "diff": {
                "dtype": "array",
                "dtype_numpy": "|u1",
                "external": "STREAM:",
                "object_name": "diff",
                "shape": [
                    1216,
                    1936
                ],
                "source": "ca: //BL46P-EA-DET-01:HDF5:FullFileName_RBV"
            }
        },
        "hints": {
            "diff": {
                "fields": [
                    "diff"
                ]
            }
        },
        "name": "primary",
        "object_keys": {
            "diff": [
                "diff"
            ]
        },
        "run_start": "21a54e55-51fb-4467-b3b9-076bb66a10bf",
        "time": 1733325371.1590497,
        "uid": "fc5a56d6-3e3e-4700-b5c5-d1c19055097f"
    },
    "name": "descriptor"
}
{
    "doc": {
        "data_key": "diff",
        "mimetype": "application/x-hdf5",
        "parameters": {
            "chunk_shape": (1,
            1216,
            1936),
            "dataset": "/entry/data/data",
            "multiplier": 1,
            "swmr": False
        },
        "run_start": "21a54e55-51fb-4467-b3b9-076bb66a10bf",
        "uid": "aa3f79d3-4f5e-418a-8b92-1806ecfc6f0e",
        "uri": "file: //localhost/data/ophyd_async_tests.h5"
    },
    "name": "stream_resource"
}
{
    "doc": {
        "descriptor": "fc5a56d6-3e3e-4700-b5c5-d1c19055097f",
        "indices": {
            "start": 0,
            "stop": 15
        },
        "seq_nums": {
            "start": 1,
            "stop": 16
        },
        "stream_resource": "aa3f79d3-4f5e-418a-8b92-1806ecfc6f0e",
        "uid": "aa3f79d3-4f5e-418a-8b92-1806ecfc6f0e/0"
    },
    "name": "stream_datum"
}
{
    "doc": {
        "exit_status": "success",
        "num_events": {
            "primary": 15
        },
        "reason": "",
        "run_start": "21a54e55-51fb-4467-b3b9-076bb66a10bf",
        "time": 1733325372.8079674,
        "uid": "bc148453-3bc8-452b-b2a5-e1a89deec739"
    },
    "name": "stop"
}
```




Patch:
diff --git a/src/bluesky/plan_stubs.py b/src/bluesky/plan_stubs.py
index 68336738..71b1a073 100644
--- a/src/bluesky/plan_stubs.py
+++ b/src/bluesky/plan_stubs.py
@@ -626,7 +626,7 @@ def sleep(time: float) -> MsgGenerator:
 
 
 @plan
-def wait(group: Optional[Hashable] = None, *, timeout: Optional[float] = None, move_on: bool = False):
+def wait(group: Optional[Hashable] = None, *, timeout: Optional[float] = None, error_on_timeout: bool = True):
     """
     Wait for all statuses in a group to report being finished.
 
@@ -634,13 +634,20 @@ def wait(group: Optional[Hashable] = None, *, timeout: Optional[float] = None, m
     ----------
     group : string (or any hashable object), optional
         Identifier given to `abs_set`, `rel_set`, `trigger`; None by default
-
+    timeout : float, optional
+        The maximum duration, in seconds, to wait for all objects in the group to complete.
+        If the timeout expires and `error_on_timeout` is set to True, a TimeoutError is raised.
+
+    error_on_timeout : bool, Defaults to True
+        Specifies the behavior when the timeout is reached:
+        - If True, a TimeoutError is raised if the operations do not complete within the specified timeout.
+        - If False, the method returns once all objects are done.
     Yields
     ------
     msg : Msg
-        Msg('wait', None, group=group, move_on=move_on, timeout=timeout)
+        Msg('wait', None, group=group, error_on_timeout=error_on_timeout, timeout=timeout)
     """
-    return (yield Msg("wait", None, group=group, move_on=move_on, timeout=timeout))
+    return (yield Msg("wait", None, group=group, error_on_timeout=error_on_timeout, timeout=timeout))
 
 
 _wait = wait  # for internal references to avoid collision with 'wait' kwarg
@@ -1026,7 +1033,7 @@ def collect_while_completing(flyers, dets, flush_period=None, stream_name=None):
     yield from complete_all(*flyers, group=group, wait=False)
     done = False
     while not done:
-        done = yield from wait(group=group, timeout=flush_period, move_on=True)
+        done = yield from wait(group=group, timeout=flush_period, error_on_timeout=False)
         yield from collect(*dets, name=stream_name)
 
 
diff --git a/src/bluesky/run_engine.py b/src/bluesky/run_engine.py
index 767b40d2..bad742ec 100644
--- a/src/bluesky/run_engine.py
+++ b/src/bluesky/run_engine.py
@@ -2321,23 +2321,23 @@ class RunEngine:
         """Block progress until every object that was triggered or set
         with the keyword argument `group=<GROUP>` is done. Returns a boolean that is
         true when all triggered objects are done. When the keyword argument
-        `move_on=<MOVE_ON>` is true, this method can return before all objects are done
+        `error_on_timeout=<error_on_timeout>` is false, this method can return before all objects are done
         after a flush period given by the `timeout=<TIMEOUT>` keyword argument.
 
         Expected message object is:
 
-            Msg('wait', group=<GROUP>, move_on=<MOVE_ON>)
+            Msg('wait', group=<GROUP>, error_on_timeout=<ERROR_ON_TIMEOUT>)
 
-        where ``<GROUP>`` is any hashable key and ``<MOVE_ON>`` is a boolean.
+        where ``<GROUP>`` is any hashable key and ``<ERROR_ON_TIMEOUT>`` is a boolean.
         """
         _set_span_msg_attributes(trace.get_current_span(), msg)
         done = False  # boolean that tracks whether waiting is complete
         if msg.args:
             (group,) = msg.args
-            move_on = False
+            error_on_timeout = True
         else:
             group = msg.kwargs["group"]
-            move_on = msg.kwargs.get("move_on", False)
+            error_on_timeout = msg.kwargs.get("error_on_timeout", True)
         if group:
             trace.get_current_span().set_attribute("group", group)
         else:
@@ -2346,11 +2346,11 @@ class RunEngine:
         if futs:
             status_objs = self._status_objs.pop(group)
             try:
-                if move_on:
+                if not error_on_timeout:
                     if group not in self._seen_wait_and_move_on_keys:
                         self._seen_wait_and_move_on_keys.add(group)
                         self._call_waiting_hook(status_objs)
-                else:  # if move_on False
+                else:  # if error_on_timeout False
                     # Notify the waiting_hook function that the RunEngine is
                     # waiting for these status_objs to complete. Users can use
                     # the information these encapsulate to create a progress
@@ -2361,10 +2361,10 @@ class RunEngine:
                 # We might wait to call wait again, so put the futures and status objects back in
                 self._groups[group] = futs
                 self._status_objs[group] = status_objs
-                if not move_on:
+                if error_on_timeout:
                     raise
             finally:
-                if not move_on:
+                if error_on_timeout:
                     # Notify the waiting_hook function that we have moved on by
                     # sending it `None`. If all goes well, it could have
                     # inferred this from the status_obj, but there are edge


Test patch:
diff --git a/src/bluesky/tests/test_new_examples.py b/src/bluesky/tests/test_new_examples.py
index 667a8c29..b982fb07 100644
--- a/src/bluesky/tests/test_new_examples.py
+++ b/src/bluesky/tests/test_new_examples.py
@@ -111,8 +111,8 @@ from bluesky.utils import IllegalMessageSequence, all_safe_rewind
         (trigger, ("det",), {}, [Msg("trigger", "det", group=None)]),
         (trigger, ("det",), {"group": "A"}, [Msg("trigger", "det", group="A")]),
         (sleep, (2,), {}, [Msg("sleep", None, 2)]),
-        (wait, (), {}, [Msg("wait", None, move_on=False, group=None, timeout=None)]),
-        (wait, ("A",), {}, [Msg("wait", None, group="A", move_on=False, timeout=None)]),
+        (wait, (), {}, [Msg("wait", None, error_on_timeout=True, group=None, timeout=None)]),
+        (wait, ("A",), {}, [Msg("wait", None, group="A", error_on_timeout=True, timeout=None)]),
         (checkpoint, (), {}, [Msg("checkpoint")]),
         (clear_checkpoint, (), {}, [Msg("clear_checkpoint")]),
         (pause, (), {}, [Msg("pause", None, defer=False)]),
@@ -702,7 +702,7 @@ def test_trigger_and_read(hw):
     msgs = list(trigger_and_read([det]))
     expected = [
         Msg("trigger", det),
-        Msg("wait", move_on=False),
+        Msg("wait", error_on_timeout=True),
         Msg("create", name="primary"),
         Msg("read", det),
         Msg("save"),
@@ -715,7 +715,7 @@ def test_trigger_and_read(hw):
     msgs = list(trigger_and_read([det], "custom"))
     expected = [
         Msg("trigger", det),
-        Msg("wait", move_on=False),
+        Msg("wait", error_on_timeout=True),
         Msg("create", name="custom"),
         Msg("read", det),
         Msg("save"),