Skip to content

Commit 99cb94a

Browse files
ChsudeeptaChsudeepta
authored andcommitted
resolved merge issues
2 parents a3c23ee + efdc84c commit 99cb94a

File tree

12 files changed

+342
-2
lines changed

12 files changed

+342
-2
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: test-against-main
2+
on:
3+
schedule:
4+
- cron: "0 0 * * *"
5+
jobs:
6+
tests:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
version: ['3.11', "3.12"]
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: ${{ matrix.version }}
16+
- name: install requirements
17+
run: pip install -e .[dev]
18+
- name: install latest bluesky and ophyd-async
19+
run: pip install --upgrade --force-reinstall git+https://github.com/bluesky/bluesky.git@main git+https://github.com/bluesky/ophyd-async.git@main
20+
- name: run ruff
21+
run: python -m ruff --check
22+
- name: run pyright
23+
run: python -m pyright
24+
- name: run pytest
25+
run: python -m pytest
26+
results:
27+
if: ${{ always() }}
28+
runs-on: ubuntu-latest
29+
name: Final Results
30+
needs: [tests]
31+
steps:
32+
- run: exit 1
33+
# see https://stackoverflow.com/a/67532120/4907315
34+
if: >-
35+
${{
36+
contains(needs.*.result, 'failure')
37+
|| contains(needs.*.result, 'cancelled')
38+
}}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# ibex_bluesky_core
1+
![ibex_bluesky_core](https://github.com/IsisComputingGroup/ibex_bluesky_core/blob/main/doc/logo.png?raw=True)
22

33
Core bluesky plan stubs & devices for use at ISIS. Not instrument/technique specific.
44

doc/conf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646

4747
html_theme = "sphinx_rtd_theme"
4848
html_static_path = ["_static"]
49+
html_logo = "logo.png"
50+
html_theme_options = {
51+
"logo_only": False,
52+
"display_version": False,
53+
"style_nav_header_background": "#343131",
54+
}
55+
html_favicon = "favicon.png"
4956

5057
autoclass_content = "both"
5158
myst_heading_anchors = 3

doc/favicon.png

15.4 KB
Loading

doc/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ Reference documentation
9999

100100
fitting/*
101101

102+
.. toctree::
103+
:maxdepth: 2
104+
:caption: Plan stubs
105+
:glob:
106+
107+
plan_stubs/*
108+
102109
.. toctree::
103110
:maxdepth: 2
104111
:caption: Preprocessors

doc/logo.png

45.6 KB
Loading

doc/plan_stubs/external_code.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# `call_sync` (calling external code)
2+
3+
API reference: {py:obj}`ibex_bluesky_core.plan_stubs.call_sync`
4+
5+
All interaction with the "outside world" should be via bluesky messages, and **not** directly called from
6+
within a plan. For example, the following is **bad**:
7+
8+
```python
9+
import bluesky.plan_stubs as bps
10+
from genie_python import genie as g
11+
12+
def bad_plan():
13+
yield from bps.open_run()
14+
g.cset("foo", 123) # This is bad - must not do this
15+
yield from bps.close_run()
16+
```
17+
18+
```{danger}
19+
External I/O - including most `genie_python` or `inst` functions - should never be done directly in a plan,
20+
as it will break:
21+
- Rewindability (for example, the ability to interrupt a scan and then later seamlessly continue it)
22+
- Simulation (the `cset` above would be executed during a simulation)
23+
- Error handling (including ctrl-c handling)
24+
- Ability to emit documents
25+
- Ability to use bluesky signals
26+
- ...
27+
```
28+
29+
In the above case, a good plan, which uses bluesky messages in a better way using
30+
a bluesky-native `Block` object, would be:
31+
32+
```python
33+
import bluesky.plan_stubs as bps
34+
from ophyd_async.plan_stubs import ensure_connected
35+
from ibex_bluesky_core.devices.block import block_rw
36+
37+
foo = block_rw(float, "foo")
38+
39+
def good_plan():
40+
yield from ensure_connected(foo)
41+
yield from bps.open_run()
42+
yield from bps.mv(foo, 123)
43+
yield from bps.close_run()
44+
```
45+
46+
However, if the functionality you want to use is not yet natively available in bluesky, a fallback option
47+
for synchronous functions is available using the `call_sync` plan stub:
48+
49+
```python
50+
import bluesky.plan_stubs as bps
51+
from ibex_bluesky_core.plan_stubs import call_sync
52+
from genie_python import genie as g
53+
54+
def good_plan():
55+
yield from bps.open_run()
56+
57+
# Note use of g.some_function, rather than g.some_function() - i.e. a function reference
58+
# We can also access the returned value from the call.
59+
return_value = yield from call_sync(g.some_function, 123, keyword_argument=456)
60+
yield from bps.checkpoint()
61+
yield from bps.close_run()
62+
```
63+
64+
It is strongly recommended that any functions run in this way are "fast" (i.e. less than a few seconds).
65+
In particular, avoid doing arbitrarily-long waits - for example, waiting for detector data
66+
or sample environment. For these long-running tasks, seek to implement at least the long-running parts using
67+
native bluesky mechanisms.
68+
69+
```{note}
70+
`bps.checkpoint()` above instructs bluesky that this is a safe point from which to resume a plan.
71+
`call_sync` always clears an active checkpoint first, as the code it runs may have arbitrary external
72+
side effects.
73+
74+
If a plan is interrupted with no checkpoint active, it cannot be resumed later (it effectively forces
75+
the plan to abort rather than pause). You will see `bluesky.utils.FailedPause` as part of the traceback
76+
on ctrl-c, if this is the case.
77+
```

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ classifiers = [
2828
# 5 - Production/Stable
2929
"Development Status :: 3 - Alpha",
3030
"Intended Audience :: Developers",
31-
"License :: OSI Approved :: MIT License",
31+
"License :: OSI Approved :: BSD License",
3232

3333
# Specify the Python versions you support here. In particular, ensure
3434
# that you indicate you support Python 3. These classifiers are *not*
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,43 @@
11
"""Core plan stubs."""
2+
3+
from typing import Callable, Generator, ParamSpec, TypeVar, cast
4+
5+
import bluesky.plan_stubs as bps
6+
from bluesky.utils import Msg
7+
8+
P = ParamSpec("P")
9+
T = TypeVar("T")
10+
11+
12+
CALL_SYNC_MSG_KEY = "ibex_bluesky_core_call_sync"
13+
14+
15+
def call_sync(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> Generator[Msg, None, T]:
16+
"""Call a synchronous user function in a plan, and returns the result of that call.
17+
18+
Attempts to guard against the most common pitfalls of naive implementations, for example:
19+
20+
- Blocking the whole event loop
21+
- Breaking keyboard interrupt handling
22+
- Not clearing the active checkpoint
23+
24+
It does not necessarily guard against all possible cases, and as such it is *recommended* to
25+
use native bluesky functionality wherever possible in preference to this plan stub. This should
26+
be seen as an escape-hatch.
27+
28+
The wrapped function will be run in a new thread.
29+
30+
This plan stub will clear any active checkpoints before running the external code, because
31+
in general the external code is not safe to re-run later once it has started (e.g. it may have
32+
done relative sets, or may have started some external process). This means that if a plan is
33+
interrupted at any point between a call_sync and the next checkpoint, the plan cannot be
34+
resumed - in this case bluesky.utils.FailedPause will appear in the ctrl-c stack trace.
35+
36+
Args:
37+
func: A callable to run.
38+
args: Arbitrary arguments to be passed to the wrapped function
39+
kwargs: Arbitrary keyword arguments to be passed to the wrapped function
40+
41+
"""
42+
yield from bps.clear_checkpoint()
43+
return cast(T, (yield Msg(CALL_SYNC_MSG_KEY, func, *args, **kwargs)))

src/ibex_bluesky_core/run_engine.py renamed to src/ibex_bluesky_core/run_engine/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919

2020
logger = logging.getLogger(__name__)
21+
from ibex_bluesky_core.plan_stubs import CALL_SYNC_MSG_KEY
22+
from ibex_bluesky_core.preprocessors import add_rb_number_processor
23+
from ibex_bluesky_core.run_engine._msg_handlers import call_sync_handler
2124

2225

2326
class _DuringTask(DuringTask):
@@ -93,6 +96,8 @@ def get_run_engine() -> RunEngine:
9396
log_callback = DocLoggingCallback()
9497
RE.subscribe(log_callback)
9598

99+
RE.register_command(CALL_SYNC_MSG_KEY, call_sync_handler)
100+
96101
RE.preprocessors.append(functools.partial(bpp.plan_mutator, msg_proc=add_rb_number_processor))
97102

98103
return RE

0 commit comments

Comments
 (0)