blissdata 0.3.2__tar.gz → 0.3.4__tar.gz
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-0.3.2 → blissdata-0.3.4}/PKG-INFO +14 -2
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/__init__.py +1 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/beacon/_base.py +3 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/common/utils.py +16 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/lima.py +25 -5
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/expiration.py +1 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/channel.py +4 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/scan.py +9 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/remote_node.py +1 -11
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/scan.py +7 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/bliss.py +2 -5
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/hdf5.py +68 -15
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/hdf5_retry.py +1 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/caching.py +23 -28
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/connection.py +1 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/proxy.py +2 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/settings.py +16 -2
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/streaming.py +2 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/h5api/scanner.py +2 -2
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata.egg-info/PKG-INFO +14 -2
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata.egg-info/requires.txt +1 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/pyproject.toml +1 -4
- {blissdata-0.3.2 → blissdata-0.3.4}/setup.cfg +1 -1
- {blissdata-0.3.2 → blissdata-0.3.4}/LICENSE +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/README.md +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/beacon/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/beacon/config.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/beacon/data.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/beacon/files.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/client.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/common/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/channel.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/node.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/scan.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/events/walk.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/lima_image.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/node.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/dataset.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/dataset_collection.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/lima.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/node_ref_channel.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/proposal.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/data/nodes/scan_group.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/abstract.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/dynamic_hdf5.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/file_arguments.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/static_hdf5.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/lima.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/h5api/utils/types.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/manager.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/redis/scripting.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/streaming_events.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/beacon/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/beacon/test_config.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/beacon/test_data.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/beacon/test_files.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/conftest.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/h5api/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/h5api/test_dynamic_files.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/h5api/test_static_files.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/redis/__init__.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata/tests/redis/manager.py +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata.egg-info/SOURCES.txt +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata.egg-info/dependency_links.txt +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/blissdata.egg-info/top_level.txt +0 -0
- {blissdata-0.3.2 → blissdata-0.3.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: blissdata
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Bliss data streaming client
|
|
5
5
|
Home-page: https://gitlab.esrf.fr/bliss/bliss/-/tree/master/blissdata
|
|
6
6
|
Author: BCU (ESRF)
|
|
@@ -10,11 +10,23 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Requires-Python: <3.10,>=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: gevent>=20.9.0
|
|
15
|
+
Requires-Dist: h5py>=2.9
|
|
16
|
+
Requires-Dist: numpy>=1.13
|
|
17
|
+
Requires-Dist: silx>=1.1.0
|
|
13
18
|
Provides-Extra: tango
|
|
19
|
+
Requires-Dist: pytango>=9.3.1; extra == "tango"
|
|
14
20
|
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
15
23
|
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
26
|
+
Requires-Dist: black==22.3; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8>=4; extra == "dev"
|
|
16
28
|
Provides-Extra: beacon
|
|
17
|
-
|
|
29
|
+
Requires-Dist: ruamel.yaml; extra == "beacon"
|
|
18
30
|
|
|
19
31
|
# blissdata
|
|
20
32
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Base client to communicate with Beacon."""
|
|
2
2
|
|
|
3
|
+
import platform
|
|
3
4
|
import socket
|
|
4
5
|
import struct
|
|
5
6
|
import threading
|
|
@@ -28,7 +29,8 @@ class BeaconClient:
|
|
|
28
29
|
else:
|
|
29
30
|
self._address = host, port
|
|
30
31
|
connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
31
|
-
|
|
32
|
+
if platform.system() != "Windows":
|
|
33
|
+
connection.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10)
|
|
32
34
|
connection.connect(self._address)
|
|
33
35
|
connection.settimeout(timeout)
|
|
34
36
|
self._connection = connection
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
import fnmatch
|
|
9
|
+
import re
|
|
9
10
|
from collections.abc import Mapping
|
|
10
11
|
|
|
11
12
|
|
|
@@ -141,3 +142,18 @@ def get_matching_names(patterns, names, strict_pattern_as_short_name=False):
|
|
|
141
142
|
matches[pat] = matching_names
|
|
142
143
|
|
|
143
144
|
return matches
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def natural_sort(words):
|
|
148
|
+
def convert(text):
|
|
149
|
+
return int(text) if text.isdigit() else text.lower()
|
|
150
|
+
|
|
151
|
+
def alphanum_key(key):
|
|
152
|
+
return [
|
|
153
|
+
convert(c)
|
|
154
|
+
for c in re.split(
|
|
155
|
+
"([0-9]+)", key.decode() if isinstance(key, bytes) else key
|
|
156
|
+
)
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
return sorted(words, key=alphanum_key)
|
|
@@ -244,9 +244,24 @@ class LimaImageStatusEvent(streaming_events.StreamEvent):
|
|
|
244
244
|
images_per_acquisition = info["saving_frame_per_acquisition"]
|
|
245
245
|
|
|
246
246
|
first_file_index_in_scan = info["saving_next_number"]
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
if "saving_managed_mode" not in info:
|
|
248
|
+
raise RuntimeError(
|
|
249
|
+
"Lima info does not have 'saving_managed_mode'. Upgrade Bliss to solve it."
|
|
250
|
+
)
|
|
251
|
+
if info["saving_managed_mode"] == "HARDWARE":
|
|
252
|
+
# Assume Dectris hardware saving
|
|
253
|
+
subdir_format = (
|
|
254
|
+
info["saving_prefix"]
|
|
255
|
+
+ "_data_"
|
|
256
|
+
+ info["saving_index_format"]
|
|
257
|
+
+ info["saving_suffix"]
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
subdir_format = (
|
|
261
|
+
info["saving_prefix"]
|
|
262
|
+
+ info["saving_index_format"]
|
|
263
|
+
+ info["saving_suffix"]
|
|
264
|
+
)
|
|
250
265
|
path_format = os.path.join(info["saving_directory"], subdir_format)
|
|
251
266
|
file_format = info["saving_format"]
|
|
252
267
|
|
|
@@ -260,9 +275,13 @@ class LimaImageStatusEvent(streaming_events.StreamEvent):
|
|
|
260
275
|
)
|
|
261
276
|
if file_index_in_scan is None:
|
|
262
277
|
break
|
|
263
|
-
file_path = path_format % file_index_in_scan
|
|
264
278
|
if file_format.lower().startswith("hdf5"):
|
|
265
|
-
if info["
|
|
279
|
+
if info["saving_managed_mode"] == "HARDWARE":
|
|
280
|
+
# Assume Dectris hardware saving
|
|
281
|
+
path_in_file = "/entry/data/data"
|
|
282
|
+
# Dectris starts counting from 1 instead of 0 like lima does
|
|
283
|
+
file_index_in_scan += 1
|
|
284
|
+
elif info["lima_version"] == "<1.9.1":
|
|
266
285
|
# 'old' lima
|
|
267
286
|
path_in_file = (
|
|
268
287
|
"/entry_0000/instrument/"
|
|
@@ -277,6 +296,7 @@ class LimaImageStatusEvent(streaming_events.StreamEvent):
|
|
|
277
296
|
)
|
|
278
297
|
else:
|
|
279
298
|
path_in_file = ""
|
|
299
|
+
file_path = path_format % file_index_in_scan
|
|
280
300
|
yield file_path, path_in_file, image_index_in_file, file_format
|
|
281
301
|
|
|
282
302
|
@staticmethod
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Distributed under the GNU LGPLv3. See LICENSE for more info.
|
|
7
7
|
|
|
8
8
|
from blissdata.data.node import DataNodeContainer
|
|
9
|
-
from blissdata.data.nodes.channel import ChannelDataNode
|
|
9
|
+
from blissdata.data.nodes.channel import ChannelDataNode, RedisDataExpiredError
|
|
10
10
|
from blissdata.data.nodes.lima import LimaImageChannelDataNode
|
|
11
11
|
from blissdata.streaming_events import StreamEvent
|
|
12
12
|
from blissdata.data.events import (
|
|
@@ -179,6 +179,14 @@ def get_data_from_nodes(pipeline, *nodes):
|
|
|
179
179
|
for i, (channel_name, get_data_func) in enumerate(
|
|
180
180
|
scan_channel_get_data_func.items()
|
|
181
181
|
):
|
|
182
|
+
if not result[i]:
|
|
183
|
+
raise RedisDataExpiredError("Redis data channel expired.")
|
|
184
|
+
|
|
185
|
+
if result[i][0][0] != b"1-0": # first event is 1-0, not 0-0
|
|
186
|
+
raise RedisDataExpiredError(
|
|
187
|
+
f"Redis data channel {channel_name} have been trimmed out."
|
|
188
|
+
)
|
|
189
|
+
|
|
182
190
|
yield channel_name, get_data_func(result[i])
|
|
183
191
|
for channel_name, view in scan_image_get_view.items():
|
|
184
192
|
yield channel_name, view
|
|
@@ -199,16 +199,6 @@ class RemoteNodeWalker:
|
|
|
199
199
|
node.node_type,
|
|
200
200
|
node.name,
|
|
201
201
|
node.db_name,
|
|
202
|
-
|
|
202
|
+
node.info.get_all(),
|
|
203
203
|
redis_url,
|
|
204
204
|
)
|
|
205
|
-
|
|
206
|
-
@staticmethod
|
|
207
|
-
def pack_info(info):
|
|
208
|
-
info_dict = info.get_all()
|
|
209
|
-
try:
|
|
210
|
-
# ScanState objects brings Bliss dependencies with them, replace with name
|
|
211
|
-
info_dict["state"] = info["state"].name
|
|
212
|
-
except KeyError:
|
|
213
|
-
pass
|
|
214
|
-
return info_dict
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import sys
|
|
10
10
|
import numpy
|
|
11
11
|
import gevent
|
|
12
|
+
import gevent.event
|
|
12
13
|
import typing
|
|
13
14
|
import warnings
|
|
14
15
|
import contextlib
|
|
@@ -22,6 +23,12 @@ from blissdata.data.events import EventType
|
|
|
22
23
|
from blissdata.streaming import DataStreamReaderStopHandler
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
def get_data(scan):
|
|
27
|
+
# keep get_data() wrapper for scripts backward compatibility until the
|
|
28
|
+
# new data API provide the same functionality (expected for Bliss 1.12)
|
|
29
|
+
return scan.get_data()
|
|
30
|
+
|
|
31
|
+
|
|
25
32
|
def get_counter_names(scan):
|
|
26
33
|
"""
|
|
27
34
|
Return a list of counter names
|
|
@@ -102,7 +102,7 @@ class BlissDynamicHDF5Handler(hdf5.DynamicHDF5Handler):
|
|
|
102
102
|
if h5item.name == hdf5.SEP:
|
|
103
103
|
return False
|
|
104
104
|
scan = [s for s in h5item.name.split(hdf5.SEP) if s][0]
|
|
105
|
-
nxentry = self.file_obj
|
|
105
|
+
nxentry = self.file_obj[scan]
|
|
106
106
|
except AttributeError:
|
|
107
107
|
raise RetryError("file is closed")
|
|
108
108
|
if "end_time" in nxentry:
|
|
@@ -119,10 +119,7 @@ class BlissDynamicHDF5Handler(hdf5.DynamicHDF5Handler):
|
|
|
119
119
|
nxentry = self.file_obj[scan]
|
|
120
120
|
except AttributeError:
|
|
121
121
|
raise RetryError("file is closed")
|
|
122
|
-
|
|
123
|
-
# Last dataset written to the file
|
|
124
|
-
return True
|
|
125
|
-
return self._get_writer_status(nxentry) in ("SUCCEEDED", "FAILED")
|
|
122
|
+
return "end_time" in nxentry # Last dataset written to the file
|
|
126
123
|
|
|
127
124
|
def _get_writer_status(self, nxentry: h5py.Dataset) -> Optional[str]:
|
|
128
125
|
nxnote = nxentry.get("writer", None)
|
|
@@ -10,7 +10,7 @@ from . import types
|
|
|
10
10
|
from . import lima
|
|
11
11
|
from .hdf5_retry import retry_file_access
|
|
12
12
|
from .hdf5_retry import ignore_retry_timeout
|
|
13
|
-
from .hdf5_retry import RetryError, SoftRetryError
|
|
13
|
+
from .hdf5_retry import RetryTimeoutError, RetryError, SoftRetryError
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
HDF5Item = Union[h5py.Dataset, h5py.Group, h5py.File, lima.LimaDataset, lima.LimaGroup]
|
|
@@ -71,13 +71,19 @@ class DynamicHDF5Handler:
|
|
|
71
71
|
return self._file_obj
|
|
72
72
|
|
|
73
73
|
def get_item(self, name: str) -> HDF5Item:
|
|
74
|
-
|
|
74
|
+
try:
|
|
75
|
+
return self._retry_get_item(name)
|
|
76
|
+
except RetryTimeoutError:
|
|
77
|
+
raise KeyError(name)
|
|
75
78
|
|
|
76
79
|
def slice_dataset(self, name: str, idx: types.DataIndexType) -> types.DataType:
|
|
77
80
|
return self._retry_slice_dataset(name, idx)
|
|
78
81
|
|
|
79
82
|
def get_attr(self, name: str, key: str) -> Any:
|
|
80
|
-
|
|
83
|
+
try:
|
|
84
|
+
return self._retry_get_attr(name, key)
|
|
85
|
+
except RetryTimeoutError:
|
|
86
|
+
raise AttributeError(name)
|
|
81
87
|
|
|
82
88
|
@ignore_retry_timeout
|
|
83
89
|
def iter_attrs(self, name: str) -> Iterator[str]:
|
|
@@ -138,7 +144,24 @@ class DynamicHDF5Handler:
|
|
|
138
144
|
item = self._native_items.get(name)
|
|
139
145
|
if item is not None:
|
|
140
146
|
return item
|
|
141
|
-
|
|
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
|
|
142
165
|
self._native_items[name] = item
|
|
143
166
|
return item
|
|
144
167
|
|
|
@@ -152,16 +175,28 @@ class DynamicHDF5Handler:
|
|
|
152
175
|
|
|
153
176
|
def _get_attr(self, name: str, key: str) -> Any:
|
|
154
177
|
item = self._get_item(name)
|
|
155
|
-
|
|
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
|
|
156
190
|
|
|
157
191
|
def _iter_attrs(self, name: str, start_index: int = 0) -> Iterator[str]:
|
|
158
192
|
item = self._get_item(name)
|
|
193
|
+
is_complete = self._is_initialized(item)
|
|
159
194
|
if start_index == 0:
|
|
160
195
|
yield from item.attrs
|
|
161
196
|
else:
|
|
162
197
|
yield from list(item.attrs)[start_index:]
|
|
163
|
-
if not
|
|
164
|
-
raise RetryError("
|
|
198
|
+
if not is_complete:
|
|
199
|
+
raise RetryError("attributes could still be added")
|
|
165
200
|
|
|
166
201
|
def _len_attrs(self, name: str) -> int:
|
|
167
202
|
item = self._get_item(name)
|
|
@@ -173,19 +208,19 @@ class DynamicHDF5Handler:
|
|
|
173
208
|
item = self._get_item(name)
|
|
174
209
|
self._check_dataset_before_read(item)
|
|
175
210
|
if self.is_group(item):
|
|
176
|
-
|
|
211
|
+
is_complete = self._is_initialized(item)
|
|
177
212
|
for key in self._iter_group(item, start_index):
|
|
178
|
-
|
|
213
|
+
is_complete = False
|
|
179
214
|
yield key
|
|
180
|
-
if not
|
|
181
|
-
raise RetryError("
|
|
215
|
+
if not is_complete:
|
|
216
|
+
raise RetryError("children could still be added to the group")
|
|
182
217
|
else:
|
|
183
|
-
|
|
218
|
+
is_complete = self._is_finished(item)
|
|
184
219
|
for data in self._iter_dataset(item, start_index):
|
|
185
|
-
|
|
220
|
+
is_complete = False
|
|
186
221
|
yield data
|
|
187
|
-
if not
|
|
188
|
-
raise RetryError("
|
|
222
|
+
if not is_complete:
|
|
223
|
+
raise RetryError("dataset could still grow")
|
|
189
224
|
|
|
190
225
|
@staticmethod
|
|
191
226
|
def is_group(h5item: HDF5Item):
|
|
@@ -225,3 +260,21 @@ class DynamicHDF5Handler:
|
|
|
225
260
|
|
|
226
261
|
def _is_finished(self, h5item: HDF5Item) -> bool:
|
|
227
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)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Client-side Redis cache with an invalidation connection.
|
|
2
2
|
"""
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import socket
|
|
5
5
|
import gevent
|
|
6
6
|
import gevent.event
|
|
7
|
+
import gevent.select
|
|
7
8
|
from functools import wraps
|
|
8
9
|
from contextlib import ExitStack, contextmanager
|
|
9
10
|
from collections.abc import MutableMapping
|
|
@@ -38,7 +39,7 @@ class CachingConnectionGreenlet(gevent.Greenlet):
|
|
|
38
39
|
self._started_event = started_event
|
|
39
40
|
self._tracking_redirect_id = None
|
|
40
41
|
self.connection = None
|
|
41
|
-
self.
|
|
42
|
+
self._rp, self._wp = None, None
|
|
42
43
|
super().__init__()
|
|
43
44
|
|
|
44
45
|
def stop(self):
|
|
@@ -47,11 +48,11 @@ class CachingConnectionGreenlet(gevent.Greenlet):
|
|
|
47
48
|
|
|
48
49
|
Calling `kill` might not return the pubsub connection to the pool.
|
|
49
50
|
"""
|
|
50
|
-
self._send_fd(self.
|
|
51
|
+
self._send_fd(self._wp, b"X")
|
|
51
52
|
|
|
52
53
|
def _run(self):
|
|
53
54
|
with ExitStack() as stack:
|
|
54
|
-
ctx = self.
|
|
55
|
+
ctx = self._setup_wp()
|
|
55
56
|
rp, stop_callback = stack.enter_context(ctx)
|
|
56
57
|
|
|
57
58
|
ctx = self._setup_invalidation_connection(stop_callback)
|
|
@@ -64,44 +65,38 @@ class CachingConnectionGreenlet(gevent.Greenlet):
|
|
|
64
65
|
self._started_event.set()
|
|
65
66
|
self._invalidation_loop(pubsub, rp)
|
|
66
67
|
|
|
67
|
-
@
|
|
68
|
-
def
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def stop_callback(*args, **kw):
|
|
68
|
+
@staticmethod
|
|
69
|
+
def _close_fd(fd):
|
|
70
|
+
if fd is not None:
|
|
72
71
|
try:
|
|
73
|
-
|
|
72
|
+
fd.close()
|
|
74
73
|
except OSError:
|
|
75
74
|
# already closed
|
|
76
75
|
pass
|
|
77
76
|
|
|
78
|
-
try:
|
|
79
|
-
self._close_pipe = wp
|
|
80
|
-
yield rp, stop_callback
|
|
81
|
-
finally:
|
|
82
|
-
self._close_pipe = None
|
|
83
|
-
self._close_fd(wp)
|
|
84
|
-
self._close_fd(rp)
|
|
85
|
-
|
|
86
77
|
@staticmethod
|
|
87
|
-
def
|
|
78
|
+
def _send_fd(fd, msg):
|
|
88
79
|
if fd is None:
|
|
89
80
|
return
|
|
90
81
|
try:
|
|
91
|
-
|
|
82
|
+
fd.send(msg)
|
|
92
83
|
except OSError:
|
|
93
84
|
# already closed
|
|
94
85
|
pass
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
def _close_wp(self):
|
|
88
|
+
return self._close_fd(self._wp)
|
|
89
|
+
|
|
90
|
+
@contextmanager
|
|
91
|
+
def _setup_wp(self):
|
|
92
|
+
self._rp, self._wp = socket.socketpair()
|
|
93
|
+
|
|
100
94
|
try:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
yield self._rp, self._close_wp
|
|
96
|
+
finally:
|
|
97
|
+
self._close_fd(self._rp)
|
|
98
|
+
self._close_fd(self._wp)
|
|
99
|
+
self._rp, self._wp = None, None
|
|
105
100
|
|
|
106
101
|
@contextmanager
|
|
107
102
|
def _setup_invalidation_connection(self, stop_callback):
|
|
@@ -13,7 +13,7 @@ import logging
|
|
|
13
13
|
import numpy
|
|
14
14
|
from blissdata.client import get_redis_proxy
|
|
15
15
|
from blissdata.redis.scripting import register_script, evaluate_script
|
|
16
|
-
from blissdata.common.utils import Undefined
|
|
16
|
+
from blissdata.common.utils import Undefined, natural_sort
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
@@ -712,7 +712,7 @@ local set_size = redis.call("ZCARD", setkey)
|
|
|
712
712
|
if set_max_score > set_size then
|
|
713
713
|
-- we need to reassign scores as some was deleted
|
|
714
714
|
local new_order = 1
|
|
715
|
-
table = redis.call("ZRANGE", setkey, 0, -1)
|
|
715
|
+
local table = redis.call("ZRANGE", setkey, 0, -1)
|
|
716
716
|
for k, attr in pairs(table)
|
|
717
717
|
do
|
|
718
718
|
--redis.call("ZADD", setkey, new_order, attr)
|
|
@@ -760,6 +760,20 @@ class OrderedHashSetting(BaseHashSetting):
|
|
|
760
760
|
orderedhashsetting_helper_script,
|
|
761
761
|
)
|
|
762
762
|
|
|
763
|
+
# check if this ordered hash is built from an existing hash setting
|
|
764
|
+
if self.connection.exists(self.name) and not self.connection.exists(
|
|
765
|
+
self._name_order
|
|
766
|
+
):
|
|
767
|
+
if self.connection.type(self.name) == b"hash": # noqa E721
|
|
768
|
+
# we can provide a default order (alphabetical), to ensure an ordered hash
|
|
769
|
+
# can be built on top of an existing hash ; this is to preserve
|
|
770
|
+
# settings coherency in case of type "upgrade" (like when Lima ROIs
|
|
771
|
+
# have been ordered from "normal" hashes)
|
|
772
|
+
sorted_keys = natural_sort(self.connection.hkeys(self.name))
|
|
773
|
+
self.connection.zadd(
|
|
774
|
+
self._name_order, {v: k for k, v in enumerate(sorted_keys)}
|
|
775
|
+
)
|
|
776
|
+
|
|
763
777
|
@property
|
|
764
778
|
def _name_order(self):
|
|
765
779
|
return self._name + ":creation_order"
|
|
@@ -268,7 +268,7 @@ def writer_context(filename: str, scan_number: int) -> Iterator[h5py.Group]:
|
|
|
268
268
|
|
|
269
269
|
try:
|
|
270
270
|
entry.attrs["NX_class"] = "NXentry"
|
|
271
|
-
entry
|
|
271
|
+
entry["start_time"] = timestamp()
|
|
272
272
|
writer = entry.create_group("writer")
|
|
273
273
|
writer.attrs["NX_class"] = "NXnote"
|
|
274
274
|
writer["status"] = "STARTING"
|
|
@@ -279,7 +279,7 @@ def writer_context(filename: str, scan_number: int) -> Iterator[h5py.Group]:
|
|
|
279
279
|
else:
|
|
280
280
|
writer["status"][()] = "SUCCEEDED"
|
|
281
281
|
finally:
|
|
282
|
-
entry
|
|
282
|
+
entry["end_time"] = timestamp()
|
|
283
283
|
|
|
284
284
|
|
|
285
285
|
@contextmanager
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: blissdata
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Bliss data streaming client
|
|
5
5
|
Home-page: https://gitlab.esrf.fr/bliss/bliss/-/tree/master/blissdata
|
|
6
6
|
Author: BCU (ESRF)
|
|
@@ -10,11 +10,23 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Requires-Python: <3.10,>=3.7
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: gevent>=20.9.0
|
|
15
|
+
Requires-Dist: h5py>=2.9
|
|
16
|
+
Requires-Dist: numpy>=1.13
|
|
17
|
+
Requires-Dist: silx>=1.1.0
|
|
13
18
|
Provides-Extra: tango
|
|
19
|
+
Requires-Dist: pytango>=9.3.1; extra == "tango"
|
|
14
20
|
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
22
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
15
23
|
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
26
|
+
Requires-Dist: black==22.3; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8>=4; extra == "dev"
|
|
16
28
|
Provides-Extra: beacon
|
|
17
|
-
|
|
29
|
+
Requires-Dist: ruamel.yaml; extra == "beacon"
|
|
18
30
|
|
|
19
31
|
# blissdata
|
|
20
32
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|