-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use external service to configure ophyd-async detector data writing (#…
…315) Production version of #283 This PR allows coordination with a central service for creating unique groups of data (called collections) and configures ophyd-async detectors to write their data to the same location for a given collection. Changes: - Add mechanism to preprocess all plans with set bluesky [preprocessors](https://blueskyproject.io/bluesky/plans.html#plan-preprocessors) - Create directory provider that knows how to talk to GDA's visit directory API and provide a unique collection number to group data files - Create dummy directory provider that works in a similar way without the need for an external server (useful for development) - Create preprocessor that uses the directory provider and groups detectors by staging, also bundles the data group information into run start documents on a best effort basis. - Add tests --------- Co-authored-by: Rose Yemelyanova <[email protected]>
- Loading branch information
1 parent
c3b2c83
commit d80eca0
Showing
22 changed files
with
791 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,4 @@ | |
}, | ||
"esbonio.server.enabled": true, | ||
"esbonio.sphinx.confDir": "", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import logging | ||
from abc import ABC, abstractmethod | ||
from pathlib import Path | ||
from typing import Optional | ||
|
||
from aiohttp import ClientSession | ||
from ophyd_async.core import DirectoryInfo, DirectoryProvider | ||
from pydantic import BaseModel | ||
|
||
|
||
class DataCollectionIdentifier(BaseModel): | ||
collectionNumber: int | ||
|
||
|
||
class VisitServiceClientBase(ABC): | ||
""" | ||
Object responsible for I/O in determining collection number | ||
""" | ||
|
||
@abstractmethod | ||
async def create_new_collection(self) -> DataCollectionIdentifier: | ||
... | ||
|
||
@abstractmethod | ||
async def get_current_collection(self) -> DataCollectionIdentifier: | ||
... | ||
|
||
|
||
class VisitServiceClient(VisitServiceClientBase): | ||
_url: str | ||
|
||
def __init__(self, url: str) -> None: | ||
self._url = url | ||
|
||
async def create_new_collection(self) -> DataCollectionIdentifier: | ||
async with ClientSession() as session: | ||
async with session.post(f"{self._url}/numtracker") as response: | ||
if response.status == 200: | ||
json = await response.json() | ||
return DataCollectionIdentifier.parse_obj(json) | ||
else: | ||
raise Exception(response.status) | ||
|
||
async def get_current_collection(self) -> DataCollectionIdentifier: | ||
async with ClientSession() as session: | ||
async with session.get(f"{self._url}/numtracker") as response: | ||
if response.status == 200: | ||
json = await response.json() | ||
return DataCollectionIdentifier.parse_obj(json) | ||
else: | ||
raise Exception(response.status) | ||
|
||
|
||
class LocalVisitServiceClient(VisitServiceClientBase): | ||
_count: int | ||
|
||
def __init__(self) -> None: | ||
self._count = 0 | ||
|
||
async def create_new_collection(self) -> DataCollectionIdentifier: | ||
self._count += 1 | ||
return DataCollectionIdentifier(collectionNumber=self._count) | ||
|
||
async def get_current_collection(self) -> DataCollectionIdentifier: | ||
return DataCollectionIdentifier(collectionNumber=self._count) | ||
|
||
|
||
class VisitDirectoryProvider(DirectoryProvider): | ||
""" | ||
Gets information from a remote service to construct the path that detectors | ||
should write to, and determine how their files should be named. | ||
""" | ||
|
||
_data_group_name: str | ||
_data_directory: Path | ||
|
||
_client: VisitServiceClientBase | ||
_current_collection: Optional[DirectoryInfo] | ||
_session: Optional[ClientSession] | ||
|
||
def __init__( | ||
self, | ||
data_group_name: str, | ||
data_directory: Path, | ||
client: VisitServiceClientBase, | ||
): | ||
self._data_group_name = data_group_name | ||
self._data_directory = data_directory | ||
self._client = client | ||
|
||
self._current_collection = None | ||
self._session = None | ||
|
||
async def update(self) -> None: | ||
""" | ||
Calls the visit service to create a new data collection in the current visit. | ||
""" | ||
# TODO: After visit service is more feature complete: | ||
# TODO: Allow selecting visit as part of the request to BlueAPI | ||
# TODO: Consume visit information from BlueAPI and pass down to this class | ||
# TODO: Query visit service to get information about visit and data collection | ||
# TODO: Use AuthN information as part of verification with visit service | ||
|
||
try: | ||
collection_id_info = await self._client.create_new_collection() | ||
self._current_collection = self._generate_directory_info(collection_id_info) | ||
except Exception as ex: | ||
# TODO: The catch all is needed because the RunEngine will not | ||
# currently handle it, see | ||
# https://github.com/bluesky/bluesky/pull/1623 | ||
self._current_collection = None | ||
logging.exception(ex) | ||
|
||
def _generate_directory_info( | ||
self, | ||
collection_id_info: DataCollectionIdentifier, | ||
) -> DirectoryInfo: | ||
collection_id = collection_id_info.collectionNumber | ||
file_prefix = f"{self._data_group_name}-{collection_id}" | ||
return DirectoryInfo(str(self._data_directory), file_prefix) | ||
|
||
def __call__(self) -> DirectoryInfo: | ||
if self._current_collection is not None: | ||
return self._current_collection | ||
else: | ||
raise ValueError( | ||
"No current collection, update() needs to be called at least once" | ||
) |
Oops, something went wrong.