QuLab 2.7.12__tar.gz → 2.7.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {qulab-2.7.12 → qulab-2.7.14}/PKG-INFO +1 -1
  2. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/PKG-INFO +1 -1
  3. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/SOURCES.txt +3 -0
  4. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/cli.py +16 -0
  5. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/load.py +42 -74
  6. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/schedule.py +31 -15
  7. qulab-2.7.14/qulab/executor/template.py +173 -0
  8. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/transform.py +2 -0
  9. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/utils.py +12 -0
  10. qulab-2.7.14/qulab/tools/__init__.py +0 -0
  11. qulab-2.7.14/qulab/tools/connection_helper.py +39 -0
  12. qulab-2.7.14/qulab/version.py +1 -0
  13. qulab-2.7.12/qulab/version.py +0 -1
  14. {qulab-2.7.12 → qulab-2.7.14}/LICENSE +0 -0
  15. {qulab-2.7.12 → qulab-2.7.14}/MANIFEST.in +0 -0
  16. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/dependency_links.txt +0 -0
  17. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/entry_points.txt +0 -0
  18. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/requires.txt +0 -0
  19. {qulab-2.7.12 → qulab-2.7.14}/QuLab.egg-info/top_level.txt +0 -0
  20. {qulab-2.7.12 → qulab-2.7.14}/README.md +0 -0
  21. {qulab-2.7.12 → qulab-2.7.14}/pyproject.toml +0 -0
  22. {qulab-2.7.12 → qulab-2.7.14}/qulab/__init__.py +0 -0
  23. {qulab-2.7.12 → qulab-2.7.14}/qulab/__main__.py +0 -0
  24. {qulab-2.7.12 → qulab-2.7.14}/qulab/cli/__init__.py +0 -0
  25. {qulab-2.7.12 → qulab-2.7.14}/qulab/cli/commands.py +0 -0
  26. {qulab-2.7.12 → qulab-2.7.14}/qulab/cli/config.py +0 -0
  27. {qulab-2.7.12 → qulab-2.7.14}/qulab/dicttree.py +0 -0
  28. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/__init__.py +0 -0
  29. {qulab-2.7.12 → qulab-2.7.14}/qulab/executor/storage.py +0 -0
  30. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/__init__.py +0 -0
  31. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/__main__.py +0 -0
  32. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/config.py +0 -0
  33. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/dataset.py +0 -0
  34. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/event_queue.py +0 -0
  35. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/mainwindow.py +0 -0
  36. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/monitor.py +0 -0
  37. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/ploter.py +0 -0
  38. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/qt_compat.py +0 -0
  39. {qulab-2.7.12 → qulab-2.7.14}/qulab/monitor/toolbar.py +0 -0
  40. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/__init__.py +0 -0
  41. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/curd.py +0 -0
  42. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/expression.py +0 -0
  43. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/models.py +0 -0
  44. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/optimize.py +0 -0
  45. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/query.py +0 -0
  46. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/record.py +0 -0
  47. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/scan.py +0 -0
  48. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/server.py +0 -0
  49. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/space.py +0 -0
  50. {qulab-2.7.12 → qulab-2.7.14}/qulab/scan/utils.py +0 -0
  51. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/__init__.py +0 -0
  52. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/__main__.py +0 -0
  53. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/backend/__init__.py +0 -0
  54. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/backend/redis.py +0 -0
  55. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/base_dataset.py +0 -0
  56. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/chunk.py +0 -0
  57. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/dataset.py +0 -0
  58. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/file.py +0 -0
  59. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/__init__.py +0 -0
  60. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/base.py +0 -0
  61. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/config.py +0 -0
  62. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/file.py +0 -0
  63. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/ipy.py +0 -0
  64. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/models.py +0 -0
  65. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/record.py +0 -0
  66. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/report.py +0 -0
  67. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/models/tag.py +0 -0
  68. {qulab-2.7.12 → qulab-2.7.14}/qulab/storage/storage.py +0 -0
  69. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/__init__.py +0 -0
  70. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/chat.py +0 -0
  71. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/device/__init__.py +0 -0
  72. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/device/basedevice.py +0 -0
  73. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/device/loader.py +0 -0
  74. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/device/utils.py +0 -0
  75. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/drivers/FakeInstrument.py +0 -0
  76. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/drivers/__init__.py +0 -0
  77. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/ipy_events.py +0 -0
  78. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/__init__.py +0 -0
  79. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/bencoder.py +0 -0
  80. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/cli.py +0 -0
  81. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/dhcp.py +0 -0
  82. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/dhcpd.py +0 -0
  83. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/kad.py +0 -0
  84. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/kcp.py +0 -0
  85. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/net/nginx.py +0 -0
  86. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/progress.py +0 -0
  87. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/__init__.py +0 -0
  88. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/client.py +0 -0
  89. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/exceptions.py +0 -0
  90. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/msgpack.py +0 -0
  91. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/msgpack.pyi +0 -0
  92. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/router.py +0 -0
  93. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/rpc.py +0 -0
  94. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/serialize.py +0 -0
  95. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/server.py +0 -0
  96. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/socket.py +0 -0
  97. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/utils.py +0 -0
  98. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/worker.py +0 -0
  99. {qulab-2.7.12 → qulab-2.7.14}/qulab/sys/rpc/zmq_socket.py +0 -0
  100. {qulab-2.7.12 → qulab-2.7.14}/qulab/typing.py +0 -0
  101. {qulab-2.7.12 → qulab-2.7.14}/qulab/utils.py +0 -0
  102. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/__init__.py +0 -0
  103. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/__main__.py +0 -0
  104. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/_autoplot.py +0 -0
  105. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/plot_circ.py +0 -0
  106. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/plot_layout.py +0 -0
  107. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/plot_seq.py +0 -0
  108. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/qdat.py +0 -0
  109. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/rot3d.py +0 -0
  110. {qulab-2.7.12 → qulab-2.7.14}/qulab/visualization/widgets.py +0 -0
  111. {qulab-2.7.12 → qulab-2.7.14}/setup.cfg +0 -0
  112. {qulab-2.7.12 → qulab-2.7.14}/setup.py +0 -0
  113. {qulab-2.7.12 → qulab-2.7.14}/src/qulab.h +0 -0
  114. {qulab-2.7.12 → qulab-2.7.14}/tests/test_kad.py +0 -0
  115. {qulab-2.7.12 → qulab-2.7.14}/tests/test_scan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: QuLab
3
- Version: 2.7.12
3
+ Version: 2.7.14
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: QuLab
3
- Version: 2.7.12
3
+ Version: 2.7.14
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -23,6 +23,7 @@ qulab/executor/cli.py
23
23
  qulab/executor/load.py
24
24
  qulab/executor/schedule.py
25
25
  qulab/executor/storage.py
26
+ qulab/executor/template.py
26
27
  qulab/executor/transform.py
27
28
  qulab/executor/utils.py
28
29
  qulab/monitor/__init__.py
@@ -95,6 +96,8 @@ qulab/sys/rpc/socket.py
95
96
  qulab/sys/rpc/utils.py
96
97
  qulab/sys/rpc/worker.py
97
98
  qulab/sys/rpc/zmq_socket.py
99
+ qulab/tools/__init__.py
100
+ qulab/tools/connection_helper.py
98
101
  qulab/visualization/__init__.py
99
102
  qulab/visualization/__main__.py
100
103
  qulab/visualization/_autoplot.py
@@ -161,6 +161,15 @@ def get(key, api):
161
161
  def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
162
162
  """
163
163
  Run a workflow.
164
+
165
+ If the workflow has entries, run all entries.
166
+ If `--no-dependents` is set, only run the workflow itself.
167
+ If `--retry` is set, retry the workflow when calibration failed.
168
+ If `--freeze` is set, freeze the config table.
169
+ If `--plot` is set, plot the report.
170
+ If `--api` is set, use the api to get and update the config table.
171
+ If `--code` is not set, use the current working directory.
172
+ If `--data` is not set, use the `logs` directory in the code path.
164
173
  """
165
174
  logger.info(
166
175
  f'[CMD]: run {workflow} --code {code} --data {data} --api {api}'
@@ -227,6 +236,13 @@ def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
227
236
  def maintain(workflow, code, data, api, retry, plot):
228
237
  """
229
238
  Maintain a workflow.
239
+
240
+ If the workflow has entries, run all entries.
241
+ If `--retry` is set, retry the workflow when calibration failed.
242
+ If `--plot` is set, plot the report.
243
+ If `--api` is set, use the api to get and update the config table.
244
+ If `--code` is not set, use the current working directory.
245
+ If `--data` is not set, use the `logs` directory in the code path.
230
246
  """
231
247
  logger.info(
232
248
  f'[CMD]: maintain {workflow} --code {code} --data {data} --api {api}'
@@ -1,12 +1,6 @@
1
- import base64
2
1
  import graphlib
3
- import hashlib
4
2
  import inspect
5
- import lzma
6
3
  import pickle
7
- import re
8
- import string
9
- import textwrap
10
4
  import warnings
11
5
  from importlib.util import module_from_spec, spec_from_file_location
12
6
  from pathlib import Path
@@ -16,6 +10,8 @@ from typing import Any
16
10
  from loguru import logger
17
11
 
18
12
  from .storage import Report
13
+ from .template import (TemplateKeyError, TemplateTypeError, decode_mapping,
14
+ inject_mapping)
19
15
 
20
16
 
21
17
  class SetConfigWorkflow():
@@ -330,22 +326,6 @@ def load_workflow_from_file(file_name: str,
330
326
  return module
331
327
 
332
328
 
333
- def encode_mapping(mapping):
334
- mapping_bytes = lzma.compress(pickle.dumps(mapping))
335
- hash_str = hashlib.md5(mapping_bytes).hexdigest()[:8]
336
- mappping_code = '\n'.join(
337
- textwrap.wrap(base64.b64encode(mapping_bytes).decode(), 100))
338
- return hash_str, mappping_code
339
-
340
-
341
- def decode_mapping(hash_str, mappping_code):
342
- mapping_bytes = base64.b64decode(mappping_code.replace('\n', ''))
343
- if hash_str != hashlib.md5(mapping_bytes).hexdigest()[:8]:
344
- raise ValueError("Hash does not match")
345
- mapping = pickle.loads(lzma.decompress(mapping_bytes))
346
- return mapping
347
-
348
-
349
329
  def load_workflow_from_template(template_path: str,
350
330
  mapping: dict[str, str],
351
331
  base_path: str | Path,
@@ -359,36 +339,9 @@ def load_workflow_from_template(template_path: str,
359
339
  content = f.read()
360
340
 
361
341
  mtime = max((base_path / template_path).stat().st_mtime, mtime)
362
- hash_str, mapping_code = encode_mapping(mapping)
363
-
364
- def replace(text):
365
- """
366
- 将给定文本中的所有 VAR("var") 替换为 __VAR_{hash_str}["var"]。
367
-
368
- Args:
369
- text (str): 包含 VAR 调用的字符串。
370
-
371
- Returns:
372
- str: 已经替换的新字符串。
373
- """
374
- pattern = re.compile(r'VAR\s*\(\s*(["\'])(\w+)\1\s*\)')
375
- replacement = f'__VAR_{hash_str}' + r'[\1\2\1]'
376
- new_text = re.sub(pattern, replacement, text)
377
- return new_text
378
-
379
- template = string.Template(replace(content))
380
- keys = template.get_identifiers()
381
- missing = set(keys) - set(mapping.keys())
382
- if missing:
383
- raise KeyError(f"{template_path}: Missing keys in mapping: {missing}")
384
- content = template.substitute(mapping)
385
-
386
- inject_code = [
387
- "from qulab.executor.load import decode_mapping",
388
- f"__VAR_{hash_str} = decode_mapping(\"{hash_str}\",",
389
- f"\"\"\"{mapping_code}\"\"\")"
390
- ]
391
- content = '\n'.join(inject_code + [content])
342
+
343
+ content, hash_str = inject_mapping(content, mapping, str(path))
344
+
392
345
  if target_path is None:
393
346
  if path.stem == 'template':
394
347
  path = path.parent / f'tmp{hash_str}.py'
@@ -424,16 +377,27 @@ def load_workflow_from_template(template_path: str,
424
377
  def load_workflow(workflow: str | tuple[str, dict],
425
378
  base_path: str | Path,
426
379
  package='workflows',
427
- mtime: float = 0) -> WorkflowType:
380
+ mtime: float = 0,
381
+ inject: dict | None = None) -> WorkflowType:
428
382
  if isinstance(workflow, tuple):
429
383
  if len(workflow) == 2:
430
384
  file_name, mapping = workflow
431
- w = load_workflow_from_template(file_name, mapping, base_path,
432
- None, package, mtime)
385
+ if inject is None:
386
+ w = load_workflow_from_template(file_name, mapping, base_path,
387
+ None, package, mtime)
388
+ else:
389
+ w = load_workflow_from_template(file_name, inject, base_path,
390
+ None, package, mtime)
433
391
  elif len(workflow) == 3:
434
392
  template_path, target_path, mapping = workflow
435
- w = load_workflow_from_template(template_path, mapping, base_path,
436
- target_path, package, mtime)
393
+ if inject is None:
394
+ w = load_workflow_from_template(template_path, mapping,
395
+ base_path, target_path,
396
+ package, mtime)
397
+ else:
398
+ w = load_workflow_from_template(template_path, inject,
399
+ base_path, target_path,
400
+ package, mtime)
437
401
  else:
438
402
  raise ValueError(f"Invalid workflow: {workflow}")
439
403
  w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
@@ -451,6 +415,22 @@ def load_workflow(workflow: str | tuple[str, dict],
451
415
  return w
452
416
 
453
417
 
418
+ def _load_workflow_list(workflow, lst, code_path):
419
+ ret = []
420
+ for i, n in enumerate(lst):
421
+ try:
422
+ ret.append(load_workflow(n, code_path, mtime=workflow.__mtime__))
423
+ except TemplateKeyError:
424
+ raise TemplateKeyError(
425
+ f"Workflow {workflow.__workflow_id__} missing key in {i}th {n[0]} dependent mapping."
426
+ )
427
+ except TemplateTypeError:
428
+ raise TemplateTypeError(
429
+ f"Workflow {workflow.__workflow_id__} type error in {i}th {n[0]} dependent mapping."
430
+ )
431
+ return ret
432
+
433
+
454
434
  def get_dependents(workflow: WorkflowType,
455
435
  code_path: str | Path) -> list[WorkflowType]:
456
436
  if callable(getattr(workflow, 'depends', None)):
@@ -458,16 +438,10 @@ def get_dependents(workflow: WorkflowType,
458
438
  raise AttributeError(
459
439
  f'Workflow {workflow.__workflow_id__} "depends" function should not have any parameters'
460
440
  )
461
- return [
462
- load_workflow(n, code_path, mtime=workflow.__mtime__)
463
- for n in workflow.depends()
464
- ]
441
+ return _load_workflow_list(workflow, workflow.depends(), code_path)
465
442
  elif isinstance(getattr(workflow, 'depends', None), (list, tuple)):
466
- return [
467
- load_workflow(n, code_path, mtime=workflow.__mtime__)
468
- for n in workflow.depends
469
- ]
470
- elif getattr(workflow, 'entries', None) is None:
443
+ return _load_workflow_list(workflow, workflow.depends, code_path)
444
+ elif getattr(workflow, 'depends', None) is None:
471
445
  return []
472
446
  else:
473
447
  raise AttributeError(
@@ -482,15 +456,9 @@ def get_entries(workflow: WorkflowType,
482
456
  raise AttributeError(
483
457
  f'Workflow {workflow.__workflow_id__} "entries" function should not have any parameters'
484
458
  )
485
- return [
486
- load_workflow(n, code_path, mtime=workflow.__mtime__)
487
- for n in workflow.entries()
488
- ]
459
+ return _load_workflow_list(workflow, workflow.entries(), code_path)
489
460
  elif isinstance(getattr(workflow, 'entries', None), (list, tuple)):
490
- return [
491
- load_workflow(n, code_path, mtime=workflow.__mtime__)
492
- for n in workflow.entries
493
- ]
461
+ return _load_workflow_list(workflow, workflow.entries, code_path)
494
462
  elif getattr(workflow, 'entries', None) is None:
495
463
  return []
496
464
  else:
@@ -1,4 +1,5 @@
1
1
  import functools
2
+ import inspect
2
3
  import pickle
3
4
  import uuid
4
5
  from datetime import datetime, timedelta
@@ -231,21 +232,7 @@ def call_analyzer(node: WorkflowType,
231
232
  if hasattr(node, 'oracle') and callable(node.oracle):
232
233
  logger.debug(
233
234
  f'"{node.__workflow_id__}" has oracle method, calling ...')
234
- try:
235
- report = node.oracle(report,
236
- history=history,
237
- system_state=get_heads(report.base_path))
238
- except Exception as e:
239
- logger.exception(e)
240
- report.oracle = {}
241
- if not isinstance(report, Report):
242
- raise TypeError(
243
- f'"{node.__workflow_id__}" : function "oracle" must return a Report object'
244
- )
245
- if not is_pickleable(report.oracle):
246
- raise TypeError(
247
- f'"{node.__workflow_id__}" : function "oracle" return not pickleable data'
248
- )
235
+ report = call_oracle(node, report, history)
249
236
  report.fully_calibrated = True
250
237
  save_report(node.__workflow_id__, report, state_path, overwrite=True)
251
238
  if plot:
@@ -253,6 +240,35 @@ def call_analyzer(node: WorkflowType,
253
240
  return report
254
241
 
255
242
 
243
+ def call_oracle(node: WorkflowType, report: Report, history: Report | None):
244
+ sig = inspect.signature(node.oracle)
245
+ try:
246
+ if 'history' in sig.parameters and 'system_state' in sig.parameters:
247
+ report = node.oracle(report,
248
+ history=history,
249
+ system_state=get_heads(report.base_path))
250
+ elif 'history' in sig.parameters:
251
+ report = node.oracle(report, history=history)
252
+ elif 'system_state' in sig.parameters:
253
+ report = node.oracle(report,
254
+ system_state=get_heads(report.base_path))
255
+ else:
256
+ report = node.oracle(report)
257
+ except Exception as e:
258
+ logger.exception(e)
259
+ report.oracle = {}
260
+ return report
261
+ if not isinstance(report, Report):
262
+ raise TypeError(
263
+ f'"{node.__workflow_id__}" : function "oracle" must return a Report object'
264
+ )
265
+ if not is_pickleable(report.oracle):
266
+ raise TypeError(
267
+ f'"{node.__workflow_id__}" : function "oracle" return not pickleable data'
268
+ )
269
+ return report
270
+
271
+
256
272
  def check_data(workflow: WorkflowType, state_path: str | Path, plot: bool,
257
273
  session_id: str) -> Report:
258
274
  """
@@ -0,0 +1,173 @@
1
+ import ast
2
+ import base64
3
+ import hashlib
4
+ import lzma
5
+ import pickle
6
+ import re
7
+ import string
8
+ import textwrap
9
+ from typing import Any
10
+
11
+
12
+ def encode_mapping(mapping):
13
+ mapping_bytes = lzma.compress(pickle.dumps(mapping))
14
+ hash_str = hashlib.md5(mapping_bytes).hexdigest()[:8]
15
+ mappping_code = '\n'.join(
16
+ textwrap.wrap(base64.b64encode(mapping_bytes).decode(),
17
+ 90,
18
+ initial_indent=' ',
19
+ subsequent_indent=' '))
20
+ return hash_str, mappping_code
21
+
22
+
23
+ def decode_mapping(hash_str, mappping_code):
24
+ mapping_bytes = base64.b64decode(mappping_code)
25
+ if hash_str != hashlib.md5(mapping_bytes).hexdigest()[:8]:
26
+ raise ValueError("Hash does not match")
27
+ mapping = pickle.loads(lzma.decompress(mapping_bytes))
28
+ return mapping
29
+
30
+
31
+ class TemplateTypeError(TypeError):
32
+ pass
33
+
34
+
35
+ class TemplateKeyError(KeyError):
36
+ pass
37
+
38
+
39
+ class TemplateVarExtractor(ast.NodeVisitor):
40
+
41
+ def __init__(self, fname, mapping):
42
+ self.var_func_def = (0, 0)
43
+ self.variables = set()
44
+ self.str_variables = set()
45
+ self.replacements = {}
46
+ self.fname = fname
47
+ self.mapping = mapping
48
+
49
+ def visit_Constant(self, node):
50
+ if isinstance(node.value, str):
51
+ self._process_string(node.value, node.lineno, node.col_offset,
52
+ node.end_lineno, node.end_col_offset)
53
+
54
+ def visit_JoinedStr(self, node):
55
+ for value in node.values:
56
+ if isinstance(value, ast.Constant) and isinstance(
57
+ value.value, str):
58
+ self._process_string(value.value, value.lineno,
59
+ value.col_offset, value.end_lineno,
60
+ value.end_col_offset)
61
+ self.generic_visit(node)
62
+
63
+ def visit_FunctionDef(self, node):
64
+ if node.name == 'VAR':
65
+ self.var_func_def = (node.lineno, node.end_lineno)
66
+ self.generic_visit(node)
67
+
68
+ def visit_Call(self, node):
69
+ if isinstance(node.func, ast.Name) and node.func.id == 'VAR':
70
+ arg = node.args[0]
71
+ if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
72
+ if arg.value not in self.mapping:
73
+ raise TemplateKeyError(
74
+ f"The variable '{arg.value}' is not provided in mapping. {self.fname}:{node.lineno}"
75
+ )
76
+ self.variables.add(arg.value)
77
+ # new_node = ast.Subscript(value=ast.Name(id="__VAR",
78
+ # ctx=ast.Load()),
79
+ # slice=ast.Constant(value=arg.value),
80
+ # ctx=ast.Load())
81
+ # ast.fix_missing_locations(new_node)
82
+ # new_source = ast.unparse(new_node)
83
+ self.replacements[(node.lineno, node.end_lineno,
84
+ node.col_offset,
85
+ node.end_col_offset)] = ('VAR', arg.value,
86
+ None, None)
87
+ else:
88
+ raise SyntaxError(
89
+ f"Argument of VAR function must be a string. {self.fname}:{node.lineno}"
90
+ )
91
+ self.generic_visit(node)
92
+
93
+ def _process_string(self, s: str, lineno: int, col_offset: int,
94
+ end_lineno: int, end_col_offset: int):
95
+ """解析字符串内容,提取模板变量"""
96
+ lines = s.split('\n')
97
+ for offset, line in enumerate(lines):
98
+ current_lineno = lineno + offset
99
+ template = string.Template(line)
100
+ for var_name in template.get_identifiers():
101
+ if var_name not in self.mapping:
102
+ raise TemplateKeyError(
103
+ f"The variable '{var_name}' is not provided in mapping. {self.fname}:{current_lineno}"
104
+ )
105
+ if not isinstance(self.mapping[var_name], str):
106
+ raise TemplateTypeError(
107
+ f"Mapping value for '{var_name}' must be a string. {self.fname}:{current_lineno}"
108
+ )
109
+ self.str_variables.add(var_name)
110
+ start, stop = 0, len(line)
111
+ if current_lineno == lineno:
112
+ start = col_offset
113
+ if current_lineno == end_lineno:
114
+ stop = end_col_offset
115
+ self.replacements[(current_lineno, current_lineno, start,
116
+ stop)] = ('STR', var_name, None, None)
117
+
118
+
119
+ def inject_mapping(source: str, mapping: dict[str, Any],
120
+ fname: str) -> list[tuple[str, int]]:
121
+ hash_str, mapping_code = encode_mapping(mapping)
122
+
123
+ tree = ast.parse(source)
124
+ lines = source.splitlines()
125
+ extractor = TemplateVarExtractor(fname, mapping)
126
+ extractor.visit(tree)
127
+
128
+ # remove VAR function definition
129
+ if extractor.var_func_def != (0, 0):
130
+ for i in range(extractor.var_func_def[0] - 1,
131
+ extractor.var_func_def[1]):
132
+ lines[i] = ''
133
+
134
+ for (lineno, end_lineno, col_offset,
135
+ end_col_offset), (kind, name, old_source,
136
+ new_source) in extractor.replacements.items():
137
+ head = lines[lineno - 1][:col_offset]
138
+ tail = lines[end_lineno - 1][end_col_offset:]
139
+ content = lines[lineno - 1:end_lineno]
140
+ content[0] = content[0].removeprefix(head)
141
+ content[-1] = content[-1].removesuffix(tail)
142
+ content = '\n'.join(content)
143
+
144
+ if kind == 'STR':
145
+ template = string.Template(content)
146
+ formated_lines = template.substitute(mapping).splitlines()
147
+ formated_lines[0] = head + formated_lines[0]
148
+ formated_lines[-1] = formated_lines[-1] + tail
149
+ if len(formated_lines) == 1:
150
+ lines[lineno - 1] = formated_lines[0]
151
+ else:
152
+ lines[lineno - 1:end_lineno] = formated_lines
153
+ else:
154
+ pattern = re.compile(r'VAR\s*\(\s*(["\'])(\w+)\1\s*\)')
155
+ replacement = f'__VAR_{hash_str}' + r'[\1\2\1]'
156
+ new_content = re.sub(pattern, replacement, content)
157
+
158
+ if lineno == end_lineno:
159
+ lines[lineno - 1] = head + new_content + tail
160
+ else:
161
+ lines[lineno - 1] = head + new_content[:-1]
162
+ for i in range(lineno, end_lineno - 1):
163
+ lines[i] = ''
164
+ lines[end_lineno - 1] = ']' + tail
165
+
166
+ injected_code = '\n'.join([
167
+ f"__QULAB_TEMPLATE__ = \"{fname}\"",
168
+ f"from qulab.executor.template import decode_mapping as __decode_{hash_str}",
169
+ f"__VAR_{hash_str} = __decode_{hash_str}(\"{hash_str}\", \"\"\"",
170
+ mapping_code, " \"\"\")", *lines
171
+ ])
172
+
173
+ return injected_code, hash_str
@@ -79,6 +79,8 @@ def set_config_api(query_method, update_method, export_method):
79
79
  the method should take a key and return the value.
80
80
  update_method: The update method.
81
81
  the method should take a dict of updates.
82
+ export_method: The export method.
83
+ the method should return a dict of the config.
82
84
  """
83
85
  global query_config, update_config, export_config
84
86
 
@@ -169,6 +169,18 @@ def debug_analyze(
169
169
  wf = load_workflow(workflow, code_path)
170
170
  if wf is None:
171
171
  raise ValueError(f'Invalid workflow: {workflow}')
172
+ if hasattr(wf, '__QULAB_TEMPLATE__'):
173
+ template_mtime = (Path(code_path) / wf.__QULAB_TEMPLATE__).stat().st_mtime
174
+ if template_mtime > wf.__mtime__:
175
+ for k in dir(wf):
176
+ if k.startswith('__VAR_') and len(k) == len('__VAR_17fb4dde'):
177
+ var_dict = getattr(wf, k)
178
+ break
179
+ else:
180
+ var_dict = {}
181
+ wf = load_workflow((wf.__QULAB_TEMPLATE__, workflow, var_dict),
182
+ code_path)
183
+
172
184
  report = wf.analyze(report, report.previous)
173
185
  if hasattr(wf, 'plot'):
174
186
  wf.plot(report)
File without changes
@@ -0,0 +1,39 @@
1
+ class _Missing():
2
+
3
+ def __repr__(self):
4
+ return "Missing"
5
+
6
+
7
+ Missing = _Missing()
8
+
9
+
10
+ def connect_trace(*mappings):
11
+ if not mappings:
12
+ return {}
13
+ result = {}
14
+ first_mapping = mappings[0]
15
+ for key in first_mapping:
16
+ current_value = key
17
+ trajectory = []
18
+ for mapping in mappings:
19
+ if current_value is Missing:
20
+ trajectory.append(Missing)
21
+ continue
22
+ if current_value in mapping:
23
+ next_value = mapping[current_value]
24
+ trajectory.append(next_value)
25
+ current_value = next_value
26
+ else:
27
+ trajectory.append(Missing)
28
+ current_value = Missing
29
+ result[key] = trajectory
30
+ return result
31
+
32
+
33
+ def connect(*mappings):
34
+ if not mappings:
35
+ return {}
36
+ return {
37
+ k: v[-1]
38
+ for k, v in connect_trace(*mappings).items() if v[-1] is not Missing
39
+ }
@@ -0,0 +1 @@
1
+ __version__ = "2.7.14"
@@ -1 +0,0 @@
1
- __version__ = "2.7.12"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes