QuLab 2.3.3__cp310-cp310-macosx_10_9_universal2.whl → 2.3.4__cp310-cp310-macosx_10_9_universal2.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.3.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
  [![View build status](https://travis-ci.org/feihoo87/QuLab.svg?branch=master)](https://travis-ci.org/feihoo87/QuLab)
@@ -1,7 +1,7 @@
1
1
  qulab/__init__.py,sha256=P-Mx2p4TVmL91SoxoeXcj8Qm0x4xUf5Q_FLk0Yc_gIQ,138
2
2
  qulab/__main__.py,sha256=ZC1NKaoxKyy60DaCfB8vYnB1z3RXQ2j8E1sRZ4A8sXE,428
3
- qulab/fun.cpython-310-darwin.so,sha256=EOGb46gUpfcUMp6umfTCrC5pq-XWOx8aVISFT_8OKrg,159632
4
- qulab/version.py,sha256=_TojZ2sN-5aiITSzim-_br5UuW9pwpo2Tx8vnDWpsqE,21
3
+ qulab/fun.cpython-310-darwin.so,sha256=Sr82tYA3bW2CSKKXnJaWdMqPV85avNqZnKlzFD-98aE,159632
4
+ qulab/version.py,sha256=MtmOJMh3yHY9aTdRsZDgbpPfbzm9xdycdi9jjMKzYjM,21
5
5
  qulab/monitor/__init__.py,sha256=nTHelnDpxRS_fl_B38TsN0njgq8eVTEz9IAnN3NbDlM,42
6
6
  qulab/monitor/__main__.py,sha256=w3yUcqq195LzSnXTkQcuC1RSFRhy4oQ_PEBmucXguME,97
7
7
  qulab/monitor/config.py,sha256=fQ5JcsMApKc1UwANEnIvbDQZl8uYW0tle92SaYtX9lI,744
@@ -19,10 +19,10 @@ qulab/scan/models.py,sha256=5Jpo25WGMWs0GtLzYLsWO61G3-FFYx5BHhBr2b6rOTE,17681
19
19
  qulab/scan/optimize.py,sha256=jYceGazEabuwYqmZE8P6Nnq_KsLyUP0p-p88VbbJLhM,2559
20
20
  qulab/scan/query.py,sha256=-5uHMhXSyGovK1oy_uUbGVEbRFzaMBkP78ZMNfI3jD0,11809
21
21
  qulab/scan/record.py,sha256=yIHPANf6nuBXy8Igf-dMtGJ7wuFTLYlBaaAUc0AzwyU,21280
22
- qulab/scan/scan.py,sha256=GhRhBLWb5PVFi9qkyXJl0p1XXbpjdnjoSz_wPegf9Vs,38783
23
- qulab/scan/server.py,sha256=tGQ4bVePqvFACb0L26viA57UBD7LkkZI9q5kQGffapA,19112
22
+ qulab/scan/scan.py,sha256=hrCnoIROMPoYXEZnU9GTZUR-aX9qP58d40wlN3DTQCE,40602
23
+ qulab/scan/server.py,sha256=_4YrOJcnqDTnxryZXE8lEKGzuT4LS-8q9bN6jrTEqUQ,19950
24
24
  qulab/scan/space.py,sha256=X5anTvw7X0XGN83y0o0C-zhvh6bDEU-kshGgo004pNE,6076
25
- qulab/scan/utils.py,sha256=Pg_tCf3SUKTiPSBqb6Enkgx4bAyQJAkDGe9uYys1xVU,3613
25
+ qulab/scan/utils.py,sha256=zCV_Td8hBgb16Hv3XKo64KYDDpvBw2rQ83fxq1hvjG8,4586
26
26
  qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  qulab/storage/__main__.py,sha256=3emxxRry8BB0m8hUZvJ_oBqkPy7ksV7flHB_KEDXZuI,1692
28
28
  qulab/storage/base_dataset.py,sha256=4aKhqBNdZfdlm_z1Qy5dv0HrvgpvMdy8pbyfua8uE-4,11865
@@ -78,9 +78,9 @@ qulab/visualization/plot_layout.py,sha256=clNw9QjE_kVNpIIx2Ob4YhAz2fucPGMuzkoIrO
78
78
  qulab/visualization/plot_seq.py,sha256=lphYF4VhkEdc_wWr1kFBwrx2yujkyFPFaJ3pjr61awI,2693
79
79
  qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
80
80
  qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
81
- QuLab-2.3.3.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
82
- QuLab-2.3.3.dist-info/METADATA,sha256=DcGCVF8ABDW4sJ8mRhZ2N8jWlYXRA3yvHV6sLt_T24o,3510
83
- QuLab-2.3.3.dist-info/WHEEL,sha256=ClVOi9g10rI0HLG8M8rsO0ZIZ1smbNYM10V9YDSXCJY,114
84
- QuLab-2.3.3.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
85
- QuLab-2.3.3.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
86
- QuLab-2.3.3.dist-info/RECORD,,
81
+ QuLab-2.3.4.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
82
+ QuLab-2.3.4.dist-info/METADATA,sha256=Noz4IplxqE9TBtcN2Qr38B1J0vtpEkCWsjAjvRMGF_I,3633
83
+ QuLab-2.3.4.dist-info/WHEEL,sha256=ClVOi9g10rI0HLG8M8rsO0ZIZ1smbNYM10V9YDSXCJY,114
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,,
Binary file
qulab/scan/scan.py CHANGED
@@ -26,7 +26,7 @@ from .optimize import NgOptimizer
26
26
  from .record import Record
27
27
  from .server import default_record_port
28
28
  from .space import Optimizer, OptimizeSpace, Space
29
- from .utils import async_zip, call_function, dump_globals
29
+ from .utils import async_zip, call_function, dump_dict, dump_globals
30
30
 
31
31
  try:
32
32
  from tqdm.notebook import tqdm
@@ -205,19 +205,6 @@ def _run_function_in_process(buf):
205
205
  return func(*args, **kwds)
206
206
 
207
207
 
208
- def _dump_description(description):
209
- d = {}
210
- for key, value in description.items():
211
- if key in [
212
- 'intrinsic_loops', 'app', 'tags', 'loops',
213
- 'independent_variables', 'axis', 'config', 'entry'
214
- ]:
215
- d[key] = value
216
- else:
217
- d[key] = dill.dumps(value)
218
- return dill.dumps(description)
219
-
220
-
221
208
  class Scan():
222
209
 
223
210
  def __new__(cls, *args, mixin=None, **kwds):
@@ -277,6 +264,7 @@ class Scan():
277
264
  self._current_level = 0
278
265
  self._variables = {}
279
266
  self._main_task = None
267
+ self._background_tasks = ()
280
268
  self._sock = None
281
269
  self._sem = asyncio.Semaphore(max_promise + 1)
282
270
  self._bar: dict[int, tqdm] = {}
@@ -300,6 +288,7 @@ class Scan():
300
288
  del state['record']
301
289
  del state['_sock']
302
290
  del state['_main_task']
291
+ del state['_background_tasks']
303
292
  del state['_bar']
304
293
  del state['_msg_queue']
305
294
  del state['_prm_queue']
@@ -312,6 +301,7 @@ class Scan():
312
301
  self.record = None
313
302
  self._sock = None
314
303
  self._main_task = None
304
+ self._background_tasks = ()
315
305
  self._bar = {}
316
306
  self._prm_queue = asyncio.Queue()
317
307
  self._msg_queue = asyncio.Queue(self._max_message)
@@ -412,7 +402,11 @@ class Scan():
412
402
  'method':
413
403
  'record_create',
414
404
  'description':
415
- _dump_description(self.description)
405
+ dump_dict(self.description,
406
+ keys=[
407
+ 'intrinsic_loops', 'app', 'tags', 'loops',
408
+ 'independent_variables', 'axis', 'config', 'entry'
409
+ ])
416
410
  })
417
411
 
418
412
  record_id = await self._sock.recv_pyobj()
@@ -612,27 +606,33 @@ class Scan():
612
606
  await task
613
607
  self._msg_queue.task_done()
614
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
+
615
633
  async def run(self):
616
634
  assymbly(self.description)
617
-
618
- @contextlib.asynccontextmanager
619
- async def send_msg_and_update_bar(self):
620
- send_msg_task = asyncio.create_task(self._send_msg())
621
- update_progress_task = asyncio.create_task(self._update_progress())
622
- try:
623
- yield
624
- finally:
625
- update_progress_task.cancel()
626
- send_msg_task.cancel()
627
- while True:
628
- try:
629
- task = self._prm_queue.get_nowait()
630
- except:
631
- break
632
- try:
633
- task.cancel()
634
- except:
635
- pass
635
+ self._background_tasks = ()
636
636
 
637
637
  if isinstance(
638
638
  self.description['database'],
@@ -641,12 +641,14 @@ class Scan():
641
641
  connect=self.description['database'],
642
642
  socket=self._sock) as socket:
643
643
  self._sock = socket
644
- async with send_msg_and_update_bar(self):
644
+ async with self._send_msg_and_update_bar() as background_tasks:
645
+ self._background_tasks = background_tasks
645
646
  await self._run()
646
647
  else:
647
648
  if self.config:
648
649
  self.description['config'] = copy.deepcopy(self.config)
649
- async with send_msg_and_update_bar(self):
650
+ async with self._send_msg_and_update_bar() as background_tasks:
651
+ self._background_tasks = background_tasks
650
652
  await self._run()
651
653
 
652
654
  async def _run(self):
@@ -671,11 +673,11 @@ class Scan():
671
673
  self.description['functions'], self.variables)
672
674
  await update_variables(self.variables, updates,
673
675
  self.description['setters'])
674
-
676
+ await self._check_background_tasks()
675
677
  await self.work()
676
678
  for level, bar in self._bar.items():
677
679
  bar.close()
678
-
680
+ await self._check_background_tasks()
679
681
  if self._single_step:
680
682
  self.variables.update(await call_many_functions(
681
683
  self.description['order'].get(-1, []),
@@ -683,7 +685,7 @@ class Scan():
683
685
 
684
686
  await self.emit(0, 0, 0, self.variables)
685
687
  await self.emit(-1, 0, 0, {})
686
-
688
+ await self._check_background_tasks()
687
689
  await self._prm_queue.join()
688
690
  await self._msg_queue.join()
689
691
  return self.variables
@@ -747,6 +749,7 @@ class Scan():
747
749
  | {'config': self._synchronize_config},
748
750
  self.description['optimizers'], self.description['setters'],
749
751
  self.description['getters']):
752
+ await self._check_background_tasks()
750
753
  self._current_level += 1
751
754
  if await self._filter(variables, self.current_level - 1):
752
755
  yield variables
@@ -758,11 +761,13 @@ class Scan():
758
761
  self._current_level -= 1
759
762
  self._prm_queue.put_nowait(
760
763
  self._update_progress_bar(self.current_level, 1))
764
+ await self._check_background_tasks()
761
765
  if self.current_level == 0:
762
766
  await self.emit(self.current_level - 1, 0, 0, {})
763
767
  for name, value in self.variables.items():
764
768
  if inspect.isawaitable(value):
765
769
  self.variables[name] = await value
770
+ await self._check_background_tasks()
766
771
  await self._prm_queue.join()
767
772
 
768
773
  async def work(self, **kwds):
@@ -823,7 +828,7 @@ class Scan():
823
828
  return await awaitable
824
829
 
825
830
 
826
- def assymbly(description):
831
+ def _get_environment(description):
827
832
  import __main__
828
833
  from IPython import get_ipython
829
834
 
@@ -853,6 +858,10 @@ def assymbly(description):
853
858
 
854
859
  description['entry']['env'] = {k: v for k, v in os.environ.items()}
855
860
 
861
+ return description
862
+
863
+
864
+ def _mapping_levels(description):
856
865
  mapping = {
857
866
  label: level
858
867
  for level, label in enumerate(
@@ -885,8 +894,23 @@ def assymbly(description):
885
894
  len(space))
886
895
  except:
887
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
+
888
908
 
909
+ def _build_dependents(description, levels, independent_variables):
889
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)
890
914
 
891
915
  for level in levels:
892
916
  range_list = description['loops'].get(level, [])
@@ -899,6 +923,13 @@ def assymbly(description):
899
923
  dependents[name] = set()
900
924
  dependents[name].add(f'#__loop_{level}')
901
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
+
902
933
  def _get_all_depends(key, graph):
903
934
  ret = set()
904
935
  if key not in graph:
@@ -912,7 +943,13 @@ def assymbly(description):
912
943
  full_depends = {}
913
944
  for key in dependents:
914
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
915
950
 
951
+
952
+ def _build_order(description, levels, dependents, full_depends):
916
953
  levels = {}
917
954
  passed = set()
918
955
  all_keys = set(description['consts'].keys())
@@ -957,8 +994,9 @@ def assymbly(description):
957
994
  description['order'][level].append(ready)
958
995
  keys -= set(ready)
959
996
 
997
+
998
+ def _make_axis(description):
960
999
  axis = {}
961
- independent_variables = set(description['intrinsic_loops'].keys())
962
1000
 
963
1001
  for name in description['consts']:
964
1002
  axis[name] = ()
@@ -967,8 +1005,6 @@ def assymbly(description):
967
1005
  if isinstance(iterable, OptimizeSpace):
968
1006
  axis[name] = tuple(range(level + 1))
969
1007
  continue
970
- elif isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
971
- independent_variables.add(name)
972
1008
  axis[name] = (level, )
973
1009
 
974
1010
  for level, group in description['order'].items():
@@ -989,8 +1025,20 @@ def assymbly(description):
989
1025
  k: tuple([x for x in v if x >= 0])
990
1026
  for k, v in axis.items()
991
1027
  }
1028
+
1029
+
1030
+ def assymbly(description):
1031
+ _get_environment(description)
1032
+ levels = _mapping_levels(description)
1033
+ independent_variables = _get_independent_variables(description)
992
1034
  description['independent_variables'] = independent_variables
993
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
+
994
1042
  return description
995
1043
 
996
1044
 
qulab/scan/server.py CHANGED
@@ -20,6 +20,7 @@ from .models import Cell, Notebook
20
20
  from .models import Record as RecordInDB
21
21
  from .models import Session, create_engine, create_tables, sessionmaker, utcnow
22
22
  from .record import BufferList, Record, random_path
23
+ from .utils import dump_dict, load_dict
23
24
 
24
25
  try:
25
26
  default_record_port = int(os.getenv('QULAB_RECORD_PORT', 6789))
@@ -53,6 +54,16 @@ class Request():
53
54
  return f"Request({self.method})"
54
55
 
55
56
 
57
+ class Response():
58
+ pass
59
+
60
+
61
+ class ErrorResponse(Response):
62
+
63
+ def __init__(self, error):
64
+ self.error = error
65
+
66
+
56
67
  async def reply(req, resp):
57
68
  await req.sock.send_multipart([req.identity, pickle.dumps(resp)])
58
69
 
@@ -173,7 +184,7 @@ def record_delete(session: Session, record_id: int, datapath: Path):
173
184
  session.commit()
174
185
 
175
186
 
176
- @logger.catch
187
+ @logger.catch(reraise=True)
177
188
  async def handle(session: Session, request: Request, datapath: Path):
178
189
 
179
190
  msg = request.msg
@@ -222,7 +233,7 @@ async def handle(session: Session, request: Request, datapath: Path):
222
233
  logger.debug(f"end bufferlist_iter_exit: {msg}")
223
234
  case 'record_create':
224
235
  logger.debug(f"record_create")
225
- description = dill.loads(msg['description'])
236
+ description = load_dict(msg['description'])
226
237
  await reply(request, record_create(session, description, datapath))
227
238
  logger.debug(f"reply record_create")
228
239
  case 'record_append':
@@ -349,29 +360,16 @@ async def handle_with_timeout(session: Session, request: Request,
349
360
  f"Task handling request {request} timed out and was cancelled.")
350
361
  await reply(request, 'timeout')
351
362
  except Exception as e:
352
- await reply(request, f'{e!r}')
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.")
353
366
 
354
367
 
355
368
  async def serv(port,
356
369
  datapath,
357
370
  url='',
358
371
  buffer_size=1024 * 1024 * 1024,
359
- interval=60,
360
- log='stderr',
361
- debug=False):
362
- if debug:
363
- level = 'DEBUG'
364
- else:
365
- level = 'INFO'
366
-
367
- if log == 'stderr':
368
- pass
369
- #logger.add(sys.stderr, level=level)
370
- elif log == 'stdout':
371
- logger.add(sys.stdout, level=level)
372
- else:
373
- logger.add(log, level=level)
374
-
372
+ interval=60):
375
373
  logger.debug('Creating socket...')
376
374
  async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
377
375
  logger.info(f'Server started at port {port}.')
@@ -392,8 +390,11 @@ async def serv(port,
392
390
  received += len(msg)
393
391
  try:
394
392
  req = Request(sock, identity, msg)
395
- except:
393
+ except Exception as e:
396
394
  logger.exception('bad request')
395
+ await sock.send_multipart(
396
+ [identity,
397
+ pickle.dumps(ErrorResponse(f'{e!r}'))])
397
398
  continue
398
399
  asyncio.create_task(
399
400
  handle_with_timeout(session, req, datapath,
@@ -415,9 +416,21 @@ async def main(port,
415
416
  no_watch=True,
416
417
  debug=False):
417
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}")
418
432
  logger.info('Server starting...')
419
- await serv(port, datapath, url, buffer * 1024 * 1024, interval, log,
420
- debug)
433
+ await serv(port, datapath, url, buffer * 1024 * 1024, interval)
421
434
  else:
422
435
  process = None
423
436
 
@@ -444,10 +457,24 @@ async def main(port,
444
457
  f'killed process. PID={process.pid}, returncode={process.returncode}'
445
458
  )
446
459
  cmd = [
447
- sys.executable, "-m", "qulab", "server", "--port",
448
- f"{port}", "--datapath", f"{datapath}", "--url", f"{url}",
449
- "--timeout", f"{timeout}", "--buffer", f"{buffer}",
450
- "--interval", f"{interval}", "--log", f"{log}",
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}",
451
478
  ]
452
479
  if url:
453
480
  cmd.extend(['--url', url])
@@ -455,10 +482,7 @@ async def main(port,
455
482
  cmd.append('--debug')
456
483
  cmd.append("--no-watch")
457
484
  logger.debug(f"starting process: {' '.join(cmd)}")
458
- process = subprocess.Popen(cmd,
459
- stdout=subprocess.PIPE,
460
- stderr=subprocess.PIPE,
461
- cwd=os.getcwd())
485
+ process = subprocess.Popen(cmd, cwd=os.getcwd())
462
486
  logger.debug(
463
487
  f'process started. PID={process.pid}, returncode={process.returncode}'
464
488
  )
@@ -490,9 +514,15 @@ async def main(port,
490
514
  @click.option('--debug', is_flag=True, help='Debug mode.')
491
515
  def server(port, datapath, url, timeout, buffer, interval, log, no_watch,
492
516
  debug):
493
- asyncio.run(
494
- main(port, Path(datapath), url, timeout, buffer, interval, log,
495
- True, 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))
496
526
 
497
527
 
498
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.3"
1
+ __version__ = "2.3.4"
File without changes
File without changes