QuLab 2.3.2__cp312-cp312-win_amd64.whl → 2.3.4__cp312-cp312-win_amd64.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.
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/METADATA +4 -1
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/RECORD +13 -13
- qulab/fun.cp312-win_amd64.pyd +0 -0
- qulab/scan/expression.py +29 -1
- qulab/scan/record.py +3 -1
- qulab/scan/scan.py +126 -43
- qulab/scan/server.py +176 -35
- qulab/scan/utils.py +40 -0
- qulab/version.py +1 -1
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/LICENSE +0 -0
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/WHEEL +0 -0
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/entry_points.txt +0 -0
- {QuLab-2.3.2.dist-info → QuLab-2.3.4.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: QuLab
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: contral instruments and manage data
|
|
5
5
|
Author-email: feihoo87 <feihoo87@gmail.com>
|
|
6
6
|
Maintainer-email: feihoo87 <feihoo87@gmail.com>
|
|
@@ -38,6 +38,9 @@ Requires-Dist: ply >=3.11
|
|
|
38
38
|
Requires-Dist: pyzmq >=25.1.0
|
|
39
39
|
Requires-Dist: scipy >=1.0.0
|
|
40
40
|
Requires-Dist: watchdog >=4.0.0
|
|
41
|
+
Provides-Extra: full
|
|
42
|
+
Requires-Dist: SQLAlchemy >=2.0.19 ; extra == 'full'
|
|
43
|
+
Requires-Dist: uvloop >=0.19.0 ; extra == 'full'
|
|
41
44
|
|
|
42
45
|
# QuLab
|
|
43
46
|
[](https://travis-ci.org/feihoo87/QuLab)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
qulab/__init__.py,sha256=vkFybY8YSsQilYdThPRD83-btPAR41sy_WCXiM-6mME,141
|
|
2
2
|
qulab/__main__.py,sha256=V7iokU7awstgjCeiF_hoOdFyrqJwC_4QetiLe7cWvOQ,454
|
|
3
|
-
qulab/fun.cp312-win_amd64.pyd,sha256=
|
|
4
|
-
qulab/version.py,sha256=
|
|
3
|
+
qulab/fun.cp312-win_amd64.pyd,sha256=cVkHz7_RIsRL4Ij6Ku9j5XNMr2PPk6iNBG1kxgNB528,32256
|
|
4
|
+
qulab/version.py,sha256=MtmOJMh3yHY9aTdRsZDgbpPfbzm9xdycdi9jjMKzYjM,21
|
|
5
5
|
qulab/monitor/__init__.py,sha256=xEVDkJF8issrsDeLqQmDsvtRmrf-UiViFcGTWuzdlFU,43
|
|
6
6
|
qulab/monitor/__main__.py,sha256=k2H1H5Zf9LLXTDLISJkbikLH-z0f1e5i5i6wXXYPOrE,105
|
|
7
7
|
qulab/monitor/config.py,sha256=y_5StMkdrbZO1ziyKBrvIkB7Jclp9RCPK1QbsOhCxnY,785
|
|
@@ -14,15 +14,15 @@ qulab/monitor/qt_compat.py,sha256=Eq7zlA4_XstB92NhtAqebtWU_Btw4lcwFO30YxZ-TPE,80
|
|
|
14
14
|
qulab/monitor/toolbar.py,sha256=HxqG6ywKFyQJM2Q1s7SnhuzjbyeROczAZKwxztD1WJ8,8213
|
|
15
15
|
qulab/scan/__init__.py,sha256=RR_0NQcr8Mi3vpWdypydbijQ1rXA0D3DEidQ7xjNslM,133
|
|
16
16
|
qulab/scan/curd.py,sha256=yaTglGiS6mlk0GqDHi_w8T02XGBMvDZtXSdML7zDywk,7117
|
|
17
|
-
qulab/scan/expression.py,sha256=
|
|
17
|
+
qulab/scan/expression.py,sha256=l7TYBmcJIo0M5GJm1TtrkrMFj5mCmrfLOMCILKbOivU,20712
|
|
18
18
|
qulab/scan/models.py,sha256=ZvXkJEt5Yz3Sjx0JKzYka-q2Uo-w_iVzHgH8A6DbjF0,18236
|
|
19
19
|
qulab/scan/optimize.py,sha256=ACfGSfrFpPgmZo_P5kD4mquahXImYipdP7E86dtFQO8,2635
|
|
20
20
|
qulab/scan/query.py,sha256=RM8bG4Tcx_PaNk8tv9HdlTZ1dGuuSr3sZVkYVq2BtfQ,12183
|
|
21
|
-
qulab/scan/record.py,sha256=
|
|
22
|
-
qulab/scan/scan.py,sha256=
|
|
23
|
-
qulab/scan/server.py,sha256=
|
|
21
|
+
qulab/scan/record.py,sha256=MVmxhIzwmOju7eWxJEWsqJZlVgrDeRXGMfNvXImj7Ms,21883
|
|
22
|
+
qulab/scan/scan.py,sha256=PDU0oZkwCntucOM4aNNgY22t-Ng8XChmFrhTqPqxDX8,41814
|
|
23
|
+
qulab/scan/server.py,sha256=ahdVpTKm8P2qB7E9HKM8b95HF89YlCJ5WWLmdJhMfgw,20479
|
|
24
24
|
qulab/scan/space.py,sha256=S-jHaXXf12FzjajOhPsZ-mkpGzAxrFAI3tRuQrvTBKg,6274
|
|
25
|
-
qulab/scan/utils.py,sha256=
|
|
25
|
+
qulab/scan/utils.py,sha256=j5ai6E-pK2tYEPYlL1FpgHG7q2DL1IVAa-hPPgz5SlQ,4757
|
|
26
26
|
qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
qulab/storage/__main__.py,sha256=6-EjN0waX1yfcMPJXqpIr9UlrIEsSCFApm5G-ZeaPMQ,1742
|
|
28
28
|
qulab/storage/base_dataset.py,sha256=28y3-OZrqJ52p5sbirEpUgjb7hqwLLpd38KU9DCkD24,12217
|
|
@@ -78,9 +78,9 @@ qulab/visualization/plot_layout.py,sha256=yAnMONOms7_szCdng-8wPpUMPis5UnbaNNzV4K
|
|
|
78
78
|
qulab/visualization/plot_seq.py,sha256=h9D0Yl_yO64IwlvBgzMu9EBKr9gg6y8QE55gu2PfTns,2783
|
|
79
79
|
qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
|
|
80
80
|
qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
|
|
81
|
-
QuLab-2.3.
|
|
82
|
-
QuLab-2.3.
|
|
83
|
-
QuLab-2.3.
|
|
84
|
-
QuLab-2.3.
|
|
85
|
-
QuLab-2.3.
|
|
86
|
-
QuLab-2.3.
|
|
81
|
+
QuLab-2.3.4.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
|
|
82
|
+
QuLab-2.3.4.dist-info/METADATA,sha256=t-ZisaizQKo8edK0p_-vQD8ljsuXo9cl2dg5wE-sFsU,3735
|
|
83
|
+
QuLab-2.3.4.dist-info/WHEEL,sha256=h3VxxLkZstcnkT86k1ujrti5ToMTN-RbBnzIRuTWCMs,101
|
|
84
|
+
QuLab-2.3.4.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
|
|
85
|
+
QuLab-2.3.4.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
|
86
|
+
QuLab-2.3.4.dist-info/RECORD,,
|
qulab/fun.cp312-win_amd64.pyd
CHANGED
|
Binary file
|
qulab/scan/expression.py
CHANGED
|
@@ -419,7 +419,7 @@ class Expression():
|
|
|
419
419
|
def is_const(self, env) -> bool:
|
|
420
420
|
return False
|
|
421
421
|
|
|
422
|
-
def value(self, env):
|
|
422
|
+
def value(self, env=_default_env):
|
|
423
423
|
if self.changed(env):
|
|
424
424
|
self.cache = self.eval(env)
|
|
425
425
|
return self.cache
|
|
@@ -612,3 +612,31 @@ class Symbol(Expression):
|
|
|
612
612
|
|
|
613
613
|
def __repr__(self) -> str:
|
|
614
614
|
return self.name
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
sin = Symbol('sin')
|
|
618
|
+
cos = Symbol('cos')
|
|
619
|
+
tan = Symbol('tan')
|
|
620
|
+
pi = Symbol('pi')
|
|
621
|
+
e = Symbol('e')
|
|
622
|
+
log = Symbol('log')
|
|
623
|
+
log2 = Symbol('log2')
|
|
624
|
+
log10 = Symbol('log10')
|
|
625
|
+
exp = Symbol('exp')
|
|
626
|
+
sqrt = Symbol('sqrt')
|
|
627
|
+
abs = Symbol('abs')
|
|
628
|
+
sinh = Symbol('sinh')
|
|
629
|
+
cosh = Symbol('cosh')
|
|
630
|
+
tanh = Symbol('tanh')
|
|
631
|
+
arcsin = Symbol('arcsin')
|
|
632
|
+
arccos = Symbol('arccos')
|
|
633
|
+
arctan = Symbol('arctan')
|
|
634
|
+
arctan2 = Symbol('arctan2')
|
|
635
|
+
arcsinh = Symbol('arcsinh')
|
|
636
|
+
arccosh = Symbol('arccosh')
|
|
637
|
+
arctanh = Symbol('arctanh')
|
|
638
|
+
sinc = Symbol('sinc')
|
|
639
|
+
sign = Symbol('sign')
|
|
640
|
+
heaviside = Symbol('heaviside')
|
|
641
|
+
erf = Symbol('erf')
|
|
642
|
+
erfc = Symbol('erfc')
|
qulab/scan/record.py
CHANGED
|
@@ -474,7 +474,9 @@ class Record():
|
|
|
474
474
|
'method': 'record_keys',
|
|
475
475
|
'record_id': self.id
|
|
476
476
|
})
|
|
477
|
-
|
|
477
|
+
keys = socket.recv_pyobj()
|
|
478
|
+
self._items = {key: None for key in keys}
|
|
479
|
+
return keys
|
|
478
480
|
else:
|
|
479
481
|
return list(self._items.keys())
|
|
480
482
|
|
qulab/scan/scan.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
2
3
|
import copy
|
|
3
4
|
import inspect
|
|
4
5
|
import itertools
|
|
@@ -25,7 +26,7 @@ from .optimize import NgOptimizer
|
|
|
25
26
|
from .record import Record
|
|
26
27
|
from .server import default_record_port
|
|
27
28
|
from .space import Optimizer, OptimizeSpace, Space
|
|
28
|
-
from .utils import async_zip, call_function, dump_globals
|
|
29
|
+
from .utils import async_zip, call_function, dump_dict, dump_globals
|
|
29
30
|
|
|
30
31
|
try:
|
|
31
32
|
from tqdm.notebook import tqdm
|
|
@@ -263,6 +264,7 @@ class Scan():
|
|
|
263
264
|
self._current_level = 0
|
|
264
265
|
self._variables = {}
|
|
265
266
|
self._main_task = None
|
|
267
|
+
self._background_tasks = ()
|
|
266
268
|
self._sock = None
|
|
267
269
|
self._sem = asyncio.Semaphore(max_promise + 1)
|
|
268
270
|
self._bar: dict[int, tqdm] = {}
|
|
@@ -286,6 +288,7 @@ class Scan():
|
|
|
286
288
|
del state['record']
|
|
287
289
|
del state['_sock']
|
|
288
290
|
del state['_main_task']
|
|
291
|
+
del state['_background_tasks']
|
|
289
292
|
del state['_bar']
|
|
290
293
|
del state['_msg_queue']
|
|
291
294
|
del state['_prm_queue']
|
|
@@ -298,6 +301,7 @@ class Scan():
|
|
|
298
301
|
self.record = None
|
|
299
302
|
self._sock = None
|
|
300
303
|
self._main_task = None
|
|
304
|
+
self._background_tasks = ()
|
|
301
305
|
self._bar = {}
|
|
302
306
|
self._prm_queue = asyncio.Queue()
|
|
303
307
|
self._msg_queue = asyncio.Queue(self._max_message)
|
|
@@ -331,6 +335,9 @@ class Scan():
|
|
|
331
335
|
await _unpack(key, variables)
|
|
332
336
|
elif inspect.isawaitable(value) and not self.hiden(key):
|
|
333
337
|
variables[key] = await value
|
|
338
|
+
|
|
339
|
+
if self.record is None:
|
|
340
|
+
self.record = await self.create_record()
|
|
334
341
|
if self._sock is not None:
|
|
335
342
|
await self._sock.send_pyobj({
|
|
336
343
|
'task': self.id,
|
|
@@ -374,20 +381,37 @@ class Scan():
|
|
|
374
381
|
return True
|
|
375
382
|
|
|
376
383
|
async def create_record(self):
|
|
377
|
-
if self._sock is
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
'
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
if self._sock is None:
|
|
385
|
+
return Record(None, self.description['database'], self.description)
|
|
386
|
+
|
|
387
|
+
if self.config:
|
|
388
|
+
self.description['config'] = await create_config(
|
|
389
|
+
self.config, self.description['database'], self._sock)
|
|
390
|
+
if current_notebook() is None:
|
|
391
|
+
await create_notebook('untitle', self.description['database'],
|
|
392
|
+
self._sock)
|
|
393
|
+
cell_id = await save_input_cells(current_notebook(),
|
|
394
|
+
self.description['entry']['scripts'],
|
|
395
|
+
self.description['database'],
|
|
396
|
+
self._sock)
|
|
397
|
+
self.description['entry']['scripts'] = cell_id
|
|
398
|
+
|
|
399
|
+
await self._sock.send_pyobj({
|
|
400
|
+
'task':
|
|
401
|
+
self.id,
|
|
402
|
+
'method':
|
|
403
|
+
'record_create',
|
|
404
|
+
'description':
|
|
405
|
+
dump_dict(self.description,
|
|
406
|
+
keys=[
|
|
407
|
+
'intrinsic_loops', 'app', 'tags', 'loops',
|
|
408
|
+
'independent_variables', 'axis', 'config', 'entry'
|
|
409
|
+
])
|
|
410
|
+
})
|
|
386
411
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return Record(None, self.description['database'], self.description)
|
|
412
|
+
record_id = await self._sock.recv_pyobj()
|
|
413
|
+
return Record(record_id, self.description['database'],
|
|
414
|
+
self.description)
|
|
391
415
|
|
|
392
416
|
def get(self, name: str):
|
|
393
417
|
if name in self.description['consts']:
|
|
@@ -481,7 +505,7 @@ class Scan():
|
|
|
481
505
|
elif isinstance(space, OptimizeSpace):
|
|
482
506
|
space.name = name
|
|
483
507
|
space.optimizer.dimensions[name] = space.space
|
|
484
|
-
if space.suggestion:
|
|
508
|
+
if space.suggestion is not None:
|
|
485
509
|
space.optimizer.suggestion[name] = space.suggestion
|
|
486
510
|
self._add_search_space(name, space.optimizer.level, space)
|
|
487
511
|
self.add_depends(space.optimizer.name, [name])
|
|
@@ -582,8 +606,34 @@ class Scan():
|
|
|
582
606
|
await task
|
|
583
607
|
self._msg_queue.task_done()
|
|
584
608
|
|
|
609
|
+
@contextlib.asynccontextmanager
|
|
610
|
+
async def _send_msg_and_update_bar(self):
|
|
611
|
+
send_msg_task = asyncio.create_task(self._send_msg())
|
|
612
|
+
update_progress_task = asyncio.create_task(self._update_progress())
|
|
613
|
+
try:
|
|
614
|
+
yield (send_msg_task, update_progress_task)
|
|
615
|
+
finally:
|
|
616
|
+
update_progress_task.cancel()
|
|
617
|
+
send_msg_task.cancel()
|
|
618
|
+
while True:
|
|
619
|
+
try:
|
|
620
|
+
task = self._prm_queue.get_nowait()
|
|
621
|
+
except:
|
|
622
|
+
break
|
|
623
|
+
try:
|
|
624
|
+
task.cancel()
|
|
625
|
+
except:
|
|
626
|
+
pass
|
|
627
|
+
|
|
628
|
+
async def _check_background_tasks(self):
|
|
629
|
+
for task in self._background_tasks:
|
|
630
|
+
if task.done():
|
|
631
|
+
await task
|
|
632
|
+
|
|
585
633
|
async def run(self):
|
|
586
634
|
assymbly(self.description)
|
|
635
|
+
self._background_tasks = ()
|
|
636
|
+
|
|
587
637
|
if isinstance(
|
|
588
638
|
self.description['database'],
|
|
589
639
|
str) and self.description['database'].startswith("tcp://"):
|
|
@@ -591,27 +641,17 @@ class Scan():
|
|
|
591
641
|
connect=self.description['database'],
|
|
592
642
|
socket=self._sock) as socket:
|
|
593
643
|
self._sock = socket
|
|
594
|
-
|
|
595
|
-
self.
|
|
596
|
-
|
|
597
|
-
if current_notebook() is None:
|
|
598
|
-
await create_notebook('untitle',
|
|
599
|
-
self.description['database'],
|
|
600
|
-
self._sock)
|
|
601
|
-
cell_id = await save_input_cells(
|
|
602
|
-
current_notebook(), self.description['entry']['scripts'],
|
|
603
|
-
self.description['database'], self._sock)
|
|
604
|
-
self.description['entry']['scripts'] = cell_id
|
|
605
|
-
await self._run()
|
|
644
|
+
async with self._send_msg_and_update_bar() as background_tasks:
|
|
645
|
+
self._background_tasks = background_tasks
|
|
646
|
+
await self._run()
|
|
606
647
|
else:
|
|
607
648
|
if self.config:
|
|
608
649
|
self.description['config'] = copy.deepcopy(self.config)
|
|
609
|
-
|
|
650
|
+
async with self._send_msg_and_update_bar() as background_tasks:
|
|
651
|
+
self._background_tasks = background_tasks
|
|
652
|
+
await self._run()
|
|
610
653
|
|
|
611
654
|
async def _run(self):
|
|
612
|
-
send_msg_task = asyncio.create_task(self._send_msg())
|
|
613
|
-
update_progress_task = asyncio.create_task(self._update_progress())
|
|
614
|
-
|
|
615
655
|
self._variables = {'self': self, 'config': self.config}
|
|
616
656
|
|
|
617
657
|
consts = {}
|
|
@@ -633,12 +673,11 @@ class Scan():
|
|
|
633
673
|
self.description['functions'], self.variables)
|
|
634
674
|
await update_variables(self.variables, updates,
|
|
635
675
|
self.description['setters'])
|
|
636
|
-
|
|
637
|
-
self.record = await self.create_record()
|
|
676
|
+
await self._check_background_tasks()
|
|
638
677
|
await self.work()
|
|
639
678
|
for level, bar in self._bar.items():
|
|
640
679
|
bar.close()
|
|
641
|
-
|
|
680
|
+
await self._check_background_tasks()
|
|
642
681
|
if self._single_step:
|
|
643
682
|
self.variables.update(await call_many_functions(
|
|
644
683
|
self.description['order'].get(-1, []),
|
|
@@ -646,11 +685,9 @@ class Scan():
|
|
|
646
685
|
|
|
647
686
|
await self.emit(0, 0, 0, self.variables)
|
|
648
687
|
await self.emit(-1, 0, 0, {})
|
|
649
|
-
|
|
688
|
+
await self._check_background_tasks()
|
|
650
689
|
await self._prm_queue.join()
|
|
651
|
-
update_progress_task.cancel()
|
|
652
690
|
await self._msg_queue.join()
|
|
653
|
-
send_msg_task.cancel()
|
|
654
691
|
return self.variables
|
|
655
692
|
|
|
656
693
|
async def done(self):
|
|
@@ -712,6 +749,7 @@ class Scan():
|
|
|
712
749
|
| {'config': self._synchronize_config},
|
|
713
750
|
self.description['optimizers'], self.description['setters'],
|
|
714
751
|
self.description['getters']):
|
|
752
|
+
await self._check_background_tasks()
|
|
715
753
|
self._current_level += 1
|
|
716
754
|
if await self._filter(variables, self.current_level - 1):
|
|
717
755
|
yield variables
|
|
@@ -723,11 +761,13 @@ class Scan():
|
|
|
723
761
|
self._current_level -= 1
|
|
724
762
|
self._prm_queue.put_nowait(
|
|
725
763
|
self._update_progress_bar(self.current_level, 1))
|
|
764
|
+
await self._check_background_tasks()
|
|
726
765
|
if self.current_level == 0:
|
|
727
766
|
await self.emit(self.current_level - 1, 0, 0, {})
|
|
728
767
|
for name, value in self.variables.items():
|
|
729
768
|
if inspect.isawaitable(value):
|
|
730
769
|
self.variables[name] = await value
|
|
770
|
+
await self._check_background_tasks()
|
|
731
771
|
await self._prm_queue.join()
|
|
732
772
|
|
|
733
773
|
async def work(self, **kwds):
|
|
@@ -776,10 +816,10 @@ class Scan():
|
|
|
776
816
|
buf = dill.dumps((awaitable, args, kwds))
|
|
777
817
|
task = asyncio.get_running_loop().run_in_executor(
|
|
778
818
|
self._executors, _run_function_in_process, buf)
|
|
779
|
-
self._prm_queue.put_nowait(task)
|
|
780
|
-
return Promise(task)
|
|
781
819
|
except:
|
|
782
820
|
return awaitable(*args, **kwds)
|
|
821
|
+
self._prm_queue.put_nowait(task)
|
|
822
|
+
return Promise(task)
|
|
783
823
|
else:
|
|
784
824
|
return awaitable
|
|
785
825
|
|
|
@@ -788,7 +828,7 @@ class Scan():
|
|
|
788
828
|
return await awaitable
|
|
789
829
|
|
|
790
830
|
|
|
791
|
-
def
|
|
831
|
+
def _get_environment(description):
|
|
792
832
|
import __main__
|
|
793
833
|
from IPython import get_ipython
|
|
794
834
|
|
|
@@ -818,6 +858,10 @@ def assymbly(description):
|
|
|
818
858
|
|
|
819
859
|
description['entry']['env'] = {k: v for k, v in os.environ.items()}
|
|
820
860
|
|
|
861
|
+
return description
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def _mapping_levels(description):
|
|
821
865
|
mapping = {
|
|
822
866
|
label: level
|
|
823
867
|
for level, label in enumerate(
|
|
@@ -850,8 +894,23 @@ def assymbly(description):
|
|
|
850
894
|
len(space))
|
|
851
895
|
except:
|
|
852
896
|
pass
|
|
897
|
+
return levels
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _get_independent_variables(description):
|
|
901
|
+
independent_variables = set(description['intrinsic_loops'].keys())
|
|
902
|
+
for level, loops in description['loops'].items():
|
|
903
|
+
for name, iterable in loops:
|
|
904
|
+
if isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
|
|
905
|
+
independent_variables.add(name)
|
|
906
|
+
return independent_variables
|
|
907
|
+
|
|
853
908
|
|
|
909
|
+
def _build_dependents(description, levels, independent_variables):
|
|
854
910
|
dependents = copy.deepcopy(description['dependents'])
|
|
911
|
+
all_nodes = set(description['dependents'].keys())
|
|
912
|
+
for key, deps in dependents.items():
|
|
913
|
+
all_nodes.update(deps)
|
|
855
914
|
|
|
856
915
|
for level in levels:
|
|
857
916
|
range_list = description['loops'].get(level, [])
|
|
@@ -864,6 +923,13 @@ def assymbly(description):
|
|
|
864
923
|
dependents[name] = set()
|
|
865
924
|
dependents[name].add(f'#__loop_{level}')
|
|
866
925
|
|
|
926
|
+
after_yield = set()
|
|
927
|
+
for key in all_nodes:
|
|
928
|
+
if key not in independent_variables and key not in description[
|
|
929
|
+
'consts']:
|
|
930
|
+
if key not in dependents:
|
|
931
|
+
after_yield.add(key)
|
|
932
|
+
|
|
867
933
|
def _get_all_depends(key, graph):
|
|
868
934
|
ret = set()
|
|
869
935
|
if key not in graph:
|
|
@@ -877,7 +943,13 @@ def assymbly(description):
|
|
|
877
943
|
full_depends = {}
|
|
878
944
|
for key in dependents:
|
|
879
945
|
full_depends[key] = _get_all_depends(key, dependents)
|
|
946
|
+
if full_depends[key] & after_yield:
|
|
947
|
+
after_yield.add(key)
|
|
948
|
+
|
|
949
|
+
return dependents, full_depends, after_yield
|
|
950
|
+
|
|
880
951
|
|
|
952
|
+
def _build_order(description, levels, dependents, full_depends):
|
|
881
953
|
levels = {}
|
|
882
954
|
passed = set()
|
|
883
955
|
all_keys = set(description['consts'].keys())
|
|
@@ -922,8 +994,9 @@ def assymbly(description):
|
|
|
922
994
|
description['order'][level].append(ready)
|
|
923
995
|
keys -= set(ready)
|
|
924
996
|
|
|
997
|
+
|
|
998
|
+
def _make_axis(description):
|
|
925
999
|
axis = {}
|
|
926
|
-
independent_variables = set(description['intrinsic_loops'].keys())
|
|
927
1000
|
|
|
928
1001
|
for name in description['consts']:
|
|
929
1002
|
axis[name] = ()
|
|
@@ -932,8 +1005,6 @@ def assymbly(description):
|
|
|
932
1005
|
if isinstance(iterable, OptimizeSpace):
|
|
933
1006
|
axis[name] = tuple(range(level + 1))
|
|
934
1007
|
continue
|
|
935
|
-
elif isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
|
|
936
|
-
independent_variables.add(name)
|
|
937
1008
|
axis[name] = (level, )
|
|
938
1009
|
|
|
939
1010
|
for level, group in description['order'].items():
|
|
@@ -954,8 +1025,20 @@ def assymbly(description):
|
|
|
954
1025
|
k: tuple([x for x in v if x >= 0])
|
|
955
1026
|
for k, v in axis.items()
|
|
956
1027
|
}
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def assymbly(description):
|
|
1031
|
+
_get_environment(description)
|
|
1032
|
+
levels = _mapping_levels(description)
|
|
1033
|
+
independent_variables = _get_independent_variables(description)
|
|
957
1034
|
description['independent_variables'] = independent_variables
|
|
958
1035
|
|
|
1036
|
+
dependents, full_depends, after_yield = _build_dependents(
|
|
1037
|
+
description, levels, independent_variables)
|
|
1038
|
+
|
|
1039
|
+
_build_order(description, levels, dependents, full_depends)
|
|
1040
|
+
_make_axis(description)
|
|
1041
|
+
|
|
959
1042
|
return description
|
|
960
1043
|
|
|
961
1044
|
|
qulab/scan/server.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import os
|
|
3
3
|
import pickle
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
4
6
|
import time
|
|
5
7
|
import uuid
|
|
6
8
|
from pathlib import Path
|
|
@@ -18,6 +20,7 @@ from .models import Cell, Notebook
|
|
|
18
20
|
from .models import Record as RecordInDB
|
|
19
21
|
from .models import Session, create_engine, create_tables, sessionmaker, utcnow
|
|
20
22
|
from .record import BufferList, Record, random_path
|
|
23
|
+
from .utils import dump_dict, load_dict
|
|
21
24
|
|
|
22
25
|
try:
|
|
23
26
|
default_record_port = int(os.getenv('QULAB_RECORD_PORT', 6789))
|
|
@@ -51,6 +54,16 @@ class Request():
|
|
|
51
54
|
return f"Request({self.method})"
|
|
52
55
|
|
|
53
56
|
|
|
57
|
+
class Response():
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ErrorResponse(Response):
|
|
62
|
+
|
|
63
|
+
def __init__(self, error):
|
|
64
|
+
self.error = error
|
|
65
|
+
|
|
66
|
+
|
|
54
67
|
async def reply(req, resp):
|
|
55
68
|
await req.sock.send_multipart([req.identity, pickle.dumps(resp)])
|
|
56
69
|
|
|
@@ -59,33 +72,45 @@ def clear_cache():
|
|
|
59
72
|
if len(record_cache) < CACHE_SIZE:
|
|
60
73
|
return
|
|
61
74
|
|
|
75
|
+
logger.debug(f"clear_cache record_cache: {len(record_cache)}")
|
|
62
76
|
for k, (t, _) in zip(sorted(record_cache.items(), key=lambda x: x[1][0]),
|
|
63
77
|
range(len(record_cache) - CACHE_SIZE)):
|
|
64
78
|
del record_cache[k]
|
|
65
79
|
|
|
80
|
+
logger.debug(f"clear_cache buffer_list_cache: {len(buffer_list_cache)}")
|
|
66
81
|
for k, (t,
|
|
67
82
|
_) in zip(sorted(buffer_list_cache.items(), key=lambda x: x[1][0]),
|
|
68
83
|
range(len(buffer_list_cache) - CACHE_SIZE)):
|
|
69
84
|
del buffer_list_cache[k]
|
|
85
|
+
logger.debug(f"clear_cache done.")
|
|
70
86
|
|
|
71
87
|
|
|
72
88
|
def flush_cache():
|
|
89
|
+
logger.debug(f"flush_cache: {len(record_cache)}")
|
|
73
90
|
for k, (t, r) in record_cache.items():
|
|
74
91
|
r.flush()
|
|
92
|
+
logger.debug(f"flush_cache done.")
|
|
75
93
|
|
|
76
94
|
|
|
77
95
|
def get_local_record(session: Session, id: int, datapath: Path) -> Record:
|
|
96
|
+
logger.debug(f"get_local_record: {id}")
|
|
78
97
|
record_in_db = session.get(RecordInDB, id)
|
|
79
98
|
if record_in_db is None:
|
|
99
|
+
logger.debug(f"record not found: {id=}")
|
|
80
100
|
return None
|
|
81
101
|
record_in_db.atime = utcnow()
|
|
82
102
|
|
|
83
103
|
if record_in_db.file.endswith('.zip'):
|
|
84
|
-
|
|
104
|
+
logger.debug(f"load record from zip: {record_in_db.file}")
|
|
105
|
+
record = Record.load(datapath / 'objects' / record_in_db.file)
|
|
106
|
+
logger.debug(f"load record from zip done.")
|
|
107
|
+
return record
|
|
85
108
|
|
|
86
109
|
path = datapath / 'objects' / record_in_db.file
|
|
87
110
|
with open(path, 'rb') as f:
|
|
111
|
+
logger.debug(f"load record from file: {path}")
|
|
88
112
|
record = dill.load(f)
|
|
113
|
+
logger.debug(f"load record from file done.")
|
|
89
114
|
record.database = datapath
|
|
90
115
|
record._file = path
|
|
91
116
|
return record
|
|
@@ -95,13 +120,16 @@ def get_record(session: Session, id: int, datapath: Path) -> Record:
|
|
|
95
120
|
if id not in record_cache:
|
|
96
121
|
record = get_local_record(session, id, datapath)
|
|
97
122
|
else:
|
|
123
|
+
logger.debug(f"get_record from cache: {id=}")
|
|
98
124
|
record = record_cache[id][1]
|
|
99
125
|
clear_cache()
|
|
126
|
+
logger.debug(f"update lru time for record cache: {id=}")
|
|
100
127
|
record_cache[id] = time.time(), record
|
|
101
128
|
return record
|
|
102
129
|
|
|
103
130
|
|
|
104
131
|
def record_create(session: Session, description: dict, datapath: Path) -> int:
|
|
132
|
+
logger.debug(f"record_create: {description['app']}")
|
|
105
133
|
record = Record(None, datapath, description)
|
|
106
134
|
record_in_db = RecordInDB()
|
|
107
135
|
if 'app' in description:
|
|
@@ -111,28 +139,39 @@ def record_create(session: Session, description: dict, datapath: Path) -> int:
|
|
|
111
139
|
record_in_db.file = '/'.join(record._file.parts[-4:])
|
|
112
140
|
record_in_db.config_id = description['config']
|
|
113
141
|
record._file = datapath / 'objects' / record_in_db.file
|
|
142
|
+
logger.debug(f"record_create generate random file: {record_in_db.file}")
|
|
114
143
|
session.add(record_in_db)
|
|
115
144
|
try:
|
|
116
145
|
session.commit()
|
|
146
|
+
logger.debug(f"record_create commited: record.id={record_in_db.id}")
|
|
117
147
|
record.id = record_in_db.id
|
|
118
148
|
clear_cache()
|
|
119
149
|
record_cache[record.id] = time.time(), record
|
|
120
150
|
return record.id
|
|
121
151
|
except:
|
|
152
|
+
logger.debug(f"record_create rollback")
|
|
122
153
|
session.rollback()
|
|
123
154
|
raise
|
|
124
155
|
|
|
125
156
|
|
|
126
157
|
def record_append(session: Session, record_id: int, level: int, step: int,
|
|
127
158
|
position: int, variables: dict, datapath: Path):
|
|
159
|
+
logger.debug(f"record_append: {record_id}")
|
|
128
160
|
record = get_record(session, record_id, datapath)
|
|
161
|
+
logger.debug(f"record_append: {record_id}, {level}, {step}, {position}")
|
|
129
162
|
record.append(level, step, position, variables)
|
|
163
|
+
logger.debug(f"record_append done.")
|
|
130
164
|
try:
|
|
165
|
+
logger.debug(f"record_append update SQL database.")
|
|
131
166
|
record_in_db = session.get(RecordInDB, record_id)
|
|
167
|
+
logger.debug(f"record_append get RecordInDB: {record_in_db}")
|
|
132
168
|
record_in_db.mtime = utcnow()
|
|
133
169
|
record_in_db.atime = utcnow()
|
|
170
|
+
logger.debug(f"record_append update RecordInDB: {record_in_db}")
|
|
134
171
|
session.commit()
|
|
172
|
+
logger.debug(f"record_append commited.")
|
|
135
173
|
except:
|
|
174
|
+
logger.debug(f"record_append rollback.")
|
|
136
175
|
session.rollback()
|
|
137
176
|
raise
|
|
138
177
|
|
|
@@ -145,16 +184,20 @@ def record_delete(session: Session, record_id: int, datapath: Path):
|
|
|
145
184
|
session.commit()
|
|
146
185
|
|
|
147
186
|
|
|
148
|
-
@logger.catch
|
|
187
|
+
@logger.catch(reraise=True)
|
|
149
188
|
async def handle(session: Session, request: Request, datapath: Path):
|
|
150
189
|
|
|
151
190
|
msg = request.msg
|
|
152
191
|
|
|
192
|
+
if request.method not in ['ping']:
|
|
193
|
+
logger.debug(f"handle: {request.method}")
|
|
194
|
+
|
|
153
195
|
match request.method:
|
|
154
196
|
case 'ping':
|
|
155
197
|
await reply(request, 'pong')
|
|
156
198
|
case 'bufferlist_iter':
|
|
157
|
-
|
|
199
|
+
logger.debug(f"bufferlist_iter: {msg}")
|
|
200
|
+
if msg['iter_id'] and msg['iter_id'] in buffer_list_cache:
|
|
158
201
|
it = buffer_list_cache[msg['iter_id']][1]
|
|
159
202
|
iter_id = msg['iter_id']
|
|
160
203
|
else:
|
|
@@ -174,22 +217,30 @@ async def handle(session: Session, request: Request, datapath: Path):
|
|
|
174
217
|
except StopIteration:
|
|
175
218
|
end = True
|
|
176
219
|
break
|
|
220
|
+
logger.debug(f"bufferlist_iter: {iter_id}, {end}")
|
|
177
221
|
await reply(request, (iter_id, ret, end))
|
|
222
|
+
logger.debug(f"reply bufferlist_iter: {iter_id}, {end}")
|
|
178
223
|
buffer_list_cache[iter_id] = time.time(), it
|
|
179
224
|
clear_cache()
|
|
180
225
|
case 'bufferlist_iter_exit':
|
|
226
|
+
logger.debug(f"bufferlist_iter_exit: {msg}")
|
|
181
227
|
try:
|
|
182
228
|
it = buffer_list_cache.pop(msg['iter_id'])[1]
|
|
183
229
|
it.throw(Exception)
|
|
184
230
|
except:
|
|
185
231
|
pass
|
|
186
232
|
clear_cache()
|
|
233
|
+
logger.debug(f"end bufferlist_iter_exit: {msg}")
|
|
187
234
|
case 'record_create':
|
|
188
|
-
|
|
235
|
+
logger.debug(f"record_create")
|
|
236
|
+
description = load_dict(msg['description'])
|
|
189
237
|
await reply(request, record_create(session, description, datapath))
|
|
238
|
+
logger.debug(f"reply record_create")
|
|
190
239
|
case 'record_append':
|
|
240
|
+
logger.debug(f"record_append")
|
|
191
241
|
record_append(session, msg['record_id'], msg['level'], msg['step'],
|
|
192
242
|
msg['position'], msg['variables'], datapath)
|
|
243
|
+
logger.debug(f"reply record_append")
|
|
193
244
|
case 'record_description':
|
|
194
245
|
record = get_record(session, msg['record_id'], datapath)
|
|
195
246
|
await reply(request, dill.dumps(record))
|
|
@@ -295,6 +346,9 @@ async def handle(session: Session, request: Request, datapath: Path):
|
|
|
295
346
|
case _:
|
|
296
347
|
logger.error(f"Unknown method: {msg['method']}")
|
|
297
348
|
|
|
349
|
+
if request.method not in ['ping']:
|
|
350
|
+
logger.debug(f"finished handle: {request.method}")
|
|
351
|
+
|
|
298
352
|
|
|
299
353
|
async def handle_with_timeout(session: Session, request: Request,
|
|
300
354
|
datapath: Path, timeout: float):
|
|
@@ -306,35 +360,45 @@ async def handle_with_timeout(session: Session, request: Request,
|
|
|
306
360
|
f"Task handling request {request} timed out and was cancelled.")
|
|
307
361
|
await reply(request, 'timeout')
|
|
308
362
|
except Exception as e:
|
|
309
|
-
|
|
363
|
+
logger.error(f"Task handling request {request} failed: {e!r}")
|
|
364
|
+
await reply(request, ErrorResponse(f'{e!r}'))
|
|
365
|
+
logger.debug(f"Task handling request {request} finished.")
|
|
310
366
|
|
|
311
367
|
|
|
312
368
|
async def serv(port,
|
|
313
369
|
datapath,
|
|
314
|
-
url=
|
|
370
|
+
url='',
|
|
315
371
|
buffer_size=1024 * 1024 * 1024,
|
|
316
372
|
interval=60):
|
|
317
|
-
logger.
|
|
373
|
+
logger.debug('Creating socket...')
|
|
318
374
|
async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
|
|
319
|
-
|
|
375
|
+
logger.info(f'Server started at port {port}.')
|
|
376
|
+
logger.info(f'Data path: {datapath}.')
|
|
377
|
+
if not url or url == 'sqlite':
|
|
320
378
|
url = 'sqlite:///' + str(datapath / 'data.db')
|
|
321
379
|
engine = create_engine(url)
|
|
322
380
|
create_tables(engine)
|
|
323
381
|
Session = sessionmaker(engine)
|
|
324
382
|
with Session() as session:
|
|
325
|
-
logger.info('
|
|
383
|
+
logger.info(f'Database connected: {url}.')
|
|
326
384
|
received = 0
|
|
327
385
|
last_flush_time = time.time()
|
|
328
386
|
while True:
|
|
387
|
+
logger.debug('Waiting for request...')
|
|
329
388
|
identity, msg = await sock.recv_multipart()
|
|
389
|
+
logger.debug('Received request.')
|
|
330
390
|
received += len(msg)
|
|
331
391
|
try:
|
|
332
392
|
req = Request(sock, identity, msg)
|
|
333
|
-
except:
|
|
393
|
+
except Exception as e:
|
|
334
394
|
logger.exception('bad request')
|
|
395
|
+
await sock.send_multipart(
|
|
396
|
+
[identity,
|
|
397
|
+
pickle.dumps(ErrorResponse(f'{e!r}'))])
|
|
335
398
|
continue
|
|
336
399
|
asyncio.create_task(
|
|
337
|
-
handle_with_timeout(session, req, datapath,
|
|
400
|
+
handle_with_timeout(session, req, datapath,
|
|
401
|
+
timeout=3600.0))
|
|
338
402
|
if received > buffer_size or time.time(
|
|
339
403
|
) - last_flush_time > interval:
|
|
340
404
|
flush_cache()
|
|
@@ -342,46 +406,123 @@ async def serv(port,
|
|
|
342
406
|
last_flush_time = time.time()
|
|
343
407
|
|
|
344
408
|
|
|
345
|
-
async def
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
409
|
+
async def main(port,
|
|
410
|
+
datapath,
|
|
411
|
+
url,
|
|
412
|
+
timeout=1,
|
|
413
|
+
buffer=1024,
|
|
414
|
+
interval=60,
|
|
415
|
+
log='stderr',
|
|
416
|
+
no_watch=True,
|
|
417
|
+
debug=False):
|
|
418
|
+
if no_watch:
|
|
419
|
+
logger.remove()
|
|
420
|
+
if debug:
|
|
421
|
+
level = 'DEBUG'
|
|
422
|
+
else:
|
|
423
|
+
level = 'INFO'
|
|
424
|
+
if log == 'stderr':
|
|
425
|
+
logger.add(sys.stderr, level=level)
|
|
426
|
+
elif log == 'stdout':
|
|
427
|
+
logger.add(sys.stdout, level=level)
|
|
428
|
+
else:
|
|
429
|
+
logger.add(sys.stderr, level=level)
|
|
430
|
+
logger.add(log, level=level)
|
|
431
|
+
logger.debug(f"logging level: {level}")
|
|
432
|
+
logger.info('Server starting...')
|
|
433
|
+
await serv(port, datapath, url, buffer * 1024 * 1024, interval)
|
|
434
|
+
else:
|
|
435
|
+
process = None
|
|
436
|
+
|
|
349
437
|
while True:
|
|
350
438
|
try:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
sock.
|
|
354
|
-
|
|
355
|
-
|
|
439
|
+
with ZMQContextManager(
|
|
440
|
+
zmq.DEALER, connect=f"tcp://127.0.0.1:{port}") as sock:
|
|
441
|
+
sock.setsockopt(zmq.LINGER, 0)
|
|
442
|
+
sock.send_pyobj({"method": "ping"})
|
|
443
|
+
logger.debug('ping.')
|
|
444
|
+
if sock.poll(int(1000 * timeout)):
|
|
445
|
+
sock.recv()
|
|
446
|
+
logger.debug('recv pong.')
|
|
447
|
+
else:
|
|
448
|
+
logger.debug('timeout.')
|
|
449
|
+
raise asyncio.TimeoutError()
|
|
356
450
|
except (zmq.error.ZMQError, asyncio.TimeoutError):
|
|
357
|
-
|
|
358
|
-
|
|
451
|
+
if process is not None:
|
|
452
|
+
logger.debug(
|
|
453
|
+
f'killing process... PID={process.pid}, returncode={process.returncode}'
|
|
454
|
+
)
|
|
455
|
+
process.kill()
|
|
456
|
+
logger.debug(
|
|
457
|
+
f'killed process. PID={process.pid}, returncode={process.returncode}'
|
|
458
|
+
)
|
|
459
|
+
cmd = [
|
|
460
|
+
sys.executable,
|
|
461
|
+
"-m",
|
|
462
|
+
"qulab",
|
|
463
|
+
"server",
|
|
464
|
+
"--port",
|
|
465
|
+
f"{port}",
|
|
466
|
+
"--datapath",
|
|
467
|
+
f"{datapath}",
|
|
468
|
+
"--url",
|
|
469
|
+
f"{url}",
|
|
470
|
+
"--timeout",
|
|
471
|
+
f"{timeout}",
|
|
472
|
+
"--buffer",
|
|
473
|
+
f"{buffer}",
|
|
474
|
+
"--interval",
|
|
475
|
+
f"{interval}",
|
|
476
|
+
"--log",
|
|
477
|
+
f"{log}",
|
|
478
|
+
]
|
|
479
|
+
if url:
|
|
480
|
+
cmd.extend(['--url', url])
|
|
481
|
+
if debug:
|
|
482
|
+
cmd.append('--debug')
|
|
483
|
+
cmd.append("--no-watch")
|
|
484
|
+
logger.debug(f"starting process: {' '.join(cmd)}")
|
|
485
|
+
process = subprocess.Popen(cmd, cwd=os.getcwd())
|
|
486
|
+
logger.debug(
|
|
487
|
+
f'process started. PID={process.pid}, returncode={process.returncode}'
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
# Capture and log the output
|
|
491
|
+
# stdout, stderr = process.communicate(timeout=5)
|
|
492
|
+
# if stdout:
|
|
493
|
+
# logger.info(f'Server stdout: {stdout.decode()}')
|
|
494
|
+
# if stderr:
|
|
495
|
+
# logger.error(f'Server stderr: {stderr.decode()}')
|
|
496
|
+
|
|
497
|
+
await asyncio.sleep(5)
|
|
359
498
|
await asyncio.sleep(timeout)
|
|
360
499
|
|
|
361
500
|
|
|
362
|
-
async def main(port, datapath, url, timeout=1, buffer=1024, interval=60):
|
|
363
|
-
task = await watch(port=port,
|
|
364
|
-
datapath=datapath,
|
|
365
|
-
url=url,
|
|
366
|
-
timeout=timeout,
|
|
367
|
-
buffer=buffer,
|
|
368
|
-
interval=interval)
|
|
369
|
-
await task
|
|
370
|
-
|
|
371
|
-
|
|
372
501
|
@click.command()
|
|
373
502
|
@click.option('--port',
|
|
374
503
|
default=os.getenv('QULAB_RECORD_PORT', 6789),
|
|
375
504
|
help='Port of the server.')
|
|
376
505
|
@click.option('--datapath', default=datapath, help='Path of the data.')
|
|
377
|
-
@click.option('--url', default=
|
|
506
|
+
@click.option('--url', default='sqlite', help='URL of the database.')
|
|
378
507
|
@click.option('--timeout', default=1, help='Timeout of ping.')
|
|
379
508
|
@click.option('--buffer', default=1024, help='Buffer size (MB).')
|
|
380
509
|
@click.option('--interval',
|
|
381
510
|
default=60,
|
|
382
511
|
help='Interval of flush cache, in unit of second.')
|
|
383
|
-
|
|
384
|
-
|
|
512
|
+
@click.option('--log', default='stderr', help='Log file.')
|
|
513
|
+
@click.option('--no-watch', is_flag=True, help='Watch the server.')
|
|
514
|
+
@click.option('--debug', is_flag=True, help='Debug mode.')
|
|
515
|
+
def server(port, datapath, url, timeout, buffer, interval, log, no_watch,
|
|
516
|
+
debug):
|
|
517
|
+
try:
|
|
518
|
+
import uvloop
|
|
519
|
+
uvloop.run(
|
|
520
|
+
main(port, Path(datapath), url, timeout, buffer, interval, log,
|
|
521
|
+
True, debug))
|
|
522
|
+
except ImportError:
|
|
523
|
+
asyncio.run(
|
|
524
|
+
main(port, Path(datapath), url, timeout, buffer, interval, log,
|
|
525
|
+
True, debug))
|
|
385
526
|
|
|
386
527
|
|
|
387
528
|
if __name__ == "__main__":
|
qulab/scan/utils.py
CHANGED
|
@@ -29,6 +29,46 @@ class TooLarge:
|
|
|
29
29
|
return f'<TooLarge: {self.type} at 0x{id(self):x}>'
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
def dump_dict(d, keys=[]):
|
|
33
|
+
ret = {}
|
|
34
|
+
|
|
35
|
+
for key, value in d.items():
|
|
36
|
+
if key in keys:
|
|
37
|
+
ret[key] = value
|
|
38
|
+
continue
|
|
39
|
+
if isinstance(value, dict) and isinstance(key, str):
|
|
40
|
+
ret[key] = dump_dict(value,
|
|
41
|
+
keys=[
|
|
42
|
+
k[len(key) + 1:] for k in keys
|
|
43
|
+
if k.startswith(f'{key}.')
|
|
44
|
+
])
|
|
45
|
+
else:
|
|
46
|
+
try:
|
|
47
|
+
ret[key] = dill.dumps(value)
|
|
48
|
+
except:
|
|
49
|
+
ret[key] = Unpicklable(value)
|
|
50
|
+
|
|
51
|
+
return dill.dumps(ret)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_dict(buff):
|
|
55
|
+
if isinstance(buff, dict):
|
|
56
|
+
return {key: load_dict(value) for key, value in buff.items()}
|
|
57
|
+
|
|
58
|
+
if not isinstance(buff, bytes):
|
|
59
|
+
return buff
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
ret = dill.loads(buff)
|
|
63
|
+
except:
|
|
64
|
+
return buff
|
|
65
|
+
|
|
66
|
+
if isinstance(ret, dict):
|
|
67
|
+
return load_dict(ret)
|
|
68
|
+
else:
|
|
69
|
+
return ret
|
|
70
|
+
|
|
71
|
+
|
|
32
72
|
def dump_globals(ns=None, *, size_limit=10 * 1024 * 1024, warn=False):
|
|
33
73
|
import __main__
|
|
34
74
|
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.3.
|
|
1
|
+
__version__ = "2.3.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|