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
blissdata/data/scan.py ADDED
@@ -0,0 +1,666 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of the bliss project
4
+ #
5
+ # Copyright (c) 2015-2023 Beamline Control Unit, ESRF
6
+ # Distributed under the GNU LGPLv3. See LICENSE for more info.
7
+
8
+
9
+ import sys
10
+ import numpy
11
+ import gevent
12
+ import gevent.event
13
+ import typing
14
+ import warnings
15
+ import contextlib
16
+
17
+ from typing import Dict
18
+
19
+ from blissdata.data.nodes.channel import ChannelDataNode
20
+ from blissdata.data.node import get_or_create_node
21
+ from blissdata.data.events import Event
22
+ from blissdata.data.events import EventType
23
+ from blissdata.streaming import DataStreamReaderStopHandler
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
+
32
+ def get_counter_names(scan):
33
+ """
34
+ Return a list of counter names
35
+ """
36
+ return [
37
+ node.name for node in scan.nodes.values() if isinstance(node, ChannelDataNode)
38
+ ]
39
+
40
+
41
+ class ScansObserver:
42
+ """
43
+ Observer for the `ScansWatcher`.
44
+
45
+ Provides methods which can be inherited to follow the life cycle of the
46
+ scans of a session.
47
+ """
48
+
49
+ def on_event_received(self, event: Event):
50
+ """
51
+ Called upon new event
52
+
53
+ Mostly used for backward compatibility with `DefaultScanObserver`.
54
+ """
55
+ pass
56
+
57
+ def on_scan_created(self, scan_db_name: str, scan_info: Dict):
58
+ """
59
+ Called upon scan created (devices are not yet prepared).
60
+
61
+ Arguments:
62
+ scan_db_name: Identifier of the scan
63
+ scan_info: Dictionary containing scan metadata
64
+ """
65
+ pass
66
+
67
+ def on_scan_started(self, scan_db_name: str, scan_info: Dict):
68
+ """
69
+ Called upon scan started (the devices was prepared).
70
+
71
+ Arguments:
72
+ scan_db_name: Identifier of the scan
73
+ scan_info: Dictionary containing scan metadata updated with metadata
74
+ prepared metadata from controllers
75
+ """
76
+ pass
77
+
78
+ def on_child_created(self, scan_db_name: str, node):
79
+ """
80
+ Called upon scan child creation (e.g. channel node)
81
+
82
+ Arguments:
83
+ scan_db_name: Identifier of the parent scan
84
+ node: Redis node of this child
85
+ """
86
+ pass
87
+
88
+ def on_scalar_data_received(
89
+ self,
90
+ scan_db_name: str,
91
+ channel_name: str,
92
+ index: int,
93
+ data_bunch: typing.Union[list, numpy.ndarray],
94
+ ):
95
+ """
96
+ Called upon a bunch of scalar data (0dim) from a `top_master` was
97
+ received.
98
+
99
+ Arguments:
100
+ scan_db_name: Identifier of the parent scan
101
+ channel_name: Name of the updated channel
102
+ index: Start index of the data bunch in the real data stream.
103
+ There could be wholes between 2 bunches of data.
104
+ data_bunch: The list of data received, as a bunch of data.
105
+ """
106
+ pass
107
+
108
+ def on_ndim_data_received(
109
+ self,
110
+ scan_db_name: str,
111
+ channel_name: str,
112
+ dim: int,
113
+ index: int,
114
+ data_bunch: typing.Union[list, numpy.ndarray],
115
+ ):
116
+ """Called upon a ndim data (except 0dim, except data ref) data was
117
+ received.
118
+
119
+ - For 0dim data, see `on_scalar_data_received`.
120
+
121
+ Arguments:
122
+ scan_db_name: Identifier of the parent scan
123
+ channel_name: Name of the channel emitting the data
124
+ dim: Dimension of this data (MCA is 1, image is 2)
125
+ index: Start index of the data bunch in the real data stream.
126
+ There could be wholes between 2 bunches of data.
127
+ data_bunch: The list of data received, as a bunch of data.
128
+ """
129
+ pass
130
+
131
+ def on_lima_ref_received(
132
+ self, scan_db_name: str, channel_name: str, dim: int, source_node, event_data
133
+ ):
134
+ """Called upon a ndim (except 0dim) data was received.
135
+
136
+ For 0dim data, see `on_scalar_data_received`.
137
+
138
+ Arguments:
139
+ scan_db_name: Identifier of the parent scan
140
+ channel_name: Name of the channel emitting the data
141
+ dim: Dimension of this data
142
+ source_node: Node containing the updated data
143
+ event_data: Data of the event
144
+ """
145
+ pass
146
+
147
+ def on_scan_finished(self, scan_db_name: str, scan_info: Dict):
148
+ """
149
+ Called upon scan end.
150
+
151
+ Arguments:
152
+ scan_db_name: Identifier of the parent scan
153
+ scan_info: Dictionary containing scan metadata updated with
154
+ prepared and finished metadata from controllers
155
+ Other fields like positioners and datetime are also
156
+ updated.
157
+ """
158
+ pass
159
+
160
+
161
+ class ScansWatcher:
162
+ """
163
+ Watch scans from a specific session.
164
+
165
+ Arguments:
166
+ session_name: Name of the BLISS session
167
+ """
168
+
169
+ def __init__(self, session_name: str):
170
+ self._session_name = session_name
171
+ self._exclude_existing_scans = False
172
+ self._watch_scan_group = False
173
+ self._observer: ScansObserver = None
174
+
175
+ self._running = False
176
+ """True if the watcher was started."""
177
+
178
+ self._running_scans = set()
179
+ """Store running scans"""
180
+
181
+ self._ready_event = gevent.event.Event()
182
+ """Handle the ready event"""
183
+
184
+ self._terminated_event = gevent.event.Event()
185
+ """Handle the end event"""
186
+
187
+ self._no_scans_event = gevent.event.Event()
188
+ """Handle the amount of listened scans"""
189
+
190
+ self._stop_handler = DataStreamReaderStopHandler()
191
+ """Handler to be able to stop the event loop"""
192
+
193
+ self._no_scans_event.set()
194
+
195
+ def wait_ready(self, timeout: float = None):
196
+ """Wait until the scan watcher is ready to receive new event.
197
+
198
+ The steps between `started` and `ready` can takes few seconds depending
199
+ on the amount of data and the load of Redis.
200
+
201
+ Arguments:
202
+ timeout: If not `None`, it should be a floating point number
203
+ specifying a timeout for the operation in seconds
204
+ (or fractions thereof).
205
+ """
206
+ self._ready_event.wait(timeout=timeout)
207
+
208
+ def wait_no_more_running_scans(self, timeout: float = None):
209
+ """Wait until there is no more running scans in this watcher."""
210
+ self._no_scans_event.wait(timeout=timeout)
211
+
212
+ def wait_terminated(self, timeout: float = None):
213
+ """Wait until the scan watcher is terminated.
214
+
215
+ The steps between `started` and `ready` can takes few seconds depending
216
+ on the amount of data and the load of Redis.
217
+
218
+ Arguments:
219
+ timeout: If not `None`, it should be a floating point number
220
+ specifying a timeout for the operation in seconds
221
+ (or fractions thereof).
222
+ """
223
+ self._terminated_event.wait(timeout=timeout)
224
+
225
+ def running_scan_names(self) -> typing.Sequence[str]:
226
+ """
227
+ Returns the known running scans with there db names.
228
+
229
+ Only managed scans are listed here. For example, if `watch_scan_group`
230
+ was set to False, this scans will not be shown.
231
+ """
232
+ return list(self._running_scans)
233
+
234
+ def set_exclude_existing_scans(self, exclude: bool):
235
+ """
236
+ Include or exclude existing scans. Default is False.
237
+
238
+ Will become True by default in the future.
239
+
240
+ It have to be set before start.
241
+ """
242
+ assert not self._running
243
+ self._exclude_existing_scans = exclude
244
+
245
+ def set_watch_scan_group(self, watch: bool):
246
+ """
247
+ Set to True to include scan groups like any other scans. Default is False.
248
+
249
+ It have to be set before start.
250
+ """
251
+ assert not self._running
252
+ self._watch_scan_group = watch
253
+
254
+ def set_observer(self, observer: ScansObserver):
255
+ """
256
+ Set the observer to use with this watcher process.
257
+
258
+ If not set, the `run` method will raise an exception.
259
+ """
260
+ assert not self._running
261
+ self._observer = observer
262
+
263
+ def _set_stop_handler(self, stop_handler):
264
+ """
265
+ Backward compatibility code with `watch_session_scans`.
266
+
267
+ This function have to be removed with `watch_session_scans`.
268
+ """
269
+ assert not self._running
270
+ self._stop_handler = stop_handler
271
+
272
+ def _get_scan_db_name_from_child(self, db_name: str) -> str:
273
+ """
274
+ Returns the scan_db_name from the db_name of a child.
275
+
276
+ It also works with the `scan_db_name`.
277
+ """
278
+ for key in self._running_scans:
279
+ if db_name.startswith(key):
280
+ return key
281
+ return None
282
+
283
+ @contextlib.contextmanager
284
+ def watch(self):
285
+ """Context manager to start and stop the watcher.
286
+
287
+ It uses a gevent spawn.
288
+
289
+ Yield:
290
+ The spawned greenlet
291
+ """
292
+ try:
293
+ gwatcher = gevent.spawn(self.run)
294
+ gwatcher.name = "bliss_scans_watcher"
295
+ self.wait_ready(timeout=3)
296
+ yield gwatcher
297
+ finally:
298
+ self.wait_no_more_running_scans(timeout=2)
299
+ self.stop()
300
+ try:
301
+ self.wait_terminated(timeout=1)
302
+ finally:
303
+ try:
304
+ gwatcher.join(timeout=1)
305
+ except Exception:
306
+ gwatcher.kill()
307
+
308
+ def run(self):
309
+ """
310
+ Run watching scan events.
311
+
312
+ This method is blocking. But can be terminated by calling `stop`.
313
+
314
+ Any scan node that is created before the `ready_event` will not be watched
315
+ when `exclude_existing_scans` is True.
316
+ """
317
+ assert not self._running
318
+ self._terminated_event.clear()
319
+ self._running = True
320
+ try:
321
+ session_node = get_or_create_node(self._session_name, node_type="session")
322
+ if session_node is None:
323
+ return
324
+
325
+ observer = self._observer
326
+ if observer is None:
327
+ raise RuntimeError("No observer was set")
328
+
329
+ if self._exclude_existing_scans:
330
+ exclude_existing_children = "scan", "scan_group"
331
+ else:
332
+ exclude_existing_children = None
333
+
334
+ for event in session_node.walk_on_new_events(
335
+ stop_handler=self._stop_handler,
336
+ exclude_existing_children=exclude_existing_children,
337
+ started_event=self._ready_event,
338
+ ):
339
+ event_type, node, event_data = event
340
+ try:
341
+ observer.on_event_received(event)
342
+ except Exception:
343
+ sys.excepthook(*sys.exc_info())
344
+
345
+ if event_type == EventType.NEW_NODE:
346
+ node_type = node.type
347
+ db_name = node.db_name
348
+ if node_type == "scan":
349
+ # New scan was created
350
+ scan_info = node.info.get_all()
351
+ self._running_scans.add(db_name)
352
+ self._no_scans_event.clear()
353
+ observer.on_scan_created(db_name, scan_info)
354
+ elif node_type == "scan_group":
355
+ if self._watch_scan_group:
356
+ # New scan was created
357
+ scan_info = node.info.get_all()
358
+ self._running_scans.add(db_name)
359
+ self._no_scans_event.clear()
360
+ observer.on_scan_created(db_name, scan_info)
361
+ else:
362
+ scan_db_name = self._get_scan_db_name_from_child(db_name)
363
+ if scan_db_name is not None:
364
+ try:
365
+ observer.on_child_created(scan_db_name, node)
366
+ except Exception:
367
+ sys.excepthook(*sys.exc_info())
368
+ elif event_type == EventType.NEW_DATA:
369
+ db_name = node.db_name
370
+ if not hasattr(node, "fullname"):
371
+ # not a node we want to do anything with here
372
+ continue
373
+
374
+ fullname = node.fullname
375
+
376
+ scan_db_name = self._get_scan_db_name_from_child(db_name)
377
+ if scan_db_name is not None:
378
+ if node.type == "channel":
379
+ description = event_data.description
380
+ shape = description.get("shape")
381
+ dim = len(shape)
382
+ if dim == 0:
383
+ try:
384
+ observer.on_scalar_data_received(
385
+ scan_db_name=scan_db_name,
386
+ channel_name=fullname,
387
+ index=event_data.first_index,
388
+ data_bunch=event_data.data,
389
+ )
390
+ except Exception:
391
+ sys.excepthook(*sys.exc_info())
392
+ else:
393
+ try:
394
+ observer.on_ndim_data_received(
395
+ scan_db_name=scan_db_name,
396
+ channel_name=fullname,
397
+ dim=dim,
398
+ index=event_data.first_index,
399
+ data_bunch=event_data.data,
400
+ )
401
+ except Exception:
402
+ sys.excepthook(*sys.exc_info())
403
+ elif node.type == "lima":
404
+ # Lima and only Lima deals with ref for now
405
+ # FIXME: It would be good to have a dedicated event type for that
406
+ try:
407
+ observer.on_lima_ref_received(
408
+ scan_db_name=scan_db_name,
409
+ channel_name=fullname,
410
+ source_node=node,
411
+ dim=2,
412
+ event_data=event_data,
413
+ )
414
+ except Exception:
415
+ sys.excepthook(*sys.exc_info())
416
+ elif event_type == EventType.PREPARED_SCAN:
417
+ node_type = node.type
418
+ if self._watch_scan_group or node_type == "scan":
419
+ db_name = node.db_name
420
+ if db_name in self._running_scans:
421
+ scan_info = node.info.get_all()
422
+ try:
423
+ observer.on_scan_started(db_name, scan_info)
424
+ except Exception:
425
+ sys.excepthook(*sys.exc_info())
426
+ elif event_type == EventType.END_SCAN:
427
+ node_type = node.type
428
+ if self._watch_scan_group or node_type == "scan":
429
+ db_name = node.db_name
430
+ if db_name in self._running_scans:
431
+ try:
432
+ scan_info = node.info.get_all()
433
+ try:
434
+ observer.on_scan_finished(db_name, scan_info)
435
+ except Exception:
436
+ sys.excepthook(*sys.exc_info())
437
+ finally:
438
+ self._running_scans.discard(db_name)
439
+ if len(self._running_scans) == 0:
440
+ self._no_scans_event.set()
441
+ gevent.idle()
442
+ finally:
443
+ self._running = False
444
+ self._terminated_event.set()
445
+
446
+ def stop(self):
447
+ """Call it to stop the event loop."""
448
+ if self._running:
449
+ self._stop_handler.stop()
450
+
451
+
452
+ class DefaultScansObserver(ScansObserver):
453
+ """Default scan observer.
454
+
455
+ This observer provides a compatibility with the previous implementation:
456
+
457
+ - Backward compatible API for callbacks (BLISS <= 1.7)
458
+ - Storing scan_info per scans
459
+ - Storing the whole data for each scalar channels
460
+ """
461
+
462
+ class _ScanDescription(typing.NamedTuple):
463
+ scan_info: Dict
464
+ """Scan_info of the scan"""
465
+ channels_to_master: Dict[str, str]
466
+ """Describe the master for each channels"""
467
+ channels_data: Dict[str, numpy.ndarray]
468
+ """Store the full data per scalar channels"""
469
+
470
+ def __init__(self):
471
+ self._running_scans: Dict[str, self._ScanDescription] = {}
472
+ self.scan_new_callback: typing.Callable[[Dict], None] = None
473
+ self.scan_new_child_callback: typing.Callable[[Dict, typing.Any], None] = None
474
+ self.scan_data_callback: typing.Callable[[str, str, Dict], None] = None
475
+ self.scan_end_callback: typing.Callable[[Dict], None] = None
476
+ self._current_event: Event = None
477
+ """
478
+ Used to store a tuple with `event_type`, `node`, `event_data`
479
+ during a callback event.
480
+
481
+ Never None inside callbacks
482
+ """
483
+
484
+ def _get_scan_description(self, scan_db_name) -> _ScanDescription:
485
+ return self._running_scans.get(scan_db_name)
486
+
487
+ def on_event_received(self, event):
488
+ """
489
+ Called upon new event
490
+
491
+ Mostly used for backward compatibility with `DefaultScanObserver`.
492
+ """
493
+ self._current_event = event
494
+
495
+ def on_scan_created(self, scan_db_name: str, scan_info: Dict):
496
+ # Pre-compute mapping from each channels to its master
497
+ top_master_per_channels = {}
498
+ for top_master, meta in scan_info["acquisition_chain"].items():
499
+ for device_name in meta["devices"]:
500
+ device_meta = scan_info["devices"][device_name]
501
+ for channel_name in device_meta.get("channels", []):
502
+ top_master_per_channels[channel_name] = top_master
503
+ self._running_scans[scan_db_name] = self._ScanDescription(
504
+ scan_info, top_master_per_channels, {}
505
+ )
506
+ if self.scan_new_callback is not None:
507
+ self.scan_new_callback(scan_info)
508
+
509
+ def on_scan_finished(self, scan_db_name: str, scan_info: Dict):
510
+ self._running_scans.pop(scan_db_name)
511
+ if self.scan_end_callback is not None:
512
+ self.scan_end_callback(scan_info)
513
+
514
+ def on_child_created(self, scan_db_name: str, node):
515
+ scan_desciption = self._get_scan_description(scan_db_name)
516
+ if scan_desciption is None:
517
+ # Scan not part of the listened scans
518
+ return
519
+
520
+ if self.scan_new_child_callback is not None:
521
+ self.scan_new_child_callback(scan_desciption.scan_info, node)
522
+
523
+ def on_scalar_data_received(
524
+ self,
525
+ scan_db_name: str,
526
+ channel_name: str,
527
+ index: int,
528
+ data_bunch: typing.Union[list, numpy.ndarray],
529
+ ):
530
+ if self.scan_data_callback is None:
531
+ return
532
+
533
+ scan_desciption = self._get_scan_description(scan_db_name)
534
+ if scan_desciption is None:
535
+ # Scan not part of the listened scans
536
+ return
537
+
538
+ # in case of zerod, we keep all data value during the scan
539
+ prev_data = scan_desciption.channels_data.get(channel_name, [])
540
+ data = numpy.concatenate((prev_data, data_bunch))
541
+ scan_desciption.channels_data[channel_name] = data
542
+
543
+ top_master = scan_desciption.channels_to_master[channel_name]
544
+ self.scan_data_callback(
545
+ "0d",
546
+ top_master,
547
+ {
548
+ "data": scan_desciption.channels_data,
549
+ "scan_info": scan_desciption.scan_info,
550
+ },
551
+ )
552
+
553
+ def on_ndim_data_received(
554
+ self,
555
+ scan_db_name: str,
556
+ channel_name: str,
557
+ dim: int,
558
+ index: int,
559
+ data_bunch: typing.Union[list, numpy.ndarray],
560
+ ):
561
+ if self.scan_data_callback is None:
562
+ return
563
+
564
+ scan_desciption = self._get_scan_description(scan_db_name)
565
+ if scan_desciption is None:
566
+ # Scan not part of the listened scans
567
+ return
568
+
569
+ source_node = self._current_event.node
570
+ event_data = self._current_event.data
571
+
572
+ top_master = scan_desciption.channels_to_master[channel_name]
573
+ self.scan_data_callback(
574
+ f"{dim}d",
575
+ top_master,
576
+ {
577
+ "index": index,
578
+ "data": data_bunch,
579
+ "description": event_data.description,
580
+ "channel_name": channel_name,
581
+ "channel_data_node": source_node,
582
+ "scan_info": scan_desciption.scan_info,
583
+ },
584
+ )
585
+
586
+ def on_lima_ref_received(
587
+ self, scan_db_name: str, channel_name: str, dim: int, source_node, event_data
588
+ ):
589
+ if self.scan_data_callback is None:
590
+ return
591
+
592
+ scan_desciption = self._get_scan_description(scan_db_name)
593
+ if scan_desciption is None:
594
+ # Scan not part of the listened scans
595
+ return
596
+
597
+ top_master = scan_desciption.channels_to_master[channel_name]
598
+ self.scan_data_callback(
599
+ f"{dim}d",
600
+ top_master,
601
+ {
602
+ "index": event_data.first_index,
603
+ "data": event_data.data,
604
+ "description": event_data.description,
605
+ "channel_name": channel_name,
606
+ "channel_data_node": source_node,
607
+ "scan_info": scan_desciption.scan_info,
608
+ },
609
+ )
610
+
611
+
612
+ def watch_session_scans(
613
+ session_name,
614
+ scan_new_callback,
615
+ scan_new_child_callback,
616
+ scan_data_callback,
617
+ scan_end_callback=None,
618
+ ready_event=None,
619
+ stop_handler=None,
620
+ watch_scan_group: bool = False,
621
+ exclude_existing_scans=None,
622
+ ):
623
+ """Any scan node that is created before the `ready_event` will not be watched
624
+ when `exclude_existing_scans=True`.
625
+
626
+ :param str session_name:
627
+ :param callable scan_new_callback: called upon scan start
628
+ :param callable scan_new_child_callback: called upon scan child creation (e.g. channel node)
629
+ :param callable scan_data_callback: data processing callback
630
+ :param callable scan_end_callback: called upon scan end
631
+ :param Event ready_event: started listening to Redis
632
+ :param DataStreamReaderStopHandler stop_handler:
633
+ :param bool watch_scan_group: If True the scan groups are also listed like any other scans
634
+ :param bool exclude_existing_scans: False by default (will become True by default in the future)
635
+ """
636
+ if exclude_existing_scans is None:
637
+ exclude_existing_scans = False
638
+ warnings.warn("'exclude_existing_scans' will be True by default", FutureWarning)
639
+
640
+ watcher = ScansWatcher(session_name)
641
+ watcher.set_exclude_existing_scans(exclude_existing_scans)
642
+ watcher.set_watch_scan_group(watch_scan_group)
643
+ if stop_handler is not None:
644
+ watcher._set_stop_handler(stop_handler)
645
+
646
+ if ready_event is not None:
647
+
648
+ def wait_ready():
649
+ nonlocal ready_event
650
+ watcher.wait_ready()
651
+ ready_event.set()
652
+
653
+ local_store_g = gevent.spawn(wait_ready)
654
+ else:
655
+ local_store_g = None
656
+
657
+ observer = DefaultScansObserver()
658
+ observer.scan_new_callback = scan_new_callback
659
+ observer.scan_new_child_callback = scan_new_child_callback
660
+ observer.scan_data_callback = scan_data_callback
661
+ observer.scan_end_callback = scan_end_callback
662
+
663
+ watcher.set_observer(observer)
664
+ watcher.run()
665
+ if local_store_g is not None:
666
+ local_store_g.kill()
@@ -0,0 +1 @@
1
+ """Definition and implementation of the h5py-like Bliss Data API"""