diff --git a/.vscode/launch.json b/.vscode/launch.json index c9e8f73..97d952d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,27 +8,29 @@ "name": "Eiger IOC", "type": "debugpy", "request": "launch", - "module": "fastcs_eiger", "justMyCode": false, - "console": "integratedTerminal", + "module": "fastcs_eiger", "args": [ - "ioc", - "EIGER", - ] + "run", + "${workspaceFolder:fastcs-eiger}/src/fastcs_eiger/fastcs-eiger.yaml", + "--log-level", + "TRACE", + ], + "console": "integratedTerminal", }, { "name": "Eiger Odin IOC", "type": "debugpy", "request": "launch", - "module": "fastcs_eiger", "justMyCode": false, - "console": "integratedTerminal", + "module": "fastcs_eiger", "args": [ - "ioc", - "EIGER", - "--odin-ip", - "127.0.0.1", - ] + "run", + "${workspaceFolder:fastcs-eiger}/src/fastcs_eiger/fastcs-eiger-odin.yaml", + "--log-level", + "TRACE", + ], + "console": "integratedTerminal", }, { "name": "Eiger Asyncio", @@ -37,7 +39,9 @@ "module": "fastcs_eiger", "justMyCode": false, "console": "integratedTerminal", - "args": ["asyncio"] + "args": [ + "asyncio" + ] }, { "name": "Debug Unit Test", diff --git a/pyproject.toml b/pyproject.toml index 9c9969e..e6689ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ classifiers = [ description = "Eiger control system integration with FastCS" dependencies = [ "aiohttp", - "fastcs-odin~=0.9.0", + "fastcs-odin @ git+https://github.com/DiamondLightSource/fastcs-odin.git@update_to_fastcs_0.14.0", "numpy", "pillow", "typer", diff --git a/src/fastcs_eiger/__main__.py b/src/fastcs_eiger/__main__.py index e2dc962..19e9b54 100644 --- a/src/fastcs_eiger/__main__.py +++ b/src/fastcs_eiger/__main__.py @@ -1,88 +1,8 @@ -from pathlib import Path -from typing import Optional - -import softioc.pvlog # noqa: F401 -import typer -from fastcs.connections import IPConnectionSettings -from fastcs.launch import FastCS -from fastcs.logging import LogLevel, configure_logging, intercept_std_logger -from fastcs.transports.epics import EpicsGUIOptions, EpicsIOCOptions -from fastcs.transports.epics.ca.transport import EpicsCATransport +from fastcs.launch import launch from fastcs_eiger import __version__ -from fastcs_eiger.controllers.eiger_controller import EigerController -from fastcs_eiger.controllers.odin.eiger_odin_controller import EigerOdinController -from fastcs_eiger.eiger_parameter import EigerAPIVersion - -__all__ = ["main"] - - -app = typer.Typer() - - -def version_callback(value: bool): - if value: - typer.echo(__version__) - raise typer.Exit() - - -@app.callback() -def main( - # TODO: typer does not support `bool | None` yet - # https://github.com/tiangolo/typer/issues/533 - version: Optional[bool] = typer.Option( # noqa - None, - "--version", - callback=version_callback, - is_eager=True, - help="Print the version and exit", - ), -): - pass - - -OPI_PATH = Path("/epics/opi") - - -@app.command() -def ioc( - pv_prefix: str = typer.Argument(), - ip: str = typer.Option("127.0.0.1", help="IP address of Eiger detector"), - port: int = typer.Option(8081, help="Port of Eiger HTTP server"), - api_version: EigerAPIVersion = typer.Option("1.8.0", help="Version of Eiger API"), # noqa: B008 - odin_ip: str | None = typer.Option(None, help="IP address of odin control server"), - odin_port: int = typer.Option(8888, help="Port of odin control server"), - log_level: LogLevel = LogLevel.TRACE, -): - ui_path = OPI_PATH if OPI_PATH.is_dir() else Path.cwd() / "opi" - - configure_logging(log_level) - intercept_std_logger("root") - - if odin_ip is None: - controller = EigerController( - connection_settings=IPConnectionSettings(ip=ip, port=port), - api_version=api_version, - ) - else: - controller = EigerOdinController( - detector_connection_settings=IPConnectionSettings(ip=ip, port=port), - odin_connection_settings=IPConnectionSettings(ip=odin_ip, port=odin_port), - api_version=api_version, - ) - - transports = [ - EpicsCATransport( - epicsca=EpicsIOCOptions(pv_prefix=pv_prefix), - gui=EpicsGUIOptions( - output_path=ui_path / "eiger.bob", title=f"Eiger - {pv_prefix}" - ), - ), - ] - launcher = FastCS(controller, transports) - launcher.run() +from .controllers.eiger_controller import EigerController +from .controllers.odin.eiger_odin_controller import EigerOdinController -# test with: python -m fastcs_eiger -if __name__ == "__main__": - app() +launch(controller_classes=[EigerController, EigerOdinController], version=__version__) diff --git a/src/fastcs_eiger/controllers/eiger_controller.py b/src/fastcs_eiger/controllers/eiger_controller.py index 1d2eea9..e3cd216 100644 --- a/src/fastcs_eiger/controllers/eiger_controller.py +++ b/src/fastcs_eiger/controllers/eiger_controller.py @@ -1,5 +1,6 @@ import asyncio from collections.abc import Coroutine +from dataclasses import dataclass from fastcs.attributes import AttrR, AttrRW from fastcs.connections import IPConnectionSettings @@ -18,6 +19,12 @@ COMMAND_GROUP = "Command" +@dataclass +class EigerControllerSettings: + connection_settings: IPConnectionSettings + api_version: EigerAPIVersion + + class EigerController(Controller): """Root controller for Eiger detectors @@ -37,16 +44,14 @@ class EigerController(Controller): group=COMMAND_GROUP, ) - def __init__( - self, connection_settings: IPConnectionSettings, api_version: EigerAPIVersion - ) -> None: + def __init__(self, settings: EigerControllerSettings) -> None: super().__init__() - self.connection_settings = connection_settings + self.connection_settings = settings.connection_settings - self.connection = HTTPConnection(connection_settings) + self.connection = HTTPConnection(settings.connection_settings) self._parameter_update_lock = asyncio.Lock() self.queue = asyncio.Queue() - self._api_version: EigerAPIVersion = api_version + self._api_version: EigerAPIVersion = settings.api_version async def initialise(self) -> None: """Create attributes by introspecting detector. diff --git a/src/fastcs_eiger/controllers/odin/eiger_odin_controller.py b/src/fastcs_eiger/controllers/odin/eiger_odin_controller.py index 4aef392..450f266 100644 --- a/src/fastcs_eiger/controllers/odin/eiger_odin_controller.py +++ b/src/fastcs_eiger/controllers/odin/eiger_odin_controller.py @@ -1,13 +1,23 @@ import asyncio +from dataclasses import dataclass from fastcs.attributes import AttrRW from fastcs.connections import IPConnectionSettings from fastcs.datatypes import Bool, Int from fastcs.methods import command +from fastcs_odin.controllers.odin_controller import OdinControllerSettings -from fastcs_eiger.controllers.eiger_controller import COMMAND_GROUP, EigerController +from fastcs_eiger.controllers.eiger_controller import ( + COMMAND_GROUP, + EigerController, + EigerControllerSettings, +) from fastcs_eiger.controllers.odin.odin_controller import OdinController -from fastcs_eiger.eiger_parameter import EigerAPIVersion + + +@dataclass +class EigerOdinControllerSettings(EigerControllerSettings): + odin_connection_settings: IPConnectionSettings class EigerOdinController(EigerController): @@ -21,15 +31,14 @@ class EigerOdinController(EigerController): ) enable_vds_creation = AttrRW(Bool()) - def __init__( - self, - detector_connection_settings: IPConnectionSettings, - odin_connection_settings: IPConnectionSettings, - api_version: EigerAPIVersion, - ) -> None: - super().__init__(detector_connection_settings, api_version) + def __init__(self, settings: EigerOdinControllerSettings) -> None: + super().__init__( + EigerControllerSettings(settings.connection_settings, settings.api_version) + ) - self.OD = OdinController(odin_connection_settings) + self.OD = OdinController( + OdinControllerSettings(settings.odin_connection_settings) + ) async def initialise(self) -> None: """Initialise eiger controller and odin controller""" diff --git a/src/fastcs_eiger/fastcs-eiger-odin.yaml b/src/fastcs_eiger/fastcs-eiger-odin.yaml new file mode 100644 index 0000000..7b25c31 --- /dev/null +++ b/src/fastcs_eiger/fastcs-eiger-odin.yaml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=schema.json +controllers: + - id: EIGER + type: fastcs_eiger.EigerOdinController + connection_settings: + ip: "localhost" + port: 8081 + odin_connection_settings: + ip: "localhost" + port: 8888 + api_version: "1.8.0" +transport: + - epicsca: {} + gui: + title: "Eiger - EIGER" + output_dir: ./opi/ diff --git a/src/fastcs_eiger/fastcs-eiger.yaml b/src/fastcs_eiger/fastcs-eiger.yaml new file mode 100644 index 0000000..aa72165 --- /dev/null +++ b/src/fastcs_eiger/fastcs-eiger.yaml @@ -0,0 +1,13 @@ +# yaml-language-server: $schema=schema.json +controllers: + - id: EIGER + type: fastcs_eiger.EigerController + connection_settings: + ip: "localhost" + port: 8081 + api_version: "1.8.0" +transport: + - epicsca: {} + gui: + title: "Eiger - EIGER" + output_dir: ./opi/ diff --git a/src/fastcs_eiger/schema.json b/src/fastcs_eiger/schema.json new file mode 100644 index 0000000..800e4f7 --- /dev/null +++ b/src/fastcs_eiger/schema.json @@ -0,0 +1,311 @@ +{ + "$defs": { + "EigerControllerEntry": { + "additionalProperties": false, + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "type": { + "const": "fastcs_eiger.EigerController", + "title": "Type", + "type": "string" + }, + "connection_settings": { + "$ref": "#/$defs/IPConnectionSettings" + }, + "api_version": { + "enum": [ + "1.6.0", + "1.8.0" + ], + "title": "Api Version", + "type": "string" + } + }, + "required": [ + "id", + "type", + "connection_settings", + "api_version" + ], + "title": "EigerControllerEntry", + "type": "object" + }, + "EigerOdinControllerEntry": { + "additionalProperties": false, + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "type": { + "const": "fastcs_eiger.EigerOdinController", + "title": "Type", + "type": "string" + }, + "connection_settings": { + "$ref": "#/$defs/IPConnectionSettings" + }, + "api_version": { + "enum": [ + "1.6.0", + "1.8.0" + ], + "title": "Api Version", + "type": "string" + }, + "odin_connection_settings": { + "$ref": "#/$defs/IPConnectionSettings" + } + }, + "required": [ + "id", + "type", + "connection_settings", + "api_version", + "odin_connection_settings" + ], + "title": "EigerOdinControllerEntry", + "type": "object" + }, + "EpicsCAOptions": { + "additionalProperties": false, + "properties": { + "aliases": { + "additionalProperties": { + "type": "string" + }, + "title": "Aliases", + "type": "object" + } + }, + "title": "EpicsCAOptions", + "type": "object" + }, + "EpicsCATransport": { + "additionalProperties": false, + "properties": { + "epicsca": { + "$ref": "#/$defs/EpicsCAOptions" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/$defs/EpicsDocsOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "gui": { + "anyOf": [ + { + "$ref": "#/$defs/EpicsGUIOptions" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "title": "EpicsCATransport", + "type": "object" + }, + "EpicsDocsOptions": { + "properties": { + "output_dir": { + "default": ".", + "format": "path", + "title": "Output Dir", + "type": "string" + }, + "title": { + "default": "FastCS Devices", + "title": "Title", + "type": "string" + }, + "depth": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Depth" + } + }, + "title": "EpicsDocsOptions", + "type": "object" + }, + "EpicsGUIFormat": { + "description": "The format of an EPICS GUI.", + "enum": [ + ".bob", + ".edl" + ], + "title": "EpicsGUIFormat", + "type": "string" + }, + "EpicsGUIOptions": { + "properties": { + "output_dir": { + "default": ".", + "format": "path", + "title": "Output Dir", + "type": "string" + }, + "file_format": { + "$ref": "#/$defs/EpicsGUIFormat", + "default": ".bob" + }, + "title": { + "default": "FastCS Devices", + "title": "Title", + "type": "string" + } + }, + "title": "EpicsGUIOptions", + "type": "object" + }, + "EpicsPVAOptions": { + "additionalProperties": false, + "properties": {}, + "title": "EpicsPVAOptions", + "type": "object" + }, + "EpicsPVATransport": { + "additionalProperties": false, + "properties": { + "epicspva": { + "$ref": "#/$defs/EpicsPVAOptions" + }, + "docs": { + "anyOf": [ + { + "$ref": "#/$defs/EpicsDocsOptions" + }, + { + "type": "null" + } + ], + "default": null + }, + "gui": { + "anyOf": [ + { + "$ref": "#/$defs/EpicsGUIOptions" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "title": "EpicsPVATransport", + "type": "object" + }, + "IPConnectionSettings": { + "properties": { + "ip": { + "default": "127.0.0.1", + "title": "Ip", + "type": "string" + }, + "port": { + "default": 25565, + "title": "Port", + "type": "integer" + } + }, + "title": "IPConnectionSettings", + "type": "object" + }, + "RestServerOptions": { + "properties": { + "host": { + "default": "localhost", + "title": "Host", + "type": "string" + }, + "port": { + "default": 8080, + "title": "Port", + "type": "integer" + }, + "log_level": { + "default": "info", + "title": "Log Level", + "type": "string" + } + }, + "title": "RestServerOptions", + "type": "object" + }, + "RestTransport": { + "additionalProperties": false, + "properties": { + "rest": { + "$ref": "#/$defs/RestServerOptions" + } + }, + "title": "RestTransport", + "type": "object" + } + }, + "additionalProperties": false, + "properties": { + "controllers": { + "items": { + "discriminator": { + "mapping": { + "fastcs_eiger.EigerController": "#/$defs/EigerControllerEntry", + "fastcs_eiger.EigerOdinController": "#/$defs/EigerOdinControllerEntry" + }, + "propertyName": "type" + }, + "oneOf": [ + { + "$ref": "#/$defs/EigerControllerEntry" + }, + { + "$ref": "#/$defs/EigerOdinControllerEntry" + } + ] + }, + "title": "Controllers", + "type": "array" + }, + "transport": { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/EpicsCATransport" + }, + { + "$ref": "#/$defs/EpicsPVATransport" + }, + { + "$ref": "#/$defs/RestTransport" + } + ] + }, + "title": "Transport", + "type": "array" + } + }, + "required": [ + "controllers", + "transport" + ], + "title": "FastCS", + "type": "object" +} diff --git a/tests/conftest.py b/tests/conftest.py index 5e6ce84..40f8f0a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,10 @@ from fastcs.connections import IPConnectionSettings from pytest_mock import MockerFixture -from fastcs_eiger.controllers.eiger_controller import EigerController +from fastcs_eiger.controllers.eiger_controller import ( + EigerController, + EigerControllerSettings, +) # Stolen from tickit-devices @@ -40,7 +43,9 @@ def sim_eiger(request): @pytest.fixture def mock_connection(mocker: MockerFixture): eiger_controller = EigerController( - IPConnectionSettings("127.0.0.1", 80), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) connection = mocker.patch.object(eiger_controller, "connection") connection.get = mock.AsyncMock() diff --git a/tests/system/test_eiger_introspection.py b/tests/system/test_eiger_introspection.py index 04c68c5..07a3862 100644 --- a/tests/system/test_eiger_introspection.py +++ b/tests/system/test_eiger_introspection.py @@ -11,7 +11,10 @@ from pydantic import ValidationError from pytest_mock import MockerFixture -from fastcs_eiger.controllers.eiger_controller import EigerController +from fastcs_eiger.controllers.eiger_controller import ( + EigerController, + EigerControllerSettings, +) from fastcs_eiger.controllers.eiger_detector_controller import EigerDetectorController from fastcs_eiger.controllers.eiger_monitor_controller import EigerMonitorController from fastcs_eiger.controllers.eiger_stream_controller import EigerStreamController @@ -46,7 +49,9 @@ def _serialise_parameter(parameter: EigerParameterRef) -> dict: @pytest.mark.parametrize("sim_eiger", [str(HERE / "eiger.yaml")], indirect=True) async def test_attribute_creation(sim_eiger): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() serialised_parameters: dict[str, dict[str, Any]] = {} @@ -98,7 +103,9 @@ async def test_attribute_creation(sim_eiger): @pytest.mark.parametrize("sim_eiger", [str(HERE / "eiger.yaml")], indirect=True) async def test_controller_groups_and_parameters(sim_eiger): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() @@ -131,7 +138,9 @@ async def test_threshold_mode_api_inconsistency_handled( sim_eiger, mocker: MockerFixture ): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() @@ -164,7 +173,9 @@ async def test_fetch_before_returning_parameters(sim_eiger, mocker: MockerFixtur # Need to mock @scan to spy controller.update() with patch("fastcs_eiger.controllers.eiger_controller.scan"): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() @@ -218,7 +229,9 @@ async def test_stale_propagates_to_top_controller( sim_eiger, ): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() @@ -279,7 +292,9 @@ async def test_eiger_controller_trigger_correctly_introspected( mocker: MockerFixture, sim_eiger ): controller = EigerController( - IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + EigerControllerSettings( + IPConnectionSettings("127.0.0.1", 8081), api_version="1.8.0" + ) ) await controller.initialise() diff --git a/tests/test_cli.py b/tests/test_cli.py index 407aac6..60dc92b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -7,4 +7,4 @@ def test_cli_version(): cmd = [sys.executable, "-m", "fastcs_eiger", "--version"] stdout = subprocess.check_output(cmd).decode().strip().split("\n") - assert __version__ in stdout + assert any(__version__ in line for line in stdout) diff --git a/tests/test_eiger_odin_controller.py b/tests/test_eiger_odin_controller.py index 1859bfc..d3a9578 100644 --- a/tests/test_eiger_odin_controller.py +++ b/tests/test_eiger_odin_controller.py @@ -5,7 +5,10 @@ from pytest_mock import MockerFixture from fastcs_eiger.controllers.eiger_controller import EigerController -from fastcs_eiger.controllers.odin.eiger_odin_controller import EigerOdinController +from fastcs_eiger.controllers.odin.eiger_odin_controller import ( + EigerOdinController, + EigerOdinControllerSettings, +) from fastcs_eiger.controllers.odin.odin_controller import OdinController @@ -14,7 +17,11 @@ def eiger_odin_controller(mocker: MockerFixture): detector_connection_settings = IPConnectionSettings("127.0.0.1", 8000) odin_connection_settings = IPConnectionSettings("127.0.0.1", 8001) controller = EigerOdinController( - detector_connection_settings, odin_connection_settings, api_version="1.8.0" + EigerOdinControllerSettings( + connection_settings=detector_connection_settings, + odin_connection_settings=odin_connection_settings, + api_version="1.8.0", + ) ) controller.OD.file_path = AttrRW(String(), initial_value="/tmp/data") # pyright: ignore[reportAttributeAccessIssue] diff --git a/tests/test_odin_controller.py b/tests/test_odin_controller.py index a8b7c6c..17d3238 100644 --- a/tests/test_odin_controller.py +++ b/tests/test_odin_controller.py @@ -1,6 +1,7 @@ import pytest from fastcs.connections import IPConnectionSettings from fastcs_odin.controllers import MetaWriterAdapterController +from fastcs_odin.controllers.odin_controller import OdinControllerSettings from fastcs_odin.util import OdinParameter, OdinParameterMetadata from pytest_mock import MockerFixture @@ -13,7 +14,7 @@ @pytest.mark.asyncio async def test_create_adapter_controller(mocker: MockerFixture): - controller = OdinController(IPConnectionSettings("", 0)) + controller = OdinController(OdinControllerSettings(IPConnectionSettings("", 0))) controller.connection = mocker.AsyncMock() parameters = [ OdinParameter( diff --git a/uv.lock b/uv.lock index 757c3ac..bdb6818 100644 --- a/uv.lock +++ b/uv.lock @@ -960,7 +960,7 @@ wheels = [ [[package]] name = "fastcs" -version = "0.12.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aioserial" }, @@ -974,9 +974,9 @@ dependencies = [ { name = "ruamel-yaml" }, { name = "stdio-socket" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/00/00f9ccc779c5fa3e6cf8f3efd68d6f9b586cc0d3a0be6811ee9ea588467c/fastcs-0.12.0.tar.gz", hash = "sha256:79e643454e46b4e52d7c93b2edd4b82018ed15c3b674d0935597970fa495c27c", size = 335386, upload-time = "2026-03-09T13:04:08.991Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/5e/58bda0b55981560e0d0484baf5249c12c86792ded39d3ee240b03668d8a8/fastcs-0.14.0.tar.gz", hash = "sha256:633f504b02828a5df662039a7a0e9706dcd9b4c6926b74d5538e965b5dd6916b", size = 416304, upload-time = "2026-05-19T15:18:30.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/42/3b1d2ef39e46b38c025991f4d1ebfd6b0f65e6add0898ceced08ce40a2c0/fastcs-0.12.0-py3-none-any.whl", hash = "sha256:898ed85bcaa5f1ee22580139457f6ba43b7d3f956001a82157469b223b1930db", size = 83130, upload-time = "2026-03-09T13:04:07.526Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/98061586236deb4067ba87780cd6a622b4936b710090a31f26bf0488cb75/fastcs-0.14.0-py3-none-any.whl", hash = "sha256:878aa2e8a7d56c427f769df971dbc1124133125dd2d119b7db34d0dcf1e100f3", size = 94136, upload-time = "2026-05-19T15:18:29.691Z" }, ] [package.optional-dependencies] @@ -1021,7 +1021,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "aiohttp" }, - { name = "fastcs-odin", specifier = "~=0.9.0" }, + { name = "fastcs-odin", git = "https://github.com/DiamondLightSource/fastcs-odin.git?rev=update_to_fastcs_0.14.0" }, { name = "h5py" }, { name = "numpy" }, { name = "pillow" }, @@ -1051,15 +1051,12 @@ dev = [ [[package]] name = "fastcs-odin" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } +version = "0.9.1.dev9+g3bd3d9238" +source = { git = "https://github.com/DiamondLightSource/fastcs-odin.git?rev=update_to_fastcs_0.14.0#3bd3d9238bdaed4acdd90dc130114ccbaeff6f73" } dependencies = [ { name = "aiohttp" }, { name = "fastcs", extra = ["epicsca"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/7f/66997ceb65ef8ea4946f4c51e675ea0aca3f724def893769d0e4b9a9d7d5/fastcs_odin-0.9.0.tar.gz", hash = "sha256:819f45e2fa20ba1a80447374f47b54e01a377ba325c0a7db0a98d6e7c3decdc3", size = 261671, upload-time = "2026-03-09T18:00:54.629Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/75/f05356f837f02e76bb9bdab4b000d1335b9f9ad392d6ef9d336e57720a50/fastcs_odin-0.9.0-py3-none-any.whl", hash = "sha256:b25049f724cfdec11905caf9e7c0497535bb1df69b02b9b866ebdbbb8e8e85a6", size = 29703, upload-time = "2026-03-09T18:00:53.519Z" }, + { name = "h5py" }, ] [[package]]