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,204 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import signal
|
|
5
|
+
import gevent
|
|
6
|
+
import multiprocessing
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from queue import Empty
|
|
10
|
+
|
|
11
|
+
from blissdata.client import configure_with_beacon_address
|
|
12
|
+
from blissdata.data.events import Event, EventType
|
|
13
|
+
from blissdata.data.node import get_node, get_session_node, DataNodeContainer
|
|
14
|
+
from blissdata.redis.manager import RedisAddress
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@contextmanager
|
|
18
|
+
def run_process(process):
|
|
19
|
+
try:
|
|
20
|
+
process.start()
|
|
21
|
+
yield
|
|
22
|
+
finally:
|
|
23
|
+
process.terminate()
|
|
24
|
+
process.join()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class NodeContent:
|
|
29
|
+
"""A pickle-able class to embed a node's data attributes (no implementation related stuff)"""
|
|
30
|
+
|
|
31
|
+
type: EventType
|
|
32
|
+
name: str
|
|
33
|
+
db_name: str
|
|
34
|
+
info: dict
|
|
35
|
+
redis_url: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RemoteNodeWalker:
|
|
39
|
+
"""Node-like object implementing walking functions through a subprocess, thus monkey-patching
|
|
40
|
+
can be avoided locally.
|
|
41
|
+
RemoteNodeWalker and NodeContent objects represent the node's implementation and data, this way
|
|
42
|
+
interprocess communication only sees the data part.
|
|
43
|
+
|
|
44
|
+
USER-PROCESS SUB-PROCESS
|
|
45
|
+
┌─────────────────────────────┐ ┌───────────────┐
|
|
46
|
+
│ ┌──────────────────┐ │ │ BLISSDATA │
|
|
47
|
+
│USER │ RemoteNodeWalker │ │ │ ┌─────────┐ │
|
|
48
|
+
│CODE: │ │ │ │ │real Node│ │
|
|
49
|
+
│walk()<┼───── queue <─────┼──┼───────┼──┤ walk() │ │
|
|
50
|
+
│ └──────────────────┘ │ │ └─────────┘ │
|
|
51
|
+
│ (GEVENT-FREE) │ │ (GEVENT) │
|
|
52
|
+
└─────────────────────────────┘ └───────────────┘
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, beacon_host: str, beacon_port: int, node_db_name: str):
|
|
56
|
+
self._queue = multiprocessing.Queue()
|
|
57
|
+
self._beacon_host = beacon_host
|
|
58
|
+
self._beacon_port = beacon_port
|
|
59
|
+
self._node_db_name = node_db_name
|
|
60
|
+
self._keep_running = True
|
|
61
|
+
|
|
62
|
+
def stop(self):
|
|
63
|
+
self._keep_running = False
|
|
64
|
+
|
|
65
|
+
def walk(self, *args, **kwargs):
|
|
66
|
+
yield from self._run_remote_walk("walk", *args, **kwargs)
|
|
67
|
+
|
|
68
|
+
def walk_from_last(self, *args, **kwargs):
|
|
69
|
+
yield from self._run_remote_walk("walk_from_last", *args, **kwargs)
|
|
70
|
+
|
|
71
|
+
def walk_events(self, *args, **kwargs):
|
|
72
|
+
yield from self._run_remote_walk("walk_events", *args, **kwargs)
|
|
73
|
+
|
|
74
|
+
def walk_on_new_events(self, *args, **kwargs):
|
|
75
|
+
yield from self._run_remote_walk("walk_on_new_events", *args, **kwargs)
|
|
76
|
+
|
|
77
|
+
def _run_remote_walk(
|
|
78
|
+
self,
|
|
79
|
+
walk_func_name,
|
|
80
|
+
*walk_func_args,
|
|
81
|
+
started_event=None,
|
|
82
|
+
stop_handler=None,
|
|
83
|
+
**walk_func_kwargs,
|
|
84
|
+
):
|
|
85
|
+
if stop_handler is not None:
|
|
86
|
+
raise RuntimeError("RemoteNodeWalker doesn't support `stop_handler`")
|
|
87
|
+
|
|
88
|
+
user_started_event = None
|
|
89
|
+
if started_event is not None:
|
|
90
|
+
user_started_event = started_event
|
|
91
|
+
started_event = multiprocessing.Event()
|
|
92
|
+
walk_func_kwargs["started_event"] = started_event
|
|
93
|
+
|
|
94
|
+
process = multiprocessing.Process(
|
|
95
|
+
target=RemoteNodeWalker._remote_task,
|
|
96
|
+
daemon=True,
|
|
97
|
+
args=(
|
|
98
|
+
self._beacon_host,
|
|
99
|
+
self._beacon_port,
|
|
100
|
+
self._node_db_name,
|
|
101
|
+
self._queue,
|
|
102
|
+
walk_func_name,
|
|
103
|
+
walk_func_args,
|
|
104
|
+
walk_func_kwargs,
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
with run_process(process):
|
|
109
|
+
if started_event is not None:
|
|
110
|
+
while process.is_alive() and self._keep_running:
|
|
111
|
+
if started_event.wait(timeout=0.2):
|
|
112
|
+
user_started_event.set()
|
|
113
|
+
break
|
|
114
|
+
while process.is_alive() and self._keep_running:
|
|
115
|
+
try:
|
|
116
|
+
yield self._queue.get(timeout=0.2)
|
|
117
|
+
except Empty:
|
|
118
|
+
pass
|
|
119
|
+
if not process.is_alive():
|
|
120
|
+
raise RuntimeError("RemoteNodeWalker subprocess failed.")
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def _remote_task(
|
|
124
|
+
beacon_host,
|
|
125
|
+
beacon_port,
|
|
126
|
+
node_db_name,
|
|
127
|
+
queue,
|
|
128
|
+
walk_func_name,
|
|
129
|
+
walk_func_args,
|
|
130
|
+
walk_func_kwargs,
|
|
131
|
+
):
|
|
132
|
+
"""Suprocess main function, don't use it on user side to prevent gevent patching"""
|
|
133
|
+
gevent.monkey.patch_all(thread=False)
|
|
134
|
+
|
|
135
|
+
# A watchdog looking at parent's PID to exit if parent dies. If the parent encounters an
|
|
136
|
+
# exception below python level, context managers or "try" statements won't exit properly.
|
|
137
|
+
# Thus, the child has to keep an eye on its parent.
|
|
138
|
+
def watchdog():
|
|
139
|
+
parent_pid = os.getppid()
|
|
140
|
+
while True:
|
|
141
|
+
try:
|
|
142
|
+
# send null signal to test pid
|
|
143
|
+
os.kill(parent_pid, 0)
|
|
144
|
+
except OSError:
|
|
145
|
+
sys.exit(0)
|
|
146
|
+
gevent.sleep(1)
|
|
147
|
+
|
|
148
|
+
# Inhibit SIGINT handler before running watchdog, so it is protected from Ctrl-C
|
|
149
|
+
def dummy_handler(signum, frame):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
signal.signal(signal.SIGINT, dummy_handler)
|
|
153
|
+
|
|
154
|
+
gevent.spawn(watchdog)
|
|
155
|
+
|
|
156
|
+
configure_with_beacon_address(beacon_host, beacon_port)
|
|
157
|
+
|
|
158
|
+
# if a node_db_name contains no ":", it is a session, therefore it should be
|
|
159
|
+
# accessed with get_session_node(), which allows to start walking on it before
|
|
160
|
+
# it even exists in Redis.
|
|
161
|
+
if ":" in node_db_name:
|
|
162
|
+
target_node = get_node(node_db_name)
|
|
163
|
+
if target_node is None:
|
|
164
|
+
raise RuntimeError(f'Node "{node_db_name}" doesn\'t exist.')
|
|
165
|
+
else:
|
|
166
|
+
target_node = get_session_node(node_db_name)
|
|
167
|
+
|
|
168
|
+
walk_function = getattr(target_node, walk_func_name)
|
|
169
|
+
|
|
170
|
+
for result in walk_function(*walk_func_args, **walk_func_kwargs):
|
|
171
|
+
if isinstance(result, Event):
|
|
172
|
+
ev_type, node, data = result
|
|
173
|
+
packed_event = Event(ev_type, RemoteNodeWalker.pack_node(node), data)
|
|
174
|
+
queue.put(packed_event)
|
|
175
|
+
elif isinstance(result, DataNodeContainer):
|
|
176
|
+
packed_node = RemoteNodeWalker.pack_node(result)
|
|
177
|
+
queue.put(packed_node)
|
|
178
|
+
else:
|
|
179
|
+
RuntimeError(
|
|
180
|
+
f"Oops, unexpected walk function return type: {type(result)}"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def pack_node(node):
|
|
185
|
+
"""Pack revelant node's information to be pickled, without embedding Gevent or Redis related stuff."""
|
|
186
|
+
redis_kw = node.db_connection.connection_pool.connection_kwargs
|
|
187
|
+
if "path" in redis_kw:
|
|
188
|
+
redis_url = (
|
|
189
|
+
RedisAddress.factory(f"localhost:{redis_kw['path']}").url
|
|
190
|
+
+ f"?db={redis_kw['db']}"
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
redis_url = (
|
|
194
|
+
RedisAddress.factory(f"{redis_kw['host']}:{redis_kw['port']}").url
|
|
195
|
+
+ f"/{redis_kw['db']}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return NodeContent(
|
|
199
|
+
node.node_type,
|
|
200
|
+
node.name,
|
|
201
|
+
node.db_name,
|
|
202
|
+
node.info.get_all(),
|
|
203
|
+
redis_url,
|
|
204
|
+
)
|