ophyd-async 0.12__py3-none-any.whl → 0.12.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ophyd_async/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.12'
21
- __version_tuple__ = version_tuple = (0, 12)
20
+ __version__ = version = '0.12.2'
21
+ __version_tuple__ = version_tuple = (0, 12, 2)
@@ -1,6 +1,4 @@
1
1
  from collections.abc import Iterator
2
- from pathlib import Path
3
- from urllib.parse import urlunparse
4
2
 
5
3
  from bluesky.protocols import StreamAsset
6
4
  from event_model import ( # type: ignore
@@ -48,22 +46,11 @@ class HDFDocumentComposer:
48
46
 
49
47
  def __init__(
50
48
  self,
51
- full_file_name: Path,
49
+ file_uri: str,
52
50
  datasets: list[HDFDatasetDescription],
53
- hostname: str = "localhost",
54
51
  ) -> None:
55
52
  self._last_emitted = 0
56
- self._hostname = hostname
57
- uri = urlunparse(
58
- (
59
- "file",
60
- self._hostname,
61
- str(full_file_name.absolute()),
62
- "",
63
- "",
64
- None,
65
- )
66
- )
53
+ uri = file_uri
67
54
  bundler_composer = ComposeStreamResource()
68
55
  self._bundles: list[ComposeStreamResourceBundle] = [
69
56
  bundler_composer(
@@ -1,11 +1,11 @@
1
- import os
2
1
  import uuid
3
2
  from abc import abstractmethod
4
3
  from collections.abc import Callable
5
4
  from dataclasses import dataclass
6
5
  from datetime import date
7
- from pathlib import Path
6
+ from pathlib import PurePath, PureWindowsPath
8
7
  from typing import Protocol
8
+ from urllib.parse import urlunparse
9
9
 
10
10
 
11
11
  @dataclass
@@ -16,11 +16,36 @@ class PathInfo:
16
16
  :param filename: Base filename to use generated by FilenameProvider, w/o extension
17
17
  :param create_dir_depth: Optional depth of directories to create if they do not
18
18
  exist
19
+ :param directory_uri: Optional URI to use for reading back resources. If not set,
20
+ it will be generated from the directory path.
19
21
  """
20
22
 
21
- directory_path: Path
23
+ directory_path: PurePath
22
24
  filename: str
23
25
  create_dir_depth: int = 0
26
+ directory_uri: str | None = None
27
+
28
+ def __post_init__(self):
29
+ if not self.directory_path.is_absolute():
30
+ raise ValueError(
31
+ f"directory_path must be an absolute path, got {self.directory_path}"
32
+ )
33
+
34
+ # If directory uri is not set, set it using the directory path.
35
+ if self.directory_uri is None:
36
+ self.directory_uri = urlunparse(
37
+ (
38
+ "file",
39
+ "localhost",
40
+ f"{self.directory_path.as_posix()}/",
41
+ "",
42
+ "",
43
+ None,
44
+ )
45
+ )
46
+ elif not self.directory_uri.endswith("/"):
47
+ # Ensure the directory URI ends with a slash.
48
+ self.directory_uri += "/"
24
49
 
25
50
 
26
51
  class FilenameProvider(Protocol):
@@ -112,11 +137,13 @@ class StaticPathProvider(PathProvider):
112
137
  def __init__(
113
138
  self,
114
139
  filename_provider: FilenameProvider,
115
- directory_path: Path | str,
140
+ directory_path: PurePath,
141
+ directory_uri: str | None = None,
116
142
  create_dir_depth: int = 0,
117
143
  ) -> None:
118
144
  self._filename_provider = filename_provider
119
- self._directory_path = Path(directory_path)
145
+ self._directory_path = directory_path
146
+ self._directory_uri = directory_uri
120
147
  self._create_dir_depth = create_dir_depth
121
148
 
122
149
  def __call__(self, device_name: str | None = None) -> PathInfo:
@@ -124,6 +151,7 @@ class StaticPathProvider(PathProvider):
124
151
 
125
152
  return PathInfo(
126
153
  directory_path=self._directory_path,
154
+ directory_uri=self._directory_uri,
127
155
  filename=filename,
128
156
  create_dir_depth=self._create_dir_depth,
129
157
  )
@@ -135,7 +163,8 @@ class AutoIncrementingPathProvider(PathProvider):
135
163
  def __init__(
136
164
  self,
137
165
  filename_provider: FilenameProvider,
138
- base_directory_path: Path,
166
+ base_directory_path: PurePath,
167
+ base_directory_uri: str | None = None,
139
168
  create_dir_depth: int = 0,
140
169
  max_digits: int = 5,
141
170
  starting_value: int = 0,
@@ -146,6 +175,12 @@ class AutoIncrementingPathProvider(PathProvider):
146
175
  ) -> None:
147
176
  self._filename_provider = filename_provider
148
177
  self._base_directory_path = base_directory_path
178
+ self._base_directory_uri = base_directory_uri
179
+ if (
180
+ self._base_directory_uri is not None
181
+ and not self._base_directory_uri.endswith("/")
182
+ ):
183
+ self._base_directory_uri += "/"
149
184
  self._create_dir_depth = create_dir_depth
150
185
  self._base_name = base_name
151
186
  self._starting_value = starting_value
@@ -174,8 +209,13 @@ class AutoIncrementingPathProvider(PathProvider):
174
209
  self._inc_counter = 0
175
210
  self._current_value += self._increment
176
211
 
212
+ directory_uri = None
213
+ if self._base_directory_uri is not None:
214
+ directory_uri = f"{self._base_directory_uri}{auto_inc_dir_name}"
215
+
177
216
  return PathInfo(
178
217
  directory_path=self._base_directory_path / auto_inc_dir_name,
218
+ directory_uri=directory_uri,
179
219
  filename=filename,
180
220
  create_dir_depth=self._create_dir_depth,
181
221
  )
@@ -187,34 +227,52 @@ class YMDPathProvider(PathProvider):
187
227
  def __init__(
188
228
  self,
189
229
  filename_provider: FilenameProvider,
190
- base_directory_path: Path,
230
+ base_directory_path: PurePath,
231
+ base_directory_uri: str | None = None,
191
232
  create_dir_depth: int = -3, # Default to -3 to create YMD dirs
192
233
  device_name_as_base_dir: bool = False,
193
234
  ) -> None:
194
235
  self._filename_provider = filename_provider
195
- self._base_directory_path = Path(base_directory_path)
236
+ self._base_directory_path = base_directory_path
237
+ self._base_directory_uri = base_directory_uri
238
+ if (
239
+ self._base_directory_uri is not None
240
+ and not self._base_directory_uri.endswith("/")
241
+ ):
242
+ self._base_directory_uri += "/"
196
243
  self._create_dir_depth = create_dir_depth
197
244
  self._device_name_as_base_dir = device_name_as_base_dir
198
245
 
199
246
  def __call__(self, device_name: str | None = None) -> PathInfo:
200
- sep = os.path.sep
247
+ path_type = type(self._base_directory_path)
248
+ if path_type == PureWindowsPath:
249
+ sep = "\\"
250
+ else:
251
+ sep = "/"
252
+
201
253
  current_date = date.today().strftime(f"%Y{sep}%m{sep}%d")
202
254
  if device_name is None:
203
255
  ymd_dir_path = current_date
204
256
  elif self._device_name_as_base_dir:
205
- ymd_dir_path = os.path.join(
257
+ ymd_dir_path = path_type(
206
258
  current_date,
207
259
  device_name,
208
260
  )
209
261
  else:
210
- ymd_dir_path = os.path.join(
262
+ ymd_dir_path = path_type(
211
263
  device_name,
212
264
  current_date,
213
265
  )
214
266
 
215
267
  filename = self._filename_provider(device_name)
268
+
269
+ directory_uri = None
270
+ if self._base_directory_uri is not None:
271
+ directory_uri = f"{self._base_directory_uri}{ymd_dir_path}"
272
+
216
273
  return PathInfo(
217
274
  directory_path=self._base_directory_path / ymd_dir_path,
275
+ directory_uri=directory_uri,
218
276
  filename=filename,
219
277
  create_dir_depth=self._create_dir_depth,
220
278
  )
@@ -148,7 +148,7 @@ class SoftSignalBackend(SignalBackend[SignalDatatypeT]):
148
148
  """Set the current value, alarm and timestamp."""
149
149
  self.reading = Reading(
150
150
  value=self.converter.write_value(value),
151
- timestamp=time.monotonic(),
151
+ timestamp=time.time(),
152
152
  alarm_severity=0,
153
153
  )
154
154
  if self.callback:
@@ -14,6 +14,7 @@ from ._core_io import (
14
14
  NDCBFlushOnSoftTrgMode,
15
15
  NDFileHDFIO,
16
16
  NDFileIO,
17
+ NDFilePluginIO,
17
18
  NDPluginBaseIO,
18
19
  NDPluginCBIO,
19
20
  NDPluginStatsIO,
@@ -45,6 +46,7 @@ __all__ = [
45
46
  "ContAcqAreaDetector",
46
47
  "NDArrayBaseIO",
47
48
  "NDFileIO",
49
+ "NDFilePluginIO",
48
50
  "NDFileHDFIO",
49
51
  "NDPluginBaseIO",
50
52
  "NDPluginStatsIO",
@@ -131,11 +131,11 @@ class ADCompression(StrictEnum):
131
131
  JPEG = "JPEG"
132
132
 
133
133
 
134
- class NDFileIO(NDPluginBaseIO):
135
- """Base class from which file plugins are derived.
134
+ class NDFileIO(NDArrayBaseIO):
135
+ """Base class from which file writing drivers are derived.
136
136
 
137
- This mirrors the interface provided by ADCore/db/NDFile.template.
138
- See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginFile.html
137
+ This mirrors the interface provided by ADCore/ADApp/Db/NDFile.template.
138
+ It does not include any plugin-related fields, for that see NDFilePluginIO.
139
139
  """
140
140
 
141
141
  file_path: A[SignalRW[str], PvSuffix.rbv("FilePath")]
@@ -154,7 +154,17 @@ class NDFileIO(NDPluginBaseIO):
154
154
  create_directory: A[SignalRW[int], PvSuffix("CreateDirectory")]
155
155
 
156
156
 
157
- class NDFileHDFIO(NDFileIO):
157
+ class NDFilePluginIO(NDPluginBaseIO, NDFileIO):
158
+ """Base class from which file plugins are derived.
159
+
160
+ This mirrors the interface provided by ADCore/db/NDFilePlugin.template.
161
+ See HTML docs at https://areadetector.github.io/areaDetector/ADCore/NDPluginFile.html
162
+ """
163
+
164
+ ...
165
+
166
+
167
+ class NDFileHDFIO(NDFilePluginIO):
158
168
  """Plugin for storing data in HDF5 file format.
159
169
 
160
170
  This mirrors the interface provided by ADCore/db/NDFileHDF5.template.
@@ -65,7 +65,7 @@ class ADBaseController(DetectorController, Generic[ADBaseIOT]):
65
65
  async def disarm(self):
66
66
  # We can't use caput callback as we already used it in arm() and we can't have
67
67
  # 2 or they will deadlock
68
- await stop_busy_record(self.driver.acquire, False, timeout=1)
68
+ await stop_busy_record(self.driver.acquire, False)
69
69
 
70
70
  async def set_exposure_time_and_acquire_period_if_supplied(
71
71
  self,
@@ -218,7 +218,7 @@ class ADBaseContAcqController(ADBaseController[ADBaseIO]):
218
218
  await self.cb_plugin.trigger.set(True, wait=False)
219
219
 
220
220
  async def disarm(self) -> None:
221
- await stop_busy_record(self.cb_plugin.capture, False, timeout=1)
221
+ await stop_busy_record(self.cb_plugin.capture, False)
222
222
  if self._arm_status and not self._arm_status.done:
223
223
  await self._arm_status
224
224
  self._arm_status = None
@@ -1,8 +1,7 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncGenerator, AsyncIterator
3
- from pathlib import Path
3
+ from pathlib import PureWindowsPath
4
4
  from typing import Generic, TypeVar, get_args
5
- from urllib.parse import urlunparse
6
5
 
7
6
  from bluesky.protocols import Hints, StreamAsset
8
7
  from event_model import ( # type: ignore
@@ -13,14 +12,14 @@ from event_model import ( # type: ignore
13
12
  from pydantic import PositiveInt
14
13
 
15
14
  from ophyd_async.core._detector import DetectorWriter
16
- from ophyd_async.core._providers import DatasetDescriber, PathProvider
15
+ from ophyd_async.core._providers import DatasetDescriber, PathInfo, PathProvider
17
16
  from ophyd_async.core._signal import (
18
17
  observe_value,
19
18
  set_and_wait_for_value,
20
19
  wait_for_value,
21
20
  )
22
21
  from ophyd_async.core._status import AsyncStatus
23
- from ophyd_async.core._utils import DEFAULT_TIMEOUT
22
+ from ophyd_async.core._utils import DEFAULT_TIMEOUT, error_if_none
24
23
 
25
24
  # from ophyd_async.epics.adcore._core_logic import ADBaseDatasetDescriber
26
25
  from ._core_io import (
@@ -28,6 +27,7 @@ from ._core_io import (
28
27
  ADCallbacks,
29
28
  NDArrayBaseIO,
30
29
  NDFileIO,
30
+ NDFilePluginIO,
31
31
  NDPluginBaseIO,
32
32
  )
33
33
  from ._utils import ADFileWriteMode
@@ -52,7 +52,8 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
52
52
  ) -> None:
53
53
  self._plugins = plugins or {}
54
54
  self.fileio = fileio
55
- self._path_provider = path_provider
55
+ self._path_provider: PathProvider = path_provider
56
+ self._path_info: PathInfo | None = None
56
57
  self._dataset_describer = dataset_describer
57
58
  self._file_extension = file_extension
58
59
  self._mimetype = mimetype
@@ -82,19 +83,32 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
82
83
  writer = cls(fileio, path_provider, dataset_describer, plugins=plugins)
83
84
  return writer
84
85
 
85
- async def begin_capture(self, name: str) -> None:
86
- info = self._path_provider(device_name=name)
86
+ async def _begin_capture(self, name: str) -> None:
87
+ path_info = error_if_none(
88
+ self._path_info, "Writer must be opened before beginning capture!"
89
+ )
87
90
 
88
- await self.fileio.enable_callbacks.set(ADCallbacks.ENABLE)
91
+ if isinstance(self.fileio, NDFilePluginIO):
92
+ await self.fileio.enable_callbacks.set(ADCallbacks.ENABLE)
89
93
 
90
94
  # Set the directory creation depth first, since dir creation callback happens
91
95
  # when directory path PV is processed.
92
- await self.fileio.create_directory.set(info.create_dir_depth)
96
+ await self.fileio.create_directory.set(path_info.create_dir_depth)
97
+
98
+ # Need to ensure that trailing separator is added to the directory path.
99
+ # When setting the path for windows based AD IOCs, a '/' is added rather than
100
+ # a '\\', which will cause the readback to never register the same value.
101
+ dir_path_as_str = str(path_info.directory_path)
102
+ separator = "/"
103
+ if isinstance(path_info.directory_path, PureWindowsPath):
104
+ separator = "\\"
105
+
106
+ dir_path_as_str += separator
93
107
 
94
108
  await asyncio.gather(
95
109
  # See https://github.com/bluesky/ophyd-async/issues/122
96
- self.fileio.file_path.set(str(info.directory_path)),
97
- self.fileio.file_name.set(info.filename),
110
+ self.fileio.file_path.set(dir_path_as_str),
111
+ self.fileio.file_name.set(path_info.filename),
98
112
  self.fileio.file_write_mode.set(ADFileWriteMode.STREAM),
99
113
  # For non-HDF file writers, use AD file templating mechanism
100
114
  # for generating multi-image datasets
@@ -106,7 +120,7 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
106
120
  )
107
121
 
108
122
  if not await self.fileio.file_path_exists.get_value():
109
- msg = f"File path {info.directory_path} for file plugin does not exist"
123
+ msg = f"Path {dir_path_as_str} doesn't exist or not writable!"
110
124
  raise FileNotFoundError(msg)
111
125
 
112
126
  # Overwrite num_capture to go forever
@@ -125,7 +139,9 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
125
139
  frame_shape = await self._dataset_describer.shape()
126
140
  dtype_numpy = await self._dataset_describer.np_datatype()
127
141
 
128
- await self.begin_capture(name)
142
+ self._path_info = self._path_provider(device_name=name)
143
+
144
+ await self._begin_capture(name)
129
145
 
130
146
  describe = {
131
147
  name: DataKey(
@@ -152,30 +168,22 @@ class ADWriter(DetectorWriter, Generic[NDFileIOT]):
152
168
  async def collect_stream_docs(
153
169
  self, name: str, indices_written: int
154
170
  ) -> AsyncIterator[StreamAsset]:
171
+ path_info = error_if_none(
172
+ self._path_info, "Writer must be opened before collecting stream docs!"
173
+ )
174
+
155
175
  if indices_written:
156
176
  if not self._emitted_resource:
157
- file_path = Path(await self.fileio.file_path.get_value())
158
177
  file_name = await self.fileio.file_name.get_value()
159
178
  file_template = file_name + "_{:06d}" + self._file_extension
160
179
 
161
180
  frame_shape = await self._dataset_describer.shape()
162
181
 
163
- uri = urlunparse(
164
- (
165
- "file",
166
- "localhost",
167
- str(file_path.absolute()) + "/",
168
- "",
169
- "",
170
- None,
171
- )
172
- )
173
-
174
182
  bundler_composer = ComposeStreamResource()
175
183
 
176
184
  self._emitted_resource = bundler_composer(
177
185
  mimetype=self._mimetype,
178
- uri=uri,
186
+ uri=str(path_info.directory_uri),
179
187
  # TODO no reference to detector's name
180
188
  data_key=name,
181
189
  parameters={
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncIterator
3
- from pathlib import Path
4
3
  from typing import TypeGuard
5
4
  from xml.etree import ElementTree as ET
6
5
 
@@ -65,8 +64,10 @@ class ADHDFWriter(ADWriter[NDFileHDFIO]):
65
64
  self.fileio.xml_file_name.set(""),
66
65
  )
67
66
 
67
+ self._path_info = self._path_provider(device_name=name)
68
+
68
69
  # Set common AD file plugin params, begin capturing
69
- await self.begin_capture(name)
70
+ await self._begin_capture(name)
70
71
 
71
72
  detector_shape = await self._dataset_describer.shape()
72
73
  np_dtype = await self._dataset_describer.np_datatype()
@@ -100,7 +101,7 @@ class ADHDFWriter(ADWriter[NDFileHDFIO]):
100
101
 
101
102
  self._composer = HDFDocumentComposer(
102
103
  # See https://github.com/bluesky/ophyd-async/issues/122
103
- Path(await self.fileio.full_file_name.get_value()),
104
+ f"{self._path_info.directory_uri}{self._path_info.filename}{self._file_extension}",
104
105
  self._datasets,
105
106
  )
106
107
 
@@ -1,15 +1,15 @@
1
1
  from ophyd_async.core import DatasetDescriber, PathProvider
2
2
 
3
- from ._core_io import NDFileIO, NDPluginBaseIO
3
+ from ._core_io import NDFilePluginIO, NDPluginBaseIO
4
4
  from ._core_writer import ADWriter
5
5
 
6
6
 
7
- class ADJPEGWriter(ADWriter[NDFileIO]):
7
+ class ADJPEGWriter(ADWriter[NDFilePluginIO]):
8
8
  default_suffix: str = "JPEG1:"
9
9
 
10
10
  def __init__(
11
11
  self,
12
- fileio: NDFileIO,
12
+ fileio: NDFilePluginIO,
13
13
  path_provider: PathProvider,
14
14
  dataset_describer: DatasetDescriber,
15
15
  plugins: dict[str, NDPluginBaseIO] | None = None,
@@ -1,15 +1,15 @@
1
1
  from ophyd_async.core import DatasetDescriber, PathProvider
2
2
 
3
- from ._core_io import NDFileIO, NDPluginBaseIO
3
+ from ._core_io import NDFilePluginIO, NDPluginBaseIO
4
4
  from ._core_writer import ADWriter
5
5
 
6
6
 
7
- class ADTIFFWriter(ADWriter[NDFileIO]):
7
+ class ADTIFFWriter(ADWriter[NDFilePluginIO]):
8
8
  default_suffix: str = "TIFF1:"
9
9
 
10
10
  def __init__(
11
11
  self,
12
- fileio: NDFileIO,
12
+ fileio: NDFilePluginIO,
13
13
  path_provider: PathProvider,
14
14
  dataset_describer: DatasetDescriber,
15
15
  plugins: dict[str, NDPluginBaseIO] | None = None,
@@ -140,7 +140,6 @@ async def stop_busy_record(
140
140
  signal: SignalRW[SignalDatatypeT],
141
141
  value: SignalDatatypeT,
142
142
  timeout: float = DEFAULT_TIMEOUT,
143
- status_timeout: float | None = None,
144
143
  ) -> None:
145
- await signal.set(value, wait=False, timeout=status_timeout)
144
+ await signal.set(value, wait=False)
146
145
  await wait_for_value(signal, value, timeout=timeout)
@@ -250,10 +250,12 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
250
250
  datatype: type[SignalDatatypeT] | None,
251
251
  read_pv: str = "",
252
252
  write_pv: str = "",
253
+ all_updates: bool = True,
253
254
  ):
254
255
  self.converter: CaConverter = DisconnectedCaConverter(float, dbr.DBR_DOUBLE)
255
256
  self.initial_values: dict[str, AugmentedValue] = {}
256
257
  self.subscription: Subscription | None = None
258
+ self._all_updates = all_updates
257
259
  super().__init__(datatype, read_pv, write_pv)
258
260
 
259
261
  def source(self, name: str, read: bool):
@@ -356,4 +358,5 @@ class CaSignalBackend(EpicsSignalBackend[SignalDatatypeT]):
356
358
  lambda v: callback(self._make_reading(v)),
357
359
  datatype=self.converter.read_dbr,
358
360
  format=FORMAT_TIME,
361
+ all_updates=self._all_updates,
359
362
  )
@@ -40,7 +40,7 @@ class TestingIOC:
40
40
  assert self._process.stdout # noqa: S101 # this is to make Pylance happy
41
41
  start_time = time.monotonic()
42
42
  while "iocRun: All initialization complete" not in self.output:
43
- if time.monotonic() - start_time > 10:
43
+ if time.monotonic() - start_time > 15:
44
44
  self.stop()
45
45
  raise TimeoutError(f"IOC did not start in time:\n{self.output}")
46
46
  self.output += self._process.stdout.readline()
@@ -1,6 +1,5 @@
1
1
  import asyncio
2
2
  from collections.abc import AsyncGenerator, AsyncIterator
3
- from pathlib import Path
4
3
 
5
4
  from bluesky.protocols import StreamAsset
6
5
  from event_model import DataKey
@@ -67,8 +66,7 @@ class PandaHDFWriter(DetectorWriter):
67
66
  describe = await self._describe(name)
68
67
 
69
68
  self._composer = HDFDocumentComposer(
70
- Path(await self.panda_data_block.hdf_directory.get_value())
71
- / Path(await self.panda_data_block.hdf_file_name.get_value()),
69
+ f"{info.directory_uri}{info.filename}.h5",
72
70
  self._datasets,
73
71
  )
74
72
 
@@ -1,6 +1,7 @@
1
1
  """Used for tutorial `Using Devices`."""
2
2
 
3
3
  # Import bluesky and ophyd
4
+ from pathlib import PurePath
4
5
  from tempfile import mkdtemp
5
6
 
6
7
  import bluesky.plan_stubs as bps # noqa: F401
@@ -27,7 +28,7 @@ pattern_generator = sim.PatternGenerator()
27
28
 
28
29
  # Make a path provider that makes UUID filenames within a static
29
30
  # temporary directory
30
- path_provider = StaticPathProvider(UUIDFilenameProvider(), mkdtemp())
31
+ path_provider = StaticPathProvider(UUIDFilenameProvider(), PurePath(mkdtemp()))
31
32
 
32
33
  # All Devices created within this block will be
33
34
  # connected and named at the end of the with block
@@ -1,5 +1,4 @@
1
1
  from collections.abc import AsyncGenerator, AsyncIterator
2
- from pathlib import Path
3
2
 
4
3
  import numpy as np
5
4
  from bluesky.protocols import Hints, StreamAsset
@@ -26,14 +25,14 @@ class BlobDetectorWriter(DetectorWriter):
26
25
  ) -> None:
27
26
  self.pattern_generator = pattern_generator
28
27
  self.path_provider = path_provider
29
- self.path: Path | None = None
30
28
  self.composer: HDFDocumentComposer | None = None
31
29
  self.datasets: list[HDFDatasetDescription] = []
32
30
 
33
31
  async def open(self, name: str, exposures_per_event: int = 1) -> dict[str, DataKey]:
34
32
  path_info = self.path_provider(name)
35
- self.path = path_info.directory_path / f"{path_info.filename}.h5"
36
- self.pattern_generator.open_file(self.path, WIDTH, HEIGHT)
33
+ write_path = path_info.directory_path / f"{path_info.filename}.h5"
34
+ read_path_uri = f"{path_info.directory_uri}{path_info.filename}.h5"
35
+ self.pattern_generator.open_file(write_path, WIDTH, HEIGHT)
37
36
  self.exposures_per_event = exposures_per_event
38
37
  # We know it will write data and sum, so emit those
39
38
  self.datasets = [
@@ -52,7 +51,7 @@ class BlobDetectorWriter(DetectorWriter):
52
51
  chunk_shape=(1024,),
53
52
  ),
54
53
  ]
55
- self.composer = HDFDocumentComposer(self.path, self.datasets)
54
+ self.composer = HDFDocumentComposer(read_path_uri, self.datasets)
56
55
  describe = {
57
56
  ds.data_key: DataKey(
58
57
  source="sim://pattern-generator-hdf-file",
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import time
5
- from pathlib import Path
5
+ from pathlib import PurePath
6
6
 
7
7
  import h5py
8
8
  import numpy as np
@@ -35,7 +35,7 @@ def generate_interesting_pattern(
35
35
  class PatternFile:
36
36
  def __init__(
37
37
  self,
38
- path: Path,
38
+ path: PurePath,
39
39
  width: int = 320,
40
40
  height: int = 240,
41
41
  ):
@@ -94,7 +94,7 @@ class PatternGenerator:
94
94
  offset = 100 if high_energy else 10
95
95
  return generate_interesting_pattern(self._x, self._y, channel, offset)
96
96
 
97
- def open_file(self, path: Path, width: int, height: int):
97
+ def open_file(self, path: PurePath, width: int, height: int):
98
98
  self._file = PatternFile(path, width, height)
99
99
 
100
100
  def _get_file(self) -> PatternFile:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ophyd-async
3
- Version: 0.12
3
+ Version: 0.12.2
4
4
  Summary: Asynchronous Bluesky hardware abstraction code, compatible with control systems like EPICS and Tango
5
5
  Author-email: Tom Cobb <tom.cobb@diamond.ac.uk>
6
6
  License: BSD 3-Clause License
@@ -1,7 +1,7 @@
1
1
  ophyd_async/__init__.py,sha256=dcAA3qsj1nNIMe5l-v2tlduZ_ypwBmyuHe45Lsq4k4w,206
2
2
  ophyd_async/__main__.py,sha256=n_U4O9bgm97OuboUB_9eK7eFiwy8BZSgXJ0OzbE0DqU,481
3
3
  ophyd_async/_docs_parser.py,sha256=gPYrigfSbYCF7QoSf2UvE-cpQu4snSssl7ZWN-kKDzI,352
4
- ophyd_async/_version.py,sha256=DJRqD0JYfzL1dCvH3-tt5p9TxBGyEU8MKFd2lrz6Yqw,508
4
+ ophyd_async/_version.py,sha256=zZwBOgFsGuwXoMQE3ufj9-wsWZuT7V5jwWvKP04rOSE,513
5
5
  ophyd_async/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  ophyd_async/core/__init__.py,sha256=RtYk6FdJxc7lxoSReRV1D7weTRYFu9ylhNNd3DyN904,4752
7
7
  ophyd_async/core/_derived_signal.py,sha256=TuZza_j3J1Bw4QSqBYB9Ta2FyQP5BycO3nSHVtJ890Q,13015
@@ -10,16 +10,16 @@ ophyd_async/core/_detector.py,sha256=x1o-eSkvemQ-fTk47440owkTmYhuckA2ILNOCoJlHCY
10
10
  ophyd_async/core/_device.py,sha256=lSm8FBul9NTn9VO0rsAlV9pctJyUsMdU2ztEf5CqH5M,14716
11
11
  ophyd_async/core/_device_filler.py,sha256=MDz8eQQ-eEAwo-UEMxfqPfpcBuMG01tLCGR6utwVnmE,14825
12
12
  ophyd_async/core/_flyer.py,sha256=8zKyU5aQOr_t59GIUwsYeb8NSabdvBp0swwuRe4v5VQ,3457
13
- ophyd_async/core/_hdf_dataset.py,sha256=qh4QMJAXuOUQUciLXdMTX9yPnmWp5l-ugGCrJWUXQDQ,2954
13
+ ophyd_async/core/_hdf_dataset.py,sha256=0bIX_ZbFSMdXqDwRtEvV-0avHnwXhjPddE5GVNmo7H8,2608
14
14
  ophyd_async/core/_log.py,sha256=DxKR4Nz3SgTaTzKBZWqt-w48yT8WUAr_3Qr223TEWRw,3587
15
15
  ophyd_async/core/_mock_signal_backend.py,sha256=SPdCbVWss6-iL9C3t9u0IvR_Ln9JeDypVd18WlivdjE,3156
16
16
  ophyd_async/core/_protocol.py,sha256=wQ_snxhTprHqEjQb1HgFwBljwolMY6A8C3xgV1PXwdU,4051
17
- ophyd_async/core/_providers.py,sha256=1XuLUw9sT1pKMfH_PsDEpIi1gulla7NfPSp3IR3KfEA,7545
17
+ ophyd_async/core/_providers.py,sha256=WBht3QCgvGc0stNcwH6z4Zr6hAz3e01-88NjsYI2w6I,9740
18
18
  ophyd_async/core/_readable.py,sha256=iBo1YwA5bsAbzLbznvmSnzKDWUuGkLh850Br3BXsgeU,11707
19
19
  ophyd_async/core/_settings.py,sha256=_ZccbXKP7j5rG6-bMKk7aaLr8hChdRDAPY_YSR71XXM,4213
20
20
  ophyd_async/core/_signal.py,sha256=wKbDJYpN7zWgRf_jAARFqZwCGXcdzPRDNXwvjGUR6dw,28217
21
21
  ophyd_async/core/_signal_backend.py,sha256=PvwTbbSVEGqM-2s5BNRrKGwM_MiYL71qMxYAgyZ7wRM,6930
22
- ophyd_async/core/_soft_signal_backend.py,sha256=zrE7H2ojHY6oQBucLkFgukszrkdvbIZuavLjEUqc_xM,6227
22
+ ophyd_async/core/_soft_signal_backend.py,sha256=NJUuyaCKtBZjggt8WKi7_lKQRHasToxviuQvl5xbhLU,6222
23
23
  ophyd_async/core/_status.py,sha256=h4TtWFM7wFtpxxyAYYSITgcVzArYZdYBHbya6qIX5t0,6553
24
24
  ophyd_async/core/_table.py,sha256=ai-_W-_WMZcy9f69BDYRv9vjVl-AVeOPN_uHYoGCSsc,6905
25
25
  ophyd_async/core/_utils.py,sha256=fePvt3g7eQ6CRQcMVkMeqxcbvYZboZ2sf1fVVZL-26M,12450
@@ -35,16 +35,16 @@ ophyd_async/epics/adaravis/__init__.py,sha256=ZQaJVQiwcQn9hUZADrYgBE1sDfFEwjhVBJ
35
35
  ophyd_async/epics/adaravis/_aravis.py,sha256=Ju2wuebz9_ovl-Kza39s5VQ1pV-Omt_BaIWKqP4kcGA,1315
36
36
  ophyd_async/epics/adaravis/_aravis_controller.py,sha256=WiFR7_FAAu6_88zG-yzGLsR9YcO4L6xR73Wnjw9n0i4,1908
37
37
  ophyd_async/epics/adaravis/_aravis_io.py,sha256=af5RxeXF2ligvAXwMNMKHA4QHTR_WmNFz-f18qD2dbg,855
38
- ophyd_async/epics/adcore/__init__.py,sha256=GipuBZwaAju4g15WjvGs78S4zjGVxmbPel4E29zHFvE,1583
38
+ ophyd_async/epics/adcore/__init__.py,sha256=sDvQO3TshdXfDvLGqzsSNJT95Wk7tUAmOowDwB00Td0,1625
39
39
  ophyd_async/epics/adcore/_core_detector.py,sha256=mRDaHgXCTZF-MIVsU1csoQx9jObutYDpMWayugx2-jI,2631
40
- ophyd_async/epics/adcore/_core_io.py,sha256=jm4gQUSq727xnPriuH6jFHqlDBZceKxr_XyBNj5F65U,7392
41
- ophyd_async/epics/adcore/_core_logic.py,sha256=IH7iOSVIsVj4e97ClhdBFpmdMkb8TznSaLkd3ohEhUs,8884
42
- ophyd_async/epics/adcore/_core_writer.py,sha256=S58plRmbNzig-r7gubKjDVDni_nf4CASaV1OJvudbHw,8019
43
- ophyd_async/epics/adcore/_hdf_writer.py,sha256=FYM84XYFVH6m2lM3CmZiyW1Eb-chUWLyjHUz3YhH9EQ,5743
44
- ophyd_async/epics/adcore/_jpeg_writer.py,sha256=7XC4Twx_MaCBjeol27UA-hStOCQEjkEAb3ToVMPUlZ8,670
40
+ ophyd_async/epics/adcore/_core_io.py,sha256=c1GqAUdv8lAQjklbKHtraLMhPOWEttDCsH9ow7M5I0U,7690
41
+ ophyd_async/epics/adcore/_core_logic.py,sha256=3xZF06o6h1XD3FfBDzkZKOGBHepOAwIzZSZ3SwrS34w,8862
42
+ ophyd_async/epics/adcore/_core_writer.py,sha256=6OEU8fcn7ATd3dml_aBZ_LmFgVGcdbNFyg1Sx2zqV7s,8522
43
+ ophyd_async/epics/adcore/_hdf_writer.py,sha256=AvCv_5dd2HdQoE12_CR6vH2hig2EdEIWklOu6CbPVlc,5816
44
+ ophyd_async/epics/adcore/_jpeg_writer.py,sha256=VYpUWQGEjrKG2kiRGQZlBCPXVJ1BzWb9GyB9KhxPWgo,688
45
45
  ophyd_async/epics/adcore/_single_trigger.py,sha256=tFGLT1b_rZzAvbqWP-hyCccxJMRY26T5IER-VAqKXmc,1275
46
- ophyd_async/epics/adcore/_tiff_writer.py,sha256=Na30osfkgrq4VQhUzDcuS52Gy7FS08CzbgEmKwljTmk,671
47
- ophyd_async/epics/adcore/_utils.py,sha256=jMmZnyDwRDBq-N_x8qOWjW2RMN9uw2KuoAmukPbb404,4252
46
+ ophyd_async/epics/adcore/_tiff_writer.py,sha256=197Ky9ltsJjUKNwl8_OAuoCe8dWIc7zCFs7wautwC7Y,689
47
+ ophyd_async/epics/adcore/_utils.py,sha256=HOI-4a1m184QIn8wVQxLTxovHWfbFINUtttQ2E-OA3o,4187
48
48
  ophyd_async/epics/adkinetix/__init__.py,sha256=A9xq3lGMrmza9lfukRixC0Up_kUDVFII8JguLr2x7Bw,308
49
49
  ophyd_async/epics/adkinetix/_kinetix.py,sha256=zZv0JZ8i1RSx7KBDn_1HGNOY0BoIP81mRK5TKq7d4eA,1302
50
50
  ophyd_async/epics/adkinetix/_kinetix_controller.py,sha256=UI-XcQpGj7jq-_e1ceoMOZkyfejwG6H5wX-Ntp_NJjg,1481
@@ -62,7 +62,7 @@ ophyd_async/epics/advimba/_vimba.py,sha256=4XlEnsJMGDzHLuYaIDUmaxx0gtOAehn5BKBZM
62
62
  ophyd_async/epics/advimba/_vimba_controller.py,sha256=v0av2bGnaJ01w9Igksupt2IlkuBEFlAeRCPOVma-Xa4,1980
63
63
  ophyd_async/epics/advimba/_vimba_io.py,sha256=cb2Nfp05fBZAcNVXpz-rqRIRS-TiZW5DPUJOmaFyAw0,1589
64
64
  ophyd_async/epics/core/__init__.py,sha256=8NoQxEEc2Ny_L9nrD2fnGSf_2gJr1wCR1LwUeLNcIJo,588
65
- ophyd_async/epics/core/_aioca.py,sha256=py5dM5hbT2OXah_Lzt_fAZ9RdaHhv8pdUtkOF8iOYO4,13015
65
+ ophyd_async/epics/core/_aioca.py,sha256=elNR5c2-YcDUoyzuvTEVURoqx92wkVoMDNjwjeZ0WmA,13136
66
66
  ophyd_async/epics/core/_epics_connector.py,sha256=S4z_wbj-aogVcjqCyUgjhcq5Y4gDC7y6wXbsSz2nODY,1918
67
67
  ophyd_async/epics/core/_epics_device.py,sha256=wGdR24I7GSPh3HmM7jsWKZhBZgt4IyLrCn4Ut7Wx_xo,510
68
68
  ophyd_async/epics/core/_p4p.py,sha256=uWh3oWPme74G4YfeJ6k8ZlHdKOwcf8Xp1J82b9aa_JI,16407
@@ -85,7 +85,7 @@ ophyd_async/epics/pmac/__init__.py,sha256=RWqo5nYE2MMBdwvMxdeVG213MN38b9VPlpDHQW
85
85
  ophyd_async/epics/pmac/_pmac_io.py,sha256=yMQpZ0Osh4l8VRd2aqWQE9ebJDfh5FwM_0pp1pe8-C0,3564
86
86
  ophyd_async/epics/testing/__init__.py,sha256=aTIv4D2DYrpnGco5RQF8QuLG1SfFkIlTyM2uYEKXltA,522
87
87
  ophyd_async/epics/testing/_example_ioc.py,sha256=uUmfMXV_Pd2SMFyb0y_4uTc6gkGRUqU1cJ-XQC2ROW8,3915
88
- ophyd_async/epics/testing/_utils.py,sha256=6sqJ0BCwubSkK-WOJbmpKNqZKG0AmCoevzaMGaRmuJs,1702
88
+ ophyd_async/epics/testing/_utils.py,sha256=9gxpwaWX0HGtacu1LTupcw7viXN8G78RmuNciU_-cjs,1702
89
89
  ophyd_async/epics/testing/test_records.db,sha256=SgWQPZgtmc__JiLkH2VPwe5KZOua6ZCIgTLGT_5SDDc,3589
90
90
  ophyd_async/epics/testing/test_records_pva.db,sha256=HJAJSvLtPWG5B5dKv8OZ0_hPJxRFrDoYp6ROcF2lqyA,4202
91
91
  ophyd_async/fastcs/__init__.py,sha256=qlIM9-pjJ8yWfnzTM9-T9cw7zQLKjeeNROQTni5Dr6M,80
@@ -101,7 +101,7 @@ ophyd_async/fastcs/panda/_control.py,sha256=xtW3dH_MLQoycgP-4vJtYx1M9alHjWo13iu9
101
101
  ophyd_async/fastcs/panda/_hdf_panda.py,sha256=tL_OWHxlMQcMZGq9sxHLSeag6hP9MRIbTPn1W0u0iNI,1237
102
102
  ophyd_async/fastcs/panda/_table.py,sha256=maKGoKypEuYqTSVWGgDO6GMEKOtlDm9Dn5YiYdBzu6c,2486
103
103
  ophyd_async/fastcs/panda/_trigger.py,sha256=TLd0ST-ZgsCGpONGUe76qHOaH74TlZNIGNkh-10eRa8,3404
104
- ophyd_async/fastcs/panda/_writer.py,sha256=9cXDrfzj_e8LyGoG7kvgmftt8D_TS6r0fnlO8uR1fHw,6298
104
+ ophyd_async/fastcs/panda/_writer.py,sha256=UqsU44u0uIqkDNky3mIzhW3OhLeZ8TSqFS666qrRdmA,6181
105
105
  ophyd_async/plan_stubs/__init__.py,sha256=sRe1Jna_6i7aKjE3pPzsP4iNMWeWdtiptLnOq9pov9M,619
106
106
  ophyd_async/plan_stubs/_ensure_connected.py,sha256=YR6VRj7koccJ4x35NV-Ugl4ZbxgAoGN9PjVIjhv0gpw,894
107
107
  ophyd_async/plan_stubs/_fly.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -111,14 +111,14 @@ ophyd_async/plan_stubs/_settings.py,sha256=e3dGVSUV-Htay_9fKXyQTAQLdjunetGI3OBYp
111
111
  ophyd_async/plan_stubs/_utils.py,sha256=zClRo5ve8RGia7wQnby41W-Zprj-slOA5da1LfYnuhw,45
112
112
  ophyd_async/plan_stubs/_wait_for_awaitable.py,sha256=PGct_dGezKrLhm0W_GD83dwevSccG_vsmj0WSlMNVWc,364
113
113
  ophyd_async/sim/__init__.py,sha256=TC86iJGt4u5UtRJniNEaUJbYB2C03kmjZF-jr2zPExY,709
114
- ophyd_async/sim/__main__.py,sha256=mx6natJxnvUBTQXbS4R5OGH_MZVgYiXQkoh3JBJ8NxU,1693
114
+ ophyd_async/sim/__main__.py,sha256=TPN2nc4qYdBEKpNq4FYsRMvW5a0TEFx1sM92ScIybow,1732
115
115
  ophyd_async/sim/_blob_detector.py,sha256=bJa-G2JF6pPLJx4YIEvFTG07DvQ18ZNSYbtde6qnWPY,1033
116
116
  ophyd_async/sim/_blob_detector_controller.py,sha256=y1aSNQJUPnsT2qnj2sk254Mp18anmgQy7ctHlYQZ_B0,1788
117
- ophyd_async/sim/_blob_detector_writer.py,sha256=-Qj0uSSNgqtLNe9wwsczbWKnSXLKDXISlwX1EHR_-UY,3327
117
+ ophyd_async/sim/_blob_detector_writer.py,sha256=_Pd0OaP4_mZfwxtUF35v7hsktLP_wYljR4nylr5CzJo,3346
118
118
  ophyd_async/sim/_mirror_horizontal.py,sha256=Jsqa8Snjy1jQDboZtAQFJjGor5uKk8FBC7OCe-GoZDw,1478
119
119
  ophyd_async/sim/_mirror_vertical.py,sha256=HUD44mYT0jQ0GKiQKxD7k_7y6o6OdE6TztgdPUJIK_g,2085
120
120
  ophyd_async/sim/_motor.py,sha256=7s2jBNwWm4CI6I6l_LEpe7z61QdWy82JdZBKSFOnYe4,8994
121
- ophyd_async/sim/_pattern_generator.py,sha256=FjPEWiBQh_7tYP_8WPhbVXnTGPPOaV6By7Skz7YNIrY,3722
121
+ ophyd_async/sim/_pattern_generator.py,sha256=kuxvyX2gIxrywhQRhaO1g8YluBT7LBkE20IsurZS-6o,3734
122
122
  ophyd_async/sim/_point_detector.py,sha256=nXgL_1aJZciNBw8Zr2wMYaMbzzAEKXV3yV8FQz2nS_4,2940
123
123
  ophyd_async/sim/_stage.py,sha256=qaeyZbUVL1v2pTHJiZxq-y6BKpA1l_DAKyzAQppQx70,772
124
124
  ophyd_async/tango/__init__.py,sha256=g9xzjlzPpUAP12YI-kYwfAoLSYPAQdL1S11R2c-cius,60
@@ -145,8 +145,8 @@ ophyd_async/testing/_one_of_everything.py,sha256=Di0hPoKwrDOSsx50-2UdSHM2EbIKrPG
145
145
  ophyd_async/testing/_single_derived.py,sha256=5-HOTzgePcZ354NK_ssVpyIbJoJmKyjVQCxSwQXUC-4,2730
146
146
  ophyd_async/testing/_utils.py,sha256=zClRo5ve8RGia7wQnby41W-Zprj-slOA5da1LfYnuhw,45
147
147
  ophyd_async/testing/_wait_for_pending.py,sha256=YZAR48n-CW0GsPey3zFRzMJ4byDAr3HvMIoawjmTrHw,732
148
- ophyd_async-0.12.dist-info/licenses/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
149
- ophyd_async-0.12.dist-info/METADATA,sha256=mpFROvfJrpnj_A-CMYhK4YST2f8ZrkvNkvWg9dVFNVc,7110
150
- ophyd_async-0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
151
- ophyd_async-0.12.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
152
- ophyd_async-0.12.dist-info/RECORD,,
148
+ ophyd_async-0.12.2.dist-info/licenses/LICENSE,sha256=pU5shZcsvWgz701EbT7yjFZ8rMvZcWgRH54CRt8ld_c,1517
149
+ ophyd_async-0.12.2.dist-info/METADATA,sha256=PsxUCpkt62MCT7kSrQCnN7-6S39f5nrFadKKmbNcf1A,7112
150
+ ophyd_async-0.12.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
151
+ ophyd_async-0.12.2.dist-info/top_level.txt,sha256=-hjorMsv5Rmjo3qrgqhjpal1N6kW5vMxZO3lD4iEaXs,12
152
+ ophyd_async-0.12.2.dist-info/RECORD,,