QuLab 2.0.2__cp310-cp310-macosx_10_9_universal2.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 (84) hide show
  1. QuLab-2.0.2.dist-info/LICENSE +21 -0
  2. QuLab-2.0.2.dist-info/METADATA +98 -0
  3. QuLab-2.0.2.dist-info/RECORD +84 -0
  4. QuLab-2.0.2.dist-info/WHEEL +5 -0
  5. QuLab-2.0.2.dist-info/entry_points.txt +2 -0
  6. QuLab-2.0.2.dist-info/top_level.txt +1 -0
  7. qulab/__init__.py +1 -0
  8. qulab/__main__.py +26 -0
  9. qulab/fun.cpython-310-darwin.so +0 -0
  10. qulab/monitor/__init__.py +1 -0
  11. qulab/monitor/__main__.py +8 -0
  12. qulab/monitor/config.py +41 -0
  13. qulab/monitor/dataset.py +77 -0
  14. qulab/monitor/event_queue.py +54 -0
  15. qulab/monitor/mainwindow.py +234 -0
  16. qulab/monitor/monitor.py +93 -0
  17. qulab/monitor/ploter.py +123 -0
  18. qulab/monitor/qt_compat.py +16 -0
  19. qulab/monitor/toolbar.py +265 -0
  20. qulab/scan/__init__.py +3 -0
  21. qulab/scan/curd.py +144 -0
  22. qulab/scan/expression.py +505 -0
  23. qulab/scan/models.py +540 -0
  24. qulab/scan/optimize.py +69 -0
  25. qulab/scan/query_record.py +361 -0
  26. qulab/scan/recorder.py +447 -0
  27. qulab/scan/scan.py +701 -0
  28. qulab/scan/utils.py +37 -0
  29. qulab/storage/__init__.py +0 -0
  30. qulab/storage/__main__.py +51 -0
  31. qulab/storage/backend/__init__.py +0 -0
  32. qulab/storage/backend/redis.py +204 -0
  33. qulab/storage/base_dataset.py +352 -0
  34. qulab/storage/chunk.py +60 -0
  35. qulab/storage/dataset.py +127 -0
  36. qulab/storage/file.py +273 -0
  37. qulab/storage/models/__init__.py +22 -0
  38. qulab/storage/models/base.py +4 -0
  39. qulab/storage/models/config.py +28 -0
  40. qulab/storage/models/file.py +89 -0
  41. qulab/storage/models/ipy.py +58 -0
  42. qulab/storage/models/models.py +88 -0
  43. qulab/storage/models/record.py +161 -0
  44. qulab/storage/models/report.py +22 -0
  45. qulab/storage/models/tag.py +93 -0
  46. qulab/storage/storage.py +95 -0
  47. qulab/sys/__init__.py +0 -0
  48. qulab/sys/chat.py +688 -0
  49. qulab/sys/device/__init__.py +3 -0
  50. qulab/sys/device/basedevice.py +221 -0
  51. qulab/sys/device/loader.py +86 -0
  52. qulab/sys/device/utils.py +46 -0
  53. qulab/sys/drivers/FakeInstrument.py +52 -0
  54. qulab/sys/drivers/__init__.py +0 -0
  55. qulab/sys/ipy_events.py +125 -0
  56. qulab/sys/net/__init__.py +0 -0
  57. qulab/sys/net/bencoder.py +205 -0
  58. qulab/sys/net/cli.py +169 -0
  59. qulab/sys/net/dhcp.py +543 -0
  60. qulab/sys/net/dhcpd.py +176 -0
  61. qulab/sys/net/kad.py +1142 -0
  62. qulab/sys/net/kcp.py +192 -0
  63. qulab/sys/net/nginx.py +192 -0
  64. qulab/sys/progress.py +190 -0
  65. qulab/sys/rpc/__init__.py +0 -0
  66. qulab/sys/rpc/client.py +0 -0
  67. qulab/sys/rpc/exceptions.py +96 -0
  68. qulab/sys/rpc/msgpack.py +1052 -0
  69. qulab/sys/rpc/msgpack.pyi +41 -0
  70. qulab/sys/rpc/rpc.py +412 -0
  71. qulab/sys/rpc/serialize.py +139 -0
  72. qulab/sys/rpc/server.py +29 -0
  73. qulab/sys/rpc/socket.py +29 -0
  74. qulab/sys/rpc/utils.py +25 -0
  75. qulab/sys/rpc/worker.py +0 -0
  76. qulab/sys/rpc/zmq_socket.py +209 -0
  77. qulab/version.py +1 -0
  78. qulab/visualization/__init__.py +188 -0
  79. qulab/visualization/__main__.py +71 -0
  80. qulab/visualization/_autoplot.py +463 -0
  81. qulab/visualization/plot_layout.py +408 -0
  82. qulab/visualization/plot_seq.py +90 -0
  83. qulab/visualization/qdat.py +152 -0
  84. qulab/visualization/widgets.py +86 -0
qulab/scan/utils.py ADDED
@@ -0,0 +1,37 @@
1
+ import inspect
2
+ from concurrent.futures import Future
3
+
4
+
5
+ def call_func_with_kwds(func, args, kwds, log=None):
6
+ funcname = getattr(func, '__name__', repr(func))
7
+ sig = inspect.signature(func)
8
+ for p in sig.parameters.values():
9
+ if p.kind == p.VAR_KEYWORD:
10
+ return func(*args, **kwds)
11
+ kw = {
12
+ k: v
13
+ for k, v in kwds.items()
14
+ if k in list(sig.parameters.keys())[len(args):]
15
+ }
16
+ try:
17
+ args = [
18
+ arg.result() if isinstance(arg, Future) else arg for arg in args
19
+ ]
20
+ kw = {
21
+ k: v.result() if isinstance(v, Future) else v
22
+ for k, v in kw.items()
23
+ }
24
+ return func(*args, **kw)
25
+ except:
26
+ if log:
27
+ log.exception(f'Call {funcname} with {args} and {kw}')
28
+ raise
29
+ finally:
30
+ if log:
31
+ log.debug(f'Call {funcname} with {args} and {kw}')
32
+
33
+
34
+ def try_to_call(x, args, kwds, log=None):
35
+ if callable(x):
36
+ return call_func_with_kwds(x, args, kwds, log)
37
+ return x
File without changes
@@ -0,0 +1,51 @@
1
+ import click
2
+
3
+
4
+ @click.command()
5
+ @click.option('--executor', default='', help='Executor address to use.')
6
+ @click.option('--port', default=8080, help='Port to run the server on.')
7
+ @click.option('--host', default='127.0.0.1', help='Host to run the server on.')
8
+ @click.option('--db-url', default=None, help='Database URL to use.')
9
+ @click.option('--data-path',
10
+ default='waveforms/data',
11
+ help='Path to the data directory.')
12
+ @click.option('--debug', is_flag=True, help='Run in debug mode.')
13
+ @click.option('--workers',
14
+ default=1,
15
+ help='Number of workers to run the server with.')
16
+ @click.option('--timeout', default=60, help='Timeout for requests.')
17
+ @click.option('--log-level',
18
+ default='INFO',
19
+ help='Log level to run the server with.')
20
+ @click.option('--log-file',
21
+ default='/var/log/waveforms/server.log',
22
+ help='Log file to run the server with.')
23
+ @click.option('--log-format',
24
+ default='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
25
+ help='Log format to run the server with.')
26
+ def main(executor, port, host, db_url, data_path, debug, workers, timeout,
27
+ log_level, log_file, log_format):
28
+ """
29
+ Main entry point for the server.
30
+ """
31
+ from waveforms.server import create_app
32
+
33
+ app = create_app(
34
+ executor=executor,
35
+ port=port,
36
+ host=host,
37
+ db_url=db_url,
38
+ data_path=data_path,
39
+ debug=debug,
40
+ workers=workers,
41
+ timeout=timeout,
42
+ log_level=log_level,
43
+ log_file=log_file,
44
+ log_format=log_format,
45
+ )
46
+ app.run()
47
+
48
+
49
+ if __name__ == '__main__':
50
+ main()
51
+
File without changes
@@ -0,0 +1,204 @@
1
+ import dill
2
+ import numpy as np
3
+ import redis
4
+
5
+
6
+ def try_dumps(value):
7
+ try:
8
+ value_b = dill.dumps(value)
9
+ except:
10
+ value_b = dill.dumps(str(value))
11
+ finally:
12
+ return value_b
13
+
14
+
15
+ class redisClient(object):
16
+
17
+ def __init__(self,
18
+ name,
19
+ server=None,
20
+ addr='redis://localhost:6379/0',
21
+ expire_time=172800):
22
+ self._r = redis.Redis.from_url(addr) if server is None else server
23
+ self.name = name
24
+ self.expire_time = int(expire_time) # 默认数据两天过期,单位秒
25
+
26
+ def delete(self):
27
+ self._r.delete(self.name)
28
+
29
+
30
+ class redisString(redisClient):
31
+ '''Redis String client'''
32
+
33
+ def set(self, value):
34
+ if value is not None:
35
+ value_b = try_dumps(value)
36
+ self._r.set(self.name, value_b, ex=self.expire_time)
37
+
38
+ def get(self):
39
+ v_b = self._r.get(self.name)
40
+ value = dill.loads(v_b) if v_b is not None else None
41
+ return value
42
+
43
+ @property
44
+ def data(self):
45
+ return self.get()
46
+
47
+
48
+ class redisList(redisClient):
49
+ '''Redis List client'''
50
+
51
+ def add(self, *arg):
52
+ arg_b = [dill.dumps(i) for i in arg]
53
+ self._r.rpush(self.name, *arg_b)
54
+ self._r.expire(self.name, self.expire_time)
55
+
56
+ def read(self, start=0, end=-1):
57
+ data_b = self._r.lrange(self.name, start, end)
58
+ data = [dill.loads(i) for i in data_b]
59
+ return data
60
+
61
+ @property
62
+ def size(self):
63
+ return self._r.llen(self.name)
64
+
65
+ @property
66
+ def data(self):
67
+ return self.read()
68
+
69
+
70
+ class redisSet(redisClient):
71
+ '''Redis Set client'''
72
+
73
+ def add(self, *arg):
74
+ arg_b = {dill.dumps(i) for i in arg}
75
+ self._r.sadd(self.name, *arg_b)
76
+ self._r.expire(self.name, self.expire_time)
77
+
78
+ def read(self):
79
+ data_b = self._r.smembers(self.name)
80
+ data = {dill.loads(i) for i in data_b}
81
+ return data
82
+
83
+ @property
84
+ def size(self):
85
+ return self._r.scard(self.name)
86
+
87
+ @property
88
+ def data(self):
89
+ return self.read()
90
+
91
+
92
+ class redisZSet(redisClient):
93
+ '''有序集合'''
94
+
95
+ def __init__(self,
96
+ name,
97
+ server=None,
98
+ addr='redis://localhost:6379/0',
99
+ expire_time=172800):
100
+ super().__init__(name, server, addr, expire_time)
101
+ self.__score = 0
102
+
103
+ def delete(self):
104
+ super().delete()
105
+ self.__score = 0
106
+
107
+ def add(self, *elements):
108
+ mapping = {}
109
+ for ele in elements:
110
+ ele_b = dill.dumps(ele)
111
+ self.__score += 1
112
+ mapping.update({ele_b: self.__score})
113
+ self._r.zadd(self.name, mapping, nx=True) # 只添加新元素
114
+ self._r.expire(self.name, self.expire_time)
115
+
116
+ def read(self, start=0, end=-1):
117
+ data_b = self._r.zrange(self.name, start, end, withscores=False)
118
+ data = [dill.loads(i) for i in data_b]
119
+ return data
120
+
121
+ @property
122
+ def size(self):
123
+ return self._r.zcard(self.name)
124
+
125
+ @property
126
+ def data(self):
127
+ return self.read()
128
+
129
+
130
+ class redisHash(redisClient):
131
+ '''Redis Hash client'''
132
+
133
+ def add(self, **kw):
134
+ kw_b = {k: try_dumps(v) for k, v in kw.items()}
135
+ self._r.hmset(self.name, kw_b)
136
+ self._r.expire(self.name, self.expire_time)
137
+
138
+ def read(self):
139
+ data_b = self._r.hgetall(self.name)
140
+ data = {k_b.decode(): dill.loads(v_b) for k_b, v_b in data_b.items()}
141
+ return data
142
+
143
+ def get(self, key):
144
+ '''读取Hash中的一个key'''
145
+ v_b = self._r.hget(self.name, key)
146
+ value = dill.loads(v_b) if v_b is not None else None
147
+ return value
148
+
149
+ @property
150
+ def size(self):
151
+ return self._r.hlen(self.name)
152
+
153
+ @property
154
+ def data(self):
155
+ return self.read()
156
+
157
+
158
+ class redisArray(redisClient):
159
+ '''Redis np.array client'''
160
+
161
+ def __init__(self,
162
+ name,
163
+ server=None,
164
+ addr='redis://localhost:6379/0',
165
+ expire_time=172800,
166
+ dtype='complex128'):
167
+ super().__init__(name, server, addr, expire_time)
168
+ _r_dtype = self._r.get(f'{name}.dtype')
169
+ if _r_dtype is None:
170
+ self._r.set(f'{name}.dtype', dtype, ex=self.expire_time)
171
+ self.dtype = dtype
172
+ else:
173
+ self.dtype = _r_dtype
174
+
175
+ def delete(self):
176
+ self._r.delete(self.name)
177
+ self._r.delete(f'{self.name}.dtype')
178
+
179
+ def add(self, *args):
180
+ for arg in args:
181
+ buf = np.asarray(arg).astype(self.dtype).tobytes()
182
+ # self._r.append(self.name, buf)
183
+ self._r.rpush(self.name, buf)
184
+ self._r.expire(self.name, self.expire_time)
185
+
186
+ def read(self):
187
+ # buf = self._r.get(self.name)
188
+ buf_list = self._r.lrange(self.name, 0, -1)
189
+ buf = b''.join(buf_list)
190
+ data = np.frombuffer(buf,
191
+ dtype=self.dtype) if buf is not None else None
192
+ return data
193
+
194
+ @property
195
+ def size(self):
196
+ array = self.data
197
+ if array is None:
198
+ return 0
199
+ else:
200
+ return array.size
201
+
202
+ @property
203
+ def data(self):
204
+ return self.read()
@@ -0,0 +1,352 @@
1
+ import bisect
2
+ from concurrent.futures import Future
3
+ from datetime import datetime
4
+ from itertools import chain
5
+ from multiprocessing import Lock
6
+ from queue import Queue
7
+ from typing import Any, Sequence
8
+
9
+ from ..scan.base import StepStatus, Tracker, _get_all_dependence
10
+
11
+ _NODEFAULT = object()
12
+
13
+
14
+ class BaseDataset(Tracker):
15
+ """
16
+ A tracker that stores the results of the steps.
17
+
18
+ Parameters
19
+ ----------
20
+ data : dict
21
+ The data of the results.
22
+ shape : tuple
23
+ The shape of the results.
24
+ ctime : datetime.datetime
25
+ The creation time of the tracker.
26
+ mtime : datetime.datetime
27
+ The modification time of the tracker.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ data: dict = None,
33
+ shape: tuple = (),
34
+ save_kwds: bool | Sequence[str] = True,
35
+ frozen_keys: tuple = (),
36
+ ignores: tuple = (),
37
+ ):
38
+ self.ctime = datetime.utcnow()
39
+ self.mtime = datetime.utcnow()
40
+ self.data = data if data is not None else {}
41
+ self.cache = {}
42
+ self.pos = {}
43
+ self.timestamps = {}
44
+ self.iteration = {}
45
+ self._init_keys = list(self.data.keys())
46
+ self._frozen_keys = frozen_keys
47
+ self._ignores = ignores
48
+ self._key_levels = ()
49
+ self.depends = {}
50
+ self.dims = {}
51
+ self.vars_dims = {}
52
+ self.shape = shape
53
+ self.count = 0
54
+ self.save_kwds = save_kwds
55
+ self.queue = Queue()
56
+ self._queue_buffer = None
57
+ self._lock = Lock()
58
+
59
+ def init(self, loops: dict, functions: dict, constants: dict, graph: dict,
60
+ order: list):
61
+ """
62
+ Initialize the tracker.
63
+
64
+ Parameters
65
+ ----------
66
+ loops : dict
67
+ The map of iterables.
68
+ functions : dict
69
+ The map of functions.
70
+ constants : dict
71
+ The map of constants.
72
+ graph : dict
73
+ The dependence graph.
74
+ order : list
75
+ The order of the dependence graph.
76
+ """
77
+ from numpy import ndarray
78
+
79
+ self.depends = graph
80
+
81
+ for level, (keys, iters) in enumerate(loops.items()):
82
+ self._key_levels = self._key_levels + ((keys, level), )
83
+ if isinstance(keys, str):
84
+ keys = (keys, )
85
+ iters = (iters, )
86
+ if (len(keys) > 1 and len(iters) == 1
87
+ and isinstance(iters[0], ndarray) and iters[0].ndim == 2
88
+ and iters[0].shape[1] == len(keys)):
89
+ iters = iters[0]
90
+ for i, key in enumerate(keys):
91
+ self.data[key] = iters[:, i]
92
+ self._frozen_keys = self._frozen_keys + (key, )
93
+ self._init_keys.append(key)
94
+ continue
95
+ if not isinstance(iters, tuple) or len(keys) != len(iters):
96
+ continue
97
+ for key, iter in zip(keys, iters):
98
+ if self.depends.get(key, set()):
99
+ dims = set()
100
+ for dep in self.depends[key]:
101
+ if dep in self.vars_dims:
102
+ dims.update(set(self.vars_dims[dep]))
103
+ dims.add(level)
104
+ self.vars_dims[key] = tuple(sorted(dims))
105
+ else:
106
+ self.vars_dims[key] = (level, )
107
+ if level not in self.dims:
108
+ self.dims[level] = ()
109
+ self.dims[level] = self.dims[level] + (key, )
110
+ if key not in self.data and isinstance(iter,
111
+ (list, range, ndarray)):
112
+ if key.startswith('__'):
113
+ continue
114
+ self.data[key] = iter
115
+ self._frozen_keys = self._frozen_keys + (key, )
116
+ self._init_keys.append(key)
117
+
118
+ for key, value in constants.items():
119
+ if key.startswith('__'):
120
+ continue
121
+ self.data[key] = value
122
+ self._init_keys.append(key)
123
+ self.vars_dims[key] = ()
124
+
125
+ for ready in order:
126
+ for key in ready:
127
+ if key in functions:
128
+ deps = _get_all_dependence(key, graph)
129
+ dims = set()
130
+ for k in deps:
131
+ dims.update(set(self.vars_dims.get(k, ())))
132
+ self.vars_dims[key] = tuple(sorted(dims))
133
+
134
+ for k, v in self.vars_dims.items():
135
+ if len(v) == 1:
136
+ if v[0] in self.dims and k not in self.dims[v[0]]:
137
+ self.dims[v[0]] = self.dims[v[0]] + (k, )
138
+ elif v[0] not in self.dims:
139
+ self.dims[v[0]] = (k, )
140
+
141
+ def feed(self,
142
+ step: StepStatus,
143
+ dataframe: dict | Future,
144
+ store=False,
145
+ **options):
146
+ """
147
+ Feed the results of the step to the dataset.
148
+
149
+ Parameters
150
+ ----------
151
+ step : StepStatus
152
+ The step.
153
+ dataframe : dict
154
+ The results of the step.
155
+ """
156
+ import numpy as np
157
+
158
+ if not store:
159
+ return
160
+ self.mtime = datetime.utcnow()
161
+ if not self.shape:
162
+ self.shape = tuple([i + 1 for i in step.pos])
163
+ else:
164
+ self.shape = tuple(
165
+ [max(i + 1, j) for i, j in zip(step.pos, self.shape)])
166
+ if self.save_kwds:
167
+ if isinstance(self.save_kwds, bool):
168
+ kwds = step.kwds
169
+ else:
170
+ kwds = {
171
+ key: step.kwds.get(key, np.nan)
172
+ for key in self.save_kwds
173
+ }
174
+ else:
175
+ kwds = {}
176
+
177
+ if isinstance(dataframe, dict):
178
+ dataframe = self._prune(dataframe)
179
+ self.queue.put_nowait(
180
+ (step.iteration, step.pos, dataframe, kwds, self.mtime))
181
+ self.flush()
182
+
183
+ def _prune(self, dataframe: dict[str, Any]) -> dict[str, Any]:
184
+ return {
185
+ k: v
186
+ for k, v in dataframe.items() if k not in self._ignores
187
+ and k not in self._frozen_keys and not k.startswith('__')
188
+ }
189
+
190
+ def _append(self, iteration, pos, dataframe, kwds, now):
191
+ for k, v in chain(kwds.items(), dataframe.items()):
192
+ if k in self._frozen_keys or k in self._ignores:
193
+ continue
194
+ if k.startswith('__'):
195
+ continue
196
+ if self.vars_dims.get(k, ()) == () and k not in dataframe:
197
+ continue
198
+ self.count += 1
199
+ if k not in self.data:
200
+ self.data[k] = [v]
201
+ if k in self.vars_dims:
202
+ self.pos[k] = tuple([pos[i]] for i in self.vars_dims[k])
203
+ else:
204
+ self.pos[k] = tuple([i] for i in pos)
205
+ self.timestamps[k] = [now.timestamp()]
206
+ self.iteration[k] = [iteration]
207
+ else:
208
+ if k in self.vars_dims:
209
+ pos_k = tuple(pos[i] for i in self.vars_dims[k])
210
+ if k not in dataframe and pos_k in zip(*self.pos[k]):
211
+ continue
212
+ for i, l in zip(pos_k, self.pos[k]):
213
+ l.append(i)
214
+ else:
215
+ for i, l in zip(pos, self.pos[k]):
216
+ l.append(i)
217
+ self.timestamps[k].append(now.timestamp())
218
+ self.iteration[k].append(iteration)
219
+ self.data[k].append(v)
220
+
221
+ def flush(self, block=False):
222
+ with self._lock:
223
+ self._flush(block=block)
224
+
225
+ def _dataframe_done(self, dataframe: Future | dict) -> bool:
226
+ if isinstance(dataframe, Future):
227
+ return dataframe.done()
228
+ else:
229
+ return all(x.done() for x in dataframe.values()
230
+ if isinstance(x, Future))
231
+
232
+ def _dataframe_result(self, dataframe: Future | dict) -> dict:
233
+ if isinstance(dataframe, Future):
234
+ return dataframe.result()
235
+ else:
236
+ return {
237
+ k: v.result() if isinstance(v, Future) else v
238
+ for k, v in dataframe.items()
239
+ }
240
+
241
+ def _flush(self, block=False):
242
+ if self._queue_buffer is not None:
243
+ iteration, pos, dataframe, kwds, now = self._queue_buffer
244
+ if self._dataframe_done(dataframe) or block:
245
+ self._append(iteration, pos,
246
+ self._prune(self._dataframe_result(dataframe)),
247
+ kwds, now)
248
+ self._queue_buffer = None
249
+ else:
250
+ return
251
+ while not self.queue.empty():
252
+ iteration, pos, dataframe, kwds, now = self.queue.get()
253
+ if not self._dataframe_done(dataframe) and not block:
254
+ self._queue_buffer = (iteration, pos, dataframe, kwds, now)
255
+ return
256
+ else:
257
+ self._append(iteration, pos,
258
+ self._prune(self._dataframe_result(dataframe)),
259
+ kwds, now)
260
+
261
+ def _get_array(self, key, shape, count):
262
+ import numpy as np
263
+
264
+ if key in self.vars_dims:
265
+ shape = tuple([shape[i] for i in self.vars_dims[key]])
266
+
267
+ data, data_shape, data_count = self.cache.get(key, (None, (), 0))
268
+ if (data_shape, data_count) == (shape, count):
269
+ return data
270
+ try:
271
+ tmp = np.asarray(self.data[key])
272
+ if data_shape != shape:
273
+ data = np.full(shape + tmp.shape[1:], np.nan, dtype=tmp.dtype)
274
+ except:
275
+ tmp = self.data[key]
276
+ if data_shape != shape:
277
+ data = np.full(shape, np.nan, dtype=object)
278
+ try:
279
+ data[self.pos[key]] = tmp
280
+ except:
281
+ raise
282
+ self.cache[key] = (data, shape, count)
283
+ return data
284
+
285
+ def _get_part(self, key, skip):
286
+ i = bisect.bisect_left(self.iteration[key], skip)
287
+ pos = tuple(p[i:] for p in self.pos[key])
288
+ iteration = self.iteration[key][i:]
289
+ data = self.data[key][i:]
290
+ return data, iteration, pos
291
+
292
+ def keys(self):
293
+ """
294
+ Get the keys of the dataset.
295
+ """
296
+ self.flush()
297
+ return self.data.keys()
298
+
299
+ def values(self):
300
+ """
301
+ Get the values of the dataset.
302
+ """
303
+ self.flush()
304
+ return [self[k] for k in self.data]
305
+
306
+ def items(self):
307
+ """
308
+ Get the items of the dataset.
309
+ """
310
+ self.flush()
311
+ return list(zip(self.keys(), self.values()))
312
+
313
+ def get(self, key, default=_NODEFAULT, skip=None, block=False):
314
+ """
315
+ Get the value of the dataset.
316
+ """
317
+ self.flush(block)
318
+ if key in self._init_keys:
319
+ return self.data[key]
320
+ elif key in self.data:
321
+ if skip is None:
322
+ return self._get_array(key, self.shape, self.count)
323
+ else:
324
+ return self._get_part(key, skip)
325
+ elif default is _NODEFAULT:
326
+ raise KeyError(key)
327
+ else:
328
+ return default
329
+
330
+ def __getitem__(self, key):
331
+ return self.get(key)
332
+
333
+ def __getstate__(self):
334
+ self.flush()
335
+ data = dict(self.items())
336
+ return {
337
+ 'data': data,
338
+ 'pos': self.pos,
339
+ 'timestamps': self.timestamps,
340
+ 'iteration': self.iteration,
341
+ 'depends': self.depends,
342
+ 'shape': self.shape,
343
+ 'dims': self.dims,
344
+ 'vars_dims': self.vars_dims,
345
+ 'ctime': self.ctime,
346
+ 'mtime': self.mtime,
347
+ '_init_keys': self._init_keys,
348
+ '_frozen_keys': self._frozen_keys,
349
+ '_ignores': self._ignores,
350
+ '_key_levels': self._key_levels,
351
+ 'save_kwds': self.save_kwds,
352
+ }
qulab/storage/chunk.py ADDED
@@ -0,0 +1,60 @@
1
+ import hashlib
2
+ import zlib
3
+ from pathlib import Path
4
+
5
+ DATAPATH = Path.home() / 'data'
6
+ CHUNKSIZE = 1024 * 1024 * 4 # 4 MB
7
+
8
+
9
+ def set_data_path(base_path: str) -> None:
10
+ global DATAPATH
11
+ DATAPATH = Path(base_path)
12
+
13
+
14
+ def get_data_path() -> Path:
15
+ return DATAPATH
16
+
17
+
18
+ def save_chunk(data: bytes, compressed: bool = False) -> tuple[str, str]:
19
+ if compressed:
20
+ data = zlib.compress(data)
21
+ hashstr = hashlib.sha1(data).hexdigest()
22
+ file = get_data_path(
23
+ ) / 'chunks' / hashstr[:2] / hashstr[2:4] / hashstr[4:]
24
+ file.parent.mkdir(parents=True, exist_ok=True)
25
+ with open(file, 'wb') as f:
26
+ f.write(data)
27
+ return str('/'.join(file.parts[-4:])), len(data)
28
+
29
+
30
+ def load_chunk(file: str, compressed: bool = False) -> bytes:
31
+ if file.startswith('chunks/'):
32
+ with open(get_data_path() / file, 'rb') as f:
33
+ data = f.read()
34
+ elif file.startswith('packs/'):
35
+ *filepath, start, size = file.split('/')
36
+ filepath = '/'.join(filepath)
37
+ with open(get_data_path() / filepath, 'rb') as f:
38
+ f.seek(int(start))
39
+ data = f.read(int(size))
40
+ else:
41
+ raise ValueError('Invalid file path: ' + file)
42
+ if compressed:
43
+ data = zlib.decompress(data)
44
+ return data
45
+
46
+
47
+ def pack_chunk(pack: str, chunkfile: str) -> str:
48
+ pack = get_data_path() / 'packs' / pack
49
+ pack.parent.mkdir(parents=True, exist_ok=True)
50
+ with open(pack, 'ab') as f:
51
+ buf = load_chunk(chunkfile)
52
+ start = f.tell()
53
+ size = len(buf)
54
+ f.write(buf)
55
+ return str('/'.join(pack.parts[-2:])) + '/' + str(start) + '/' + str(size)
56
+
57
+
58
+ def delete_chunk(file: str):
59
+ file = get_data_path() / file
60
+ file.unlink()