QuLab 2.3.3__cp311-cp311-win_amd64.whl → 2.3.5__cp311-cp311-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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: QuLab
3
- Version: 2.3.3
3
+ Version: 2.3.5
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=vkFybY8YSsQilYdThPRD83-btPAR41sy_WCXiM-6mME,141
2
2
  qulab/__main__.py,sha256=V7iokU7awstgjCeiF_hoOdFyrqJwC_4QetiLe7cWvOQ,454
3
- qulab/fun.cp311-win_amd64.pyd,sha256=Tum8uq_Huf6Ix76rz3ni4O63xMAIC6TtwYo9pwYhnGA,31744
4
- qulab/version.py,sha256=_TojZ2sN-5aiITSzim-_br5UuW9pwpo2Tx8vnDWpsqE,21
3
+ qulab/fun.cp311-win_amd64.pyd,sha256=Hdu7_RHp4pqeVgzgSi9vZtC9Utq_N2pFv8thjeReiCk,31744
4
+ qulab/version.py,sha256=ctNy09QkNipfMu2fxyFBsAXtRoAHMP1bEKHsc3zRmkk,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
@@ -16,13 +16,13 @@ qulab/scan/__init__.py,sha256=RR_0NQcr8Mi3vpWdypydbijQ1rXA0D3DEidQ7xjNslM,133
16
16
  qulab/scan/curd.py,sha256=yaTglGiS6mlk0GqDHi_w8T02XGBMvDZtXSdML7zDywk,7117
17
17
  qulab/scan/expression.py,sha256=l7TYBmcJIo0M5GJm1TtrkrMFj5mCmrfLOMCILKbOivU,20712
18
18
  qulab/scan/models.py,sha256=ZvXkJEt5Yz3Sjx0JKzYka-q2Uo-w_iVzHgH8A6DbjF0,18236
19
- qulab/scan/optimize.py,sha256=ACfGSfrFpPgmZo_P5kD4mquahXImYipdP7E86dtFQO8,2635
19
+ qulab/scan/optimize.py,sha256=zOR4Wp96bLarTSiPJ-cTAfT-V_MU-YEgB-XqYsBhS30,2637
20
20
  qulab/scan/query.py,sha256=RM8bG4Tcx_PaNk8tv9HdlTZ1dGuuSr3sZVkYVq2BtfQ,12183
21
21
  qulab/scan/record.py,sha256=MVmxhIzwmOju7eWxJEWsqJZlVgrDeRXGMfNvXImj7Ms,21883
22
- qulab/scan/scan.py,sha256=HMeVxoaEWjOj0eBusrQ-QQUFDAc_ivoLv95XkDmOS70,39947
23
- qulab/scan/server.py,sha256=em2tM_JPySnUNhusvl92ZPUGxz1mM34jFo5v2ji4uy0,19611
24
- qulab/scan/space.py,sha256=S-jHaXXf12FzjajOhPsZ-mkpGzAxrFAI3tRuQrvTBKg,6274
25
- qulab/scan/utils.py,sha256=30qnYvyFyctwcWxOCUpCNxXgGysA7xdIDzYbjwxGUzA,3744
22
+ qulab/scan/scan.py,sha256=p7LZvmjDSnUHeszgILlEXFIspHVPLk9Rt2dVqFtNsaQ,40645
23
+ qulab/scan/server.py,sha256=iT9wMEUQ2Pz_5nioHA7nII1MBPpQi-rCFbEJnRdWkDg,20487
24
+ qulab/scan/space.py,sha256=t8caa_gKlnhaAIEksJyxINUTecOS7lMWAz1HDKlVcds,6909
25
+ qulab/scan/utils.py,sha256=YXFA19HEakHB5TaPOU9CjQ1Lc3GUJaSIGOtiWjAWsG4,6353
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.3.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
82
- QuLab-2.3.3.dist-info/METADATA,sha256=jKqntTp7yF3mnpI4ItBA9tLV0zjlI-v_uRTrf_AonRk,3609
83
- QuLab-2.3.3.dist-info/WHEEL,sha256=rt4ogJhfwzOIZpQG1-igUOP-t_yXz3fi7xFfo-fNzUo,101
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.5.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
82
+ QuLab-2.3.5.dist-info/METADATA,sha256=CUbsOaCfpAwZ7tf7MIywKpb0DuidIos4u6sMgE2PXKs,3735
83
+ QuLab-2.3.5.dist-info/WHEEL,sha256=YqgvAYDJ-im_5PHkg0IY61P_j_tf16xq16unjojo7Hs,101
84
+ QuLab-2.3.5.dist-info/entry_points.txt,sha256=ohBzutEnQimP_BZWiuXdSliu4QAYSHHcN0PZD8c7ZCY,46
85
+ QuLab-2.3.5.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
86
+ QuLab-2.3.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (71.0.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp311-cp311-win_amd64
5
5
 
Binary file
qulab/scan/optimize.py CHANGED
@@ -72,5 +72,5 @@ class NgOptimizer():
72
72
  if history:
73
73
  ret.x_iters = self._all_x
74
74
  ret.func_vals = self._all_y
75
- ret.fun = recommendation.value
75
+ # ret.fun = recommendation.value
76
76
  return ret
qulab/scan/scan.py CHANGED
@@ -6,9 +6,7 @@ import itertools
6
6
  import lzma
7
7
  import os
8
8
  import pickle
9
- import platform
10
9
  import re
11
- import subprocess
12
10
  import sys
13
11
  import uuid
14
12
  from concurrent.futures import ProcessPoolExecutor
@@ -26,7 +24,8 @@ from .optimize import NgOptimizer
26
24
  from .record import Record
27
25
  from .server import default_record_port
28
26
  from .space import Optimizer, OptimizeSpace, Space
29
- from .utils import async_zip, call_function, dump_globals
27
+ from .utils import (async_zip, call_function, dump_dict, dump_globals,
28
+ get_installed_packages, get_system_info, yapf_reformat)
30
29
 
31
30
  try:
32
31
  from tqdm.notebook import tqdm
@@ -58,57 +57,34 @@ else:
58
57
  default_executor = default_server
59
58
 
60
59
 
61
- def yapf_reformat(cell_text):
62
- try:
63
- import isort
64
- import yapf.yapflib.yapf_api
65
-
66
- fname = f"f{uuid.uuid1().hex}"
67
-
68
- def wrap(source):
69
- lines = [f"async def {fname}():"]
70
- for line in source.split('\n'):
71
- lines.append(" " + line)
72
- return '\n'.join(lines)
60
+ class Promise():
61
+ __slots__ = ['task', 'key', 'attr']
73
62
 
74
- def unwrap(source):
75
- lines = []
76
- for line in source.split('\n'):
77
- if line.startswith(f"async def {fname}():"):
78
- continue
79
- lines.append(line[4:])
80
- return '\n'.join(lines)
63
+ def __init__(self, task, key=None, attr=None):
64
+ self.task = task
65
+ self.key = key
66
+ self.attr = attr
81
67
 
82
- cell_text = re.sub('^%', '#%#', cell_text, flags=re.M)
83
- reformated_text = unwrap(
84
- yapf.yapflib.yapf_api.FormatCode(wrap(isort.code(cell_text)))[0])
85
- return re.sub('^#%#', '%', reformated_text, flags=re.M)
86
- except:
87
- return cell_text
68
+ def __await__(self):
88
69
 
70
+ async def _getitem(task, key):
71
+ return (await task)[key]
89
72
 
90
- def get_installed_packages():
91
- result = subprocess.run([sys.executable, '-m', 'pip', 'freeze'],
92
- stdout=subprocess.PIPE,
93
- text=True)
73
+ async def _getattr(task, attr):
74
+ return getattr(await task, attr)
94
75
 
95
- lines = result.stdout.split('\n')
96
- packages = []
97
- for line in lines:
98
- if line:
99
- packages.append(line)
100
- return packages
76
+ if self.key is not None:
77
+ return _getitem(self.task, self.key).__await__()
78
+ elif self.attr is not None:
79
+ return _getattr(self.task, self.attr).__await__()
80
+ else:
81
+ return self.task.__await__()
101
82
 
83
+ def __getitem__(self, key):
84
+ return Promise(self.task, key, None)
102
85
 
103
- def get_system_info():
104
- info = {
105
- 'OS': platform.uname()._asdict(),
106
- 'Python': sys.version,
107
- 'PythonExecutable': sys.executable,
108
- 'PythonPath': sys.path,
109
- 'packages': get_installed_packages()
110
- }
111
- return info
86
+ def __getattr__(self, attr):
87
+ return Promise(self.task, None, attr)
112
88
 
113
89
 
114
90
  def current_notebook():
@@ -170,52 +146,185 @@ def _get_depends(func: Callable):
170
146
  return args
171
147
 
172
148
 
173
- class Promise():
174
- __slots__ = ['task', 'key', 'attr']
149
+ def _run_function_in_process(buf):
150
+ func, args, kwds = dill.loads(buf)
151
+ return func(*args, **kwds)
175
152
 
176
- def __init__(self, task, key=None, attr=None):
177
- self.task = task
178
- self.key = key
179
- self.attr = attr
180
153
 
181
- def __await__(self):
154
+ async def update_variables(variables: dict[str, Any], updates: dict[str, Any],
155
+ setters: dict[str, Callable]):
156
+ coros = []
157
+ for name, value in updates.items():
158
+ if name in setters:
159
+ coro = setters[name](value)
160
+ if inspect.isawaitable(coro):
161
+ coros.append(coro)
162
+ variables[name] = value
163
+ if coros:
164
+ await asyncio.gather(*coros)
182
165
 
183
- async def _getitem(task, key):
184
- return (await task)[key]
185
166
 
186
- async def _getattr(task, attr):
187
- return getattr(await task, attr)
167
+ async def _iter_level(variables,
168
+ iters: list[tuple[str, Iterable | Expression | Callable
169
+ | OptimizeSpace]],
170
+ order: list[list[str]],
171
+ functions: dict[str, Callable | Expression],
172
+ optimizers: dict[str, Optimizer],
173
+ setters: dict[str, Callable] = {},
174
+ getters: dict[str, Callable] = {}):
175
+ iters_d = {}
176
+ env = Env()
177
+ env.variables = variables
178
+ opts = {}
188
179
 
189
- if self.key is not None:
190
- return _getitem(self.task, self.key).__await__()
191
- elif self.attr is not None:
192
- return _getattr(self.task, self.attr).__await__()
180
+ for name, iter in iters:
181
+ if isinstance(iter, OptimizeSpace):
182
+ if iter.optimizer.name not in opts:
183
+ opts[iter.optimizer.name] = iter.optimizer.create()
184
+ elif isinstance(iter, Expression):
185
+ iters_d[name] = iter.eval(env)
186
+ elif isinstance(iter, Space):
187
+ iters_d[name] = iter.toarray()
188
+ elif callable(iter):
189
+ iters_d[name] = await call_function(iter, variables)
193
190
  else:
194
- return self.task.__await__()
191
+ iters_d[name] = iter
195
192
 
196
- def __getitem__(self, key):
197
- return Promise(self.task, key, None)
193
+ maxiter = 0xffffffff
194
+ for name, opt in opts.items():
195
+ opt_cfg = optimizers[name]
196
+ maxiter = min(maxiter, opt_cfg.maxiter)
198
197
 
199
- def __getattr__(self, attr):
200
- return Promise(self.task, None, attr)
198
+ async for args in async_zip(*iters_d.values(), range(maxiter)):
199
+ await update_variables(variables, dict(zip(iters_d.keys(), args[:-1])),
200
+ setters)
201
+ for name, opt in opts.items():
202
+ args = opt.ask()
203
+ opt_cfg = optimizers[name]
204
+ await update_variables(variables, {
205
+ n: v
206
+ for n, v in zip(opt_cfg.dimensions.keys(), args)
207
+ }, setters)
201
208
 
209
+ await update_variables(
210
+ variables, await call_many_functions(order, functions, variables),
211
+ setters)
202
212
 
203
- def _run_function_in_process(buf):
204
- func, args, kwds = dill.loads(buf)
205
- return func(*args, **kwds)
213
+ yield variables
206
214
 
215
+ variables.update(await call_many_functions(order, getters, variables))
207
216
 
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
217
+ if opts:
218
+ for key in list(variables.keys()):
219
+ if key.startswith('*') or ',' in key:
220
+ await _unpack(key, variables)
221
+
222
+ for name, opt in opts.items():
223
+ opt_cfg = optimizers[name]
224
+ args = [variables[n] for n in opt_cfg.dimensions.keys()]
225
+
226
+ if name not in variables:
227
+ raise ValueError(f'{name} not in variables.')
228
+ fun = variables[name]
229
+ if inspect.isawaitable(fun):
230
+ fun = await fun
231
+ if opt_cfg.minimize:
232
+ opt.tell(args, fun)
233
+ else:
234
+ opt.tell(args, -fun)
235
+
236
+ if opts:
237
+ for name, opt in opts.items():
238
+ opt_cfg = optimizers[name]
239
+ result = opt.get_result()
240
+ await update_variables(
241
+ variables, {
242
+ name: value
243
+ for name, value in zip(opt_cfg.dimensions.keys(), result.x)
244
+ }, setters)
245
+
246
+ yield variables
247
+
248
+ variables.update(await call_many_functions(order, getters, variables))
249
+
250
+ for key in list(variables.keys()):
251
+ if key.startswith('*') or ',' in key:
252
+ await _unpack(key, variables)
253
+
254
+
255
+ async def call_many_functions(order: list[list[str]],
256
+ functions: dict[str, Callable],
257
+ variables: dict[str, Any]) -> dict[str, Any]:
258
+ ret = {}
259
+ for group in order:
260
+ waited = []
261
+ coros = []
262
+ for name in group:
263
+ if name in functions:
264
+ waited.append(name)
265
+ coros.append(call_function(functions[name], variables | ret))
266
+ if coros:
267
+ results = await asyncio.gather(*coros)
268
+ ret.update(dict(zip(waited, results)))
269
+ return ret
270
+
271
+
272
+ async def _unpack(key, variables):
273
+ x = variables[key]
274
+ if inspect.isawaitable(x):
275
+ x = await x
276
+ if key.startswith('**'):
277
+ assert isinstance(
278
+ x, dict), f"Should promise a dict for `**` symbol. {key}"
279
+ if "{key}" in key:
280
+ for k, v in x.items():
281
+ variables[key[2:].format(key=k)] = v
282
+ else:
283
+ variables.update(x)
284
+ elif key.startswith('*'):
285
+ assert isinstance(
286
+ x, (list, tuple,
287
+ np.ndarray)), f"Should promise a list for `*` symbol. {key}"
288
+ for i, v in enumerate(x):
289
+ k = key[1:].format(i=i)
290
+ variables[k] = v
291
+ elif ',' in key:
292
+ keys1, keys2 = [], []
293
+ args = None
294
+ for k in key.split(','):
295
+ if k.startswith('*'):
296
+ if args is None:
297
+ args = k
298
+ else:
299
+ raise ValueError(f'Only one `*` symbol is allowed. {key}')
300
+ elif args is None:
301
+ keys1.append(k)
302
+ else:
303
+ keys2.append(k)
304
+ assert isinstance(
305
+ x,
306
+ (list, tuple,
307
+ np.ndarray)), f"Should promise a list for multiple symbols. {key}"
308
+ if args is None:
309
+ assert len(keys1) == len(
310
+ x), f"Length of keys and values should be equal. {key}"
311
+ for k, v in zip(keys1, x):
312
+ variables[k] = v
216
313
  else:
217
- d[key] = dill.dumps(value)
218
- return dill.dumps(description)
314
+ assert len(keys1) + len(keys2) <= len(
315
+ x), f"Too many values for unpacking. {key}"
316
+ for k, v in zip(keys1, x[:len(keys1)]):
317
+ variables[k] = v
318
+ end = -len(keys2) if keys2 else None
319
+ for i, v in enumerate(x[len(keys1):end]):
320
+ k = args[1:].format(i=i)
321
+ variables[k] = v
322
+ if keys2:
323
+ for k, v in zip(keys2, x[end:]):
324
+ variables[k] = v
325
+ else:
326
+ return
327
+ del variables[key]
219
328
 
220
329
 
221
330
  class Scan():
@@ -245,6 +354,7 @@ class Scan():
245
354
  self.id = task_uuid()
246
355
  self.record = None
247
356
  self.config = {} if config is None else copy.deepcopy(config)
357
+ self._raw_config_copy = copy.deepcopy(self.config)
248
358
  self.description = {
249
359
  'app': app,
250
360
  'tags': tags,
@@ -265,7 +375,7 @@ class Scan():
265
375
  'filters': {},
266
376
  'total': {},
267
377
  'database': database,
268
- 'hiden': ['self', 'config', r'^__.*', r'.*__$'],
378
+ 'hiden': ['self', 'config', r'^__.*', r'.*__$', r'^#.*'],
269
379
  'entry': {
270
380
  'system': get_system_info(),
271
381
  'env': {},
@@ -277,6 +387,7 @@ class Scan():
277
387
  self._current_level = 0
278
388
  self._variables = {}
279
389
  self._main_task = None
390
+ self._background_tasks = ()
280
391
  self._sock = None
281
392
  self._sem = asyncio.Semaphore(max_promise + 1)
282
393
  self._bar: dict[int, tqdm] = {}
@@ -300,6 +411,7 @@ class Scan():
300
411
  del state['record']
301
412
  del state['_sock']
302
413
  del state['_main_task']
414
+ del state['_background_tasks']
303
415
  del state['_bar']
304
416
  del state['_msg_queue']
305
417
  del state['_prm_queue']
@@ -312,6 +424,7 @@ class Scan():
312
424
  self.record = None
313
425
  self._sock = None
314
426
  self._main_task = None
427
+ self._background_tasks = ()
315
428
  self._bar = {}
316
429
  self._prm_queue = asyncio.Queue()
317
430
  self._msg_queue = asyncio.Queue(self._max_message)
@@ -396,7 +509,7 @@ class Scan():
396
509
 
397
510
  if self.config:
398
511
  self.description['config'] = await create_config(
399
- self.config, self.description['database'], self._sock)
512
+ self._raw_config_copy, self.description['database'], self._sock)
400
513
  if current_notebook() is None:
401
514
  await create_notebook('untitle', self.description['database'],
402
515
  self._sock)
@@ -412,7 +525,11 @@ class Scan():
412
525
  'method':
413
526
  'record_create',
414
527
  'description':
415
- _dump_description(self.description)
528
+ dump_dict(self.description,
529
+ keys=[
530
+ 'intrinsic_loops', 'app', 'tags', 'loops',
531
+ 'independent_variables', 'axis', 'config', 'entry'
532
+ ])
416
533
  })
417
534
 
418
535
  record_id = await self._sock.recv_pyobj()
@@ -470,9 +587,9 @@ class Scan():
470
587
  if depends:
471
588
  self.add_depends(name, depends)
472
589
  s = ','.join(depends)
473
- self.description['functions'][f'_tmp_{name}'] = value
590
+ self.description['functions'][f'#{name}'] = value
474
591
  self.description['functions'][name] = eval(
475
- f"lambda self, {s}: self.description['functions']['_tmp_{name}']({s})"
592
+ f"lambda self, {s}: self.description['functions']['#{name}']({s})"
476
593
  )
477
594
  else:
478
595
  self.add_depends(name, _get_depends(value))
@@ -612,27 +729,33 @@ class Scan():
612
729
  await task
613
730
  self._msg_queue.task_done()
614
731
 
732
+ @contextlib.asynccontextmanager
733
+ async def _send_msg_and_update_bar(self):
734
+ send_msg_task = asyncio.create_task(self._send_msg())
735
+ update_progress_task = asyncio.create_task(self._update_progress())
736
+ try:
737
+ yield (send_msg_task, update_progress_task)
738
+ finally:
739
+ update_progress_task.cancel()
740
+ send_msg_task.cancel()
741
+ while True:
742
+ try:
743
+ task = self._prm_queue.get_nowait()
744
+ except:
745
+ break
746
+ try:
747
+ task.cancel()
748
+ except:
749
+ pass
750
+
751
+ async def _check_background_tasks(self):
752
+ for task in self._background_tasks:
753
+ if task.done():
754
+ await task
755
+
615
756
  async def run(self):
616
757
  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
758
+ self._background_tasks = ()
636
759
 
637
760
  if isinstance(
638
761
  self.description['database'],
@@ -641,12 +764,14 @@ class Scan():
641
764
  connect=self.description['database'],
642
765
  socket=self._sock) as socket:
643
766
  self._sock = socket
644
- async with send_msg_and_update_bar(self):
767
+ async with self._send_msg_and_update_bar() as background_tasks:
768
+ self._background_tasks = background_tasks
645
769
  await self._run()
646
770
  else:
647
771
  if self.config:
648
- self.description['config'] = copy.deepcopy(self.config)
649
- async with send_msg_and_update_bar(self):
772
+ self.description['config'] = self._raw_config_copy
773
+ async with self._send_msg_and_update_bar() as background_tasks:
774
+ self._background_tasks = background_tasks
650
775
  await self._run()
651
776
 
652
777
  async def _run(self):
@@ -671,11 +796,11 @@ class Scan():
671
796
  self.description['functions'], self.variables)
672
797
  await update_variables(self.variables, updates,
673
798
  self.description['setters'])
674
-
799
+ await self._check_background_tasks()
675
800
  await self.work()
676
801
  for level, bar in self._bar.items():
677
802
  bar.close()
678
-
803
+ await self._check_background_tasks()
679
804
  if self._single_step:
680
805
  self.variables.update(await call_many_functions(
681
806
  self.description['order'].get(-1, []),
@@ -683,7 +808,7 @@ class Scan():
683
808
 
684
809
  await self.emit(0, 0, 0, self.variables)
685
810
  await self.emit(-1, 0, 0, {})
686
-
811
+ await self._check_background_tasks()
687
812
  await self._prm_queue.join()
688
813
  await self._msg_queue.join()
689
814
  return self.variables
@@ -747,6 +872,7 @@ class Scan():
747
872
  | {'config': self._synchronize_config},
748
873
  self.description['optimizers'], self.description['setters'],
749
874
  self.description['getters']):
875
+ await self._check_background_tasks()
750
876
  self._current_level += 1
751
877
  if await self._filter(variables, self.current_level - 1):
752
878
  yield variables
@@ -758,11 +884,13 @@ class Scan():
758
884
  self._current_level -= 1
759
885
  self._prm_queue.put_nowait(
760
886
  self._update_progress_bar(self.current_level, 1))
887
+ await self._check_background_tasks()
761
888
  if self.current_level == 0:
762
889
  await self.emit(self.current_level - 1, 0, 0, {})
763
890
  for name, value in self.variables.items():
764
891
  if inspect.isawaitable(value):
765
892
  self.variables[name] = await value
893
+ await self._check_background_tasks()
766
894
  await self._prm_queue.join()
767
895
 
768
896
  async def work(self, **kwds):
@@ -823,7 +951,7 @@ class Scan():
823
951
  return await awaitable
824
952
 
825
953
 
826
- def assymbly(description):
954
+ def _get_environment(description):
827
955
  import __main__
828
956
  from IPython import get_ipython
829
957
 
@@ -853,6 +981,10 @@ def assymbly(description):
853
981
 
854
982
  description['entry']['env'] = {k: v for k, v in os.environ.items()}
855
983
 
984
+ return description
985
+
986
+
987
+ def _mapping_levels(description):
856
988
  mapping = {
857
989
  label: level
858
990
  for level, label in enumerate(
@@ -885,8 +1017,23 @@ def assymbly(description):
885
1017
  len(space))
886
1018
  except:
887
1019
  pass
1020
+ return levels
1021
+
888
1022
 
1023
+ def _get_independent_variables(description):
1024
+ independent_variables = set(description['intrinsic_loops'].keys())
1025
+ for level, loops in description['loops'].items():
1026
+ for name, iterable in loops:
1027
+ if isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
1028
+ independent_variables.add(name)
1029
+ return independent_variables
1030
+
1031
+
1032
+ def _build_dependents(description, levels, independent_variables):
889
1033
  dependents = copy.deepcopy(description['dependents'])
1034
+ all_nodes = set(description['dependents'].keys())
1035
+ for key, deps in dependents.items():
1036
+ all_nodes.update(deps)
890
1037
 
891
1038
  for level in levels:
892
1039
  range_list = description['loops'].get(level, [])
@@ -899,6 +1046,13 @@ def assymbly(description):
899
1046
  dependents[name] = set()
900
1047
  dependents[name].add(f'#__loop_{level}')
901
1048
 
1049
+ after_yield = set()
1050
+ for key in all_nodes:
1051
+ if key not in independent_variables and key not in description[
1052
+ 'consts']:
1053
+ if key not in dependents:
1054
+ after_yield.add(key)
1055
+
902
1056
  def _get_all_depends(key, graph):
903
1057
  ret = set()
904
1058
  if key not in graph:
@@ -912,7 +1066,13 @@ def assymbly(description):
912
1066
  full_depends = {}
913
1067
  for key in dependents:
914
1068
  full_depends[key] = _get_all_depends(key, dependents)
1069
+ if full_depends[key] & after_yield:
1070
+ after_yield.add(key)
1071
+
1072
+ return dependents, full_depends, after_yield
1073
+
915
1074
 
1075
+ def _build_order(description, levels, dependents, full_depends):
916
1076
  levels = {}
917
1077
  passed = set()
918
1078
  all_keys = set(description['consts'].keys())
@@ -957,8 +1117,9 @@ def assymbly(description):
957
1117
  description['order'][level].append(ready)
958
1118
  keys -= set(ready)
959
1119
 
1120
+
1121
+ def _make_axis(description):
960
1122
  axis = {}
961
- independent_variables = set(description['intrinsic_loops'].keys())
962
1123
 
963
1124
  for name in description['consts']:
964
1125
  axis[name] = ()
@@ -967,8 +1128,6 @@ def assymbly(description):
967
1128
  if isinstance(iterable, OptimizeSpace):
968
1129
  axis[name] = tuple(range(level + 1))
969
1130
  continue
970
- elif isinstance(iterable, (np.ndarray, list, tuple, range, Space)):
971
- independent_variables.add(name)
972
1131
  axis[name] = (level, )
973
1132
 
974
1133
  for level, group in description['order'].items():
@@ -989,176 +1148,19 @@ def assymbly(description):
989
1148
  k: tuple([x for x in v if x >= 0])
990
1149
  for k, v in axis.items()
991
1150
  }
992
- description['independent_variables'] = independent_variables
993
-
994
- return description
995
-
996
-
997
- async def update_variables(variables: dict[str, Any], updates: dict[str, Any],
998
- setters: dict[str, Callable]):
999
- coros = []
1000
- for name, value in updates.items():
1001
- if name in setters:
1002
- coro = setters[name](value)
1003
- if inspect.isawaitable(coro):
1004
- coros.append(coro)
1005
- variables[name] = value
1006
- if coros:
1007
- await asyncio.gather(*coros)
1008
-
1009
-
1010
- async def _iter_level(variables,
1011
- iters: list[tuple[str, Iterable | Expression | Callable
1012
- | OptimizeSpace]],
1013
- order: list[list[str]],
1014
- functions: dict[str, Callable | Expression],
1015
- optimizers: dict[str, Optimizer],
1016
- setters: dict[str, Callable] = {},
1017
- getters: dict[str, Callable] = {}):
1018
- iters_d = {}
1019
- env = Env()
1020
- env.variables = variables
1021
- opts = {}
1022
1151
 
1023
- for name, iter in iters:
1024
- if isinstance(iter, OptimizeSpace):
1025
- if iter.optimizer.name not in opts:
1026
- opts[iter.optimizer.name] = iter.optimizer.create()
1027
- elif isinstance(iter, Expression):
1028
- iters_d[name] = iter.eval(env)
1029
- elif isinstance(iter, Space):
1030
- iters_d[name] = iter.toarray()
1031
- elif callable(iter):
1032
- iters_d[name] = await call_function(iter, variables)
1033
- else:
1034
- iters_d[name] = iter
1035
-
1036
- maxiter = 0xffffffff
1037
- for name, opt in opts.items():
1038
- opt_cfg = optimizers[name]
1039
- maxiter = min(maxiter, opt_cfg.maxiter)
1040
-
1041
- async for args in async_zip(*iters_d.values(), range(maxiter)):
1042
- await update_variables(variables, dict(zip(iters_d.keys(), args[:-1])),
1043
- setters)
1044
- for name, opt in opts.items():
1045
- args = opt.ask()
1046
- opt_cfg = optimizers[name]
1047
- await update_variables(variables, {
1048
- n: v
1049
- for n, v in zip(opt_cfg.dimensions.keys(), args)
1050
- }, setters)
1051
-
1052
- await update_variables(
1053
- variables, await call_many_functions(order, functions, variables),
1054
- setters)
1055
-
1056
- yield variables
1057
-
1058
- variables.update(await call_many_functions(order, getters, variables))
1059
-
1060
- if opts:
1061
- for key in list(variables.keys()):
1062
- if key.startswith('*') or ',' in key:
1063
- await _unpack(key, variables)
1064
-
1065
- for name, opt in opts.items():
1066
- opt_cfg = optimizers[name]
1067
- args = [variables[n] for n in opt_cfg.dimensions.keys()]
1068
-
1069
- if name not in variables:
1070
- raise ValueError(f'{name} not in variables.')
1071
- fun = variables[name]
1072
- if inspect.isawaitable(fun):
1073
- fun = await fun
1074
- if opt_cfg.minimize:
1075
- opt.tell(args, fun)
1076
- else:
1077
- opt.tell(args, -fun)
1078
-
1079
- for name, opt in opts.items():
1080
- opt_cfg = optimizers[name]
1081
- result = opt.get_result()
1082
- await update_variables(
1083
- variables, {
1084
- name: value
1085
- for name, value in zip(opt_cfg.dimensions.keys(), result.x)
1086
- }, setters)
1087
- variables[name] = result.fun
1088
- if opts:
1089
- yield variables
1090
1152
 
1153
+ def assymbly(description):
1154
+ _get_environment(description)
1155
+ levels = _mapping_levels(description)
1156
+ independent_variables = _get_independent_variables(description)
1157
+ description['independent_variables'] = independent_variables
1091
1158
 
1092
- async def call_many_functions(order: list[list[str]],
1093
- functions: dict[str, Callable],
1094
- variables: dict[str, Any]) -> dict[str, Any]:
1095
- ret = {}
1096
- for group in order:
1097
- waited = []
1098
- coros = []
1099
- for name in group:
1100
- if name in functions:
1101
- waited.append(name)
1102
- coros.append(call_function(functions[name], variables | ret))
1103
- if coros:
1104
- results = await asyncio.gather(*coros)
1105
- ret.update(dict(zip(waited, results)))
1106
- return ret
1159
+ dependents, full_depends, after_yield = _build_dependents(
1160
+ description, levels, independent_variables)
1107
1161
 
1162
+ _build_order(description, levels, dependents, full_depends)
1163
+ _make_axis(description)
1108
1164
 
1109
- async def _unpack(key, variables):
1110
- x = variables[key]
1111
- if inspect.isawaitable(x):
1112
- x = await x
1113
- if key.startswith('**'):
1114
- assert isinstance(
1115
- x, dict), f"Should promise a dict for `**` symbol. {key}"
1116
- if "{key}" in key:
1117
- for k, v in x.items():
1118
- variables[key[2:].format(key=k)] = v
1119
- else:
1120
- variables.update(x)
1121
- elif key.startswith('*'):
1122
- assert isinstance(
1123
- x, (list, tuple,
1124
- np.ndarray)), f"Should promise a list for `*` symbol. {key}"
1125
- for i, v in enumerate(x):
1126
- k = key[1:].format(i=i)
1127
- variables[k] = v
1128
- elif ',' in key:
1129
- keys1, keys2 = [], []
1130
- args = None
1131
- for k in key.split(','):
1132
- if k.startswith('*'):
1133
- if args is None:
1134
- args = k
1135
- else:
1136
- raise ValueError(f'Only one `*` symbol is allowed. {key}')
1137
- elif args is None:
1138
- keys1.append(k)
1139
- else:
1140
- keys2.append(k)
1141
- assert isinstance(
1142
- x,
1143
- (list, tuple,
1144
- np.ndarray)), f"Should promise a list for multiple symbols. {key}"
1145
- if args is None:
1146
- assert len(keys1) == len(
1147
- x), f"Length of keys and values should be equal. {key}"
1148
- for k, v in zip(keys1, x):
1149
- variables[k] = v
1150
- else:
1151
- assert len(keys1) + len(keys2) <= len(
1152
- x), f"Too many values for unpacking. {key}"
1153
- for k, v in zip(keys1, x[:len(keys1)]):
1154
- variables[k] = v
1155
- end = -len(keys2) if keys2 else None
1156
- for i, v in enumerate(x[len(keys1):end]):
1157
- k = args[1:].format(i=i)
1158
- variables[k] = v
1159
- if keys2:
1160
- for k, v in zip(keys2, x[end:]):
1161
- variables[k] = v
1162
- else:
1163
- return
1164
- del variables[key]
1165
+ return description
1166
+ return description
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
 
@@ -62,14 +73,15 @@ def clear_cache():
62
73
  return
63
74
 
64
75
  logger.debug(f"clear_cache record_cache: {len(record_cache)}")
65
- for k, (t, _) in zip(sorted(record_cache.items(), key=lambda x: x[1][0]),
66
- range(len(record_cache) - CACHE_SIZE)):
76
+ for (k, (t, r),
77
+ i) in zip(sorted(record_cache.items(), key=lambda x: x[1][0]),
78
+ range(len(record_cache) - CACHE_SIZE)):
67
79
  del record_cache[k]
68
80
 
69
81
  logger.debug(f"clear_cache buffer_list_cache: {len(buffer_list_cache)}")
70
- for k, (t,
71
- _) in zip(sorted(buffer_list_cache.items(), key=lambda x: x[1][0]),
72
- range(len(buffer_list_cache) - CACHE_SIZE)):
82
+ for (k, (t, r),
83
+ i) in zip(sorted(buffer_list_cache.items(), key=lambda x: x[1][0]),
84
+ range(len(buffer_list_cache) - CACHE_SIZE)):
73
85
  del buffer_list_cache[k]
74
86
  logger.debug(f"clear_cache done.")
75
87
 
@@ -173,7 +185,7 @@ def record_delete(session: Session, record_id: int, datapath: Path):
173
185
  session.commit()
174
186
 
175
187
 
176
- @logger.catch
188
+ @logger.catch(reraise=True)
177
189
  async def handle(session: Session, request: Request, datapath: Path):
178
190
 
179
191
  msg = request.msg
@@ -222,7 +234,7 @@ async def handle(session: Session, request: Request, datapath: Path):
222
234
  logger.debug(f"end bufferlist_iter_exit: {msg}")
223
235
  case 'record_create':
224
236
  logger.debug(f"record_create")
225
- description = dill.loads(msg['description'])
237
+ description = load_dict(msg['description'])
226
238
  await reply(request, record_create(session, description, datapath))
227
239
  logger.debug(f"reply record_create")
228
240
  case 'record_append':
@@ -349,29 +361,16 @@ async def handle_with_timeout(session: Session, request: Request,
349
361
  f"Task handling request {request} timed out and was cancelled.")
350
362
  await reply(request, 'timeout')
351
363
  except Exception as e:
352
- await reply(request, f'{e!r}')
364
+ logger.error(f"Task handling request {request} failed: {e!r}")
365
+ await reply(request, ErrorResponse(f'{e!r}'))
366
+ logger.debug(f"Task handling request {request} finished.")
353
367
 
354
368
 
355
369
  async def serv(port,
356
370
  datapath,
357
371
  url='',
358
372
  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
-
373
+ interval=60):
375
374
  logger.debug('Creating socket...')
376
375
  async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
377
376
  logger.info(f'Server started at port {port}.')
@@ -392,8 +391,11 @@ async def serv(port,
392
391
  received += len(msg)
393
392
  try:
394
393
  req = Request(sock, identity, msg)
395
- except:
394
+ except Exception as e:
396
395
  logger.exception('bad request')
396
+ await sock.send_multipart(
397
+ [identity,
398
+ pickle.dumps(ErrorResponse(f'{e!r}'))])
397
399
  continue
398
400
  asyncio.create_task(
399
401
  handle_with_timeout(session, req, datapath,
@@ -415,9 +417,21 @@ async def main(port,
415
417
  no_watch=True,
416
418
  debug=False):
417
419
  if no_watch:
420
+ logger.remove()
421
+ if debug:
422
+ level = 'DEBUG'
423
+ else:
424
+ level = 'INFO'
425
+ if log == 'stderr':
426
+ logger.add(sys.stderr, level=level)
427
+ elif log == 'stdout':
428
+ logger.add(sys.stdout, level=level)
429
+ else:
430
+ logger.add(sys.stderr, level=level)
431
+ logger.add(log, level=level)
432
+ logger.debug(f"logging level: {level}")
418
433
  logger.info('Server starting...')
419
- await serv(port, datapath, url, buffer * 1024 * 1024, interval, log,
420
- debug)
434
+ await serv(port, datapath, url, buffer * 1024 * 1024, interval)
421
435
  else:
422
436
  process = None
423
437
 
@@ -444,10 +458,24 @@ async def main(port,
444
458
  f'killed process. PID={process.pid}, returncode={process.returncode}'
445
459
  )
446
460
  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}",
461
+ sys.executable,
462
+ "-m",
463
+ "qulab",
464
+ "server",
465
+ "--port",
466
+ f"{port}",
467
+ "--datapath",
468
+ f"{datapath}",
469
+ "--url",
470
+ f"{url}",
471
+ "--timeout",
472
+ f"{timeout}",
473
+ "--buffer",
474
+ f"{buffer}",
475
+ "--interval",
476
+ f"{interval}",
477
+ "--log",
478
+ f"{log}",
451
479
  ]
452
480
  if url:
453
481
  cmd.extend(['--url', url])
@@ -455,10 +483,7 @@ async def main(port,
455
483
  cmd.append('--debug')
456
484
  cmd.append("--no-watch")
457
485
  logger.debug(f"starting process: {' '.join(cmd)}")
458
- process = subprocess.Popen(cmd,
459
- stdout=subprocess.PIPE,
460
- stderr=subprocess.PIPE,
461
- cwd=os.getcwd())
486
+ process = subprocess.Popen(cmd, cwd=os.getcwd())
462
487
  logger.debug(
463
488
  f'process started. PID={process.pid}, returncode={process.returncode}'
464
489
  )
@@ -490,9 +515,15 @@ async def main(port,
490
515
  @click.option('--debug', is_flag=True, help='Debug mode.')
491
516
  def server(port, datapath, url, timeout, buffer, interval, log, no_watch,
492
517
  debug):
493
- asyncio.run(
494
- main(port, Path(datapath), url, timeout, buffer, interval, log,
495
- True, debug))
518
+ try:
519
+ import uvloop
520
+ uvloop.run(
521
+ main(port, Path(datapath), url, timeout, buffer, interval, log,
522
+ True, debug))
523
+ except ImportError:
524
+ asyncio.run(
525
+ main(port, Path(datapath), url, timeout, buffer, interval, log,
526
+ True, debug))
496
527
 
497
528
 
498
529
  if __name__ == "__main__":
qulab/scan/space.py CHANGED
@@ -25,12 +25,27 @@ class Space():
25
25
 
26
26
  @classmethod
27
27
  def fromarray(cls, space):
28
- if isinstance(space, Space):
28
+ if isinstance(space, (Space, range, enumerate, tuple)):
29
29
  return space
30
- if isinstance(space, (list, tuple)):
31
- array = np.array(space)
30
+ if isinstance(space, list):
31
+ if isinstance(space[0], int):
32
+ try:
33
+ if all(i == j for i, j in zip(
34
+ space,
35
+ range(space[0], space[-1] + 1, space[1] -
36
+ space[0]))):
37
+ return range(space[0], space[-1] + 1,
38
+ space[1] - space[0])
39
+ except:
40
+ return space
41
+ elif isinstance(space[0], (float, complex, np.ndarray)):
42
+ array = np.array(space)
43
+ else:
44
+ return space
32
45
  elif isinstance(space, np.ndarray):
33
46
  array = space
47
+ else:
48
+ return space
34
49
  try:
35
50
  a = np.linspace(array[0], array[-1], len(array), dtype=array.dtype)
36
51
  if np.allclose(a, array):
qulab/scan/utils.py CHANGED
@@ -1,6 +1,11 @@
1
1
  import ast
2
2
  import asyncio
3
3
  import inspect
4
+ import platform
5
+ import re
6
+ import subprocess
7
+ import sys
8
+ import uuid
4
9
  import warnings
5
10
  from typing import Any, Callable
6
11
 
@@ -29,6 +34,46 @@ class TooLarge:
29
34
  return f'<TooLarge: {self.type} at 0x{id(self):x}>'
30
35
 
31
36
 
37
+ def dump_dict(d, keys=[]):
38
+ ret = {}
39
+
40
+ for key, value in d.items():
41
+ if key in keys:
42
+ ret[key] = value
43
+ continue
44
+ if isinstance(value, dict) and isinstance(key, str):
45
+ ret[key] = dump_dict(value,
46
+ keys=[
47
+ k[len(key) + 1:] for k in keys
48
+ if k.startswith(f'{key}.')
49
+ ])
50
+ else:
51
+ try:
52
+ ret[key] = dill.dumps(value)
53
+ except:
54
+ ret[key] = Unpicklable(value)
55
+
56
+ return dill.dumps(ret)
57
+
58
+
59
+ def load_dict(buff):
60
+ if isinstance(buff, dict):
61
+ return {key: load_dict(value) for key, value in buff.items()}
62
+
63
+ if not isinstance(buff, bytes):
64
+ return buff
65
+
66
+ try:
67
+ ret = dill.loads(buff)
68
+ except:
69
+ return buff
70
+
71
+ if isinstance(ret, dict):
72
+ return load_dict(ret)
73
+ else:
74
+ return ret
75
+
76
+
32
77
  def dump_globals(ns=None, *, size_limit=10 * 1024 * 1024, warn=False):
33
78
  import __main__
34
79
 
@@ -129,3 +174,56 @@ async def call_function(func: Callable | Expression, variables: dict[str,
129
174
  if inspect.isawaitable(ret):
130
175
  ret = await ret
131
176
  return ret
177
+
178
+
179
+ def yapf_reformat(cell_text):
180
+ try:
181
+ import isort
182
+ import yapf.yapflib.yapf_api
183
+
184
+ fname = f"f{uuid.uuid1().hex}"
185
+
186
+ def wrap(source):
187
+ lines = [f"async def {fname}():"]
188
+ for line in source.split('\n'):
189
+ lines.append(" " + line)
190
+ return '\n'.join(lines)
191
+
192
+ def unwrap(source):
193
+ lines = []
194
+ for line in source.split('\n'):
195
+ if line.startswith(f"async def {fname}():"):
196
+ continue
197
+ lines.append(line[4:])
198
+ return '\n'.join(lines)
199
+
200
+ cell_text = re.sub('^%', '#%#', cell_text, flags=re.M)
201
+ reformated_text = unwrap(
202
+ yapf.yapflib.yapf_api.FormatCode(wrap(isort.code(cell_text)))[0])
203
+ return re.sub('^#%#', '%', reformated_text, flags=re.M)
204
+ except:
205
+ return cell_text
206
+
207
+
208
+ def get_installed_packages():
209
+ result = subprocess.run([sys.executable, '-m', 'pip', 'freeze'],
210
+ stdout=subprocess.PIPE,
211
+ text=True)
212
+
213
+ lines = result.stdout.split('\n')
214
+ packages = []
215
+ for line in lines:
216
+ if line:
217
+ packages.append(line)
218
+ return packages
219
+
220
+
221
+ def get_system_info():
222
+ info = {
223
+ 'OS': platform.uname()._asdict(),
224
+ 'Python': sys.version,
225
+ 'PythonExecutable': sys.executable,
226
+ 'PythonPath': sys.path,
227
+ 'packages': get_installed_packages()
228
+ }
229
+ return info
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.3.3"
1
+ __version__ = "2.3.5"
File without changes