blissdata 0.3.4__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.
Files changed (67) hide show
  1. blissdata/__init__.py +17 -0
  2. blissdata/beacon/__init__.py +1 -0
  3. blissdata/beacon/_base.py +106 -0
  4. blissdata/beacon/config.py +24 -0
  5. blissdata/beacon/data.py +45 -0
  6. blissdata/beacon/files.py +141 -0
  7. blissdata/client.py +62 -0
  8. blissdata/common/__init__.py +15 -0
  9. blissdata/common/utils.py +159 -0
  10. blissdata/data/__init__.py +15 -0
  11. blissdata/data/events/__init__.py +22 -0
  12. blissdata/data/events/channel.py +149 -0
  13. blissdata/data/events/lima.py +478 -0
  14. blissdata/data/events/node.py +28 -0
  15. blissdata/data/events/scan.py +49 -0
  16. blissdata/data/events/walk.py +42 -0
  17. blissdata/data/expiration.py +42 -0
  18. blissdata/data/lima_image.py +465 -0
  19. blissdata/data/node.py +1619 -0
  20. blissdata/data/nodes/__init__.py +11 -0
  21. blissdata/data/nodes/channel.py +394 -0
  22. blissdata/data/nodes/dataset.py +82 -0
  23. blissdata/data/nodes/dataset_collection.py +12 -0
  24. blissdata/data/nodes/lima.py +422 -0
  25. blissdata/data/nodes/node_ref_channel.py +34 -0
  26. blissdata/data/nodes/proposal.py +12 -0
  27. blissdata/data/nodes/scan.py +192 -0
  28. blissdata/data/nodes/scan_group.py +12 -0
  29. blissdata/data/remote_node.py +204 -0
  30. blissdata/data/scan.py +666 -0
  31. blissdata/h5api/__init__.py +1 -0
  32. blissdata/h5api/abstract.py +97 -0
  33. blissdata/h5api/dynamic_hdf5.py +153 -0
  34. blissdata/h5api/file_arguments.py +28 -0
  35. blissdata/h5api/static_hdf5.py +139 -0
  36. blissdata/h5api/utils/__init__.py +0 -0
  37. blissdata/h5api/utils/bliss.py +138 -0
  38. blissdata/h5api/utils/hdf5.py +280 -0
  39. blissdata/h5api/utils/hdf5_retry.py +98 -0
  40. blissdata/h5api/utils/lima.py +286 -0
  41. blissdata/h5api/utils/types.py +13 -0
  42. blissdata/redis/__init__.py +12 -0
  43. blissdata/redis/caching.py +390 -0
  44. blissdata/redis/connection.py +169 -0
  45. blissdata/redis/manager.py +164 -0
  46. blissdata/redis/proxy.py +971 -0
  47. blissdata/redis/scripting.py +24 -0
  48. blissdata/settings.py +1174 -0
  49. blissdata/streaming.py +819 -0
  50. blissdata/streaming_events.py +355 -0
  51. blissdata/tests/__init__.py +0 -0
  52. blissdata/tests/beacon/__init__.py +0 -0
  53. blissdata/tests/beacon/test_config.py +26 -0
  54. blissdata/tests/beacon/test_data.py +50 -0
  55. blissdata/tests/beacon/test_files.py +80 -0
  56. blissdata/tests/conftest.py +0 -0
  57. blissdata/tests/h5api/__init__.py +0 -0
  58. blissdata/tests/h5api/scanner.py +390 -0
  59. blissdata/tests/h5api/test_dynamic_files.py +309 -0
  60. blissdata/tests/h5api/test_static_files.py +169 -0
  61. blissdata/tests/redis/__init__.py +0 -0
  62. blissdata/tests/redis/manager.py +23 -0
  63. blissdata-0.3.4.dist-info/LICENSE +165 -0
  64. blissdata-0.3.4.dist-info/METADATA +79 -0
  65. blissdata-0.3.4.dist-info/RECORD +67 -0
  66. blissdata-0.3.4.dist-info/WHEEL +5 -0
  67. blissdata-0.3.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,280 @@
1
+ """Implementation of the h5py-like Bliss Data API with dynamic HDF5 files"""
2
+
3
+ from numbers import Number
4
+ from typing import Any, Iterator, Optional, Union
5
+
6
+ import h5py
7
+ from silx.io import h5py_utils
8
+
9
+ from . import types
10
+ from . import lima
11
+ from .hdf5_retry import retry_file_access
12
+ from .hdf5_retry import ignore_retry_timeout
13
+ from .hdf5_retry import RetryTimeoutError, RetryError, SoftRetryError
14
+
15
+
16
+ HDF5Item = Union[h5py.Dataset, h5py.Group, h5py.File, lima.LimaDataset, lima.LimaGroup]
17
+ HDF5Group = Union[h5py.Group, h5py.File, lima.LimaGroup]
18
+ HDF5Dataset = Union[h5py.Dataset, lima.LimaDataset]
19
+
20
+
21
+ SEP = "/"
22
+
23
+
24
+ class DynamicHDF5Handler:
25
+ """Object to access an HDF5 file which is re-opened upon re-trying failed IO operations"""
26
+
27
+ def __init__(
28
+ self,
29
+ file: str,
30
+ retry_timeout: Optional[Number] = None,
31
+ retry_period: Optional[Number] = None,
32
+ **openargs,
33
+ ):
34
+ self._file = file
35
+ self._openargs = openargs
36
+ self._file_obj = None
37
+ self._closed = False
38
+ self._retry_period = retry_period
39
+ self._retry_options = {
40
+ "retry_timeout": retry_timeout,
41
+ "retry_period": retry_period,
42
+ }
43
+ self._native_items = dict()
44
+
45
+ def close(self) -> None:
46
+ """Close the HDF5 file, cleanup all HDF5 objects and do not allow re-opening"""
47
+ self._closed = True
48
+ self._cleanup()
49
+
50
+ def reset(self) -> None:
51
+ """Close the HDF5 file, cleanup all HDF5 objects but allow re-opening"""
52
+ self._cleanup()
53
+
54
+ def _cleanup(self) -> None:
55
+ """Close the HDF5 file, cleanup all HDF5 objects"""
56
+ if self._file_obj is None:
57
+ return
58
+ self._file_obj.close()
59
+ self._file_obj = None
60
+ self._native_items = dict()
61
+
62
+ @property
63
+ def file_obj(self) -> h5py.File:
64
+ if self._file_obj is None:
65
+ if self._closed:
66
+ raise RuntimeError("File was closed")
67
+ try:
68
+ self._file_obj = h5py_utils.File(self._file, **self._openargs)
69
+ except FileNotFoundError:
70
+ raise RetryError(f"File {self._file} does not exist (yet)")
71
+ return self._file_obj
72
+
73
+ def get_item(self, name: str) -> HDF5Item:
74
+ try:
75
+ return self._retry_get_item(name)
76
+ except RetryTimeoutError:
77
+ raise KeyError(name)
78
+
79
+ def slice_dataset(self, name: str, idx: types.DataIndexType) -> types.DataType:
80
+ return self._retry_slice_dataset(name, idx)
81
+
82
+ def get_attr(self, name: str, key: str) -> Any:
83
+ try:
84
+ return self._retry_get_attr(name, key)
85
+ except RetryTimeoutError:
86
+ raise AttributeError(name)
87
+
88
+ @ignore_retry_timeout
89
+ def iter_attrs(self, name: str) -> Iterator[str]:
90
+ yield from self._retry_iter_attrs(name)
91
+
92
+ @ignore_retry_timeout
93
+ def len_attrs(self, name: str) -> int:
94
+ return self._retry_len_attrs(name)
95
+
96
+ @ignore_retry_timeout
97
+ def iter_item(self, name: str) -> Iterator[Union[str, types.DataType]]:
98
+ yield from self._retry_iter_item(name)
99
+
100
+ @ignore_retry_timeout
101
+ def len_item(self, name: str) -> int:
102
+ return self._retry_len_item(name)
103
+
104
+ def getattr_item(self, name: str, attr_name: str) -> Any:
105
+ return self._retry_getattr_item(name, attr_name)
106
+
107
+ @retry_file_access
108
+ def _retry_get_item(self, name: str) -> HDF5Item:
109
+ return self._get_item(name)
110
+
111
+ @retry_file_access
112
+ def _retry_slice_dataset(
113
+ self, name: str, idx: types.DataIndexType
114
+ ) -> types.DataType:
115
+ return self._slice_dataset(name, idx)
116
+
117
+ @retry_file_access
118
+ def _retry_get_attr(self, name: str, key: str) -> Any:
119
+ return self._get_attr(name, key)
120
+
121
+ @retry_file_access
122
+ def _retry_iter_attrs(self, name: str, start_index: int = 0) -> Iterator[str]:
123
+ yield from self._iter_attrs(name, start_index=start_index)
124
+
125
+ @retry_file_access
126
+ def _retry_len_attrs(self, name: str) -> int:
127
+ return self._len_attrs(name)
128
+
129
+ @retry_file_access
130
+ def _retry_iter_item(
131
+ self, name: str, start_index: int = 0
132
+ ) -> Iterator[Union[str, types.DataType]]:
133
+ yield from self._iter_item(name, start_index=start_index)
134
+
135
+ @retry_file_access
136
+ def _retry_len_item(self, name: str) -> int:
137
+ return self._len_item(name)
138
+
139
+ @retry_file_access
140
+ def _retry_getattr_item(self, name: str, attr_name: str) -> Any:
141
+ return self._getattr_item(name, attr_name)
142
+
143
+ def _get_item(self, name: str) -> HDF5Item:
144
+ item = self._native_items.get(name)
145
+ if item is not None:
146
+ return item
147
+ try:
148
+ item = self.file_obj[name]
149
+ except KeyError:
150
+ parent_name, _, item_name = name.rpartition(SEP)
151
+ if not parent_name:
152
+ parent_name = SEP
153
+ if parent_name == name:
154
+ raise
155
+ parent_item = self._get_item(parent_name)
156
+ if not self._is_finished(parent_item):
157
+ raise # item could still be created
158
+ try:
159
+ item = parent_item[item_name] # try one more time
160
+ except KeyError as e:
161
+ if not _key_does_not_exist(e):
162
+ raise
163
+ # item will never be created
164
+ raise RetryTimeoutError(str(e)) from e
165
+ self._native_items[name] = item
166
+ return item
167
+
168
+ def _slice_dataset(self, name: str, idx: types.DataIndexType) -> types.DataType:
169
+ item = self._get_item(name)
170
+ self._check_dataset_before_read(item)
171
+ return item[idx]
172
+
173
+ def _check_dataset_before_read(self, item: HDF5Dataset):
174
+ pass
175
+
176
+ def _get_attr(self, name: str, key: str) -> Any:
177
+ item = self._get_item(name)
178
+ try:
179
+ return item.attrs[key]
180
+ except KeyError:
181
+ if not self._is_finished(item):
182
+ raise # attribute could still be created
183
+ try:
184
+ return item.attrs[key] # try one more time
185
+ except KeyError as e:
186
+ if not _key_does_not_exist(e):
187
+ raise
188
+ # attribute will never be created
189
+ raise RetryTimeoutError(str(e)) from e
190
+
191
+ def _iter_attrs(self, name: str, start_index: int = 0) -> Iterator[str]:
192
+ item = self._get_item(name)
193
+ is_complete = self._is_initialized(item)
194
+ if start_index == 0:
195
+ yield from item.attrs
196
+ else:
197
+ yield from list(item.attrs)[start_index:]
198
+ if not is_complete:
199
+ raise RetryError("attributes could still be added")
200
+
201
+ def _len_attrs(self, name: str) -> int:
202
+ item = self._get_item(name)
203
+ return len(item.attrs)
204
+
205
+ def _iter_item(
206
+ self, name: str, start_index: int = 0
207
+ ) -> Iterator[Union[str, types.DataType]]:
208
+ item = self._get_item(name)
209
+ self._check_dataset_before_read(item)
210
+ if self.is_group(item):
211
+ is_complete = self._is_initialized(item)
212
+ for key in self._iter_group(item, start_index):
213
+ is_complete = False
214
+ yield key
215
+ if not is_complete:
216
+ raise RetryError("children could still be added to the group")
217
+ else:
218
+ is_complete = self._is_finished(item)
219
+ for data in self._iter_dataset(item, start_index):
220
+ is_complete = False
221
+ yield data
222
+ if not is_complete:
223
+ raise RetryError("dataset could still grow")
224
+
225
+ @staticmethod
226
+ def is_group(h5item: HDF5Item):
227
+ return isinstance(h5item, (h5py.Group, h5py.File))
228
+
229
+ def _iter_group(self, h5group: HDF5Group, start_index: int) -> Iterator[str]:
230
+ try:
231
+ if start_index == 0:
232
+ yield from h5group
233
+ else:
234
+ yield from list(h5group.keys())[start_index:]
235
+ except ValueError:
236
+ raise SoftRetryError(f"Failed accessing group {h5group}")
237
+
238
+ def _iter_dataset(
239
+ self, h5dataset: HDF5Dataset, start_index: int
240
+ ) -> Iterator[types.DataType]:
241
+ try:
242
+ if start_index == 0:
243
+ yield from h5dataset
244
+ else:
245
+ for i in range(start_index, len(h5dataset)):
246
+ yield h5dataset[i]
247
+ except ValueError:
248
+ raise SoftRetryError(f"Failed accessing dataset {h5dataset}")
249
+
250
+ def _len_item(self, name: str) -> int:
251
+ item = self._get_item(name)
252
+ return len(item)
253
+
254
+ def _getattr_item(self, name: str, attr_name: str) -> Any:
255
+ item = self._get_item(name)
256
+ return getattr(item, attr_name)
257
+
258
+ def _is_initialized(self, h5item: HDF5Item) -> bool:
259
+ return True
260
+
261
+ def _is_finished(self, h5item: HDF5Item) -> bool:
262
+ return True
263
+
264
+
265
+ def _key_does_not_exist(exc: KeyError) -> bool:
266
+ """
267
+ A real KeyError (meaning that the key does not exist) gives the following error message:
268
+
269
+ .. code
270
+
271
+ KeyError: "Unable to synchronously open object (object 'end_time' doesn't exist)"
272
+
273
+ An example of a KeyError caused by another reason
274
+
275
+ .. code
276
+
277
+ KeyError: 'Unable to synchronously open object (addr overflow, addr = 64392369, size = 96, eoa = 64365353)'
278
+
279
+ """
280
+ return "doesn't exist" in str(exc)
@@ -0,0 +1,98 @@
1
+ import inspect
2
+ from functools import wraps
3
+ from typing import Callable
4
+
5
+ from silx.io import h5py_utils
6
+ from silx.utils.retry import RetryTimeoutError, RetryError
7
+
8
+
9
+ class SoftRetryError(Exception):
10
+ pass
11
+
12
+
13
+ def retry_file_access(method: Callable) -> Callable:
14
+ return _reset_on_retry_failed(
15
+ h5py_utils.retry(retry_on_error=_retry_on_error)(_reset_before_retry(method))
16
+ )
17
+
18
+
19
+ def ignore_retry_timeout(method: Callable) -> Callable:
20
+ if inspect.isgeneratorfunction(method):
21
+
22
+ @wraps(method)
23
+ def wrapper(self, *args, **kw):
24
+ try:
25
+ yield from method(self, *args, **kw)
26
+ except RetryTimeoutError:
27
+ pass
28
+
29
+ else:
30
+
31
+ @wraps(method)
32
+ def wrapper(self, *args, **kw):
33
+ try:
34
+ return method(self, *args, **kw)
35
+ except RetryTimeoutError:
36
+ pass
37
+
38
+ return wrapper
39
+
40
+
41
+ def _retry_on_error(e: BaseException) -> bool:
42
+ return h5py_utils._is_h5py_exception(e) or isinstance(e, RetryError)
43
+
44
+
45
+ def _reset_before_retry(method: Callable) -> Callable:
46
+ if inspect.isgeneratorfunction(method):
47
+
48
+ @wraps(method)
49
+ def wrapper(self, *args, start_index: int = 0, **kw):
50
+ try:
51
+ yield from method(self, *args, start_index=start_index, **kw)
52
+ except SoftRetryError:
53
+ raise RetryError
54
+ except Exception as e:
55
+ if _retry_on_error(e):
56
+ self.reset()
57
+ raise
58
+
59
+ else:
60
+
61
+ @wraps(method)
62
+ def wrapper(self, *args, **kw):
63
+ try:
64
+ return method(self, *args, **kw)
65
+ except SoftRetryError:
66
+ raise RetryError
67
+ except Exception as e:
68
+ if _retry_on_error(e):
69
+ self.reset()
70
+ raise
71
+
72
+ return wrapper
73
+
74
+
75
+ def _reset_on_retry_failed(method: Callable) -> Callable:
76
+ if inspect.isgeneratorfunction(method):
77
+
78
+ @wraps(method)
79
+ def wrapper(self, *args, start_index: int = 0, **kw):
80
+ kw.update(self._retry_options)
81
+ try:
82
+ yield from method(self, *args, start_index=start_index, **kw)
83
+ except RetryTimeoutError:
84
+ self.reset()
85
+ raise
86
+
87
+ else:
88
+
89
+ @wraps(method)
90
+ def wrapper(self, *args, **kw):
91
+ kw.update(self._retry_options)
92
+ try:
93
+ return method(self, *args, **kw)
94
+ except RetryTimeoutError:
95
+ self.reset()
96
+ raise
97
+
98
+ return wrapper
@@ -0,0 +1,286 @@
1
+ import os
2
+ import re
3
+ from glob import glob
4
+ from collections import abc
5
+ from numbers import Integral
6
+ from typing import Optional, Tuple, Sequence, Iterator
7
+
8
+ import numpy
9
+ from numpy.typing import DTypeLike
10
+ from silx.utils.retry import RetryError
11
+ from silx.io import h5py_utils
12
+
13
+ from . import types
14
+
15
+ SEP = "/"
16
+
17
+
18
+ class LimaGroup(abc.Mapping):
19
+ def __init__(self, name, *args, **kwargs) -> None:
20
+ self._name = name
21
+ self._dset_args = args
22
+ self._dset_kwargs = kwargs
23
+ self._dataset = None
24
+ super().__init__()
25
+
26
+ def close(self):
27
+ if self._dataset is None:
28
+ return
29
+ self._dataset.close()
30
+ self._dataset = None
31
+
32
+ def reset(self):
33
+ if self._dataset is None:
34
+ return
35
+ self._dataset.reset()
36
+
37
+ @property
38
+ def name(self) -> str:
39
+ return self._name
40
+
41
+ @property
42
+ def attrs(self) -> dict:
43
+ return {"type": "lima"}
44
+
45
+ def __getitem__(self, key: str):
46
+ if key == "data":
47
+ if self._dataset is None:
48
+ self._dataset = LimaDataset(
49
+ self.name + SEP + "data", *self._dset_args, **self._dset_kwargs
50
+ )
51
+ return self._dataset
52
+ raise KeyError
53
+
54
+ def __iter__(self):
55
+ yield "data"
56
+
57
+ def __len__(self) -> int:
58
+ return 1
59
+
60
+
61
+ class LimaDataset(abc.Sequence):
62
+ def __init__(
63
+ self,
64
+ name: str,
65
+ dirname: str,
66
+ url_template: Optional[str] = None,
67
+ user_detector_name: Optional[str] = None,
68
+ user_instrument_name: Optional[str] = None,
69
+ ) -> None:
70
+ parts = [s for s in name.split(SEP) if s]
71
+ scan_number = int(parts[0].split(".")[0])
72
+ bliss_detector_name = parts[-2]
73
+
74
+ url_template = lima_url_template(
75
+ dirname,
76
+ scan_number,
77
+ bliss_detector_name,
78
+ url_template=url_template,
79
+ user_detector_name=user_detector_name,
80
+ user_instrument_name=user_instrument_name,
81
+ )
82
+ filename_template, self._path_in_file = url_template.split("::")
83
+ self._search_pattern = filename_template.format(file_index="*")
84
+ self._match_pattern = re.compile(
85
+ os.path.basename(filename_template.format(file_index="([0-9]+)"))
86
+ )
87
+ self._files = list()
88
+
89
+ self._first_shape = None
90
+ self._points_per_file = None
91
+ self._dtype = None
92
+ self._shape = None
93
+
94
+ self._active_filename = None
95
+ self._active_file = None
96
+ self._active_dset = None
97
+ self._name = name
98
+
99
+ @property
100
+ def name(self) -> str:
101
+ return self._name
102
+
103
+ @property
104
+ def attrs(self) -> dict:
105
+ return dict()
106
+
107
+ def reset(self):
108
+ """Close the activate HDF5 file, cleanup all HDF5 objects and search for lima files"""
109
+ self._cleanup()
110
+ self.search()
111
+
112
+ def close(self):
113
+ """Close the activate HDF5 file and cleanup all HDF5 objects"""
114
+ self._cleanup()
115
+
116
+ def _cleanup(self):
117
+ if self._active_file is None:
118
+ return
119
+ self._active_file.close()
120
+ self._active_filename = None
121
+ self._active_file = None
122
+ self._active_dset = None
123
+
124
+ def search(self):
125
+ lima_files = glob(self._search_pattern)
126
+ lima_nrs = [
127
+ int(self._match_pattern.search(os.path.basename(s)).group(1))
128
+ for s in lima_files
129
+ ]
130
+ self._files = [filename for _, filename in sorted(zip(lima_nrs, lima_files))]
131
+ self._shape = None
132
+
133
+ def _open(self, file_index: int):
134
+ try:
135
+ filename = self._files[file_index]
136
+ except IndexError:
137
+ raise RetryError(f"lima file {file_index} does not exist (yet)")
138
+ if self._active_filename == filename:
139
+ return
140
+ self.close()
141
+ # Lima has the file open until all images are written.
142
+ # Lima does not flush which means the file is never readable while it is open.
143
+ # To be sure about the later, try locking the file when reading.
144
+ # If it succeeds it means lima is done with the file.
145
+ self._active_file = h5py_utils.File(
146
+ filename, mode="r"
147
+ ) # , locking=h5py_utils.HAS_LOCKING_ARGUMENT)
148
+ self._active_dset = self._active_file[self._path_in_file]
149
+ self._active_filename = filename
150
+
151
+ def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
152
+ if isinstance(idx, Tuple):
153
+ idx0 = idx[0]
154
+ idximage = idx[1:]
155
+ else:
156
+ idx0 = idx
157
+ idximage = tuple()
158
+
159
+ dim0scalar = False
160
+ if isinstance(idx0, Integral):
161
+ idx0 = numpy.asarray([idx0])
162
+ dim0scalar = True
163
+ elif isinstance(idx0, slice):
164
+ idx0 = numpy.array(range(*idx0.indices(self.shape[0])))
165
+ elif isinstance(idx0, Sequence):
166
+ idx0 = numpy.asarray(idx0)
167
+ elif idx0 is Ellipsis:
168
+ idx0 = numpy.array(range(self.shape[0]))
169
+ else:
170
+ raise TypeError
171
+
172
+ result = list()
173
+
174
+ for scan_index in idx0:
175
+ file_index = scan_index // self.point_per_file
176
+ self._open(file_index)
177
+ idx = (scan_index % self.point_per_file,) + idximage
178
+ try:
179
+ result.append(self._active_dset[idx])
180
+ except IndexError:
181
+ raise RetryError(f"Failed slice lima dataset {self._active_dset}")
182
+
183
+ if dim0scalar:
184
+ return result[0]
185
+ else:
186
+ return numpy.array(result)
187
+
188
+ def __iter__(self) -> Iterator[types.DataType]:
189
+ for file_index in range(len(self._files)):
190
+ self._open(file_index)
191
+ yield from iter(self._active_dset)
192
+
193
+ def _cache_dataset_info(self):
194
+ if not self._files:
195
+ self.reset()
196
+ try:
197
+ filename = self._files[0]
198
+ except IndexError:
199
+ raise RetryError("no lima files exists (yet)")
200
+ with h5py_utils.File(filename, mode="r") as f:
201
+ dset = f[self._path_in_file]
202
+ self._points_per_file = len(dset)
203
+ self._first_shape = dset.shape
204
+ self._dtype = dset.dtype
205
+
206
+ def __len__(self) -> int:
207
+ return self.shape[0]
208
+
209
+ @property
210
+ def point_per_file(self) -> int:
211
+ if self._points_per_file is None:
212
+ self._cache_dataset_info()
213
+ return self._points_per_file
214
+
215
+ @property
216
+ def dtype(self) -> DTypeLike:
217
+ if self._dtype is None:
218
+ self._cache_dataset_info()
219
+ return self._dtype
220
+
221
+ @property
222
+ def shape(self) -> Tuple[int]:
223
+ if self._shape is not None:
224
+ return self._shape
225
+ if self._first_shape is None:
226
+ self._cache_dataset_info()
227
+ shape = list(self._first_shape)
228
+ shape[0] *= len(self._files)
229
+ self._shape = tuple(shape)
230
+ return self._shape
231
+
232
+ @property
233
+ def size(self) -> int:
234
+ return numpy.prod(self.shape, dtype=int)
235
+
236
+ @property
237
+ def ndim(self) -> int:
238
+ return 3
239
+
240
+
241
+ def lima_url_template(
242
+ dirname: str,
243
+ scan_number: int,
244
+ bliss_detector_name: str,
245
+ url_template: Optional[str] = None,
246
+ user_detector_name: Optional[str] = None,
247
+ user_instrument_name: Optional[str] = None,
248
+ ) -> str:
249
+ if not url_template:
250
+ if user_instrument_name:
251
+ url_template = os.path.join(
252
+ "{dirname}",
253
+ "scan{scan_number:04d}",
254
+ "{bliss_detector_name}_{{file_index}}.h5",
255
+ ) + SEP.join(
256
+ (
257
+ "::",
258
+ "entry_0000",
259
+ "{user_instrument_name}",
260
+ "{user_detector_name}",
261
+ "data",
262
+ )
263
+ )
264
+ else:
265
+ url_template = os.path.join(
266
+ "{dirname}",
267
+ "scan{scan_number:04d}",
268
+ "{bliss_detector_name}_{{file_index}}.h5",
269
+ ) + SEP.join(("::", "entry_0000", "measurement", "data"))
270
+ if not user_detector_name:
271
+ user_detector_name = bliss_detector_name
272
+ kw = dict()
273
+ if dirname:
274
+ kw["dirname"] = dirname
275
+ if scan_number:
276
+ kw["scan_number"] = scan_number
277
+ if bliss_detector_name:
278
+ kw["bliss_detector_name"] = bliss_detector_name
279
+ if user_detector_name:
280
+ kw["user_detector_name"] = user_detector_name
281
+ if user_instrument_name:
282
+ kw["user_instrument_name"] = user_instrument_name
283
+ url_template = url_template.format(**kw)
284
+ if "{file_index}" not in url_template:
285
+ raise ValueError("A lima template URL needs '{{file_index}}'")
286
+ return url_template
@@ -0,0 +1,13 @@
1
+ from typing import Tuple, Union, Sequence
2
+ from numbers import Number, Integral
3
+
4
+ try:
5
+ from types import EllipsisType
6
+
7
+ SingleDataIndexType = Union[Integral, slice, Sequence, EllipsisType]
8
+ except ImportError:
9
+ SingleDataIndexType = Union[Integral, slice, Sequence]
10
+ from numpy.typing import ArrayLike
11
+
12
+ DataType = Union[bytes, Number, ArrayLike]
13
+ DataIndexType = Union[SingleDataIndexType, Tuple[SingleDataIndexType]]
@@ -0,0 +1,12 @@
1
+ """Manage Redis connections and provide a low-level API to Redis.
2
+
3
+ The low-level API exposes all Redis commands (https://redis.io/commands)
4
+ as methods of Redis database proxies (`RedisDbProxyBase` and derived classes).
5
+
6
+ Redis database proxies are created through a Redis connection pool (`RedisDbConnectionPool`).
7
+
8
+ Redis connection pools are created through a Redis connection manager (`RedisConnectionManager`).
9
+ """
10
+
11
+ from .manager import RedisConnectionManager # noqa: F401
12
+ from .manager import RedisAddress # noqa: F401