QuLab 2.10.10__cp313-cp313-macosx_10_13_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 (107) hide show
  1. qulab/__init__.py +33 -0
  2. qulab/__main__.py +4 -0
  3. qulab/cli/__init__.py +0 -0
  4. qulab/cli/commands.py +30 -0
  5. qulab/cli/config.py +170 -0
  6. qulab/cli/decorators.py +28 -0
  7. qulab/dicttree.py +523 -0
  8. qulab/executor/__init__.py +5 -0
  9. qulab/executor/analyze.py +188 -0
  10. qulab/executor/cli.py +434 -0
  11. qulab/executor/load.py +563 -0
  12. qulab/executor/registry.py +185 -0
  13. qulab/executor/schedule.py +543 -0
  14. qulab/executor/storage.py +615 -0
  15. qulab/executor/template.py +259 -0
  16. qulab/executor/utils.py +194 -0
  17. qulab/expression.py +827 -0
  18. qulab/fun.cpython-313-darwin.so +0 -0
  19. qulab/monitor/__init__.py +1 -0
  20. qulab/monitor/__main__.py +8 -0
  21. qulab/monitor/config.py +41 -0
  22. qulab/monitor/dataset.py +77 -0
  23. qulab/monitor/event_queue.py +54 -0
  24. qulab/monitor/mainwindow.py +234 -0
  25. qulab/monitor/monitor.py +115 -0
  26. qulab/monitor/ploter.py +123 -0
  27. qulab/monitor/qt_compat.py +16 -0
  28. qulab/monitor/toolbar.py +265 -0
  29. qulab/scan/__init__.py +2 -0
  30. qulab/scan/curd.py +221 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +387 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +450 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +234 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +2 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +255 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +68 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +194 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +227 -0
  88. qulab/tools/__init__.py +0 -0
  89. qulab/tools/connection_helper.py +39 -0
  90. qulab/typing.py +2 -0
  91. qulab/utils.py +95 -0
  92. qulab/version.py +1 -0
  93. qulab/visualization/__init__.py +188 -0
  94. qulab/visualization/__main__.py +71 -0
  95. qulab/visualization/_autoplot.py +464 -0
  96. qulab/visualization/plot_circ.py +319 -0
  97. qulab/visualization/plot_layout.py +408 -0
  98. qulab/visualization/plot_seq.py +242 -0
  99. qulab/visualization/qdat.py +152 -0
  100. qulab/visualization/rot3d.py +23 -0
  101. qulab/visualization/widgets.py +86 -0
  102. qulab-2.10.10.dist-info/METADATA +110 -0
  103. qulab-2.10.10.dist-info/RECORD +107 -0
  104. qulab-2.10.10.dist-info/WHEEL +5 -0
  105. qulab-2.10.10.dist-info/entry_points.txt +2 -0
  106. qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
  107. qulab-2.10.10.dist-info/top_level.txt +1 -0
qulab/scan/record.py ADDED
@@ -0,0 +1,603 @@
1
+ import itertools
2
+ import lzma
3
+ import pickle
4
+ import sys
5
+ import uuid
6
+ import zipfile
7
+ from pathlib import Path
8
+ from threading import Lock
9
+ from types import EllipsisType
10
+
11
+ import dill
12
+ import numpy as np
13
+ import zmq
14
+
15
+ from qulab.sys.rpc.zmq_socket import ZMQContextManager
16
+
17
+ from .curd import get_config
18
+ from .space import OptimizeSpace, Space
19
+
20
+ _not_given = object()
21
+
22
+
23
+ def random_path(base):
24
+ while True:
25
+ s = uuid.uuid4().hex
26
+ path = base / s[:2] / s[2:4] / s[4:6] / s[6:]
27
+ if not path.exists():
28
+ return path
29
+
30
+
31
+ def index_in_slice(slice_obj: slice | int, index: int):
32
+ if isinstance(slice_obj, int):
33
+ return slice_obj == index
34
+ start, stop, step = slice_obj.start, slice_obj.stop, slice_obj.step
35
+ if start is None:
36
+ start = 0
37
+ if step is None:
38
+ step = 1
39
+ if stop is None:
40
+ stop = sys.maxsize
41
+
42
+ if step > 0:
43
+ return start <= index < stop and (index - start) % step == 0
44
+ else:
45
+ return stop < index <= start and (index - start) % step == 0
46
+
47
+
48
+ class BufferList():
49
+
50
+ def __init__(self, file=None, slice=None):
51
+ self._list = []
52
+ self.lu = ()
53
+ self.rd = ()
54
+ self.inner_shape = ()
55
+ self.file = file
56
+ self._slice = slice
57
+ self._lock = Lock()
58
+ self._data_id = None
59
+ self._sock = None
60
+
61
+ def __repr__(self):
62
+ return f"<BufferList: shape={self.shape}, lu={self.lu}, rd={self.rd}, slice={self._slice}>"
63
+
64
+ def __getstate__(self):
65
+ self.flush()
66
+ if isinstance(self.file, Path):
67
+ file = '/'.join(self.file.parts[-4:])
68
+ else:
69
+ file = self.file
70
+ return {
71
+ 'file': file,
72
+ 'lu': self.lu,
73
+ 'rd': self.rd,
74
+ 'inner_shape': self.inner_shape,
75
+ }
76
+
77
+ def __setstate__(self, state):
78
+ self.file = state['file']
79
+ self.lu = state['lu']
80
+ self.rd = state['rd']
81
+ self.inner_shape = state['inner_shape']
82
+ self._list = []
83
+ self._slice = None
84
+ self._lock = Lock()
85
+ self._data_id = None
86
+ self._sock = None
87
+
88
+ @property
89
+ def shape(self):
90
+ return tuple([i - j
91
+ for i, j in zip(self.rd, self.lu)]) + self.inner_shape
92
+
93
+ def flush(self):
94
+ if not self._list:
95
+ return
96
+ if isinstance(self.file, Path):
97
+ with self._lock:
98
+ with open(self.file, 'ab') as f:
99
+ for item in self._list:
100
+ dill.dump(item, f)
101
+ self._list.clear()
102
+
103
+ def delete(self):
104
+ if isinstance(self.file, Path):
105
+ self.file.unlink()
106
+ self.file = None
107
+
108
+ def append(self, pos, value, dims=None):
109
+ if dims is not None:
110
+ if any([p != 0 for i, p in enumerate(pos) if i not in dims]):
111
+ return
112
+ pos = tuple([pos[i] for i in dims])
113
+ self.lu = tuple([min(i, j) for i, j in zip(pos, self.lu)])
114
+ self.rd = tuple([max(i + 1, j) for i, j in zip(pos, self.rd)])
115
+ if hasattr(value, 'shape'):
116
+ if self.inner_shape is None:
117
+ self.inner_shape = value.shape
118
+ elif self.inner_shape != value.shape:
119
+ self.inner_shape = ()
120
+ with self._lock:
121
+ self._list.append((pos, value))
122
+ if len(self._list) > 1000:
123
+ self.flush()
124
+
125
+ def _iter_file(self):
126
+ if isinstance(self.file, Path) and self.file.exists():
127
+ with self._lock:
128
+ with open(self.file, 'rb') as f:
129
+ while True:
130
+ try:
131
+ pos, value = dill.load(f)
132
+ yield pos, value
133
+ except EOFError:
134
+ break
135
+ elif isinstance(
136
+ self.file, tuple) and len(self.file) == 2 and isinstance(
137
+ self.file[0], str) and self.file[0].endswith('.zip'):
138
+ f, name = self.file
139
+ with zipfile.ZipFile(f, 'r') as z:
140
+ with z.open(name, 'r') as f:
141
+ while True:
142
+ try:
143
+ pos, value = dill.load(f)
144
+ yield pos, value
145
+ except EOFError:
146
+ break
147
+
148
+ def iter(self):
149
+ if self._data_id is None:
150
+ for pos, value in itertools.chain(self._iter_file(), self._list):
151
+ if not self._slice:
152
+ yield pos, value
153
+ elif all(
154
+ [index_in_slice(s, i) for s, i in zip(self._slice, pos)]):
155
+ if self.inner_shape:
156
+ yield pos, value[self._slice[len(pos):]]
157
+ else:
158
+ yield pos, value
159
+ else:
160
+ server, record_id, key = self._data_id
161
+ with ZMQContextManager(zmq.DEALER,
162
+ connect=server,
163
+ socket=self._sock) as socket:
164
+ iter_id = b''
165
+ start = 0
166
+ try:
167
+ while True:
168
+ socket.send_pyobj({
169
+ 'method': 'bufferlist_iter',
170
+ 'record_id': record_id,
171
+ 'iter_id': iter_id,
172
+ 'key': key,
173
+ 'slice': self._slice,
174
+ 'start': start
175
+ })
176
+ iter_id, lst, finished = socket.recv_pyobj()
177
+ start += len(lst)
178
+ yield from lst
179
+ if finished:
180
+ break
181
+ finally:
182
+ if iter_id:
183
+ socket.send_pyobj({
184
+ 'method': 'bufferlist_iter_exit',
185
+ 'iter_id': iter_id
186
+ })
187
+
188
+ def value(self):
189
+ d = []
190
+ for pos, value in self.iter():
191
+ d.append(value)
192
+ return d
193
+
194
+ def pos(self):
195
+ p = []
196
+ for pos, value in self.iter():
197
+ p.append(pos)
198
+ return p
199
+
200
+ def items(self):
201
+ p, d = [], []
202
+ for pos, value in self.iter():
203
+ p.append(pos)
204
+ d.append(value)
205
+ return p, d
206
+
207
+ def toarray(self):
208
+ pos, data = self.items()
209
+ if self._slice:
210
+ pos = np.asarray(pos)
211
+ lu = tuple(np.min(pos, axis=0))
212
+ rd = tuple(np.max(pos, axis=0) + 1)
213
+ pos = np.asarray(pos) - np.asarray(lu)
214
+ shape = []
215
+ for k, (s, i, j) in enumerate(zip(self._slice, rd, lu)):
216
+ if s.step is not None:
217
+ pos[:, k] = pos[:, k] / s.step
218
+ shape.append(round(np.ceil((i - j) / s.step)))
219
+ else:
220
+ shape.append(i - j)
221
+ shape = tuple(shape)
222
+ else:
223
+ shape = tuple([i - j for i, j in zip(self.rd, self.lu)])
224
+ pos = np.asarray(pos) - np.asarray(self.lu)
225
+ data = np.asarray(data)
226
+ inner_shape = data.shape[1:]
227
+ x = np.full(shape + inner_shape, np.nan, dtype=data[0].dtype)
228
+ x.__setitem__(tuple(pos.T), data)
229
+ return x
230
+
231
+ def _full_slice(self, slice_tuple: slice
232
+ | tuple[slice | int | EllipsisType, ...]):
233
+ ndim = len(self.lu)
234
+ if self.inner_shape:
235
+ ndim += len(self.inner_shape)
236
+
237
+ if isinstance(slice_tuple, slice):
238
+ slice_tuple = (
239
+ slice_tuple, ) + (slice(0, sys.maxsize, 1), ) * (ndim - 1)
240
+ if slice_tuple is Ellipsis:
241
+ slice_tuple = (slice(0, sys.maxsize, 1), ) * ndim
242
+ else:
243
+ head, tail = (), ()
244
+ for i, s in enumerate(slice_tuple):
245
+ if s is Ellipsis:
246
+ head = slice_tuple[:i]
247
+ tail = slice_tuple[i + 1:]
248
+ break
249
+ else:
250
+ head = slice_tuple
251
+ tail = ()
252
+ slice_tuple = head + (slice(
253
+ 0, sys.maxsize, 1), ) * (ndim - len(head) - len(tail)) + tail
254
+ slice_list = []
255
+ contract = []
256
+ reversed = []
257
+ for i, s in enumerate(slice_tuple):
258
+ if isinstance(s, int):
259
+ if s >= 0:
260
+ slice_list.append(slice(s, s + 1, 1))
261
+ elif i < len(self.lu):
262
+ s = self.rd[i] + s
263
+ slice_list.append(slice(s, s + 1, 1))
264
+ else:
265
+ slice_list.append(slice(s, s - 1, -1))
266
+ contract.append(i)
267
+ else:
268
+ start, stop, step = s.start, s.stop, s.step
269
+ if step is None:
270
+ step = 1
271
+ if step < 0 and i < len(self.lu):
272
+ step = -step
273
+ reversed.append(i)
274
+ if start is None and stop is None:
275
+ start, stop = 0, sys.maxsize
276
+ elif start is None:
277
+ start, stop = self.lu[i], sys.maxsize
278
+ elif stop is None:
279
+ start, stop = 0, start + self.lu[i]
280
+ else:
281
+ start, stop = stop + self.lu[i] + 1, start + self.lu[
282
+ i] + 1
283
+
284
+ if start is None:
285
+ start = 0
286
+ elif start < 0 and i < len(self.lu):
287
+ start = self.rd[i] + start
288
+ if step is None:
289
+ step = 1
290
+ if stop is None:
291
+ stop = sys.maxsize
292
+ elif stop < 0 and i < len(self.lu):
293
+ stop = self.rd[i] + stop
294
+
295
+ slice_list.append(slice(start, stop, step))
296
+ return tuple(slice_list), contract, reversed
297
+
298
+ def __getitem__(self, slice_tuple: slice | EllipsisType
299
+ | tuple[slice | int | EllipsisType, ...]):
300
+ self._slice, contract, reversed = self._full_slice(slice_tuple)
301
+ ret = self.toarray()
302
+ slices = []
303
+ for i, s in enumerate(self._slice):
304
+ if i in contract:
305
+ slices.append(0)
306
+ elif isinstance(s, slice):
307
+ if i in reversed:
308
+ slices.append(slice(None, None, -1))
309
+ else:
310
+ slices.append(slice(None, None, 1))
311
+ ret = ret.__getitem__(tuple(slices))
312
+ self._slice = None
313
+ return ret
314
+
315
+
316
+ class Record():
317
+
318
+ def __init__(self, id, database, description=None):
319
+ self.id = id
320
+ self.database = database
321
+ self.description = description
322
+ self._items = {}
323
+ self._pos = []
324
+ self._last_vars = set()
325
+ self._file = None
326
+ self._sock = None
327
+
328
+ for name, value in self.description['intrinsic_loops'].items():
329
+ self._items[name] = value
330
+
331
+ for level, range_list in description['loops'].items():
332
+ for name, iterable in range_list:
333
+ if name in self.description['independent_variables']:
334
+ self._items[name] = iterable
335
+
336
+ if self.is_local_record():
337
+ self.database = Path(self.database)
338
+ self._file = random_path(self.database / 'objects')
339
+ self._file.parent.mkdir(parents=True, exist_ok=True)
340
+
341
+ def __getstate__(self) -> dict:
342
+ return {
343
+ 'id': self.id,
344
+ 'description': self.description,
345
+ '_items': self._items,
346
+ }
347
+
348
+ def __setstate__(self, state: dict):
349
+ self.id = state['id']
350
+ self.description = state['description']
351
+ self._items = state['_items']
352
+ self._pos = []
353
+ self._last_vars = set()
354
+ self.database = None
355
+ self._file = None
356
+ self._sock = None
357
+
358
+ @property
359
+ def axis(self):
360
+ return self.description.get('axis', {})
361
+
362
+ def config(self, cls=dict, session=None, datapath=None):
363
+ config_id = self.description.get('config', None)
364
+ if config_id is None:
365
+ return None
366
+ if isinstance(config_id, dict):
367
+ return cls(config_id)
368
+ if self.is_remote_record():
369
+ with ZMQContextManager(zmq.DEALER,
370
+ connect=self.database,
371
+ socket=self._sock) as socket:
372
+ socket.send_pyobj({
373
+ 'method': 'config_get',
374
+ 'config_id': config_id
375
+ })
376
+ buf = socket.recv_pyobj()
377
+ return cls(pickle.loads(lzma.decompress(buf)))
378
+ else:
379
+ assert session is not None, "session is required for local record"
380
+ assert datapath is not None, "datapath is required for local record"
381
+ config = get_config(session, config_id, base=datapath / 'objects')
382
+ session.commit()
383
+ return cls(config)
384
+
385
+ def scripts(self, session=None):
386
+ scripts = self.description['entry']['scripts']
387
+ if isinstance(scripts, list):
388
+ return scripts
389
+ else:
390
+ cell_id = scripts
391
+
392
+ if self.is_remote_record():
393
+ with ZMQContextManager(zmq.DEALER,
394
+ connect=self.database,
395
+ socket=self._sock) as socket:
396
+ socket.send_pyobj({
397
+ 'method': 'notebook_history',
398
+ 'cell_id': cell_id
399
+ })
400
+ return socket.recv_pyobj()
401
+ elif self.is_local_record():
402
+ from .models import Cell
403
+ assert session is not None, "session is required for local record"
404
+ cell = session.get(Cell, cell_id)
405
+ return [
406
+ cell.input.text
407
+ for cell in cell.notebook.cells[1:cell.index + 2]
408
+ ]
409
+
410
+ def is_local_record(self):
411
+ return not self.is_cache_record() and not self.is_remote_record()
412
+
413
+ def is_cache_record(self):
414
+ return self.database is None
415
+
416
+ def is_remote_record(self):
417
+ return isinstance(self.database,
418
+ str) and self.database.startswith("tcp://")
419
+
420
+ def __del__(self):
421
+ self.flush()
422
+
423
+ def __getitem__(self, key):
424
+ return self.get(key, buffer_to_array=True)
425
+
426
+ def get(self, key, default=_not_given, buffer_to_array=False):
427
+ if self.is_remote_record():
428
+ with ZMQContextManager(zmq.DEALER,
429
+ connect=self.database,
430
+ socket=self._sock) as socket:
431
+ socket.send_pyobj({
432
+ 'method': 'record_getitem',
433
+ 'record_id': self.id,
434
+ 'key': key
435
+ })
436
+ ret = socket.recv_pyobj()
437
+ if isinstance(ret, BufferList):
438
+ ret._data_id = self.database, self.id, key
439
+ ret._sock = socket
440
+ if buffer_to_array:
441
+ return ret.toarray()
442
+ else:
443
+ return ret
444
+ elif isinstance(ret, Space) and buffer_to_array:
445
+ return ret.toarray()
446
+ else:
447
+ return ret
448
+ else:
449
+ if default is _not_given:
450
+ d = self._items.get(key)
451
+ else:
452
+ d = self._items.get(key, default)
453
+ if isinstance(d, BufferList):
454
+ if isinstance(d.file, str):
455
+ d.file = self._file.parent.parent.parent.parent / d.file
456
+ if buffer_to_array:
457
+ return d.toarray()
458
+ else:
459
+ return d
460
+ elif isinstance(d, Space):
461
+ if buffer_to_array:
462
+ return d.toarray()
463
+ else:
464
+ return d
465
+ else:
466
+ return d
467
+
468
+ def keys(self):
469
+ if self.is_remote_record():
470
+ with ZMQContextManager(zmq.DEALER,
471
+ connect=self.database,
472
+ socket=self._sock) as socket:
473
+ socket.send_pyobj({
474
+ 'method': 'record_keys',
475
+ 'record_id': self.id
476
+ })
477
+ keys = socket.recv_pyobj()
478
+ self._items = {key: None for key in keys}
479
+ return keys
480
+ else:
481
+ return list(self._items.keys())
482
+
483
+ def append(self, level, step, position, variables):
484
+ if level < 0:
485
+ self.flush()
486
+ return
487
+
488
+ for key in set(variables.keys()) - self._last_vars:
489
+ if key not in self.axis:
490
+ self.axis[key] = tuple(range(level + 1))
491
+
492
+ self._last_vars = set(variables.keys())
493
+
494
+ if level >= len(self._pos):
495
+ l = level + 1 - len(self._pos)
496
+ self._pos.extend(([0] * (l - 1)) + [position])
497
+ pos = tuple(self._pos)
498
+ elif level == len(self._pos) - 1:
499
+ self._pos[-1] = position
500
+ pos = tuple(self._pos)
501
+ else:
502
+ self._pos = self._pos[:level + 1]
503
+ self._pos[-1] = position
504
+ pos = tuple(self._pos)
505
+ self._pos[-1] += 1
506
+
507
+ for key, value in variables.items():
508
+ if self.axis[key] == ():
509
+ if key not in self._items:
510
+ self._items[key] = value
511
+ elif level == self.axis[key][-1]:
512
+ if key not in self._items:
513
+ if self.is_local_record():
514
+ bufferlist_file = random_path(self.database /
515
+ 'objects')
516
+ bufferlist_file.parent.mkdir(parents=True,
517
+ exist_ok=True)
518
+ self._items[key] = BufferList(bufferlist_file)
519
+ else:
520
+ self._items[key] = BufferList()
521
+ self._items[key].lu = pos
522
+ self._items[key].rd = tuple([i + 1 for i in pos])
523
+ self._items[key].append(pos, value, self.axis[key])
524
+ elif isinstance(self._items[key], BufferList):
525
+ self._items[key].append(pos, value, self.axis[key])
526
+
527
+ def flush(self):
528
+ if self.is_remote_record() or self.is_cache_record():
529
+ return
530
+
531
+ for key, value in self._items.items():
532
+ if isinstance(value, BufferList):
533
+ value.flush()
534
+
535
+ with open(self._file, 'wb') as f:
536
+ dill.dump(self, f)
537
+
538
+ def delete(self):
539
+ if self.is_remote_record():
540
+ with ZMQContextManager(zmq.DEALER,
541
+ connect=self.database,
542
+ socket=self._sock) as socket:
543
+ socket.send_pyobj({
544
+ 'method': 'record_delete',
545
+ 'record_id': self.id
546
+ })
547
+ elif self.is_local_record():
548
+ for key, value in self._items.items():
549
+ if isinstance(value, BufferList):
550
+ value.delete()
551
+ self._file.unlink()
552
+
553
+ def export(self, file):
554
+ with zipfile.ZipFile(file,
555
+ 'w',
556
+ compression=zipfile.ZIP_DEFLATED,
557
+ compresslevel=9) as z:
558
+ items = {}
559
+ for key in self.keys():
560
+ value = self.get(key)
561
+ if isinstance(value, BufferList):
562
+ v = BufferList()
563
+ v.lu = value.lu
564
+ v.rd = value.rd
565
+ v.inner_shape = value.inner_shape
566
+ items[key] = v
567
+ with z.open(f'{key}.buf', 'w') as f:
568
+ for pos, data in value.iter():
569
+ dill.dump((pos, data), f)
570
+ else:
571
+ items[key] = value
572
+ with z.open('record.pkl', 'w') as f:
573
+ self.description['entry']['scripts'] = self.scripts()
574
+ dill.dump((self.description, items), f)
575
+ with z.open('config.pkl', 'w') as f:
576
+ f.write(dill.dumps(self.config()))
577
+
578
+ @classmethod
579
+ def load(cls, file: str):
580
+ with zipfile.ZipFile(file, 'r') as z:
581
+ with z.open('record.pkl', 'r') as f:
582
+ description, items = dill.load(f)
583
+ with z.open('config.pkl', 'r') as f:
584
+ config = dill.load(f)
585
+ description['config'] = config
586
+ record = cls(None, None, description)
587
+ for key, value in items.items():
588
+ if isinstance(value, BufferList):
589
+ value.file = file, f'{key}.buf'
590
+ record._items[key] = value
591
+ return record
592
+
593
+ def __repr__(self):
594
+ if self.is_remote_record() and not self._items:
595
+ self._items = {key: None for key in self.keys()}
596
+ return f"<Record: id={self.id} app={self.description['app']}, keys={self._items.keys()}>"
597
+
598
+ # def _repr_html_(self):
599
+ # return f"""
600
+ # <h3>Record: id={self.id}, app={self.description['app']}</h3>
601
+ # <p>keys={self.keys()}</p>
602
+ # <p>axis={self.axis}</p>
603
+ # """