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.
Files changed (96) hide show
  1. {qulab-2.1.0 → qulab-2.1.1}/PKG-INFO +1 -1
  2. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/PKG-INFO +1 -1
  3. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/SOURCES.txt +3 -1
  4. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/__init__.py +1 -1
  5. qulab-2.1.0/qulab/scan/query_record.py → qulab-2.1.1/qulab/scan/query.py +8 -4
  6. qulab-2.1.0/qulab/scan/recorder.py → qulab-2.1.1/qulab/scan/record.py +20 -300
  7. qulab-2.1.1/qulab/scan/recorder.py +241 -0
  8. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/scan.py +94 -132
  9. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/server.py +4 -6
  10. qulab-2.1.1/qulab/scan/space.py +172 -0
  11. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/utils.py +48 -0
  12. qulab-2.1.1/qulab/version.py +1 -0
  13. qulab-2.1.0/qulab/version.py +0 -1
  14. {qulab-2.1.0 → qulab-2.1.1}/LICENSE +0 -0
  15. {qulab-2.1.0 → qulab-2.1.1}/MANIFEST.in +0 -0
  16. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/dependency_links.txt +0 -0
  17. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/entry_points.txt +0 -0
  18. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/requires.txt +0 -0
  19. {qulab-2.1.0 → qulab-2.1.1}/QuLab.egg-info/top_level.txt +0 -0
  20. {qulab-2.1.0 → qulab-2.1.1}/README.md +0 -0
  21. {qulab-2.1.0 → qulab-2.1.1}/pyproject.toml +0 -0
  22. {qulab-2.1.0 → qulab-2.1.1}/qulab/__init__.py +0 -0
  23. {qulab-2.1.0 → qulab-2.1.1}/qulab/__main__.py +0 -0
  24. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/__init__.py +0 -0
  25. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/__main__.py +0 -0
  26. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/config.py +0 -0
  27. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/dataset.py +0 -0
  28. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/event_queue.py +0 -0
  29. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/mainwindow.py +0 -0
  30. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/monitor.py +0 -0
  31. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/ploter.py +0 -0
  32. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/qt_compat.py +0 -0
  33. {qulab-2.1.0 → qulab-2.1.1}/qulab/monitor/toolbar.py +0 -0
  34. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/curd.py +0 -0
  35. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/expression.py +0 -0
  36. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/models.py +0 -0
  37. {qulab-2.1.0 → qulab-2.1.1}/qulab/scan/optimize.py +0 -0
  38. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/__init__.py +0 -0
  39. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/__main__.py +0 -0
  40. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/backend/__init__.py +0 -0
  41. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/backend/redis.py +0 -0
  42. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/base_dataset.py +0 -0
  43. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/chunk.py +0 -0
  44. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/dataset.py +0 -0
  45. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/file.py +0 -0
  46. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/__init__.py +0 -0
  47. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/base.py +0 -0
  48. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/config.py +0 -0
  49. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/file.py +0 -0
  50. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/ipy.py +0 -0
  51. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/models.py +0 -0
  52. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/record.py +0 -0
  53. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/report.py +0 -0
  54. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/models/tag.py +0 -0
  55. {qulab-2.1.0 → qulab-2.1.1}/qulab/storage/storage.py +0 -0
  56. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/__init__.py +0 -0
  57. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/chat.py +0 -0
  58. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/__init__.py +0 -0
  59. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/basedevice.py +0 -0
  60. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/loader.py +0 -0
  61. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/device/utils.py +0 -0
  62. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/drivers/FakeInstrument.py +0 -0
  63. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/drivers/__init__.py +0 -0
  64. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/ipy_events.py +0 -0
  65. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/__init__.py +0 -0
  66. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/bencoder.py +0 -0
  67. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/cli.py +0 -0
  68. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/dhcp.py +0 -0
  69. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/dhcpd.py +0 -0
  70. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/kad.py +0 -0
  71. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/kcp.py +0 -0
  72. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/net/nginx.py +0 -0
  73. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/progress.py +0 -0
  74. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/__init__.py +0 -0
  75. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/client.py +0 -0
  76. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/exceptions.py +0 -0
  77. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/msgpack.py +0 -0
  78. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/msgpack.pyi +0 -0
  79. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/rpc.py +0 -0
  80. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/serialize.py +0 -0
  81. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/server.py +0 -0
  82. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/socket.py +0 -0
  83. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/utils.py +0 -0
  84. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/worker.py +0 -0
  85. {qulab-2.1.0 → qulab-2.1.1}/qulab/sys/rpc/zmq_socket.py +0 -0
  86. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/__init__.py +0 -0
  87. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/__main__.py +0 -0
  88. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/_autoplot.py +0 -0
  89. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/plot_layout.py +0 -0
  90. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/plot_seq.py +0 -0
  91. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/qdat.py +0 -0
  92. {qulab-2.1.0 → qulab-2.1.1}/qulab/visualization/widgets.py +0 -0
  93. {qulab-2.1.0 → qulab-2.1.1}/setup.cfg +0 -0
  94. {qulab-2.1.0 → qulab-2.1.1}/setup.py +0 -0
  95. {qulab-2.1.0 → qulab-2.1.1}/src/qulab.h +0 -0
  96. {qulab-2.1.0 → qulab-2.1.1}/tests/test_scan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.1.0
3
+ Version: 2.1.1
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -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/query_record.py
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
@@ -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, lookup, lookup_list
3
3
  from .scan import Scan
@@ -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 .recorder import Record
12
+ from .record import Record
13
+ from .scan import default_server
13
14
 
14
15
 
15
- def get_record(id, database='tcp://127.0.0.1:6789'):
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
- return Record(id, database, d)
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='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:')
@@ -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 .curd import query_record, remove_tags, tag, update_tags
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._index = state['_index']
373
- self._pos = state['_pos']
374
- self._last_vars = state['_last_vars']
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
- return self.get(key, buffer_to_array=True)
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._keys)
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.dims:
458
- self.dims[key] = tuple(range(level + 1))
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.dims[key] == ():
410
+ if self.axis[key] == ():
482
411
  if key not in self._items:
483
412
  self._items[key] = value
484
- elif level == self.dims[key][-1]:
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.dims[key])
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.dims[key])
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>dims={self.dims}</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()