QuLab 2.1.0__tar.gz → 2.1.1__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.1}/PKG-INFO +1 -1
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/PKG-INFO +1 -1
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/SOURCES.txt +3 -1
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/__init__.py +1 -1
- qulab-2.1.0/qulab/scan/query_record.py → qulab-2.1.1/qulab/scan/query.py +8 -4
- qulab-2.1.0/qulab/scan/recorder.py → qulab-2.1.1/qulab/scan/record.py +20 -300
- qulab-2.1.1/qulab/scan/recorder.py +241 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/scan.py +94 -132
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/server.py +4 -6
- qulab-2.1.1/qulab/scan/space.py +172 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/utils.py +48 -0
- qulab-2.1.1/qulab/version.py +1 -0
- qulab-2.1.0/qulab/version.py +0 -1
- {qulab-2.1.0 → qulab-2.1.1}/LICENSE +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/MANIFEST.in +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/dependency_links.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/entry_points.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/requires.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/top_level.txt +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/README.md +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/pyproject.toml +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/config.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/event_queue.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/mainwindow.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/monitor.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/ploter.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/qt_compat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/toolbar.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/curd.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/expression.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/models.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/optimize.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/backend/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/backend/redis.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/base_dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/chunk.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/dataset.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/file.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/base.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/config.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/file.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/ipy.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/models.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/record.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/report.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/tag.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/storage.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/chat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/basedevice.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/loader.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/utils.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/drivers/FakeInstrument.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/drivers/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/ipy_events.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/bencoder.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/cli.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/dhcp.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/dhcpd.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/kad.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/kcp.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/nginx.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/progress.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/client.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/exceptions.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/msgpack.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/msgpack.pyi +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/rpc.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/serialize.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/server.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/socket.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/utils.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/worker.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/zmq_socket.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/__init__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/__main__.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/_autoplot.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/plot_layout.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/plot_seq.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/qdat.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/widgets.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/setup.cfg +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/setup.py +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/src/qulab.h +0 -0
- {qulab-2.1.0 → qulab-2.1.1}/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
|
|
@@ -9,10 +9,11 @@ from IPython.display import display
|
|
|
9
9
|
|
|
10
10
|
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
|
11
11
|
|
|
12
|
-
from .
|
|
12
|
+
from .record import Record
|
|
13
|
+
from .scan import default_server
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def get_record(id, database=
|
|
16
|
+
def get_record(id, database=default_server) -> Record:
|
|
16
17
|
if isinstance(database, str) and database.startswith('tcp://'):
|
|
17
18
|
with ZMQContextManager(zmq.DEALER, connect=database) as socket:
|
|
18
19
|
socket.send_pyobj({
|
|
@@ -20,7 +21,10 @@ def get_record(id, database='tcp://127.0.0.1:6789'):
|
|
|
20
21
|
'record_id': id
|
|
21
22
|
})
|
|
22
23
|
d = dill.loads(socket.recv_pyobj())
|
|
23
|
-
|
|
24
|
+
d.id = id
|
|
25
|
+
d.database = database
|
|
26
|
+
d._file = None
|
|
27
|
+
return d
|
|
24
28
|
else:
|
|
25
29
|
from .models import Record as RecordInDB
|
|
26
30
|
from .models import create_engine, sessionmaker
|
|
@@ -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,42 +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
|
-
from collections import defaultdict
|
|
9
4
|
from pathlib import Path
|
|
10
5
|
from threading import Lock
|
|
11
6
|
from types import EllipsisType
|
|
12
7
|
|
|
13
|
-
import click
|
|
14
8
|
import dill
|
|
15
9
|
import numpy as np
|
|
16
10
|
import zmq
|
|
17
|
-
from loguru import logger
|
|
18
11
|
|
|
19
12
|
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
|
20
13
|
|
|
21
|
-
from .
|
|
22
|
-
from .models import Record as RecordInDB
|
|
23
|
-
from .models import Session, create_engine, create_tables, sessionmaker, utcnow
|
|
14
|
+
from .space import OptimizeSpace, Space
|
|
24
15
|
|
|
25
16
|
_notgiven = object()
|
|
26
17
|
|
|
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 = {}
|
|
39
|
-
|
|
40
18
|
|
|
41
19
|
def random_path(base):
|
|
42
20
|
while True:
|
|
@@ -297,51 +275,13 @@ class BufferList():
|
|
|
297
275
|
class Record():
|
|
298
276
|
|
|
299
277
|
def __init__(self, id, database, description=None):
|
|
300
|
-
from .scan import OptimizeSpace
|
|
301
|
-
|
|
302
278
|
self.id = id
|
|
303
279
|
self.database = database
|
|
304
280
|
self.description = description
|
|
305
|
-
self._keys = set()
|
|
306
281
|
self._items = {}
|
|
307
|
-
self._index = []
|
|
308
282
|
self._pos = []
|
|
309
283
|
self._last_vars = set()
|
|
310
284
|
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
285
|
|
|
346
286
|
if self.is_local_record():
|
|
347
287
|
self.database = Path(self.database)
|
|
@@ -351,32 +291,23 @@ class Record():
|
|
|
351
291
|
def __getstate__(self) -> dict:
|
|
352
292
|
return {
|
|
353
293
|
'id': self.id,
|
|
354
|
-
'database': self.database,
|
|
355
294
|
'description': self.description,
|
|
356
|
-
'_keys': self._keys,
|
|
357
295
|
'_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
296
|
}
|
|
365
297
|
|
|
366
298
|
def __setstate__(self, state: dict):
|
|
367
299
|
self.id = state['id']
|
|
368
|
-
self.database = state['database']
|
|
369
300
|
self.description = state['description']
|
|
370
|
-
self._keys = state['_keys']
|
|
371
301
|
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']
|
|
302
|
+
self._pos = []
|
|
303
|
+
self._last_vars = set()
|
|
304
|
+
self.database = None
|
|
378
305
|
self._file = None
|
|
379
306
|
|
|
307
|
+
@property
|
|
308
|
+
def axis(self):
|
|
309
|
+
return self.description.get('axis', {})
|
|
310
|
+
|
|
380
311
|
def is_local_record(self):
|
|
381
312
|
return not self.is_cache_record() and not self.is_remote_record()
|
|
382
313
|
|
|
@@ -391,7 +322,10 @@ class Record():
|
|
|
391
322
|
self.flush()
|
|
392
323
|
|
|
393
324
|
def __getitem__(self, key):
|
|
394
|
-
|
|
325
|
+
ret = self.get(key, buffer_to_array=True)
|
|
326
|
+
if isinstance(ret, Space):
|
|
327
|
+
ret = ret.toarray()
|
|
328
|
+
return ret
|
|
395
329
|
|
|
396
330
|
def get(self, key, default=_notgiven, buffer_to_array=False, slice=None):
|
|
397
331
|
if self.is_remote_record():
|
|
@@ -446,7 +380,7 @@ class Record():
|
|
|
446
380
|
})
|
|
447
381
|
return socket.recv_pyobj()
|
|
448
382
|
else:
|
|
449
|
-
return list(self.
|
|
383
|
+
return list(self._items.keys())
|
|
450
384
|
|
|
451
385
|
def append(self, level, step, position, variables):
|
|
452
386
|
if level < 0:
|
|
@@ -454,34 +388,29 @@ class Record():
|
|
|
454
388
|
return
|
|
455
389
|
|
|
456
390
|
for key in set(variables.keys()) - self._last_vars:
|
|
457
|
-
if key not in self.
|
|
458
|
-
self.
|
|
391
|
+
if key not in self.axis:
|
|
392
|
+
self.axis[key] = tuple(range(level + 1))
|
|
459
393
|
|
|
460
394
|
self._last_vars = set(variables.keys())
|
|
461
|
-
self._keys.update(variables.keys())
|
|
462
395
|
|
|
463
396
|
if level >= len(self._pos):
|
|
464
397
|
l = level + 1 - len(self._pos)
|
|
465
|
-
self._index.extend(([0] * (l - 1)) + [step])
|
|
466
398
|
self._pos.extend(([0] * (l - 1)) + [position])
|
|
467
399
|
pos = tuple(self._pos)
|
|
468
400
|
elif level == len(self._pos) - 1:
|
|
469
|
-
self._index[-1] = step
|
|
470
401
|
self._pos[-1] = position
|
|
471
402
|
pos = tuple(self._pos)
|
|
472
403
|
else:
|
|
473
|
-
self._index = self._index[:level + 1]
|
|
474
404
|
self._pos = self._pos[:level + 1]
|
|
475
|
-
self._index[-1] = step + 1
|
|
476
405
|
self._pos[-1] = position
|
|
477
406
|
pos = tuple(self._pos)
|
|
478
407
|
self._pos[-1] += 1
|
|
479
408
|
|
|
480
409
|
for key, value in variables.items():
|
|
481
|
-
if self.
|
|
410
|
+
if self.axis[key] == ():
|
|
482
411
|
if key not in self._items:
|
|
483
412
|
self._items[key] = value
|
|
484
|
-
elif level == self.
|
|
413
|
+
elif level == self.axis[key][-1]:
|
|
485
414
|
if key not in self._items:
|
|
486
415
|
if self.is_local_record():
|
|
487
416
|
bufferlist_file = random_path(self.database /
|
|
@@ -493,9 +422,9 @@ class Record():
|
|
|
493
422
|
self._items[key] = BufferList()
|
|
494
423
|
self._items[key].lu = pos
|
|
495
424
|
self._items[key].rd = tuple([i + 1 for i in pos])
|
|
496
|
-
self._items[key].append(pos, value, self.
|
|
425
|
+
self._items[key].append(pos, value, self.axis[key])
|
|
497
426
|
elif isinstance(self._items[key], BufferList):
|
|
498
|
-
self._items[key].append(pos, value, self.
|
|
427
|
+
self._items[key].append(pos, value, self.axis[key])
|
|
499
428
|
|
|
500
429
|
def flush(self):
|
|
501
430
|
if self.is_remote_record() or self.is_cache_record():
|
|
@@ -515,214 +444,5 @@ class Record():
|
|
|
515
444
|
# return f"""
|
|
516
445
|
# <h3>Record: id={self.id}, app={self.description['app']}</h3>
|
|
517
446
|
# <p>keys={self.keys()}</p>
|
|
518
|
-
# <p>
|
|
447
|
+
# <p>axis={self.axis}</p>
|
|
519
448
|
# """
|
|
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()
|