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.
- blissdata/__init__.py +17 -0
- blissdata/beacon/__init__.py +1 -0
- blissdata/beacon/_base.py +106 -0
- blissdata/beacon/config.py +24 -0
- blissdata/beacon/data.py +45 -0
- blissdata/beacon/files.py +141 -0
- blissdata/client.py +62 -0
- blissdata/common/__init__.py +15 -0
- blissdata/common/utils.py +159 -0
- blissdata/data/__init__.py +15 -0
- blissdata/data/events/__init__.py +22 -0
- blissdata/data/events/channel.py +149 -0
- blissdata/data/events/lima.py +478 -0
- blissdata/data/events/node.py +28 -0
- blissdata/data/events/scan.py +49 -0
- blissdata/data/events/walk.py +42 -0
- blissdata/data/expiration.py +42 -0
- blissdata/data/lima_image.py +465 -0
- blissdata/data/node.py +1619 -0
- blissdata/data/nodes/__init__.py +11 -0
- blissdata/data/nodes/channel.py +394 -0
- blissdata/data/nodes/dataset.py +82 -0
- blissdata/data/nodes/dataset_collection.py +12 -0
- blissdata/data/nodes/lima.py +422 -0
- blissdata/data/nodes/node_ref_channel.py +34 -0
- blissdata/data/nodes/proposal.py +12 -0
- blissdata/data/nodes/scan.py +192 -0
- blissdata/data/nodes/scan_group.py +12 -0
- blissdata/data/remote_node.py +204 -0
- blissdata/data/scan.py +666 -0
- blissdata/h5api/__init__.py +1 -0
- blissdata/h5api/abstract.py +97 -0
- blissdata/h5api/dynamic_hdf5.py +153 -0
- blissdata/h5api/file_arguments.py +28 -0
- blissdata/h5api/static_hdf5.py +139 -0
- blissdata/h5api/utils/__init__.py +0 -0
- blissdata/h5api/utils/bliss.py +138 -0
- blissdata/h5api/utils/hdf5.py +280 -0
- blissdata/h5api/utils/hdf5_retry.py +98 -0
- blissdata/h5api/utils/lima.py +286 -0
- blissdata/h5api/utils/types.py +13 -0
- blissdata/redis/__init__.py +12 -0
- blissdata/redis/caching.py +390 -0
- blissdata/redis/connection.py +169 -0
- blissdata/redis/manager.py +164 -0
- blissdata/redis/proxy.py +971 -0
- blissdata/redis/scripting.py +24 -0
- blissdata/settings.py +1174 -0
- blissdata/streaming.py +819 -0
- blissdata/streaming_events.py +355 -0
- blissdata/tests/__init__.py +0 -0
- blissdata/tests/beacon/__init__.py +0 -0
- blissdata/tests/beacon/test_config.py +26 -0
- blissdata/tests/beacon/test_data.py +50 -0
- blissdata/tests/beacon/test_files.py +80 -0
- blissdata/tests/conftest.py +0 -0
- blissdata/tests/h5api/__init__.py +0 -0
- blissdata/tests/h5api/scanner.py +390 -0
- blissdata/tests/h5api/test_dynamic_files.py +309 -0
- blissdata/tests/h5api/test_static_files.py +169 -0
- blissdata/tests/redis/__init__.py +0 -0
- blissdata/tests/redis/manager.py +23 -0
- blissdata-0.3.4.dist-info/LICENSE +165 -0
- blissdata-0.3.4.dist-info/METADATA +79 -0
- blissdata-0.3.4.dist-info/RECORD +67 -0
- blissdata-0.3.4.dist-info/WHEEL +5 -0
- blissdata-0.3.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Definition of the h5py-like Bliss Data API"""
|
|
2
|
+
|
|
3
|
+
from collections import abc
|
|
4
|
+
from abc import abstractmethod, abstractproperty
|
|
5
|
+
from typing import Iterator, Optional, Tuple
|
|
6
|
+
from numpy.typing import DTypeLike
|
|
7
|
+
from .utils import types
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Attributes(abc.Mapping):
|
|
11
|
+
"""Node attributes."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Node:
|
|
15
|
+
"""Node in the data tree."""
|
|
16
|
+
|
|
17
|
+
@abstractproperty
|
|
18
|
+
def name(self) -> str:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractproperty
|
|
22
|
+
def short_name(self) -> str:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractproperty
|
|
26
|
+
def attrs(self) -> Attributes:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractproperty
|
|
30
|
+
def parent(self) -> "Group":
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractproperty
|
|
34
|
+
def file(self) -> "File":
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Group(Node, abc.Mapping):
|
|
39
|
+
"""Node in the data tree which contains other nodes."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def __getitem__(self, key: str) -> Node:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def __iter__(self) -> Iterator[Node]:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def __len__(self) -> int:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Dataset(Node, abc.Sequence):
|
|
55
|
+
"""Node in the data tree which contains data."""
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def __len__(self) -> int:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractproperty
|
|
66
|
+
def dtype(self) -> DTypeLike:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractproperty
|
|
70
|
+
def shape(self) -> Tuple[int]:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractproperty
|
|
74
|
+
def size(self) -> int:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
@abstractproperty
|
|
78
|
+
def ndim(self) -> int:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class File(Group):
|
|
83
|
+
"""Root node in the data tree."""
|
|
84
|
+
|
|
85
|
+
def __enter__(self) -> "File":
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
def __exit__(self, exception_type, exception_value, traceback) -> Optional[bool]:
|
|
89
|
+
self.close()
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def close(self) -> None:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def parent(self) -> None:
|
|
97
|
+
return None
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Implementation of the h5py-like Bliss Data API with dynamic HDF5 files"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterator, Tuple, Type
|
|
4
|
+
from numpy.typing import DTypeLike
|
|
5
|
+
import h5py
|
|
6
|
+
from . import abstract
|
|
7
|
+
from .utils import hdf5
|
|
8
|
+
from .utils import bliss
|
|
9
|
+
from .utils import types
|
|
10
|
+
from .file_arguments import ignore_file_arguments
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Attributes(abstract.Attributes):
|
|
14
|
+
"""Node attributes."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, retry_handler: hdf5.DynamicHDF5Handler, name: str) -> None:
|
|
17
|
+
self.__retry_handler = retry_handler
|
|
18
|
+
self.__name = name
|
|
19
|
+
super().__init__()
|
|
20
|
+
|
|
21
|
+
def __getitem__(self, key: str) -> Any:
|
|
22
|
+
return self.__retry_handler.get_attr(self.__name, key)
|
|
23
|
+
|
|
24
|
+
def __iter__(self) -> Iterator[Any]:
|
|
25
|
+
yield from self.__retry_handler.iter_attrs(self.__name)
|
|
26
|
+
|
|
27
|
+
def __len__(self) -> int:
|
|
28
|
+
return self.__retry_handler.len_attrs(self.__name)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Node:
|
|
32
|
+
"""Node in the data tree."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, retry_handler: hdf5.DynamicHDF5Handler, name: str) -> None:
|
|
35
|
+
self._retry_handler = retry_handler
|
|
36
|
+
if not name:
|
|
37
|
+
name = hdf5.SEP
|
|
38
|
+
self.__name = name
|
|
39
|
+
self.__attrs = None
|
|
40
|
+
self.__parent = None
|
|
41
|
+
self.__file = None
|
|
42
|
+
super().__init__()
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return self.__name
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def short_name(self) -> str:
|
|
50
|
+
return self.name.rpartition(hdf5.SEP)[-1]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def parent(self) -> "Group":
|
|
54
|
+
if self.__parent is None:
|
|
55
|
+
parent_name = self.name.rpartition(hdf5.SEP)[0]
|
|
56
|
+
self.__parent = Group(self._retry_handler, parent_name)
|
|
57
|
+
return self.__parent
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def file(self) -> "File":
|
|
61
|
+
if self.__file is None:
|
|
62
|
+
self.__file = File(self._retry_handler)
|
|
63
|
+
return self.__file
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def attrs(self) -> Attributes:
|
|
67
|
+
if self.__attrs is None:
|
|
68
|
+
self.__attrs = Attributes(self._retry_handler, self.name)
|
|
69
|
+
return self.__attrs
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Group(Node, abstract.Group):
|
|
73
|
+
"""Node in the data tree which contains other nodes."""
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, key: str) -> Node:
|
|
76
|
+
if not key.startswith(hdf5.SEP):
|
|
77
|
+
key = f"{self.name}{hdf5.SEP}{key}"
|
|
78
|
+
h5py_obj = self._retry_handler.get_item(key)
|
|
79
|
+
if isinstance(h5py_obj, h5py.Group):
|
|
80
|
+
return Group(self._retry_handler, h5py_obj.name)
|
|
81
|
+
else:
|
|
82
|
+
return Dataset(self._retry_handler, h5py_obj.name)
|
|
83
|
+
|
|
84
|
+
def __iter__(self) -> Iterator[Node]:
|
|
85
|
+
yield from self._retry_handler.iter_item(self.name)
|
|
86
|
+
|
|
87
|
+
def __len__(self) -> int:
|
|
88
|
+
return self._retry_handler.len_item(self.name)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Dataset(Node, abstract.Dataset):
|
|
92
|
+
"""Node in the data tree which contains data."""
|
|
93
|
+
|
|
94
|
+
def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
|
|
95
|
+
return self._retry_handler.slice_dataset(self.name, idx)
|
|
96
|
+
|
|
97
|
+
def __iter__(self) -> Iterator[types.DataType]:
|
|
98
|
+
yield from self._retry_handler.iter_item(self.name)
|
|
99
|
+
|
|
100
|
+
def __len__(self) -> int:
|
|
101
|
+
return self._retry_handler.len_item(self.name)
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def dtype(self) -> DTypeLike:
|
|
105
|
+
return self._retry_handler.getattr_item(self.name, "dtype")
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def shape(self) -> Tuple[int]:
|
|
109
|
+
return self._retry_handler.getattr_item(self.name, "shape")
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def size(self) -> int:
|
|
113
|
+
return self._retry_handler.getattr_item(self.name, "size")
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def ndim(self) -> int:
|
|
117
|
+
return self._retry_handler.getattr_item(self.name, "ndim")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class File(Group, abstract.File):
|
|
121
|
+
"""Root node in the data tree."""
|
|
122
|
+
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
file: str,
|
|
126
|
+
hdf5_retry_handler: Type[
|
|
127
|
+
hdf5.DynamicHDF5Handler
|
|
128
|
+
] = bliss.BlissDynamicHDF5Handler,
|
|
129
|
+
**openargs,
|
|
130
|
+
) -> None:
|
|
131
|
+
if isinstance(file, str):
|
|
132
|
+
openargs = {
|
|
133
|
+
k: v
|
|
134
|
+
for k, v in openargs.items()
|
|
135
|
+
if k not in ignore_file_arguments("dynamic_hdf5")
|
|
136
|
+
}
|
|
137
|
+
file = hdf5_retry_handler(file, **openargs)
|
|
138
|
+
super().__init__(file, hdf5.SEP)
|
|
139
|
+
|
|
140
|
+
def close(self) -> None:
|
|
141
|
+
self._retry_handler.close()
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def parent(self) -> None:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def file(self) -> "File":
|
|
149
|
+
return self
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def short_name(self) -> str:
|
|
153
|
+
return self.name
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""The `File` class in each context has specific arguments other than the `h5py.File`
|
|
2
|
+
arguments which are specific for that context (static HDF5, dynamic HDF5, ...).
|
|
3
|
+
"""
|
|
4
|
+
from typing import Set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_CONTEXT_SPECIFIC_KEYS = {
|
|
8
|
+
"h5py": {"mode"},
|
|
9
|
+
"static_hdf5": set(),
|
|
10
|
+
"dynamic_hdf5": {
|
|
11
|
+
"hdf5_retry_handler",
|
|
12
|
+
"retry_timeout",
|
|
13
|
+
"retry_period",
|
|
14
|
+
"lima_names",
|
|
15
|
+
"instrument_name",
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def ignore_file_arguments(context: str) -> Set[str]:
|
|
21
|
+
"""Returns all keys with that should be ignore when instantiating the `File`
|
|
22
|
+
in a specific context"""
|
|
23
|
+
ignore = set()
|
|
24
|
+
for key, value in _CONTEXT_SPECIFIC_KEYS.items():
|
|
25
|
+
if key == context:
|
|
26
|
+
continue
|
|
27
|
+
ignore |= value
|
|
28
|
+
return ignore
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Implementation of the h5py-like Bliss Data API with static HDF5 files"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Iterator, Tuple
|
|
4
|
+
from numpy.typing import DTypeLike
|
|
5
|
+
import h5py
|
|
6
|
+
from silx.io import h5py_utils
|
|
7
|
+
from . import abstract
|
|
8
|
+
from .utils import hdf5
|
|
9
|
+
from .utils import types
|
|
10
|
+
from .file_arguments import ignore_file_arguments
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Attributes(abstract.Attributes):
|
|
14
|
+
"""Node attributes."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, attrs: h5py.AttributeManager) -> None:
|
|
17
|
+
self.__attrs = attrs
|
|
18
|
+
super().__init__()
|
|
19
|
+
|
|
20
|
+
def __getitem__(self, key: str) -> Any:
|
|
21
|
+
return self.__attrs[key]
|
|
22
|
+
|
|
23
|
+
def __iter__(self) -> Iterator[Any]:
|
|
24
|
+
yield from self.__attrs
|
|
25
|
+
|
|
26
|
+
def __len__(self) -> int:
|
|
27
|
+
return len(self.__attrs)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Node:
|
|
31
|
+
"""Node in the data tree."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, h5py_obj: hdf5.HDF5Item) -> None:
|
|
34
|
+
self._h5py_obj = h5py_obj
|
|
35
|
+
self.__attrs = None
|
|
36
|
+
self.__parent = None
|
|
37
|
+
self.__file = None
|
|
38
|
+
super().__init__()
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def name(self) -> str:
|
|
42
|
+
return self._h5py_obj.name
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def short_name(self) -> str:
|
|
46
|
+
return self.name.rpartition(hdf5.SEP)[-1]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def parent(self) -> "Group":
|
|
50
|
+
if self.__parent is None:
|
|
51
|
+
self.__parent = Group(self._h5py_obj.parent)
|
|
52
|
+
return self.__parent
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def file(self) -> "File":
|
|
56
|
+
if self.__file is None:
|
|
57
|
+
self.__file = File(self._h5py_obj.file)
|
|
58
|
+
return self.__file
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def attrs(self) -> Attributes:
|
|
62
|
+
if self.__attrs is None:
|
|
63
|
+
self.__attrs = Attributes(self._h5py_obj.attrs)
|
|
64
|
+
return self.__attrs
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Group(Node, abstract.Group):
|
|
68
|
+
"""Node in the data tree which contains other nodes."""
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, key: str) -> Node:
|
|
71
|
+
h5py_obj = self._h5py_obj[key]
|
|
72
|
+
if isinstance(h5py_obj, h5py.Group):
|
|
73
|
+
return Group(h5py_obj)
|
|
74
|
+
else:
|
|
75
|
+
return Dataset(h5py_obj)
|
|
76
|
+
|
|
77
|
+
def __iter__(self) -> Iterator[Node]:
|
|
78
|
+
yield from self._h5py_obj
|
|
79
|
+
|
|
80
|
+
def __len__(self) -> int:
|
|
81
|
+
return len(self._h5py_obj)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Dataset(Node, abstract.Dataset):
|
|
85
|
+
"""Node in the data tree which contains data."""
|
|
86
|
+
|
|
87
|
+
def __getitem__(self, idx: types.DataIndexType) -> types.DataType:
|
|
88
|
+
return self._h5py_obj[idx]
|
|
89
|
+
|
|
90
|
+
def __iter__(self) -> Iterator[types.DataType]:
|
|
91
|
+
yield from self._h5py_obj
|
|
92
|
+
|
|
93
|
+
def __len__(self) -> int:
|
|
94
|
+
return len(self._h5py_obj)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def dtype(self) -> DTypeLike:
|
|
98
|
+
return self._h5py_obj.dtype
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def shape(self) -> Tuple[int]:
|
|
102
|
+
return self._h5py_obj.shape
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def size(self) -> int:
|
|
106
|
+
return self._h5py_obj.size
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def ndim(self) -> int:
|
|
110
|
+
return self._h5py_obj.ndim
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class File(Group, abstract.File):
|
|
114
|
+
"""Root node in the data tree."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, file: str, **openargs) -> None:
|
|
117
|
+
if isinstance(file, str):
|
|
118
|
+
openargs = {
|
|
119
|
+
k: v
|
|
120
|
+
for k, v in openargs.items()
|
|
121
|
+
if k not in ignore_file_arguments("dynamic_hdf5")
|
|
122
|
+
}
|
|
123
|
+
file = h5py_utils.File(file, **openargs)
|
|
124
|
+
super().__init__(file)
|
|
125
|
+
|
|
126
|
+
def close(self) -> None:
|
|
127
|
+
self._h5py_obj.close()
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def parent(self) -> None:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def file(self) -> "File":
|
|
135
|
+
return self
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def short_name(self) -> str:
|
|
139
|
+
return self.name
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional, Sequence
|
|
4
|
+
|
|
5
|
+
import h5py
|
|
6
|
+
|
|
7
|
+
from . import lima
|
|
8
|
+
from . import hdf5
|
|
9
|
+
from .hdf5_retry import RetryError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BlissDynamicHDF5Handler(hdf5.DynamicHDF5Handler):
|
|
13
|
+
"""Each NXentry has a writer status which can be
|
|
14
|
+
STARTING, RUNNING, SUCCEEDED or FAILED. This status
|
|
15
|
+
is used as stop criterium for iteration.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
*args,
|
|
21
|
+
lima_names: Sequence[str] = tuple(),
|
|
22
|
+
instrument_name: Optional[str] = None,
|
|
23
|
+
**kwargs,
|
|
24
|
+
):
|
|
25
|
+
self._match_lima_group = [
|
|
26
|
+
re.compile(
|
|
27
|
+
f"{hdf5.SEP}[0-9]+\\.[0-9]+{hdf5.SEP}instrument{hdf5.SEP}{name}{hdf5.SEP}data"
|
|
28
|
+
)
|
|
29
|
+
for name in lima_names
|
|
30
|
+
]
|
|
31
|
+
self._match_lima_dataset = [
|
|
32
|
+
re.compile(
|
|
33
|
+
f"{hdf5.SEP}[0-9]+\\.[0-9]+{hdf5.SEP}instrument{hdf5.SEP}{name}{hdf5.SEP}data"
|
|
34
|
+
)
|
|
35
|
+
for name in lima_names
|
|
36
|
+
]
|
|
37
|
+
self._non_native_items = dict()
|
|
38
|
+
self._lima_kwargs = {"user_instrument_name": instrument_name}
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
def reset(self) -> None:
|
|
42
|
+
super().reset()
|
|
43
|
+
for item in self._non_native_items.values():
|
|
44
|
+
item.reset()
|
|
45
|
+
|
|
46
|
+
def close(self) -> None:
|
|
47
|
+
for item in self._non_native_items.values():
|
|
48
|
+
item.close()
|
|
49
|
+
self._non_native_items = dict()
|
|
50
|
+
super().close()
|
|
51
|
+
|
|
52
|
+
def _check_dataset_before_read(self, item: hdf5.HDF5Dataset):
|
|
53
|
+
"""Check that the last source of a virtual dataset is released by the writer."""
|
|
54
|
+
if not hasattr(item, "is_virtual"):
|
|
55
|
+
return
|
|
56
|
+
if not item.is_virtual:
|
|
57
|
+
return
|
|
58
|
+
sources = item.virtual_sources()
|
|
59
|
+
if not sources:
|
|
60
|
+
return
|
|
61
|
+
_, filename, _, _ = sources[-1]
|
|
62
|
+
if not os.path.isabs(filename):
|
|
63
|
+
filename = os.path.abspath(
|
|
64
|
+
os.path.join(os.path.dirname(item.file.filename), filename)
|
|
65
|
+
)
|
|
66
|
+
try:
|
|
67
|
+
with h5py.File(filename, "r"):
|
|
68
|
+
pass
|
|
69
|
+
except Exception:
|
|
70
|
+
raise RetryError(f"cannot open '{filename}'")
|
|
71
|
+
|
|
72
|
+
def _get_item(self, name: str) -> hdf5.HDF5Item:
|
|
73
|
+
h5item = self._non_native_items.get(name)
|
|
74
|
+
if h5item is not None:
|
|
75
|
+
return h5item
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
return super()._get_item(name)
|
|
79
|
+
except KeyError as e:
|
|
80
|
+
exception = e
|
|
81
|
+
|
|
82
|
+
if any(m.match(name) for m in self._match_lima_dataset):
|
|
83
|
+
dirname = os.path.dirname(self.file_obj.filename)
|
|
84
|
+
h5item = lima.LimaDataset(name, dirname, **self._lima_kwargs)
|
|
85
|
+
self._non_native_items[name] = h5item
|
|
86
|
+
return h5item
|
|
87
|
+
|
|
88
|
+
if any(m.match(name) for m in self._match_lima_group):
|
|
89
|
+
dirname = os.path.dirname(self.file_obj.filename)
|
|
90
|
+
h5item = lima.LimaGroup(name, dirname, **self._lima_kwargs)
|
|
91
|
+
self._non_native_items[name] = h5item
|
|
92
|
+
return h5item
|
|
93
|
+
|
|
94
|
+
raise exception
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def is_group(h5item: hdf5.HDF5Item):
|
|
98
|
+
return isinstance(h5item, (h5py.Group, h5py.File, lima.LimaGroup))
|
|
99
|
+
|
|
100
|
+
def _is_initialized(self, h5item: hdf5.HDF5Item) -> bool:
|
|
101
|
+
try:
|
|
102
|
+
if h5item.name == hdf5.SEP:
|
|
103
|
+
return False
|
|
104
|
+
scan = [s for s in h5item.name.split(hdf5.SEP) if s][0]
|
|
105
|
+
nxentry = self.file_obj[scan]
|
|
106
|
+
except AttributeError:
|
|
107
|
+
raise RetryError("file is closed")
|
|
108
|
+
if "end_time" in nxentry:
|
|
109
|
+
# Last dataset written to the file
|
|
110
|
+
return True
|
|
111
|
+
return self._get_writer_status(nxentry) in ("RUNNING", "SUCCEEDED", "FAILED")
|
|
112
|
+
|
|
113
|
+
def _is_finished(self, h5item: hdf5.HDF5Item) -> bool:
|
|
114
|
+
try:
|
|
115
|
+
if h5item.name == hdf5.SEP:
|
|
116
|
+
# Assume there could be always more scans coming
|
|
117
|
+
return False
|
|
118
|
+
scan = [s for s in h5item.name.split(hdf5.SEP) if s][0]
|
|
119
|
+
nxentry = self.file_obj[scan]
|
|
120
|
+
except AttributeError:
|
|
121
|
+
raise RetryError("file is closed")
|
|
122
|
+
return "end_time" in nxentry # Last dataset written to the file
|
|
123
|
+
|
|
124
|
+
def _get_writer_status(self, nxentry: h5py.Dataset) -> Optional[str]:
|
|
125
|
+
nxnote = nxentry.get("writer", None)
|
|
126
|
+
if nxnote is None:
|
|
127
|
+
# writer notes not created yet
|
|
128
|
+
return None
|
|
129
|
+
status = nxnote.get("status", None)
|
|
130
|
+
if status is None:
|
|
131
|
+
# writer status not set yet
|
|
132
|
+
return None
|
|
133
|
+
status = status[()]
|
|
134
|
+
try:
|
|
135
|
+
status = status.decode()
|
|
136
|
+
except AttributeError:
|
|
137
|
+
pass
|
|
138
|
+
return status
|