QuLab 2.1.0__cp310-cp310-macosx_10_9_universal2.whl → 2.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -1,7 +1,7 @@
1
- qulab/__init__.py,sha256=8zLGg-DfQhnDl2Ky0n-zXpN-8e-g7iR0AcaI4l4Vvpk,32
1
+ qulab/__init__.py,sha256=P-Mx2p4TVmL91SoxoeXcj8Qm0x4xUf5Q_FLk0Yc_gIQ,138
2
2
  qulab/__main__.py,sha256=eupSsrNVfnTFRpjgrY_knPvZIs0-Dk577LaN7qB15hI,487
3
- qulab/fun.cpython-310-darwin.so,sha256=o6uRbHabX_0Oe9NsHTJlXHN74l25fdephRn33_AuTvw,159632
4
- qulab/version.py,sha256=5VFOC9N5P61WNNAC14BfsqZmQ2X-yL6b-UnG_oixpYM,21
3
+ qulab/fun.cpython-310-darwin.so,sha256=CscJthBQevzJ48CBjcVzjUXLc3ATra1LLpR55WJHYS0,159632
4
+ qulab/version.py,sha256=FM98Inv2t7vU9Dvod2ccoaPCOtP2LV-rqmhbddOPf9U,21
5
5
  qulab/monitor/__init__.py,sha256=nTHelnDpxRS_fl_B38TsN0njgq8eVTEz9IAnN3NbDlM,42
6
6
  qulab/monitor/__main__.py,sha256=w3yUcqq195LzSnXTkQcuC1RSFRhy4oQ_PEBmucXguME,97
7
7
  qulab/monitor/config.py,sha256=fQ5JcsMApKc1UwANEnIvbDQZl8uYW0tle92SaYtX9lI,744
@@ -12,16 +12,18 @@ qulab/monitor/monitor.py,sha256=7E4bnTsO6qC85fs2ONrccGHfaYKv7SW74mtXzv6QjVc,2305
12
12
  qulab/monitor/ploter.py,sha256=CbiIjmohgtwDDTVeGzhXEGVo3XjytMdhLwU9VUkg9vo,3601
13
13
  qulab/monitor/qt_compat.py,sha256=OK71_JSO_iyXjRIKHANmaK4Lx4bILUzmXI-mwKg3QeI,788
14
14
  qulab/monitor/toolbar.py,sha256=WEag6cxAtEsOLL14XvM7pSE56EA3MO188_JuprNjdBs,7948
15
- qulab/scan/__init__.py,sha256=BflmtNkyV72f4jqkeXoO_gi7kf8CHyk-AmKHjNXwe1A,124
15
+ qulab/scan/__init__.py,sha256=ZX4WsvqYxvJeHLgGSrtJoAnVU94gxY7EHKMxYooMERg,130
16
16
  qulab/scan/curd.py,sha256=ntpK62ArZiF2mrDDewcw227VMR1E_8no0yLJSrgdgng,4518
17
17
  qulab/scan/expression.py,sha256=-aTYbjFQNI1mwOcoSBztqhKfGJpu_n4a1QnWro_xnTU,15694
18
18
  qulab/scan/models.py,sha256=S8Q9hC8nOzxyoNB10EYg-miDKqoNMnjyAECjD-TuORw,17117
19
19
  qulab/scan/optimize.py,sha256=vErjRTCtn2MwMF5Xyhs1P4gHF2IFHv_EqxsUvH_4y7k,2287
20
- qulab/scan/query_record.py,sha256=BVkNgv3yfbMXX_Kguq18fvowKOBmBiiTvUwj_CqpiF4,11493
21
- qulab/scan/recorder.py,sha256=utFdM57V_pIZhAGZVC15-iVa1zlyI0e6-2J1_IkY1No,25698
22
- qulab/scan/scan.py,sha256=V6xSCJ4LK_Ieq4va8B7X29t9dI1MT9C4K51wsektMYk,29740
23
- qulab/scan/server.py,sha256=W8z3vr0cqSSKWzIG6_b0d-lpBpDXGpHSwN6VJuv3w9U,2844
24
- qulab/scan/utils.py,sha256=n5yquKlz2QYMzciPgD9vkpBJVgzVzOqAlfvB4Qu6oOk,2551
20
+ qulab/scan/query.py,sha256=tedi7CUI15X9AluT_FQF5Xd22U-UC9wpOeaKCmO3nYI,11457
21
+ qulab/scan/record.py,sha256=FwzUI_W-wwf8TXnRBb8fLUUn8Eld3KhjZjE_AMwfbzU,16982
22
+ qulab/scan/recorder.py,sha256=zjIkSee4_QNOS8lDMrknoXR5vjJUuomnOemhF4O0hCU,8717
23
+ qulab/scan/scan.py,sha256=P1HH5la0TyREK9KU1i7JxqxjNnplfn1eaTog0FI0Suw,29716
24
+ qulab/scan/server.py,sha256=zpUbYcRSC3UI9z7yiIBnvzKR9vWejJTTJ7AwEt5iyJA,2768
25
+ qulab/scan/space.py,sha256=nwSGGnppe-Z6WPHfOqX4eeD5AutMQlIiPwrkxBZuI9I,5209
26
+ qulab/scan/utils.py,sha256=Pg_tCf3SUKTiPSBqb6Enkgx4bAyQJAkDGe9uYys1xVU,3613
25
27
  qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
28
  qulab/storage/__main__.py,sha256=3emxxRry8BB0m8hUZvJ_oBqkPy7ksV7flHB_KEDXZuI,1692
27
29
  qulab/storage/base_dataset.py,sha256=4aKhqBNdZfdlm_z1Qy5dv0HrvgpvMdy8pbyfua8uE-4,11865
@@ -77,9 +79,9 @@ qulab/visualization/plot_layout.py,sha256=clNw9QjE_kVNpIIx2Ob4YhAz2fucPGMuzkoIrO
77
79
  qulab/visualization/plot_seq.py,sha256=lphYF4VhkEdc_wWr1kFBwrx2yujkyFPFaJ3pjr61awI,2693
78
80
  qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
79
81
  qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
80
- QuLab-2.1.0.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
81
- QuLab-2.1.0.dist-info/METADATA,sha256=eiTcqFfy-xPMe8p91gzAIlO2YJ9hpeycUrxF74_nxMU,3510
82
- QuLab-2.1.0.dist-info/WHEEL,sha256=r7U64H7df5k5VoE41bE2otJ6YmrMps4wUd2_S2hHvHQ,115
83
- QuLab-2.1.0.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
84
- QuLab-2.1.0.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
85
- QuLab-2.1.0.dist-info/RECORD,,
82
+ QuLab-2.1.2.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
83
+ QuLab-2.1.2.dist-info/METADATA,sha256=oXOna9NWs48Bf1PY9WUGu535kEsWwB-JEd4JEN40jTQ,3510
84
+ QuLab-2.1.2.dist-info/WHEEL,sha256=r7U64H7df5k5VoE41bE2otJ6YmrMps4wUd2_S2hHvHQ,115
85
+ QuLab-2.1.2.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
86
+ QuLab-2.1.2.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
87
+ QuLab-2.1.2.dist-info/RECORD,,
qulab/__init__.py CHANGED
@@ -1 +1,3 @@
1
- from .version import __version__
1
+ from .scan import Scan, get_record, load_record, lookup, lookup_list
2
+ from .version import __version__
3
+ from .visualization import autoplot
Binary file
qulab/scan/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  from .expression import Expression, Symbol
2
- from .query_record import get_record, lookup, lookup_list
2
+ from .query import get_record, load_record, lookup, lookup_list
3
3
  from .scan import Scan
@@ -6,13 +6,17 @@ import dill
6
6
  import ipywidgets as widgets
7
7
  import zmq
8
8
  from IPython.display import display
9
+ from sqlalchemy import create_engine
10
+ from sqlalchemy.orm import sessionmaker
9
11
 
10
12
  from qulab.sys.rpc.zmq_socket import ZMQContextManager
11
13
 
12
- from .recorder import Record
14
+ from .record import Record
15
+ from .recorder import get_local_record
16
+ from .scan import default_server
13
17
 
14
18
 
15
- def get_record(id, database='tcp://127.0.0.1:6789'):
19
+ def get_record(id, database=default_server) -> Record:
16
20
  if isinstance(database, str) and database.startswith('tcp://'):
17
21
  with ZMQContextManager(zmq.DEALER, connect=database) as socket:
18
22
  socket.send_pyobj({
@@ -20,20 +24,20 @@ def get_record(id, database='tcp://127.0.0.1:6789'):
20
24
  'record_id': id
21
25
  })
22
26
  d = dill.loads(socket.recv_pyobj())
23
- return Record(id, database, d)
27
+ d.id = id
28
+ d.database = database
29
+ d._file = None
30
+ return d
24
31
  else:
25
- from .models import Record as RecordInDB
26
- from .models import create_engine, sessionmaker
27
-
28
32
  db_file = Path(database) / 'data.db'
29
33
  engine = create_engine(f'sqlite:///{db_file}')
30
34
  Session = sessionmaker(bind=engine)
31
35
  with Session() as session:
32
- path = Path(database) / 'objects' / session.get(RecordInDB,
33
- id).file
34
- with open(path, 'rb') as f:
35
- record = dill.load(f)
36
- return record
36
+ return get_local_record(session, id, database)
37
+
38
+
39
+ def load_record(file):
40
+ return Record.load(file)
37
41
 
38
42
 
39
43
  def _format_tag(tag):
@@ -292,7 +296,7 @@ def _on_tags_submit(tags, ui_widgets):
292
296
  _update_view(ui_widgets)
293
297
 
294
298
 
295
- def lookup(app=None, limit=10, database='tcp://127.0.0.1:6789'):
299
+ def lookup(app=None, limit=10, database=default_server):
296
300
  after = widgets.DatePicker()
297
301
  before = widgets.DatePicker()
298
302
  app_prefix = widgets.Label('App:')
qulab/scan/record.py ADDED
@@ -0,0 +1,495 @@
1
+ import itertools
2
+ import sys
3
+ import uuid
4
+ import zipfile
5
+ from pathlib import Path
6
+ from threading import Lock
7
+ from types import EllipsisType
8
+
9
+ import dill
10
+ import numpy as np
11
+ import zmq
12
+
13
+ from qulab.sys.rpc.zmq_socket import ZMQContextManager
14
+
15
+ from .space import OptimizeSpace, Space
16
+
17
+ _not_given = object()
18
+
19
+
20
+ def random_path(base):
21
+ while True:
22
+ s = uuid.uuid4().hex
23
+ path = base / s[:2] / s[2:4] / s[4:6] / s[6:]
24
+ if not path.exists():
25
+ return path
26
+
27
+
28
+ def index_in_slice(slice_obj: slice | int, index: int):
29
+ if isinstance(slice_obj, int):
30
+ return slice_obj == index
31
+ start, stop, step = slice_obj.start, slice_obj.stop, slice_obj.step
32
+ if start is None:
33
+ start = 0
34
+ if step is None:
35
+ step = 1
36
+ if stop is None:
37
+ stop = sys.maxsize
38
+
39
+ if step > 0:
40
+ return start <= index < stop and (index - start) % step == 0
41
+ else:
42
+ return stop < index <= start and (index - start) % step == 0
43
+
44
+
45
+ class BufferList():
46
+
47
+ def __init__(self, file=None, slice=None):
48
+ self._list = []
49
+ self.lu = ()
50
+ self.rd = ()
51
+ self.inner_shape = ()
52
+ self.file = file
53
+ self._slice = slice
54
+ self._lock = Lock()
55
+ self._data_id = None
56
+
57
+ def __repr__(self):
58
+ return f"<BufferList: shape={self.shape}, lu={self.lu}, rd={self.rd}, slice={self._slice}>"
59
+
60
+ def __getstate__(self):
61
+ self.flush()
62
+ if isinstance(self.file, Path):
63
+ file = '/'.join(self.file.parts[-4:])
64
+ else:
65
+ file = self.file
66
+ return {
67
+ 'file': file,
68
+ 'lu': self.lu,
69
+ 'rd': self.rd,
70
+ 'inner_shape': self.inner_shape,
71
+ }
72
+
73
+ def __setstate__(self, state):
74
+ self.file = state['file']
75
+ self.lu = state['lu']
76
+ self.rd = state['rd']
77
+ self.inner_shape = state['inner_shape']
78
+ self._list = []
79
+ self._slice = None
80
+ self._lock = Lock()
81
+ self._data_id = None
82
+
83
+ @property
84
+ def shape(self):
85
+ return tuple([i - j
86
+ for i, j in zip(self.rd, self.lu)]) + self.inner_shape
87
+
88
+ def flush(self):
89
+ if not self._list:
90
+ return
91
+ if isinstance(self.file, Path):
92
+ with self._lock:
93
+ with open(self.file, 'ab') as f:
94
+ for item in self._list:
95
+ dill.dump(item, f)
96
+ self._list.clear()
97
+
98
+ def append(self, pos, value, dims=None):
99
+ if dims is not None:
100
+ if any([p != 0 for i, p in enumerate(pos) if i not in dims]):
101
+ return
102
+ pos = tuple([pos[i] for i in dims])
103
+ self.lu = tuple([min(i, j) for i, j in zip(pos, self.lu)])
104
+ self.rd = tuple([max(i + 1, j) for i, j in zip(pos, self.rd)])
105
+ if hasattr(value, 'shape'):
106
+ if self.inner_shape is None:
107
+ self.inner_shape = value.shape
108
+ elif self.inner_shape != value.shape:
109
+ self.inner_shape = ()
110
+ self._list.append((pos, value))
111
+ if len(self._list) > 1000:
112
+ self.flush()
113
+
114
+ def _iter_file(self):
115
+ if isinstance(self.file, Path) and self.file.exists():
116
+ with self._lock:
117
+ with open(self.file, 'rb') as f:
118
+ while True:
119
+ try:
120
+ pos, value = dill.load(f)
121
+ yield pos, value
122
+ except EOFError:
123
+ break
124
+ elif isinstance(
125
+ self.file, tuple) and len(self.file) == 2 and isinstance(
126
+ self.file[0], str) and self.file[0].endswith('.zip'):
127
+ f, name = self.file
128
+ with zipfile.ZipFile(f, 'r') as z:
129
+ with z.open(name, 'r') as f:
130
+ while True:
131
+ try:
132
+ pos, value = dill.load(f)
133
+ yield pos, value
134
+ except EOFError:
135
+ break
136
+
137
+ def iter(self):
138
+ if self._data_id is None:
139
+ for pos, value in itertools.chain(self._iter_file(), self._list):
140
+ if not self._slice:
141
+ yield pos, value
142
+ elif all(
143
+ [index_in_slice(s, i) for s, i in zip(self._slice, pos)]):
144
+ if self.inner_shape:
145
+ yield pos, value[self._slice[len(pos):]]
146
+ else:
147
+ yield pos, value
148
+ else:
149
+ server, record_id, key = self._data_id
150
+ with ZMQContextManager(zmq.DEALER, connect=server) as socket:
151
+ socket.send_pyobj({
152
+ 'method': 'bufferlist_slice',
153
+ 'record_id': record_id,
154
+ 'key': key,
155
+ 'slice': self._slice
156
+ })
157
+ ret = socket.recv_pyobj()
158
+ yield from ret
159
+
160
+ def value(self):
161
+ d = []
162
+ for pos, value in self.iter():
163
+ d.append(value)
164
+ return d
165
+
166
+ def pos(self):
167
+ p = []
168
+ for pos, value in self.iter():
169
+ p.append(pos)
170
+ return p
171
+
172
+ def items(self):
173
+ p, d = [], []
174
+ for pos, value in self.iter():
175
+ p.append(pos)
176
+ d.append(value)
177
+ return p, d
178
+
179
+ def array(self):
180
+ pos, data = self.items()
181
+ if self._slice:
182
+ pos = np.asarray(pos)
183
+ lu = tuple(np.min(pos, axis=0))
184
+ rd = tuple(np.max(pos, axis=0) + 1)
185
+ pos = np.asarray(pos) - np.asarray(lu)
186
+ shape = []
187
+ for k, (s, i, j) in enumerate(zip(self._slice, rd, lu)):
188
+ if s.step is not None:
189
+ pos[:, k] = pos[:, k] / s.step
190
+ shape.append(round(np.ceil((i - j) / s.step)))
191
+ else:
192
+ shape.append(i - j)
193
+ shape = tuple(shape)
194
+ else:
195
+ shape = tuple([i - j for i, j in zip(self.rd, self.lu)])
196
+ pos = np.asarray(pos) - np.asarray(self.lu)
197
+ data = np.asarray(data)
198
+ inner_shape = data.shape[1:]
199
+ x = np.full(shape + inner_shape, np.nan, dtype=data[0].dtype)
200
+ x.__setitem__(tuple(pos.T), data)
201
+ return x
202
+
203
+ def _full_slice(self, slice_tuple: slice
204
+ | tuple[slice | int | EllipsisType, ...]):
205
+ ndim = len(self.lu)
206
+ if self.inner_shape:
207
+ ndim += len(self.inner_shape)
208
+
209
+ if isinstance(slice_tuple, slice):
210
+ slice_tuple = (
211
+ slice_tuple, ) + (slice(0, sys.maxsize, 1), ) * (ndim - 1)
212
+ if slice_tuple is Ellipsis:
213
+ slice_tuple = (slice(0, sys.maxsize, 1), ) * ndim
214
+ else:
215
+ head, tail = (), ()
216
+ for i, s in enumerate(slice_tuple):
217
+ if s is Ellipsis:
218
+ head = slice_tuple[:i]
219
+ tail = slice_tuple[i + 1:]
220
+ break
221
+ else:
222
+ head = slice_tuple
223
+ tail = ()
224
+ slice_tuple = head + (slice(
225
+ 0, sys.maxsize, 1), ) * (ndim - len(head) - len(tail)) + tail
226
+ slice_list = []
227
+ contract = []
228
+ reversed = []
229
+ for i, s in enumerate(slice_tuple):
230
+ if isinstance(s, int):
231
+ if s >= 0:
232
+ slice_list.append(slice(s, s + 1, 1))
233
+ elif i < len(self.lu):
234
+ s = self.rd[i] + s
235
+ slice_list.append(slice(s, s + 1, 1))
236
+ else:
237
+ slice_list.append(slice(s, s - 1, -1))
238
+ contract.append(i)
239
+ else:
240
+ start, stop, step = s.start, s.stop, s.step
241
+ if step is None:
242
+ step = 1
243
+ if step < 0 and i < len(self.lu):
244
+ step = -step
245
+ reversed.append(i)
246
+ if start is None and stop is None:
247
+ start, stop = 0, sys.maxsize
248
+ elif start is None:
249
+ start, stop = self.lu[i], sys.maxsize
250
+ elif stop is None:
251
+ start, stop = 0, start + self.lu[i]
252
+ else:
253
+ start, stop = stop + self.lu[i] + 1, start + self.lu[
254
+ i] + 1
255
+
256
+ if start is None:
257
+ start = 0
258
+ elif start < 0 and i < len(self.lu):
259
+ start = self.rd[i] + start
260
+ if step is None:
261
+ step = 1
262
+ if stop is None:
263
+ stop = sys.maxsize
264
+ elif stop < 0 and i < len(self.lu):
265
+ stop = self.rd[i] + stop
266
+
267
+ slice_list.append(slice(start, stop, step))
268
+ return tuple(slice_list), contract, reversed
269
+
270
+ def __getitem__(self, slice_tuple: slice | EllipsisType
271
+ | tuple[slice | int | EllipsisType, ...]):
272
+ self._slice, contract, reversed = self._full_slice(slice_tuple)
273
+ ret = self.array()
274
+ slices = []
275
+ for i, s in enumerate(self._slice):
276
+ if i in contract:
277
+ slices.append(0)
278
+ elif isinstance(s, slice):
279
+ if i in reversed:
280
+ slices.append(slice(None, None, -1))
281
+ else:
282
+ slices.append(slice(None, None, 1))
283
+ ret = ret.__getitem__(tuple(slices))
284
+ self._slice = None
285
+ return ret
286
+
287
+
288
+ class Record():
289
+
290
+ def __init__(self, id, database, description=None):
291
+ self.id = id
292
+ self.database = database
293
+ self.description = description
294
+ self._items = {}
295
+ self._pos = []
296
+ self._last_vars = set()
297
+ self._file = None
298
+
299
+ if self.is_local_record():
300
+ self.database = Path(self.database)
301
+ self._file = random_path(self.database / 'objects')
302
+ self._file.parent.mkdir(parents=True, exist_ok=True)
303
+
304
+ def __getstate__(self) -> dict:
305
+ return {
306
+ 'id': self.id,
307
+ 'description': self.description,
308
+ '_items': self._items,
309
+ }
310
+
311
+ def __setstate__(self, state: dict):
312
+ self.id = state['id']
313
+ self.description = state['description']
314
+ self._items = state['_items']
315
+ self._pos = []
316
+ self._last_vars = set()
317
+ self.database = None
318
+ self._file = None
319
+
320
+ @property
321
+ def axis(self):
322
+ return self.description.get('axis', {})
323
+
324
+ def is_local_record(self):
325
+ return not self.is_cache_record() and not self.is_remote_record()
326
+
327
+ def is_cache_record(self):
328
+ return self.database is None
329
+
330
+ def is_remote_record(self):
331
+ return isinstance(self.database,
332
+ str) and self.database.startswith("tcp://")
333
+
334
+ def __del__(self):
335
+ self.flush()
336
+
337
+ def __getitem__(self, key):
338
+ ret = self.get(key, buffer_to_array=True)
339
+ if isinstance(ret, Space):
340
+ ret = ret.toarray()
341
+ return ret
342
+
343
+ def get(self, key, default=_not_given, buffer_to_array=False, slice=None):
344
+ if self.is_remote_record():
345
+ with ZMQContextManager(zmq.DEALER,
346
+ connect=self.database) as socket:
347
+ socket.send_pyobj({
348
+ 'method': 'record_getitem',
349
+ 'record_id': self.id,
350
+ 'key': key
351
+ })
352
+ ret = socket.recv_pyobj()
353
+ if isinstance(ret, BufferList):
354
+ if buffer_to_array:
355
+ socket.send_pyobj({
356
+ 'method': 'bufferlist_slice',
357
+ 'record_id': self.id,
358
+ 'key': key,
359
+ 'slice': slice
360
+ })
361
+ lst = socket.recv_pyobj()
362
+ ret._list = lst
363
+ ret._slice = slice
364
+ return ret.array()
365
+ else:
366
+ ret._data_id = self.database, self.id, key
367
+ return ret
368
+ else:
369
+ return ret
370
+ else:
371
+ if default is _not_given:
372
+ d = self._items.get(key)
373
+ else:
374
+ d = self._items.get(key, default)
375
+ if isinstance(d, BufferList):
376
+ if isinstance(d.file, str):
377
+ d.file = self._file.parent.parent.parent.parent / d.file
378
+ d._slice = slice
379
+ if buffer_to_array:
380
+ return d.array()
381
+ else:
382
+ return d
383
+ else:
384
+ return d
385
+
386
+ def keys(self):
387
+ if self.is_remote_record():
388
+ with ZMQContextManager(zmq.DEALER,
389
+ connect=self.database) as socket:
390
+ socket.send_pyobj({
391
+ 'method': 'record_keys',
392
+ 'record_id': self.id
393
+ })
394
+ return socket.recv_pyobj()
395
+ else:
396
+ return list(self._items.keys())
397
+
398
+ def append(self, level, step, position, variables):
399
+ if level < 0:
400
+ self.flush()
401
+ return
402
+
403
+ for key in set(variables.keys()) - self._last_vars:
404
+ if key not in self.axis:
405
+ self.axis[key] = tuple(range(level + 1))
406
+
407
+ self._last_vars = set(variables.keys())
408
+
409
+ if level >= len(self._pos):
410
+ l = level + 1 - len(self._pos)
411
+ self._pos.extend(([0] * (l - 1)) + [position])
412
+ pos = tuple(self._pos)
413
+ elif level == len(self._pos) - 1:
414
+ self._pos[-1] = position
415
+ pos = tuple(self._pos)
416
+ else:
417
+ self._pos = self._pos[:level + 1]
418
+ self._pos[-1] = position
419
+ pos = tuple(self._pos)
420
+ self._pos[-1] += 1
421
+
422
+ for key, value in variables.items():
423
+ if self.axis[key] == ():
424
+ if key not in self._items:
425
+ self._items[key] = value
426
+ elif level == self.axis[key][-1]:
427
+ if key not in self._items:
428
+ if self.is_local_record():
429
+ bufferlist_file = random_path(self.database /
430
+ 'objects')
431
+ bufferlist_file.parent.mkdir(parents=True,
432
+ exist_ok=True)
433
+ self._items[key] = BufferList(bufferlist_file)
434
+ else:
435
+ self._items[key] = BufferList()
436
+ self._items[key].lu = pos
437
+ self._items[key].rd = tuple([i + 1 for i in pos])
438
+ self._items[key].append(pos, value, self.axis[key])
439
+ elif isinstance(self._items[key], BufferList):
440
+ self._items[key].append(pos, value, self.axis[key])
441
+
442
+ def flush(self):
443
+ if self.is_remote_record() or self.is_cache_record():
444
+ return
445
+
446
+ for key, value in self._items.items():
447
+ if isinstance(value, BufferList):
448
+ value.flush()
449
+
450
+ with open(self._file, 'wb') as f:
451
+ dill.dump(self, f)
452
+
453
+ def export(self, file):
454
+ with zipfile.ZipFile(file,
455
+ 'w',
456
+ compression=zipfile.ZIP_DEFLATED,
457
+ compresslevel=9) as z:
458
+ items = {}
459
+ for key in self.keys():
460
+ value = self.get(key)
461
+ if isinstance(value, BufferList):
462
+ v = BufferList()
463
+ v.lu = value.lu
464
+ v.rd = value.rd
465
+ v.inner_shape = value.inner_shape
466
+ items[key] = v
467
+ with z.open(f'{key}.buf', 'w') as f:
468
+ for pos, data in value.iter():
469
+ dill.dump((pos, data), f)
470
+ else:
471
+ items[key] = value
472
+ with z.open('record.pkl', 'w') as f:
473
+ dill.dump((self.description, items), f)
474
+
475
+ @classmethod
476
+ def load(cls, file: str):
477
+ with zipfile.ZipFile(file, 'r') as z:
478
+ with z.open('record.pkl', 'r') as f:
479
+ description, items = dill.load(f)
480
+ record = cls(None, None, description)
481
+ for key, value in items.items():
482
+ if isinstance(value, BufferList):
483
+ value.file = file, f'{key}.buf'
484
+ record._items[key] = value
485
+ return record
486
+
487
+ def __repr__(self):
488
+ return f"<Record: id={self.id} app={self.description['app']}, keys={self.keys()}>"
489
+
490
+ # def _repr_html_(self):
491
+ # return f"""
492
+ # <h3>Record: id={self.id}, app={self.description['app']}</h3>
493
+ # <p>keys={self.keys()}</p>
494
+ # <p>axis={self.axis}</p>
495
+ # """