diff --git a/dev-requirements.txt b/dev-requirements.txt index 4d8052e0f..481e74426 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,56 +2,63 @@ accessible-pygments==0.0.5 aioca==1.8.1 aiofiles==24.1.0 aiohappyeyeballs==2.6.1 +aiohttp==3.12.13 aiosignal==1.3.2 alabaster==1.0.0 annotated-types==0.7.0 anyio==4.9.0 asgiref==3.8.1 attrs==25.3.0 +awkward==2.8.4 +awkward_cpp==46 babel==2.17.0 beautifulsoup4==4.13.4 bidict==0.23.1 -bluesky==1.13.1 +blosc2==3.5.1 +bluesky==1.14.2 bluesky-stomp==0.1.6 -certifi==2025.1.31 +certifi==2025.6.15 cffi==1.17.1 cfgv==3.4.0 charset-normalizer==3.4.2 click==8.2.1 +cloudpickle==3.1.1 colorama==0.4.6 colorlog==6.9.0 compress-pickle==2.1.0 contourpy==1.3.2 copier==9.7.1 -coverage==7.8.2 -cryptography==45.0.3 +coverage==7.9.1 +cryptography==45.0.5 cycler==0.12.1 +dask==2025.5.1 dataclasses-json==0.6.7 -deepdiff==8.4.2 +deepdiff==8.5.0 deepmerge==2.0 -Deprecated==1.2.18 distlib==0.3.9 -dls-dodal==1.46.0 +dls-dodal==1.50.0 dnspython==2.7.0 docopt==0.6.2 docutils==0.21.2 -dunamai==1.23.1 +dunamai==1.24.1 email_validator==2.2.0 +entrypoints==0.4 epicscorelibs==7.0.7.99.1.2a1 event-model==1.23 -fastapi==0.115.12 +fastapi==0.115.14 fastapi-cli==0.0.7 filelock==3.18.0 flexcache==0.3 flexparser==0.4 -fonttools==4.57.0 -frozenlist==1.6.0 +fonttools==4.58.4 +frozenlist==1.7.0 +fsspec==2025.5.1 funcy==2.0 gitdb==4.0.12 GitPython==3.1.44 googleapis-common-protos==1.70.0 graypy==2.1.0 -grpcio==1.71.0 +grpcio==1.73.1 h11==0.16.0 historydict==1.2.6 httpcore==1.0.9 @@ -60,16 +67,21 @@ httpx==0.28.1 identify==2.6.12 idna==3.10 imagesize==1.4.1 -importlib_metadata==8.6.1 +importlib_metadata==8.7.0 importlib_resources==6.5.2 iniconfig==2.1.0 itsdangerous==2.2.0 Jinja2==3.1.6 jinja2-ansible-filters==1.3.2 -jsonschema==4.23.0 +json-merge-patch==0.3.0 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.24.0 jsonschema-specifications==2025.4.1 jwcrypto==1.5.6 kiwisolver==1.4.8 +llvmlite==0.44.0 +locket==1.0.0 lz4==4.4.4 markdown-it-py==3.0.0 MarkupSafe==3.0.2 @@ -79,71 +91,79 @@ mdit-py-plugins==0.4.2 mdurl==0.1.2 mistune==3.1.3 mock==5.2.0 -msgpack==1.1.0 +msgpack==1.1.1 msgpack-numpy==0.4.8 -multidict==6.4.4 +multidict==6.6.3 mypy_extensions==1.1.0 myst-parser==4.0.1 -networkx==3.4.2 +ndindex==1.10.0 +networkx==3.5 nodeenv==1.9.1 nose2==0.15.1 +numba==0.61.2 +numexpr==2.11.0 numpy==2.2.6 observability-utils==0.1.4 opencv-python-headless==4.11.0.86 -opentelemetry-api==1.32.1 -opentelemetry-distro==0.53b1 -opentelemetry-exporter-otlp==1.32.1 -opentelemetry-exporter-otlp-proto-common==1.32.1 -opentelemetry-exporter-otlp-proto-grpc==1.32.1 -opentelemetry-exporter-otlp-proto-http==1.32.1 -opentelemetry-instrumentation==0.53b1 -opentelemetry-instrumentation-asgi==0.53b1 -opentelemetry-instrumentation-fastapi==0.53b1 -opentelemetry-proto==1.32.1 -opentelemetry-sdk==1.32.1 -opentelemetry-semantic-conventions==0.53b1 -opentelemetry-util-http==0.53b1 -ophyd==1.10.6 -ophyd-async==0.10.0a3 +opentelemetry-api==1.34.1 +opentelemetry-distro==0.55b1 +opentelemetry-exporter-otlp==1.34.1 +opentelemetry-exporter-otlp-proto-common==1.34.1 +opentelemetry-exporter-otlp-proto-grpc==1.34.1 +opentelemetry-exporter-otlp-proto-http==1.34.1 +opentelemetry-instrumentation==0.55b1 +opentelemetry-instrumentation-asgi==0.55b1 +opentelemetry-instrumentation-fastapi==0.55b1 +opentelemetry-proto==1.34.1 +opentelemetry-sdk==1.34.1 +opentelemetry-semantic-conventions==0.55b1 +opentelemetry-util-http==0.55b1 +ophyd==1.10.7 +ophyd-async==0.11 orderly-set==5.4.1 orjson==3.10.18 p4p==4.2.0 packaging==25.0 +pandas==2.3.0 +partd==1.4.2 pathlib2==2.3.7.post1 pathspec==0.12.1 picobox==4.0.0 pika==1.3.2 -pillow==11.2.1 +pillow==11.3.0 Pint==0.24.4 pipdeptree==2.26.1 platformdirs==4.3.8 -pluggy==1.5.0 +pluggy==1.6.0 plumbum==1.9.0 ply==3.11 pre_commit==4.2.0 prompt_toolkit==3.0.51 -propcache==0.3.1 +propcache==0.3.2 protobuf==5.29.5 pvxslibs==1.3.3 py==1.11.0 +py-cpuinfo==9.0.0 +pyarrow==20.0.0 pycparser==2.22 pydantic==2.10.6 -pydantic-extra-types==2.10.4 -pydantic-settings==2.9.1 +pydantic-extra-types==2.10.5 +pydantic-settings==2.10.1 pydantic_core==2.27.2 pydantic_numpy==8.0.1 pydata-sphinx-theme==0.16.1 -pyepics==3.5.7 -Pygments==2.19.1 +pyepics==3.5.8 +Pygments==2.19.2 PyJWT==2.10.1 pyparsing==3.2.3 -pyright==1.1.401 -pytest==8.3.5 +pyright==1.1.402 +pytest==8.4.1 pytest-asyncio==1.0.0 -pytest-cov==6.1.1 +pytest-cov==6.2.1 python-dateutil==2.9.0.post0 -python-dotenv==1.1.0 +python-dotenv==1.1.1 python-multipart==0.0.20 +pytz==2025.2 PyYAML==6.0.2 questionary==2.1.0 redis==6.2.0 @@ -151,14 +171,15 @@ referencing==0.36.2 requests==2.32.4 responses==0.25.7 rich==14.0.0 -rich-toolkit==0.14.3 +rich-toolkit==0.14.8 roman-numerals-py==3.1.0 -rpds-py==0.24.0 -ruamel.yaml==0.18.10 +rpds-py==0.26.0 +ruamel.yaml==0.18.14 ruamel.yaml.clib==0.2.12 -ruff==0.11.7 +ruff==0.12.1 scanspec==0.7.8 semver==3.0.4 +setuptools==80.9.0 setuptools_dso==2.12.2 shellingham==1.5.4 six==1.17.0 @@ -166,6 +187,7 @@ smmap==5.0.2 sniffio==1.3.1 snowballstemmer==3.0.1 soupsieve==2.7 +sparse==0.17.0 Sphinx==8.2.3 sphinx-autobuild==2024.10.3 sphinx-click==6.0.0 @@ -180,33 +202,39 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-openapi==0.8.4 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 +stamina==25.1.0 starlette==0.46.2 stomp.py==8.2.0 super-state-machine==2.0.2 -tomlkit==0.13.2 +tenacity==9.1.2 +tiled==0.1.0b29 +tomlkit==0.13.3 toolz==1.0.0 tox==3.28.0 tox-direct==0.4 tqdm==4.67.1 -typer==0.15.2 -types-mock==5.2.0.20250306 -types-PyYAML==6.0.12.20250402 -types-requests==2.32.0.20250328 +typer==0.16.0 +types-mock==5.2.0.20250516 +types-PyYAML==6.0.12.20250516 +types-requests==2.32.4.20250611 types-urllib3==1.26.25.14 typing-inspect==0.9.0 -typing-inspection==0.4.0 -typing_extensions==4.13.2 +typing-inspection==0.4.1 +typing_extensions==4.14.0 +tzdata==2025.2 ujson==5.10.0 urllib3==2.5.0 -uvicorn==0.34.2 +uvicorn==0.35.0 uvloop==0.21.0 -virtualenv==20.30.0 -watchfiles==1.0.5 +virtualenv==20.31.2 +watchfiles==1.1.0 wcwidth==0.2.13 websocket-client==1.8.0 websockets==15.0.1 workflows==3.2 wrapt==1.17.2 -yarl==1.20.0 -zipp==3.21.0 +xarray==2025.6.1 +yarl==1.20.1 +zipp==3.23.0 zocalo==1.2.0 +zstandard==0.23.0 diff --git a/pyproject.toml b/pyproject.toml index 187c88147..f043a8a41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ classifiers = [ ] description = "Lightweight bluesky-as-a-service wrapper application. Also usable as a library." dependencies = [ + "tiled[client]", "bluesky[plotting]>=1.13.1", # plotting includes matplotlib, required for BestEffortCallback in run plans "ophyd-async", "aioca", diff --git a/src/blueapi/config.py b/src/blueapi/config.py index 578b25cc9..b244fb4d2 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -65,6 +65,14 @@ class StompConfig(BlueapiBaseModel): ) +class TiledConfig(BlueapiBaseModel): + enabled: bool = Field( + description="True if blueapi should forward data to a Tiled instance", + default=False, + ) + url: HttpUrl = HttpUrl("http://localhost:8000") + + class WorkerEventConfig(BlueapiBaseModel): """ Config for event broadcasting via the message bus @@ -209,6 +217,7 @@ class ApplicationConfig(BlueapiBaseModel): """ stomp: StompConfig = Field(default_factory=StompConfig) + tiled: TiledConfig = Field(default_factory=TiledConfig) env: EnvironmentConfig = Field(default_factory=EnvironmentConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig) api: RestConfig = Field(default_factory=RestConfig) diff --git a/src/blueapi/service/interface.py b/src/blueapi/service/interface.py index 4c18d841c..63c429c4c 100644 --- a/src/blueapi/service/interface.py +++ b/src/blueapi/service/interface.py @@ -2,16 +2,18 @@ from functools import cache 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 dodal.common.beamlines.beamline_utils import ( get_path_provider, set_path_provider, ) +from tiled.client import from_uri from blueapi.cli.scratch import get_python_environment from blueapi.client.numtracker import NumtrackerClient -from blueapi.config import ApplicationConfig, OIDCConfig, StompConfig +from blueapi.config import ApplicationConfig, OIDCConfig, StompConfig, TiledConfig from blueapi.core.context import BlueskyContext from blueapi.core.event import EventStream from blueapi.log import set_up_logging @@ -111,6 +113,13 @@ def numtracker_client() -> NumtrackerClient | None: return None +@cache +def tiled_writer() -> TiledWriter: + tiled_config: TiledConfig = config().tiled + client = from_uri(tiled_config.url) + return TiledWriter(client, batch_size=1) + + def _update_scan_num(md: dict[str, Any]) -> int: numtracker = numtracker_client() if numtracker is not None: @@ -148,6 +157,9 @@ def setup(config: ApplicationConfig) -> None: ) stomp_client() + if config.tiled.enabled: + writer = tiled_writer() + context().run_engine.subscribe(writer) def _hook_run_engine_and_path_provider() -> None: @@ -165,6 +177,7 @@ def teardown() -> None: worker.cache_clear() stomp_client.cache_clear() numtracker_client.cache_clear() + tiled_writer.cache_clear() def _publish_event_streams( diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 10efd3d68..6452a5695 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -277,6 +277,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): "url": "http://localhost:61613/", "auth": {"username": "guest", "password": "guest"}, }, + "tiled": {"enabled": False, "url": "http://localhost:8000/"}, "auth_token_path": None, "env": { "events": { @@ -326,6 +327,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): "url": "https://rabbitmq.diamond.ac.uk:61613/", "auth": {"username": "guest", "password": "guest"}, }, + "tiled": {"enabled": False, "url": "http://localhost:8000/"}, "auth_token_path": None, "env": { "sources": [