QuLab 2.1.0__tar.gz → 2.1.2__tar.gz
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.
- {qulab-2.1.0 → qulab-2.1.2}/PKG-INFO +1 -1
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/PKG-INFO +1 -1
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/SOURCES.txt +3 -1
- qulab-2.1.2/qulab/__init__.py +3 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/__init__.py +1 -1
- qulab-2.1.0/qulab/scan/query_record.py → qulab-2.1.2/qulab/scan/query.py +16 -12
- qulab-2.1.0/qulab/scan/recorder.py → qulab-2.1.2/qulab/scan/record.py +70 -303
- qulab-2.1.2/qulab/scan/recorder.py +247 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/scan.py +121 -147
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/server.py +4 -6
- qulab-2.1.2/qulab/scan/space.py +172 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/utils.py +48 -0
- qulab-2.1.2/qulab/version.py +1 -0
- qulab-2.1.0/qulab/__init__.py +0 -1
- qulab-2.1.0/qulab/version.py +0 -1
- {qulab-2.1.0 → qulab-2.1.2}/LICENSE +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/MANIFEST.in +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/dependency_links.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/entry_points.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/requires.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/QuLab.egg-info/top_level.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/README.md +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/pyproject.toml +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/config.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/event_queue.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/mainwindow.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/monitor.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/ploter.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/qt_compat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/monitor/toolbar.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/curd.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/expression.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/models.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/scan/optimize.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/backend/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/backend/redis.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/base_dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/chunk.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/file.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/base.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/config.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/file.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/ipy.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/models.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/record.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/report.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/models/tag.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/storage/storage.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/chat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/device/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/device/basedevice.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/device/loader.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/device/utils.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/drivers/FakeInstrument.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/drivers/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/ipy_events.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/bencoder.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/cli.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/dhcp.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/dhcpd.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/kad.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/kcp.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/net/nginx.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/progress.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/client.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/exceptions.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/msgpack.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/msgpack.pyi +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/rpc.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/serialize.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/server.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/socket.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/utils.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/worker.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/sys/rpc/zmq_socket.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/_autoplot.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/plot_layout.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/plot_seq.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/qdat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/qulab/visualization/widgets.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/setup.cfg +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/setup.py +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/src/qulab.h +0 -0
- {qulab-2.1.0 → qulab-2.1.2}/tests/test_scan.py +0 -0
|
@@ -27,10 +27,12 @@ qulab/scan/curd.py
|
|
|
27
27
|
qulab/scan/expression.py
|
|
28
28
|
qulab/scan/models.py
|
|
29
29
|
qulab/scan/optimize.py
|
|
30
|
-
qulab/scan/
|
|
30
|
+
qulab/scan/query.py
|
|
31
|
+
qulab/scan/record.py
|
|
31
32
|
qulab/scan/recorder.py
|
|
32
33
|
qulab/scan/scan.py
|
|
33
34
|
qulab/scan/server.py
|
|
35
|
+
qulab/scan/space.py
|
|
34
36
|
qulab/scan/utils.py
|
|
35
37
|
qulab/storage/__init__.py
|
|
36
38
|
qulab/storage/__main__.py
|
|
@@ -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 .
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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=
|
|
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:')
|
|
@@ -1,41 +1,20 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import itertools
|
|
3
|
-
import os
|
|
4
|
-
import pickle
|
|
5
2
|
import sys
|
|
6
|
-
import time
|
|
7
3
|
import uuid
|
|
8
|
-
|
|
4
|
+
import zipfile
|
|
9
5
|
from pathlib import Path
|
|
10
6
|
from threading import Lock
|
|
11
7
|
from types import EllipsisType
|
|
12
8
|
|
|
13
|
-
import click
|
|
14
9
|
import dill
|
|
15
10
|
import numpy as np
|
|
16
11
|
import zmq
|
|
17
|
-
from loguru import logger
|
|
18
12
|
|
|
19
13
|
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
|
20
14
|
|
|
21
|
-
from .
|
|
22
|
-
from .models import Record as RecordInDB
|
|
23
|
-
from .models import Session, create_engine, create_tables, sessionmaker, utcnow
|
|
15
|
+
from .space import OptimizeSpace, Space
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try:
|
|
28
|
-
default_record_port = int(os.getenv('QULAB_RECORD_PORT', 6789))
|
|
29
|
-
except:
|
|
30
|
-
default_record_port = 6789
|
|
31
|
-
|
|
32
|
-
if os.getenv('QULAB_RECORD_PATH'):
|
|
33
|
-
datapath = Path(os.getenv('QULAB_RECORD_PATH'))
|
|
34
|
-
else:
|
|
35
|
-
datapath = Path.home() / 'qulab' / 'data'
|
|
36
|
-
datapath.mkdir(parents=True, exist_ok=True)
|
|
37
|
-
|
|
38
|
-
record_cache = {}
|
|
17
|
+
_not_given = object()
|
|
39
18
|
|
|
40
19
|
|
|
41
20
|
def random_path(base):
|
|
@@ -142,6 +121,18 @@ class BufferList():
|
|
|
142
121
|
yield pos, value
|
|
143
122
|
except EOFError:
|
|
144
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
|
|
145
136
|
|
|
146
137
|
def iter(self):
|
|
147
138
|
if self._data_id is None:
|
|
@@ -297,51 +288,13 @@ class BufferList():
|
|
|
297
288
|
class Record():
|
|
298
289
|
|
|
299
290
|
def __init__(self, id, database, description=None):
|
|
300
|
-
from .scan import OptimizeSpace
|
|
301
|
-
|
|
302
291
|
self.id = id
|
|
303
292
|
self.database = database
|
|
304
293
|
self.description = description
|
|
305
|
-
self._keys = set()
|
|
306
294
|
self._items = {}
|
|
307
|
-
self._index = []
|
|
308
295
|
self._pos = []
|
|
309
296
|
self._last_vars = set()
|
|
310
297
|
self._file = None
|
|
311
|
-
self.independent_variables = {}
|
|
312
|
-
self.constants = {}
|
|
313
|
-
self.dims = {}
|
|
314
|
-
|
|
315
|
-
for name, value in self.description['consts'].items():
|
|
316
|
-
if name not in self._items:
|
|
317
|
-
self._items[name] = value
|
|
318
|
-
self.constants[name] = value
|
|
319
|
-
self.dims[name] = ()
|
|
320
|
-
for level, range_list in self.description['loops'].items():
|
|
321
|
-
for name, iterable in range_list:
|
|
322
|
-
if isinstance(iterable, OptimizeSpace):
|
|
323
|
-
self.dims[name] = tuple(range(level + 1))
|
|
324
|
-
continue
|
|
325
|
-
elif isinstance(iterable, (np.ndarray, list, tuple, range)):
|
|
326
|
-
self._items[name] = iterable
|
|
327
|
-
self.independent_variables[name] = iterable
|
|
328
|
-
self.dims[name] = (level, )
|
|
329
|
-
|
|
330
|
-
for level, group in self.description['order'].items():
|
|
331
|
-
for names in group:
|
|
332
|
-
for name in names:
|
|
333
|
-
if name not in self.description['dependents']:
|
|
334
|
-
if name not in self.dims:
|
|
335
|
-
self.dims[name] = (level, )
|
|
336
|
-
else:
|
|
337
|
-
d = set()
|
|
338
|
-
for n in self.description['dependents'][name]:
|
|
339
|
-
d.update(self.dims[n])
|
|
340
|
-
if name not in self.dims:
|
|
341
|
-
self.dims[name] = tuple(sorted(d))
|
|
342
|
-
else:
|
|
343
|
-
self.dims[name] = tuple(
|
|
344
|
-
sorted(set(self.dims[name]) | d))
|
|
345
298
|
|
|
346
299
|
if self.is_local_record():
|
|
347
300
|
self.database = Path(self.database)
|
|
@@ -351,32 +304,23 @@ class Record():
|
|
|
351
304
|
def __getstate__(self) -> dict:
|
|
352
305
|
return {
|
|
353
306
|
'id': self.id,
|
|
354
|
-
'database': self.database,
|
|
355
307
|
'description': self.description,
|
|
356
|
-
'_keys': self._keys,
|
|
357
308
|
'_items': self._items,
|
|
358
|
-
'_index': self._index,
|
|
359
|
-
'_pos': self._pos,
|
|
360
|
-
'_last_vars': self._last_vars,
|
|
361
|
-
'independent_variables': self.independent_variables,
|
|
362
|
-
'constants': self.constants,
|
|
363
|
-
'dims': self.dims,
|
|
364
309
|
}
|
|
365
310
|
|
|
366
311
|
def __setstate__(self, state: dict):
|
|
367
312
|
self.id = state['id']
|
|
368
|
-
self.database = state['database']
|
|
369
313
|
self.description = state['description']
|
|
370
|
-
self._keys = state['_keys']
|
|
371
314
|
self._items = state['_items']
|
|
372
|
-
self.
|
|
373
|
-
self.
|
|
374
|
-
self.
|
|
375
|
-
self.independent_variables = state['independent_variables']
|
|
376
|
-
self.constants = state['constants']
|
|
377
|
-
self.dims = state['dims']
|
|
315
|
+
self._pos = []
|
|
316
|
+
self._last_vars = set()
|
|
317
|
+
self.database = None
|
|
378
318
|
self._file = None
|
|
379
319
|
|
|
320
|
+
@property
|
|
321
|
+
def axis(self):
|
|
322
|
+
return self.description.get('axis', {})
|
|
323
|
+
|
|
380
324
|
def is_local_record(self):
|
|
381
325
|
return not self.is_cache_record() and not self.is_remote_record()
|
|
382
326
|
|
|
@@ -391,9 +335,12 @@ class Record():
|
|
|
391
335
|
self.flush()
|
|
392
336
|
|
|
393
337
|
def __getitem__(self, key):
|
|
394
|
-
|
|
338
|
+
ret = self.get(key, buffer_to_array=True)
|
|
339
|
+
if isinstance(ret, Space):
|
|
340
|
+
ret = ret.toarray()
|
|
341
|
+
return ret
|
|
395
342
|
|
|
396
|
-
def get(self, key, default=
|
|
343
|
+
def get(self, key, default=_not_given, buffer_to_array=False, slice=None):
|
|
397
344
|
if self.is_remote_record():
|
|
398
345
|
with ZMQContextManager(zmq.DEALER,
|
|
399
346
|
connect=self.database) as socket:
|
|
@@ -421,7 +368,7 @@ class Record():
|
|
|
421
368
|
else:
|
|
422
369
|
return ret
|
|
423
370
|
else:
|
|
424
|
-
if default is
|
|
371
|
+
if default is _not_given:
|
|
425
372
|
d = self._items.get(key)
|
|
426
373
|
else:
|
|
427
374
|
d = self._items.get(key, default)
|
|
@@ -446,7 +393,7 @@ class Record():
|
|
|
446
393
|
})
|
|
447
394
|
return socket.recv_pyobj()
|
|
448
395
|
else:
|
|
449
|
-
return list(self.
|
|
396
|
+
return list(self._items.keys())
|
|
450
397
|
|
|
451
398
|
def append(self, level, step, position, variables):
|
|
452
399
|
if level < 0:
|
|
@@ -454,34 +401,29 @@ class Record():
|
|
|
454
401
|
return
|
|
455
402
|
|
|
456
403
|
for key in set(variables.keys()) - self._last_vars:
|
|
457
|
-
if key not in self.
|
|
458
|
-
self.
|
|
404
|
+
if key not in self.axis:
|
|
405
|
+
self.axis[key] = tuple(range(level + 1))
|
|
459
406
|
|
|
460
407
|
self._last_vars = set(variables.keys())
|
|
461
|
-
self._keys.update(variables.keys())
|
|
462
408
|
|
|
463
409
|
if level >= len(self._pos):
|
|
464
410
|
l = level + 1 - len(self._pos)
|
|
465
|
-
self._index.extend(([0] * (l - 1)) + [step])
|
|
466
411
|
self._pos.extend(([0] * (l - 1)) + [position])
|
|
467
412
|
pos = tuple(self._pos)
|
|
468
413
|
elif level == len(self._pos) - 1:
|
|
469
|
-
self._index[-1] = step
|
|
470
414
|
self._pos[-1] = position
|
|
471
415
|
pos = tuple(self._pos)
|
|
472
416
|
else:
|
|
473
|
-
self._index = self._index[:level + 1]
|
|
474
417
|
self._pos = self._pos[:level + 1]
|
|
475
|
-
self._index[-1] = step + 1
|
|
476
418
|
self._pos[-1] = position
|
|
477
419
|
pos = tuple(self._pos)
|
|
478
420
|
self._pos[-1] += 1
|
|
479
421
|
|
|
480
422
|
for key, value in variables.items():
|
|
481
|
-
if self.
|
|
423
|
+
if self.axis[key] == ():
|
|
482
424
|
if key not in self._items:
|
|
483
425
|
self._items[key] = value
|
|
484
|
-
elif level == self.
|
|
426
|
+
elif level == self.axis[key][-1]:
|
|
485
427
|
if key not in self._items:
|
|
486
428
|
if self.is_local_record():
|
|
487
429
|
bufferlist_file = random_path(self.database /
|
|
@@ -493,9 +435,9 @@ class Record():
|
|
|
493
435
|
self._items[key] = BufferList()
|
|
494
436
|
self._items[key].lu = pos
|
|
495
437
|
self._items[key].rd = tuple([i + 1 for i in pos])
|
|
496
|
-
self._items[key].append(pos, value, self.
|
|
438
|
+
self._items[key].append(pos, value, self.axis[key])
|
|
497
439
|
elif isinstance(self._items[key], BufferList):
|
|
498
|
-
self._items[key].append(pos, value, self.
|
|
440
|
+
self._items[key].append(pos, value, self.axis[key])
|
|
499
441
|
|
|
500
442
|
def flush(self):
|
|
501
443
|
if self.is_remote_record() or self.is_cache_record():
|
|
@@ -508,6 +450,40 @@ class Record():
|
|
|
508
450
|
with open(self._file, 'wb') as f:
|
|
509
451
|
dill.dump(self, f)
|
|
510
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
|
+
|
|
511
487
|
def __repr__(self):
|
|
512
488
|
return f"<Record: id={self.id} app={self.description['app']}, keys={self.keys()}>"
|
|
513
489
|
|
|
@@ -515,214 +491,5 @@ class Record():
|
|
|
515
491
|
# return f"""
|
|
516
492
|
# <h3>Record: id={self.id}, app={self.description['app']}</h3>
|
|
517
493
|
# <p>keys={self.keys()}</p>
|
|
518
|
-
# <p>
|
|
494
|
+
# <p>axis={self.axis}</p>
|
|
519
495
|
# """
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
class Request():
|
|
523
|
-
__slots__ = ['sock', 'identity', 'msg', 'method']
|
|
524
|
-
|
|
525
|
-
def __init__(self, sock, identity, msg):
|
|
526
|
-
self.sock = sock
|
|
527
|
-
self.identity = identity
|
|
528
|
-
self.msg = pickle.loads(msg)
|
|
529
|
-
self.method = self.msg.get('method', '')
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
async def reply(req, resp):
|
|
533
|
-
await req.sock.send_multipart([req.identity, pickle.dumps(resp)])
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
def clear_cache():
|
|
537
|
-
if len(record_cache) < 1024:
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
for k, (t, _) in zip(sorted(record_cache.items(), key=lambda x: x[1][0]),
|
|
541
|
-
range(len(record_cache) - 1024)):
|
|
542
|
-
del record_cache[k]
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
def flush_cache():
|
|
546
|
-
for k, (t, r) in record_cache.items():
|
|
547
|
-
r.flush()
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
def get_record(session: Session, id: int, datapath: Path) -> Record:
|
|
551
|
-
if id not in record_cache:
|
|
552
|
-
record_in_db = session.get(RecordInDB, id)
|
|
553
|
-
record_in_db.atime = utcnow()
|
|
554
|
-
path = datapath / 'objects' / record_in_db.file
|
|
555
|
-
with open(path, 'rb') as f:
|
|
556
|
-
record = dill.load(f)
|
|
557
|
-
record._file = path
|
|
558
|
-
else:
|
|
559
|
-
record = record_cache[id][1]
|
|
560
|
-
clear_cache()
|
|
561
|
-
record_cache[id] = time.time(), record
|
|
562
|
-
return record
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
def record_create(session: Session, description: dict, datapath: Path) -> int:
|
|
566
|
-
record = Record(None, datapath, description)
|
|
567
|
-
record_in_db = RecordInDB()
|
|
568
|
-
if 'app' in description:
|
|
569
|
-
record_in_db.app = description['app']
|
|
570
|
-
if 'tags' in description:
|
|
571
|
-
record_in_db.tags = [tag(session, t) for t in description['tags']]
|
|
572
|
-
record_in_db.file = '/'.join(record._file.parts[-4:])
|
|
573
|
-
session.add(record_in_db)
|
|
574
|
-
try:
|
|
575
|
-
session.commit()
|
|
576
|
-
record.id = record_in_db.id
|
|
577
|
-
clear_cache()
|
|
578
|
-
record_cache[record.id] = time.time(), record
|
|
579
|
-
return record.id
|
|
580
|
-
except:
|
|
581
|
-
session.rollback()
|
|
582
|
-
raise
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
def record_append(session: Session, record_id: int, level: int, step: int,
|
|
586
|
-
position: int, variables: dict, datapath: Path):
|
|
587
|
-
record = get_record(session, record_id, datapath)
|
|
588
|
-
record.append(level, step, position, variables)
|
|
589
|
-
try:
|
|
590
|
-
record_in_db = session.get(RecordInDB, record_id)
|
|
591
|
-
record_in_db.mtime = utcnow()
|
|
592
|
-
record_in_db.atime = utcnow()
|
|
593
|
-
session.commit()
|
|
594
|
-
except:
|
|
595
|
-
session.rollback()
|
|
596
|
-
raise
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
@logger.catch
|
|
600
|
-
async def handle(session: Session, request: Request, datapath: Path):
|
|
601
|
-
|
|
602
|
-
msg = request.msg
|
|
603
|
-
|
|
604
|
-
match request.method:
|
|
605
|
-
case 'ping':
|
|
606
|
-
await reply(request, 'pong')
|
|
607
|
-
case 'bufferlist_slice':
|
|
608
|
-
record = get_record(session, msg['record_id'], datapath)
|
|
609
|
-
bufferlist = record.get(msg['key'],
|
|
610
|
-
buffer_to_array=False,
|
|
611
|
-
slice=msg['slice'])
|
|
612
|
-
await reply(request, list(bufferlist.iter()))
|
|
613
|
-
case 'record_create':
|
|
614
|
-
description = dill.loads(msg['description'])
|
|
615
|
-
await reply(request, record_create(session, description, datapath))
|
|
616
|
-
case 'record_append':
|
|
617
|
-
record_append(session, msg['record_id'], msg['level'], msg['step'],
|
|
618
|
-
msg['position'], msg['variables'], datapath)
|
|
619
|
-
case 'record_description':
|
|
620
|
-
record = get_record(session, msg['record_id'], datapath)
|
|
621
|
-
await reply(request, dill.dumps(record.description))
|
|
622
|
-
case 'record_getitem':
|
|
623
|
-
record = get_record(session, msg['record_id'], datapath)
|
|
624
|
-
await reply(request, record.get(msg['key'], buffer_to_array=False))
|
|
625
|
-
case 'record_keys':
|
|
626
|
-
record = get_record(session, msg['record_id'], datapath)
|
|
627
|
-
await reply(request, record.keys())
|
|
628
|
-
case 'record_query':
|
|
629
|
-
total, apps, table = query_record(session,
|
|
630
|
-
offset=msg.get('offset', 0),
|
|
631
|
-
limit=msg.get('limit', 10),
|
|
632
|
-
app=msg.get('app', None),
|
|
633
|
-
tags=msg.get('tags', ()),
|
|
634
|
-
before=msg.get('before', None),
|
|
635
|
-
after=msg.get('after', None))
|
|
636
|
-
await reply(request, (total, apps, table))
|
|
637
|
-
case 'record_get_tags':
|
|
638
|
-
record_in_db = session.get(RecordInDB, msg['record_id'])
|
|
639
|
-
await reply(request, [t.name for t in record_in_db.tags])
|
|
640
|
-
case 'record_remove_tags':
|
|
641
|
-
remove_tags(session, msg['record_id'], msg['tags'])
|
|
642
|
-
case 'record_add_tags':
|
|
643
|
-
update_tags(session, msg['record_id'], msg['tags'], True)
|
|
644
|
-
case 'record_replace_tags':
|
|
645
|
-
update_tags(session, msg['record_id'], msg['tags'], False)
|
|
646
|
-
case _:
|
|
647
|
-
logger.error(f"Unknown method: {msg['method']}")
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
async def _handle(session: Session, request: Request, datapath: Path):
|
|
651
|
-
try:
|
|
652
|
-
await handle(session, request, datapath)
|
|
653
|
-
except:
|
|
654
|
-
await reply(request, 'error')
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
async def serv(port,
|
|
658
|
-
datapath,
|
|
659
|
-
url=None,
|
|
660
|
-
buffer_size=1024 * 1024 * 1024,
|
|
661
|
-
interval=60):
|
|
662
|
-
logger.info('Server starting.')
|
|
663
|
-
async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
|
|
664
|
-
if url is None:
|
|
665
|
-
url = 'sqlite:///' + str(datapath / 'data.db')
|
|
666
|
-
engine = create_engine(url)
|
|
667
|
-
create_tables(engine)
|
|
668
|
-
Session = sessionmaker(engine)
|
|
669
|
-
with Session() as session:
|
|
670
|
-
logger.info('Server started.')
|
|
671
|
-
received = 0
|
|
672
|
-
last_flush_time = time.time()
|
|
673
|
-
while True:
|
|
674
|
-
identity, msg = await sock.recv_multipart()
|
|
675
|
-
received += len(msg)
|
|
676
|
-
req = Request(sock, identity, msg)
|
|
677
|
-
asyncio.create_task(_handle(session, req, datapath))
|
|
678
|
-
if received > buffer_size or time.time(
|
|
679
|
-
) - last_flush_time > interval:
|
|
680
|
-
flush_cache()
|
|
681
|
-
received = 0
|
|
682
|
-
last_flush_time = time.time()
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
async def watch(port, datapath, url=None, timeout=1, buffer=1024, interval=60):
|
|
686
|
-
with ZMQContextManager(zmq.DEALER,
|
|
687
|
-
connect=f"tcp://127.0.0.1:{port}") as sock:
|
|
688
|
-
sock.setsockopt(zmq.LINGER, 0)
|
|
689
|
-
while True:
|
|
690
|
-
try:
|
|
691
|
-
sock.send_pyobj({"method": "ping"})
|
|
692
|
-
if sock.poll(int(1000 * timeout)):
|
|
693
|
-
sock.recv()
|
|
694
|
-
else:
|
|
695
|
-
raise asyncio.TimeoutError()
|
|
696
|
-
except (zmq.error.ZMQError, asyncio.TimeoutError):
|
|
697
|
-
return asyncio.create_task(
|
|
698
|
-
serv(port, datapath, url, buffer * 1024 * 1024, interval))
|
|
699
|
-
await asyncio.sleep(timeout)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
async def main(port, datapath, url, timeout=1, buffer=1024, interval=60):
|
|
703
|
-
task = await watch(port=port,
|
|
704
|
-
datapath=datapath,
|
|
705
|
-
url=url,
|
|
706
|
-
timeout=timeout,
|
|
707
|
-
buffer=buffer,
|
|
708
|
-
interval=interval)
|
|
709
|
-
await task
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
@click.command()
|
|
713
|
-
@click.option('--port',
|
|
714
|
-
default=os.getenv('QULAB_RECORD_PORT', 6789),
|
|
715
|
-
help='Port of the server.')
|
|
716
|
-
@click.option('--datapath', default=datapath, help='Path of the data.')
|
|
717
|
-
@click.option('--url', default=None, help='URL of the database.')
|
|
718
|
-
@click.option('--timeout', default=1, help='Timeout of ping.')
|
|
719
|
-
@click.option('--buffer', default=1024, help='Buffer size (MB).')
|
|
720
|
-
@click.option('--interval',
|
|
721
|
-
default=60,
|
|
722
|
-
help='Interval of flush cache, in unit of second.')
|
|
723
|
-
def record(port, datapath, url, timeout, buffer, interval):
|
|
724
|
-
asyncio.run(main(port, Path(datapath), url, timeout, buffer, interval))
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if __name__ == "__main__":
|
|
728
|
-
record()
|