diff --git a/helm/blueapi/README.md b/helm/blueapi/README.md index 8e347d594..81ebef06e 100644 --- a/helm/blueapi/README.md +++ b/helm/blueapi/README.md @@ -49,9 +49,9 @@ A Helm chart deploying a worker pod that runs Bluesky plans | tracing | object | `{"fastapi":{"excludedURLs":"/healthz"},"otlp":{"enabled":false,"protocol":"http/protobuf","server":{"host":"http://opentelemetry-collector.tracing","port":4318}}}` | Exclude health probe requests from tracing by default to prevent spamming | | volumeMounts | list | `[]` | Additional volumeMounts on the output StatefulSet definition. Define how volumes are mounted to the container referenced by using the same name. | | volumes | list | `[]` | Additional volumes on the output StatefulSet definition. Define volumes from e.g. Secrets, ConfigMaps or the Filesystem | -| worker | object | `{"api":{"url":"http://0.0.0.0:8000/"},"env":{"sources":[{"kind":"planFunctions","module":"dodal.plans"},{"kind":"planFunctions","module":"dodal.plan_stubs.wrapped"}]},"logging":{"graylog":{"enabled":false,"url":"tcp://graylog-log-target.diamond.ac.uk:12231/"},"level":"INFO"},"scratch":{"repositories":[],"root":"/workspace"},"stomp":{"auth":{"password":"guest","username":"guest"},"enabled":false,"url":"tcp://rabbitmq:61613/"}}` | Config for the worker goes here, will be mounted into a config file | +| worker | object | `{"api":{"url":"http://0.0.0.0:8000/"},"env":{"sources":[{"kind":"planFunctions","module":"dodal.plans"}]},"logging":{"graylog":{"enabled":false,"url":"tcp://graylog-log-target.diamond.ac.uk:12231/"},"level":"INFO"},"scratch":{"repositories":[],"root":"/workspace"},"stomp":{"auth":{"password":"guest","username":"guest"},"enabled":false,"url":"tcp://rabbitmq:61613/"}}` | Config for the worker goes here, will be mounted into a config file | | worker.api.url | string | `"http://0.0.0.0:8000/"` | 0.0.0.0 required to allow non-loopback traffic If using hostNetwork, the port must be free on the host | -| worker.env.sources | list | `[{"kind":"planFunctions","module":"dodal.plans"},{"kind":"planFunctions","module":"dodal.plan_stubs.wrapped"}]` | modules (must be installed in the venv) to fetch devices/plans from | +| worker.env.sources | list | `[{"kind":"planFunctions","module":"dodal.plans"}]` | modules (must be installed in the venv) to fetch devices/plans from | | worker.logging | object | `{"graylog":{"enabled":false,"url":"tcp://graylog-log-target.diamond.ac.uk:12231/"},"level":"INFO"}` | Configures logging. Port 12231 is the `dodal` input on graylog which will be renamed `blueapi` | | worker.scratch | object | `{"repositories":[],"root":"/workspace"}` | If initContainer is enabled the default branch of python projects in this section are installed into the venv *without their dependencies* | | worker.stomp | object | `{"auth":{"password":"guest","username":"guest"},"enabled":false,"url":"tcp://rabbitmq:61613/"}` | Message bus configuration for returning status to GDA/forwarding documents downstream Password may be in the form ${ENV_VAR} to be fetched from an environment variable e.g. mounted from a SealedSecret | diff --git a/helm/blueapi/values.yaml b/helm/blueapi/values.yaml index 941575fb9..bf36a0510 100644 --- a/helm/blueapi/values.yaml +++ b/helm/blueapi/values.yaml @@ -192,8 +192,6 @@ worker: # module: dodal.beamlines.adsim - kind: planFunctions module: dodal.plans - - kind: planFunctions - module: dodal.plan_stubs.wrapped # -- Message bus configuration for returning status to GDA/forwarding documents downstream # Password may be in the form ${ENV_VAR} to be fetched from an environment variable e.g. mounted from a SealedSecret stomp: diff --git a/pyproject.toml b/pyproject.toml index e78835476..e8ac6f853 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ description = "Lightweight bluesky-as-a-service wrapper application. Also usable as a library." dependencies = [ "tiled[client]>=0.2.4", - "bluesky[plotting]>=1.14.0", # plotting includes matplotlib, required for BestEffortCallback in run plans + "bluesky[plotting] @ git+https://github.com/bluesky/bluesky.git@main", # plotting includes matplotlib, required for BestEffortCallback in run plans "ophyd-async>=0.13.5", "aioca", "pydantic>=2.0", @@ -26,8 +26,8 @@ dependencies = [ "fastapi>=0.112.0", "uvicorn>=0.52.1", "requests", - "GitPython>=3.1.58", #security base min https://github.com/advisories/GHSA-4gmw-gg2m-w46p - "event-model==1.24.0", # https://github.com/DiamondLightSource/blueapi/issues/684 + "GitPython>=3.1.58", #security base min https://github.com/advisories/GHSA-4gmw-gg2m-w46p + "event-model==1.24.0", # https://github.com/DiamondLightSource/blueapi/issues/684 "bluesky-stomp>=0.1.6", "opentelemetry-distro>=0.48b0", "opentelemetry-instrumentation-fastapi>=0.48b0", @@ -37,6 +37,7 @@ dependencies = [ "graypy>=2.1.0", "httpx>=0.28.1", "aiohttp>=3.13.5", + "bluesky-tiled-plugins>=2.0.11", ] dynamic = ["version"] license.file = "LICENSE" diff --git a/src/blueapi/core/context.py b/src/blueapi/core/context.py index 76ce74e64..297e058fd 100644 --- a/src/blueapi/core/context.py +++ b/src/blueapi/core/context.py @@ -7,8 +7,9 @@ from types import ModuleType, NoneType, UnionType from typing import Any, TypeVar, Union, get_args, get_origin, get_type_hints +import bluesky.plan_stubs as bps from bluesky.protocols import HasName -from bluesky.run_engine import RunEngine +from bluesky.run_engine import RunEngine, RunEngineMetadata from ophyd_async.core import NotConnectedError, PathProvider from pydantic import ( BaseModel, @@ -52,6 +53,16 @@ LOGGER = logging.getLogger(__name__) +DEFAULT_PLAN_STUBS = [ + bps.abs_set, + bps.rel_set, + bps.mv, + bps.mvr, + bps.sleep, + bps.wait, + bps.rd, + bps.stop, +] if "ophyd" in sys.modules: # This is an awful hack/workaround to avoid blueapi hanging on shutdown when @@ -137,6 +148,9 @@ def __post_init__(self, configuration: ApplicationConfig | None): if not configuration: return + for plan_stub in DEFAULT_PLAN_STUBS: + self.register_plan(plan_stub) + if (nt_conf := configuration.numtracker) is not None: if configuration.env.metadata is not None: self.numtracker = NumtrackerClient(url=nt_conf.url) @@ -155,7 +169,7 @@ def __post_init__(self, configuration: ApplicationConfig | None): # local reference so it's available in _update_scan_num numtracker = self.numtracker - async def _update_scan_num(md: dict[str, Any]) -> int: + async def _update_scan_num(md: RunEngineMetadata) -> int: scan = await numtracker.create_scan( md["instrument_session"], md["instrument"] ) @@ -203,7 +217,7 @@ def find_device(self, addr: str | list[str]) -> Device | None: def with_config(self, config: EnvironmentConfig) -> None: if config.metadata is not None: - self.run_engine.md |= config.metadata.model_dump() + self.run_engine.md.update(config.metadata.model_dump()) package_map = metadata.packages_distributions() packages = {src.module.split(".")[0] for src in config.sources} diff --git a/src/blueapi/service/interface.py b/src/blueapi/service/interface.py index 6c0c5befb..32886abc1 100644 --- a/src/blueapi/service/interface.py +++ b/src/blueapi/service/interface.py @@ -5,9 +5,9 @@ from multiprocessing.connection import Connection from typing import Any -from bluesky.callbacks.tiled_writer import TiledWriter from bluesky_stomp.messaging import StompClient from bluesky_stomp.models import Broker, DestinationBase, MessageTopic +from bluesky_tiled_plugins import TiledWriter from tiled.client import from_uri from blueapi.cli.scratch import get_python_environment diff --git a/tests/unit_tests/core/test_context.py b/tests/unit_tests/core/test_context.py index 06c3391c7..ea412d3dc 100644 --- a/tests/unit_tests/core/test_context.py +++ b/tests/unit_tests/core/test_context.py @@ -478,7 +478,7 @@ class ConcreteStoppable(Stoppable): def name(self) -> str: return "Concrete" - def stop(self, success: bool = True): + def stop(self, *, success: bool = True): pass diff --git a/tests/unit_tests/service/test_interface.py b/tests/unit_tests/service/test_interface.py index 892c5ad2e..f43eefb2e 100644 --- a/tests/unit_tests/service/test_interface.py +++ b/tests/unit_tests/service/test_interface.py @@ -142,7 +142,7 @@ def test_get_plan(context_mock: MagicMock): class MyDevice(Stoppable): name: str - def stop(self, success: bool = True) -> None: + def stop(self, *, success: bool = True) -> None: pass diff --git a/tests/unit_tests/service/test_rest_api.py b/tests/unit_tests/service/test_rest_api.py index a10d5ab3c..867303e52 100644 --- a/tests/unit_tests/service/test_rest_api.py +++ b/tests/unit_tests/service/test_rest_api.py @@ -148,7 +148,7 @@ def client_with_cors( class MinimalDevice(Stoppable): name: str - def stop(self, success: bool = True): + def stop(self, *, success: bool = True): pass diff --git a/uv.lock b/uv.lock index ce7c4c20a..3fab6a292 100644 --- a/uv.lock +++ b/uv.lock @@ -3,10 +3,10 @@ revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", "python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version < '3.12' and sys_platform == 'emscripten'", @@ -474,6 +474,7 @@ dependencies = [ { name = "aiohttp" }, { name = "bluesky", extra = ["plotting"] }, { name = "bluesky-stomp" }, + { name = "bluesky-tiled-plugins" }, { name = "click" }, { name = "event-model" }, { name = "fastapi" }, @@ -541,8 +542,9 @@ dev = [ requires-dist = [ { name = "aioca" }, { name = "aiohttp", specifier = ">=3.13.5" }, - { name = "bluesky", extras = ["plotting"], specifier = ">=1.14.0" }, + { name = "bluesky", extras = ["plotting"], git = "https://github.com/bluesky/bluesky.git?rev=main" }, { name = "bluesky-stomp", specifier = ">=0.1.6" }, + { name = "bluesky-tiled-plugins", specifier = ">=2.0.11" }, { name = "click", specifier = ">=8.2.0" }, { name = "dls-dodal", marker = "extra == 'demo'", specifier = ">=1.69.0" }, { name = "event-model", specifier = "==1.24.0" }, @@ -605,8 +607,8 @@ dev = [ [[package]] name = "bluesky" -version = "1.15.1" -source = { registry = "https://pypi.org/simple" } +version = "1.15.2.dev268+g3fb85489e" +source = { git = "https://github.com/bluesky/bluesky.git?rev=main#3fb85489e287c8ae6b99d432ebb5a5413b7b429a" } dependencies = [ { name = "cycler" }, { name = "event-model" }, @@ -620,10 +622,6 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/83/2e5d1956a1c4cd3d4f0bb34605cc2b11f2ce33e48249773292b27e6bba88/bluesky-1.15.1.tar.gz", hash = "sha256:956e161a8f34f698af28edcd501db118fa793a6c43241001e30ed8c6aaabd95c", size = 513978, upload-time = "2026-05-06T14:06:40.116Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/99/cc217b2a727fdf730d0c0064bc8c03473dfe5d0528f2bd0c4f59fdab4179/bluesky-1.15.1-py3-none-any.whl", hash = "sha256:7b3a0f04cf2bb0a7907771e014e59fe6fced777347df8df84adec7239f6c3d2a", size = 372701, upload-time = "2026-05-06T14:06:38.618Z" }, -] [package.optional-dependencies] plotting = [ @@ -646,6 +644,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/4d/d3069d2e0675ad5037c583b8819e5df6fa07ac1814930ed18644191fa232/bluesky_stomp-0.2.0-py3-none-any.whl", hash = "sha256:b6c01504839717c2953b9efdc714f63db3cd1f2e0b2d0221f0b4f51415d7a0f2", size = 19255, upload-time = "2025-10-14T13:15:56.524Z" }, ] +[[package]] +name = "bluesky-tiled-plugins" +version = "2.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dask" }, + { name = "event-model" }, + { name = "mongoquery" }, + { name = "pytz" }, + { name = "tiled", extra = ["client"] }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/74/09a8e31991eeb72d369142a7e02f8947ba182321e70b1cf9f8ca0de4289a/bluesky_tiled_plugins-2.0.11.tar.gz", hash = "sha256:1a9acac4c40fa9d2740a1f1cdf63a98cc598a6208267a71b9ab428927403d24d", size = 105156, upload-time = "2026-09-09T17:09:09.003Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/12/3c5928bd530b339f73e670ec41a80dcfe76b3d531b3a3cbe74e18e9de1ba/bluesky_tiled_plugins-2.0.11-py3-none-any.whl", hash = "sha256:a40f850e7ae7bec8a28b99e3bf260f6e9bedae04c109285bcd10b6acb579af4a", size = 65758, upload-time = "2026-09-09T17:09:07.746Z" }, +] + [[package]] name = "cachebox" version = "5.2.3" @@ -1169,10 +1184,10 @@ version = "1.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -3240,6 +3255,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/d9/617e6af809bf3a1d468e0d58c3997b1dc219a9a9202e650d30c2fc85d481/mock-5.2.0-py3-none-any.whl", hash = "sha256:7ba87f72ca0e915175596069dbbcc7c75af7b5e9b9bc107ad6349ede0819982f", size = 31617, upload-time = "2025-03-03T12:31:41.518Z" }, ] +[[package]] +name = "mongoquery" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/ec/8e0ddb347c6c5b94e0147b2b9ea1fd9dbfda5d222d0e529996445c30faaa/mongoquery-1.4.3.tar.gz", hash = "sha256:e901f86ee5af7ef6eda2f2c26f4bd2cfe8380d81f178b7e3607dbbcdcee97239", size = 8199, upload-time = "2025-09-16T10:56:06.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/33/7ce3f09d91a0ca5db7cca67671d865b7c8310b8b59eff287a711919641f0/mongoquery-1.4.3-py3-none-any.whl", hash = "sha256:8be47635c5e361c9f363b7a901a2b86649687491b7aaca2d12601eff41cb6128", size = 6421, upload-time = "2025-09-16T10:56:05.412Z" }, +] + [[package]] name = "msgpack" version = "1.2.2" @@ -3752,10 +3779,10 @@ version = "0.17.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -3933,10 +3960,10 @@ version = "2.5.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" } @@ -4610,10 +4637,10 @@ version = "0.26.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -5364,6 +5391,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] +[[package]] +name = "pytz" +version = "2026.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/14/21/d83d6ef28c4c912c4bb4d1dcf591f7b8c6bde87b9c66f9f454677314e16d/pytz-2026.5.tar.gz", hash = "sha256:fa23724b9c486543b9ff54a327ee7569ac83ade54bb9afd0fc18676620401c86", size = 318572, upload-time = "2026-10-04T02:37:58.719Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/ef/c66110d46fb800dda0bf33164182dfadabe26a90e4476844d502a23dca8e/pytz-2026.5-py2.py3-none-any.whl", hash = "sha256:e658af3757f9e26a9d25dd2aff38335acd92bc9104f890a894b2c1ba28311b03", size = 506342, upload-time = "2026-10-04T02:37:56.814Z" }, +] + [[package]] name = "pyyaml" version = "6.0.3" @@ -6115,10 +6151,10 @@ version = "9.1.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -6719,6 +6755,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl", hash = "sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81", size = 347494, upload-time = "2026-09-12T12:56:01.67Z" }, ] +[[package]] +name = "tzlocal" +version = "5.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/5b/879b2f932adfa7a053c360d50bc896c977fa6426109185f7c12ebdd0cb9d/tzlocal-5.4.4.tar.gz", hash = "sha256:8dbb8660838688a7b6ba4fed31d18dedf842afb4d47ca050d6d891c2c15f3be4", size = 31170, upload-time = "2026-06-29T08:03:40.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a4/017a7a6cbe387d961a688ec31364ae60a5c4e22c96ae9921b79a947c855d/tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15", size = 18115, upload-time = "2026-06-29T08:03:38.666Z" }, +] + [[package]] name = "urllib3" version = "2.8.0" @@ -7149,10 +7197,10 @@ version = "3.7" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -7440,10 +7488,10 @@ version = "3.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14' and sys_platform == 'emscripten'", - "python_full_version == '3.13.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version >= '3.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", "python_full_version == '3.12.*' and sys_platform != 'emscripten'", ] dependencies = [