diff --git a/docs/resources/config_pre_made.yaml b/docs/resources/config_pre_made.yaml index b902011ac..9e2a2c756 100644 --- a/docs/resources/config_pre_made.yaml +++ b/docs/resources/config_pre_made.yaml @@ -6,8 +6,6 @@ env: module: blueapi.tutorial.devices - kind: planFunctions module: dodal.plans - - kind: planFunctions - module: dodal.plan_stubs.wrapped stomp: enabled: true url: tcp://localhost:61613/ diff --git a/docs/tutorials/4. config-add-plans.md b/docs/tutorials/4. config-add-plans.md index e7e1707cc..ef27937ed 100644 --- a/docs/tutorials/4. config-add-plans.md +++ b/docs/tutorials/4. config-add-plans.md @@ -5,10 +5,8 @@ As well as adding devices to the `config.yaml`, you can add plans. Under the cod ``` yaml - kind: planFunctions module: dodal.plans - - kind: planFunctions - module: dodal.plan_stubs.wrapped ``` -These plans that will be used in this tutorial can be found in the [dodal repository](https://github.com/DiamondLightSource/dodal/tree/b4b38f1e0cf480faeb29d67695440c45a148f169). Specifically, in `dodal/src/dodal/plans/wrapped.py` and `dodal/src/dodal/plan_stubs/wrapped.py`. +These plans that will be used in this tutorial can be found in the [dodal repository](https://github.com/DiamondLightSource/dodal/tree/b4b38f1e0cf480faeb29d67695440c45a148f169). Specifically, in `dodal/src/dodal/plans/wrapped.py` and [bluesky repository](https://github.com/bluesky/bluesky/blob/1fb2f4e13437edf4a72372dacd455cfaf905f09e/src/bluesky/plan_stubs.py#L321). Save the changes you've made to the `config.yaml` file. Navigate back to the terminal where the blueapi worker is running. Restart the server to use this updated `config.yaml` file. diff --git a/docs/tutorials/5. config-add-rabbitmq-and-run-plan-cli.md b/docs/tutorials/5. config-add-rabbitmq-and-run-plan-cli.md index 1822819db..4630267d5 100644 --- a/docs/tutorials/5. config-add-rabbitmq-and-run-plan-cli.md +++ b/docs/tutorials/5. config-add-rabbitmq-and-run-plan-cli.md @@ -48,8 +48,6 @@ env: module: blueapi.tutorial.devices - kind: planFunctions module: dodal.plans - - kind: planFunctions - module: dodal.plan_stubs.wrapped stomp: enabled: true url: tcp://localhost:61613/ 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 f62ee9b65..838dec1d5 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 5c5008e6b..1bb44ad11 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.18", - "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" @@ -55,7 +56,7 @@ dev = [ "prek", "pydata-sphinx-theme>=0.15.4", "pytest", - "pyright!=1.1.407", # https://github.com/bluesky/scanspec/issues/190 + "pyright!=1.1.407", # https://github.com/bluesky/scanspec/issues/190 "pytest-cov", "pytest-asyncio", "pytest-httpx>=0.35.0", diff --git a/src/blueapi/core/context.py b/src/blueapi/core/context.py index f8a96b0d6..2bcab5fbb 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 7df474557..44a9da633 100644 --- a/src/blueapi/service/interface.py +++ b/src/blueapi/service/interface.py @@ -6,9 +6,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 fastapi import status from tiled.client import from_uri from tiled.client.utils import ClientError diff --git a/tests/system_tests/config.yaml b/tests/system_tests/config.yaml index 89f94e533..1245eed48 100644 --- a/tests/system_tests/config.yaml +++ b/tests/system_tests/config.yaml @@ -8,8 +8,6 @@ env: module: dodal.beamlines.adsim - kind: planFunctions module: dodal.plans - - kind: planFunctions - module: dodal.plan_stubs.wrapped stomp: enabled: true url: tcp://localhost:61613/ diff --git a/tests/system_tests/plans.json b/tests/system_tests/plans.json index 6f84a9c38..c5ccc07ea 100644 --- a/tests/system_tests/plans.json +++ b/tests/system_tests/plans.json @@ -1,9 +1,49 @@ { "plans": [ + { + "name": "abs_set", + "description": "\nSet a value. Optionally, wait for it to complete before continuing.\n\nParameters\n----------\nobj : Device\nargs :\n passed to obj.set()\ngroup : string (or any hashable object), optional\n identifier used by 'wait'\nwait : boolean, optional\n If True, wait for completion before processing any more messages.\n False by default.\nkwargs :\n passed to obj.set()\n\nYields\n------\nmsg : Msg\n\nReturns\n-------\nstatus :\n Status that completes when the value is set. If `wait` is True,\n this will always be complete by the time it is returned.\n\nSee Also\n--------\n:func:`bluesky.plan_stubs.rel_set`\n:func:`bluesky.plan_stubs.wait`\n:func:`bluesky.plan_stubs.mv`\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "obj": { + "enum": [], + "title": "Obj", + "type": "bluesky.protocols.Movable", + "types": [ + "typing.Any" + ] + }, + "args": { + "items": {}, + "title": "Args", + "type": "array" + }, + "group": { + "title": "Group" + }, + "wait": { + "default": false, + "title": "Wait", + "type": "boolean" + }, + "kwargs": { + "additionalProperties": true, + "title": "Kwargs", + "type": "object" + } + }, + "required": [ + "obj" + ], + "title": "abs_set", + "type": "object" + } + }, { "name": "count", "description": "Reads from a number of devices.\n\nWraps bluesky.plans.count(det, num, delay, md=metadata) exposing only serializable\nparameters and metadata.\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -45,7 +85,7 @@ "type": "array" } ], - "default": 0, + "default": 0.0, "title": "Delay" }, "metadata": { @@ -64,7 +104,7 @@ { "name": "list_grid_rscan", "description": "Scan independent trajectories, relative to current positions.\n\nThe scan is defined by providing a list of points for each scan trajectory. Snakes\nall fast axes by default (all axes but the first axis provided). Wraps\nbluesky.plans.rel_list_grid_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -139,7 +179,7 @@ { "name": "list_grid_scan", "description": "Scan independent trajectories.\n\nThe scan is defined by providing a list of points for each scan trajectory. Snakes\nall fast axes by default (all axes but the first axis provided). Wraps\nbluesky.plans.list_grid_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -214,7 +254,7 @@ { "name": "list_rscan", "description": "Scan concurrent trajector(y/ies), relative to current position.\n\nThe scan is defined by providing a list of points for each scan trajectory.\nWraps bluesky.plans.rel_list_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -284,7 +324,7 @@ { "name": "list_scan", "description": "Scan concurrent single or multi-motor trajector(y/ies).\n\nThe scan is defined by providing a list of points for each scan trajectory.\nWraps bluesky.plans.list_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -351,10 +391,88 @@ "type": "object" } }, + { + "name": "mv", + "description": "\nMove one or more devices to a setpoint. Wait for all to complete.\n\nIf more than one device is specified, the movements are done in parallel.\n\nParameters\n----------\nargs :\n device1, value1, device2, value2, ...\ngroup : string, optional\n Used to mark these as a unit to be waited on.\ntimeout : float, optional\n Specify a maximum time that the move(s) can be waited for.\nkwargs :\n passed to obj.set()\n\nYields\n------\nmsg : Msg\n\nReturns\n-------\nstatuses :\n Tuple of n statuses, one for each move operation\n\nSee Also\n--------\n:func:`bluesky.plan_stubs.abs_set`\n:func:`bluesky.plan_stubs.mvr`\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "args": { + "items": { + "anyOf": [ + { + "enum": [], + "type": "bluesky.protocols.Movable", + "types": [ + "typing.Any" + ] + }, + {} + ] + }, + "title": "Args", + "type": "array" + }, + "group": { + "title": "Group" + }, + "timeout": { + "title": "Timeout", + "type": "number" + }, + "kwargs": { + "additionalProperties": true, + "title": "Kwargs", + "type": "object" + } + }, + "title": "mv", + "type": "object" + } + }, + { + "name": "mvr", + "description": "\nMove one or more devices to a relative setpoint. Wait for all to complete.\n\nIf more than one device is specified, the movements are done in parallel.\n\nParameters\n----------\nargs :\n device1, value1, device2, value2, ...\ngroup : string, optional\n Used to mark these as a unit to be waited on.\ntimeout : float, optional\n Specify a maximum time that the move(s) can be waited for.\nkwargs :\n passed to obj.set()\n\nYields\n------\nmsg : Msg\n\nReturns\n-------\nstatuses :\n Tuple of n statuses, one for each move operation\n\nSee Also\n--------\n:func:`bluesky.plan_stubs.rel_set`\n:func:`bluesky.plan_stubs.mv`\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "args": { + "items": { + "anyOf": [ + { + "enum": [], + "type": "bluesky.protocols.Movable", + "types": [ + "typing.Any" + ] + }, + {} + ] + }, + "title": "Args", + "type": "array" + }, + "group": { + "title": "Group" + }, + "timeout": { + "title": "Timeout", + "type": "number" + }, + "kwargs": { + "additionalProperties": true, + "title": "Kwargs", + "type": "object" + } + }, + "title": "mvr", + "type": "object" + } + }, { "name": "num_grid_rscan", "description": "Scan independent trajectories, relative to current positions.\n\nThe scan is defined by number of points along scan trajectories. Snakes all fast\naxes by default (all axes but the first axis provided). Wraps\nbluesky.plans.rel_grid_scan(det, *args, snake_axes, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -437,7 +555,7 @@ { "name": "num_grid_scan", "description": "Scan independent multi-motor trajectories.\n\nThe scan is defined by number of points along scan trajectories. Snakes all fast\naxes by default (all axes but the first axis provided). Wraps\nbluesky.plans.grid_scan(det, *args, snake_axes, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -520,7 +638,7 @@ { "name": "num_rscan", "description": "Scan concurrent trajector(y/ies), relative to current position(s).\n\nThe scan is defined by number of points along scan trajector(y/ies). Wraps\nbluesky.plans.rel_scan(det, *args, num, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -594,7 +712,7 @@ { "name": "num_scan", "description": "Scan concurrent single or multi-motor trajector(y/ies).\n\nThe scan is defined by number of points along scan trajector(y/ies). Wraps\nbluesky.plans.scan(det, *args, num, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -666,10 +784,94 @@ "type": "object" } }, + { + "name": "rd", + "description": "Reads a single-value non-triggered object\n\nThis is a helper plan to get the scalar value out of a Device\n(such as an EpicsMotor or a single EpicsSignal).\n\nFor devices that implement the Locatable protocol, the location is canonical\nand is returned without parsing the read keys.\n\nFor devices that have more than one read key the following rules are used:\n\n- if exactly 1 field is hinted that value is used\n- if no fields are hinted and there is exactly 1 value in the\n reading that value is used\n- if more than one field is hinted an Exception is raised\n- if no fields are hinted and there is more than one key in the reading an\n Exception is raised\n\nThe devices is not triggered and this plan does not create any Events\n\nParameters\n----------\nobj : Device\n The device to be read\n\ndefault_value : Any\n The value to return when not running in a \"live\" RunEngine.\n This come ups when ::\n\n ret = yield Msg('read', obj)\n assert ret is None\n\n the plan is passed to `list` or some other iterator that\n repeatedly sends `None` into the plan to advance the\n generator.\n\nReturns\n-------\nval : Any or None\n The \"single\" value of the device\n\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "obj": { + "enum": [ + "det", + "stage" + ], + "title": "Obj", + "type": "bluesky.protocols.Readable" + }, + "default_value": { + "default": 0, + "title": "Default Value" + } + }, + "required": [ + "obj" + ], + "title": "rd", + "type": "object" + } + }, + { + "name": "rel_set", + "description": "\nSet a value relative to current value. Optionally, wait before continuing.\n\nParameters\n----------\nobj : Device\nargs :\n passed to obj.set()\ngroup : string (or any hashable object), optional\n identifier used by 'wait'; None by default\nwait : boolean, optional\n If True, wait for completion before processing any more messages.\n False by default.\nkwargs :\n passed to obj.set()\n\nYields\n------\nmsg : Msg\n\nReturns\n-------\nstatus :\n Status that completes when the value is set. If `wait` is True,\n this will always be complete by the time it is returned.\n\nSee Also\n--------\n:func:`bluesky.plan_stubs.abs_set`\n:func:`bluesky.plan_stubs.wait`\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "obj": { + "enum": [], + "title": "Obj", + "type": "bluesky.protocols.Movable", + "types": [ + "typing.Any" + ] + }, + "args": { + "items": {}, + "title": "Args", + "type": "array" + }, + "group": { + "title": "Group" + }, + "wait": { + "default": false, + "title": "Wait", + "type": "boolean" + }, + "kwargs": { + "additionalProperties": true, + "title": "Kwargs", + "type": "object" + } + }, + "required": [ + "obj" + ], + "title": "rel_set", + "type": "object" + } + }, + { + "name": "sleep", + "description": "\nTell the RunEngine to sleep, while asynchronously doing other processing.\n\nThis is not the same as ``import time; time.sleep()`` because it allows\nother actions, like interruptions, to be processed during the sleep.\n\nParameters\n----------\ntime : float\n seconds\n\nYields\n------\nmsg : Msg\n Msg('sleep', None, time)\n", + "parameter_schema": { + "additionalProperties": false, + "properties": { + "time": { + "title": "Time", + "type": "number" + } + }, + "required": [ + "time" + ], + "title": "sleep", + "type": "object" + } + }, { "name": "spec_scan", "description": "Generic plan for reading `detectors` at every point of a ScanSpec `Spec`.\nA `Spec` is an N-dimensional path.\n", - "schema": { + "parameter_schema": { "$defs": { "Concat_Reference_": { "additionalProperties": false, @@ -1312,7 +1514,7 @@ { "name": "step_grid_rscan", "description": "Scan independent trajectories with specified step size, relative to position.\n\nGenerates list(s) of points for each trajectory, used with\nbluesky.plans.list_grid_scan(det, *args, md=metadata). Snakes all fast axes by\ndefault (all axes but the first axis provided).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -1387,7 +1589,7 @@ { "name": "step_grid_scan", "description": "Scan independent trajectories with specified step size.\n\nGenerates list(s) of points for each trajectory, used with\nbluesky.plans.list_grid_scan(det, *args, md=metadata). Snakes all fast axes by\ndefault (all axes but the first axis provided).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -1462,7 +1664,7 @@ { "name": "step_rscan", "description": "Scan concurrent trajectories with specified step size, relative to position.\n\nGenerates list(s) of points for each trajectory, used with\nbluesky.plans.rel_list_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -1532,7 +1734,7 @@ { "name": "step_scan", "description": "Scan concurrent trajectories with specified step size.\n\nGenerates list(s) of points for each trajectory, used with\nbluesky.plans.list_scan(det, *args, md=metadata).\n", - "schema": { + "parameter_schema": { "additionalProperties": false, "properties": { "detectors": { @@ -1600,198 +1802,54 @@ } }, { - "name": "set_absolute", - "description": "Set a device, wrapper for `bp.abs_set`.\n\nArgs:\n movable (Movable[T]): The device to set.\n value (T): The new value.\n group (Group | None, optional): The message group to associate with the setting,\n for sequencing. Defaults to None.\n wait (bool, optional): The group should wait until all setting is complete (e.g.\n a motor has finished moving). Defaults to False.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "movable": { - "enum": [], - "title": "Movable", - "type": "bluesky.protocols.Movable", - "types": [ - "Any" - ] - }, - "value": { - "title": "Value" - }, - "group": { - "title": "Group", - "type": "string" - }, - "wait": { - "default": true, - "title": "Wait", - "type": "boolean" - } - }, - "required": [ - "movable", - "value" - ], - "title": "set_absolute", - "type": "object" - } - }, - { - "name": "set_relative", - "description": "Change a device, wrapper for `bp.rel_set`.\n\nArgs:\n movable (Movable): The device to set.\n value (T): The new value.\n group (Group | None, optional): The message group to associate with the setting,\n for sequencing. Defaults to None.\n wait (bool, optional): The group should wait until all setting is complete (e.g.\n a motor has finished moving). Defaults to False.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { + "name": "stop", + "description": "\nStop a device.\n\nParameters\n----------\nobj : Device\n\nYields\n------\nmsg : Msg\n", + "parameter_schema": { "additionalProperties": false, "properties": { - "movable": { + "obj": { "enum": [], - "title": "Movable", - "type": "bluesky.protocols.Movable", - "types": [ - "Any" - ] - }, - "value": { - "title": "Value" - }, - "group": { - "title": "Group", - "type": "string" - }, - "wait": { - "default": true, - "title": "Wait", - "type": "boolean" - } - }, - "required": [ - "movable", - "value" - ], - "title": "set_relative", - "type": "object" - } - }, - { - "name": "move", - "description": "Move a device, wrapper for `bp.mv`.\n\nArgs:\n moves (Mapping[Movable, T]): Mapping of Movables to target positions.\n group (Group | None, optional): The message group to associate with the setting,\n for sequencing. Defaults to None.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "moves": { - "additionalProperties": true, - "title": "Moves", - "type": "object" - }, - "group": { - "title": "Group", - "type": "string" - } - }, - "required": [ - "moves" - ], - "title": "move", - "type": "object" - } - }, - { - "name": "move_relative", - "description": "Move a device relative to its current position, wrapper for `bp.mvr`.\n\nArgs:\n moves (Mapping[Movable, T]): Mapping of Movables to target deltas.\n group (Group | None, optional): The message group to associate with the\n setting, for sequencing. Defaults to None.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "moves": { - "additionalProperties": true, - "title": "Moves", - "type": "object" - }, - "group": { - "title": "Group", - "type": "string" - } - }, - "required": [ - "moves" - ], - "title": "move_relative", - "type": "object" - } - }, - { - "name": "sleep", - "description": "Suspend all action for a given time, wrapper for `bp.sleep`.\n\nArgs:\n time (float): Time to wait in seconds.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "time": { - "title": "Time", - "type": "number" + "title": "Obj", + "type": "bluesky.protocols.Stoppable" } }, "required": [ - "time" + "obj" ], - "title": "sleep", + "title": "stop", "type": "object" } }, { "name": "wait", - "description": "Wait for a group status to complete, wrapper for `bp.wait`.\nDoes not expose move_on, as when used as a stub will not fail on Timeout.\n\nArgs:\n group (Group | None, optional): The name of the group to wait for, defaults to\n None, in which case waits for all groups that have not yet been awaited.\n timeout (float | None, default=None): a timeout in seconds.\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { + "description": "\nWait for all statuses in a group to report being finished.\n\nParameters\n----------\ngroup : string (or any hashable object), optional\n Identifier given to `abs_set`, `rel_set`, `trigger`; None by default\ntimeout : float, optional\n The maximum duration, in seconds, to wait for all objects in the group to complete.\n If the timeout expires and `error_on_timeout` is set to True, a TimeoutError is raised.\n\nerror_on_timeout : bool, Defaults to True\n Specifies the behavior when the timeout is reached:\n - If True, a TimeoutError is raised if the operations do not complete within the specified timeout.\n - If False, the method returns once all objects are done.\nwatch : set of watch groups, optional\n Additional groups to monitor while waiting for the primary group. Raises an exception if any watched group\n fails.\nYields\n------\nmsg : Msg\n Msg('wait', None, group=group, error_on_timeout=error_on_timeout, timeout=timeout)\n", + "parameter_schema": { "additionalProperties": false, "properties": { "group": { - "title": "Group", - "type": "string" + "title": "Group" }, "timeout": { "title": "Timeout", "type": "number" + }, + "error_on_timeout": { + "default": true, + "title": "Error On Timeout", + "type": "boolean" + }, + "watch": { + "default": [], + "items": { + "type": "string" + }, + "title": "Watch", + "type": "array" } }, "title": "wait", "type": "object" } - }, - { - "name": "rd", - "description": "Reads a single-value non-triggered object, wrapper for `bps.rd`.\n\nArgs:\n readable (Readable): The device to be read\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "readable": { - "enum": [ - "det", - "stage" - ], - "title": "Readable", - "type": "bluesky.protocols.Readable" - } - }, - "required": [ - "readable" - ], - "title": "rd", - "type": "object" - } - }, - { - "name": "stop", - "description": "Stop a device, wrapper for `bps.stop`.\n\nArgs:\n stoppable (Stoppable): Device to be stopped\n\nReturns:\n MsgGenerator: Plan.\n\nYields:\n Iterator[MsgGenerator]: Bluesky messages.\n", - "schema": { - "additionalProperties": false, - "properties": { - "stoppable": { - "enum": [], - "title": "Stoppable", - "type": "bluesky.protocols.Stoppable" - } - }, - "required": [ - "stoppable" - ], - "title": "stop", - "type": "object" - } } ] } diff --git a/tests/system_tests/test_blueapi_client.py b/tests/system_tests/test_blueapi_client.py index f16e267cf..a3491b12c 100644 --- a/tests/system_tests/test_blueapi_client.py +++ b/tests/system_tests/test_blueapi_client.py @@ -169,7 +169,30 @@ def test_missing_sub_device(bc: BlueapiClient): def test_plans_iterable(bc: BlueapiClient): names = {plan.name for plan in bc.plans} - assert {"count", "sleep", "set_absolute"} <= names + assert { + "list_rscan", + "rel_set", + "step_rscan", + "sleep", + "list_scan", + "mv", + "step_scan", + "abs_set", + "list_grid_scan", + "list_grid_rscan", + "count", + "step_grid_rscan", + "mvr", + "rd", + "spec_scan", + "num_grid_scan", + "wait", + "num_scan", + "num_grid_rscan", + "num_rscan", + "step_grid_scan", + "stop", + } <= names assert all(isinstance(plan, Plan) for plan in bc.plans) @@ -236,7 +259,7 @@ def test_run_plan_keyword_args(bc: BlueapiClient): def test_run_stub_with_device(bc: BlueapiClient): - bc.plans.set_absolute(bc.devices.stage.x, 1.0) + bc.plans.abs_set(bc.devices.stage.x, 1.0) def test_removed_callback_not_called(bc: BlueapiClient): diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index 9feda470c..b3bdb03e1 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -659,11 +659,8 @@ def on_event(event: AnyEvent) -> None: "task", [ TaskRequest( - name="set_absolute", - params={ - "movable": "stage.x", - "value": 1.0, - }, + name="abs_set", + args=["stage.x", 1.0], instrument_session=VALID_INSTRUMENT_SESSION[User.alice], ), ], 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 db4e68760..823b282b6 100644 --- a/tests/unit_tests/service/test_interface.py +++ b/tests/unit_tests/service/test_interface.py @@ -146,7 +146,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 115ae5aae..44534fb2e 100644 --- a/tests/unit_tests/service/test_rest_api.py +++ b/tests/unit_tests/service/test_rest_api.py @@ -147,7 +147,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 62a751355..6973deb66 100644 --- a/uv.lock +++ b/uv.lock @@ -3,16 +3,16 @@ revision = 5 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.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] @@ -485,6 +485,7 @@ dependencies = [ { name = "aiohttp" }, { name = "bluesky", extra = ["plotting"] }, { name = "bluesky-stomp" }, + { name = "bluesky-tiled-plugins" }, { name = "click" }, { name = "event-model" }, { name = "fastapi" }, @@ -551,8 +552,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 = ">=3.0.2" }, { name = "event-model", specifier = "==1.24.0" }, @@ -614,8 +616,8 @@ dev = [ [[package]] name = "bluesky" -version = "1.15.1" -source = { registry = "https://pypi.org/simple" } +version = "1.15.2.dev287+gc3b883b5c" +source = { git = "https://github.com/bluesky/bluesky.git?rev=main#c3b883b5cf5ef15cacad622c4c17cc66cb29089b" } dependencies = [ { name = "cycler" }, { name = "event-model" }, @@ -629,10 +631,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 = [ @@ -655,6 +653,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" @@ -1082,10 +1097,10 @@ version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -1172,10 +1187,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 = [ @@ -3343,6 +3358,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.4" @@ -3713,10 +3740,10 @@ version = "3.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } @@ -3730,10 +3757,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'", ] sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482, upload-time = "2026-09-21T16:45:16.974Z" } @@ -3862,10 +3889,10 @@ version = "2.4.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } @@ -3949,10 +3976,10 @@ version = "2.5.4" 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/95/b0/c7453d0b6e2073c3264468b106ee1563750cecc910965e67357e3698c83e/numpy-2.5.4.tar.gz", hash = "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", size = 20866315, upload-time = "2026-10-10T20:05:31.422Z" } @@ -4642,10 +4669,10 @@ version = "0.25.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -4665,10 +4692,10 @@ version = "0.26.2" 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 = [ @@ -5481,6 +5508,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" @@ -6190,10 +6226,10 @@ version = "3.0.4" 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'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", @@ -6224,10 +6260,10 @@ version = "9.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -6292,8 +6328,8 @@ version = "9.2.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.14' and sys_platform != 'emscripten'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", "python_full_version == '3.13.*' and sys_platform != 'emscripten'", ] dependencies = [ @@ -6768,6 +6804,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/21/1e5995a1c920cce14e4bffae20c665ec10e7ed03ab25e006cd741092b718/tzdata-2026.5-py2.py3-none-any.whl", hash = "sha256:b683bd1b6659ddcd810ff02ad09ba821d4bf1065072805063eb35c49617905ac", size = 347996, upload-time = "2026-10-03T09:23:12.535Z" }, ] +[[package]] +name = "tzlocal" +version = "5.4.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/89/2bd035df331499589fc92e59375c17c6cea6a500453b2c5f6a6ec9572825/tzlocal-5.4.5.tar.gz", hash = "sha256:ba976898bad27945178c9ca25c222c19e322b30c10342b9f40b46714f70d2d5f", size = 31483, upload-time = "2026-10-22T09:09:26.38Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/c1/9be4e1a8a30db8729a86628073910e56f85aaf8a659e053f376a2e6ea5a8/tzlocal-5.4.5-py3-none-any.whl", hash = "sha256:ecab534f6d65d9deb4c4ea52c121ab6dd5958e9b878e71dcb102578269752454", size = 18215, upload-time = "2026-10-22T09:09:24.447Z" }, +] + [[package]] name = "urllib3" version = "2.8.0" @@ -7160,10 +7208,10 @@ version = "3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.11.5' and sys_platform == 'win32'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version >= '3.11.5' and python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'win32'", + "python_full_version < '3.11.5' and sys_platform == 'emscripten'", "python_full_version < '3.11.5' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] dependencies = [ @@ -7186,10 +7234,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 = [