QuLab 2.9.10__tar.gz → 2.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. {qulab-2.9.10 → qulab-2.10.1}/PKG-INFO +1 -1
  2. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/PKG-INFO +1 -1
  3. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/SOURCES.txt +2 -0
  4. {qulab-2.9.10 → qulab-2.10.1}/qulab/cli/config.py +72 -13
  5. qulab-2.10.1/qulab/cli/decorators.py +28 -0
  6. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/cli.py +42 -34
  7. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/load.py +5 -1
  8. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/schedule.py +91 -68
  9. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/utils.py +10 -3
  10. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/expression.py +187 -34
  11. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/server.py +18 -104
  12. qulab-2.10.1/qulab/version.py +1 -0
  13. qulab-2.10.1/tests/test_expression.py +171 -0
  14. qulab-2.9.10/qulab/version.py +0 -1
  15. {qulab-2.9.10 → qulab-2.10.1}/LICENSE +0 -0
  16. {qulab-2.9.10 → qulab-2.10.1}/MANIFEST.in +0 -0
  17. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/dependency_links.txt +0 -0
  18. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/entry_points.txt +0 -0
  19. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/requires.txt +0 -0
  20. {qulab-2.9.10 → qulab-2.10.1}/QuLab.egg-info/top_level.txt +0 -0
  21. {qulab-2.9.10 → qulab-2.10.1}/README.md +0 -0
  22. {qulab-2.9.10 → qulab-2.10.1}/pyproject.toml +0 -0
  23. {qulab-2.9.10 → qulab-2.10.1}/qulab/__init__.py +0 -0
  24. {qulab-2.9.10 → qulab-2.10.1}/qulab/__main__.py +0 -0
  25. {qulab-2.9.10 → qulab-2.10.1}/qulab/cli/__init__.py +0 -0
  26. {qulab-2.9.10 → qulab-2.10.1}/qulab/cli/commands.py +0 -0
  27. {qulab-2.9.10 → qulab-2.10.1}/qulab/dicttree.py +0 -0
  28. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/__init__.py +0 -0
  29. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/analyze.py +0 -0
  30. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/storage.py +0 -0
  31. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/template.py +0 -0
  32. {qulab-2.9.10 → qulab-2.10.1}/qulab/executor/transform.py +0 -0
  33. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/__init__.py +0 -0
  34. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/__main__.py +0 -0
  35. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/config.py +0 -0
  36. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/dataset.py +0 -0
  37. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/event_queue.py +0 -0
  38. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/mainwindow.py +0 -0
  39. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/monitor.py +0 -0
  40. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/ploter.py +0 -0
  41. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/qt_compat.py +0 -0
  42. {qulab-2.9.10 → qulab-2.10.1}/qulab/monitor/toolbar.py +0 -0
  43. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/__init__.py +0 -0
  44. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/curd.py +0 -0
  45. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/models.py +0 -0
  46. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/optimize.py +0 -0
  47. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/query.py +0 -0
  48. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/record.py +0 -0
  49. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/scan.py +0 -0
  50. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/space.py +0 -0
  51. {qulab-2.9.10 → qulab-2.10.1}/qulab/scan/utils.py +0 -0
  52. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/__init__.py +0 -0
  53. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/__main__.py +0 -0
  54. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/backend/__init__.py +0 -0
  55. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/backend/redis.py +0 -0
  56. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/base_dataset.py +0 -0
  57. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/chunk.py +0 -0
  58. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/dataset.py +0 -0
  59. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/file.py +0 -0
  60. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/__init__.py +0 -0
  61. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/base.py +0 -0
  62. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/config.py +0 -0
  63. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/file.py +0 -0
  64. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/ipy.py +0 -0
  65. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/models.py +0 -0
  66. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/record.py +0 -0
  67. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/report.py +0 -0
  68. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/models/tag.py +0 -0
  69. {qulab-2.9.10 → qulab-2.10.1}/qulab/storage/storage.py +0 -0
  70. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/__init__.py +0 -0
  71. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/chat.py +0 -0
  72. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/device/__init__.py +0 -0
  73. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/device/basedevice.py +0 -0
  74. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/device/loader.py +0 -0
  75. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/device/utils.py +0 -0
  76. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/drivers/FakeInstrument.py +0 -0
  77. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/drivers/__init__.py +0 -0
  78. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/ipy_events.py +0 -0
  79. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/__init__.py +0 -0
  80. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/bencoder.py +0 -0
  81. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/cli.py +0 -0
  82. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/dhcp.py +0 -0
  83. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/dhcpd.py +0 -0
  84. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/kad.py +0 -0
  85. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/kcp.py +0 -0
  86. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/net/nginx.py +0 -0
  87. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/progress.py +0 -0
  88. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/__init__.py +0 -0
  89. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/client.py +0 -0
  90. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/exceptions.py +0 -0
  91. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/msgpack.py +0 -0
  92. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/msgpack.pyi +0 -0
  93. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/router.py +0 -0
  94. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/rpc.py +0 -0
  95. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/serialize.py +0 -0
  96. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/server.py +0 -0
  97. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/socket.py +0 -0
  98. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/utils.py +0 -0
  99. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/worker.py +0 -0
  100. {qulab-2.9.10 → qulab-2.10.1}/qulab/sys/rpc/zmq_socket.py +0 -0
  101. {qulab-2.9.10 → qulab-2.10.1}/qulab/tools/__init__.py +0 -0
  102. {qulab-2.9.10 → qulab-2.10.1}/qulab/tools/connection_helper.py +0 -0
  103. {qulab-2.9.10 → qulab-2.10.1}/qulab/typing.py +0 -0
  104. {qulab-2.9.10 → qulab-2.10.1}/qulab/utils.py +0 -0
  105. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/__init__.py +0 -0
  106. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/__main__.py +0 -0
  107. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/_autoplot.py +0 -0
  108. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/plot_circ.py +0 -0
  109. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/plot_layout.py +0 -0
  110. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/plot_seq.py +0 -0
  111. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/qdat.py +0 -0
  112. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/rot3d.py +0 -0
  113. {qulab-2.9.10 → qulab-2.10.1}/qulab/visualization/widgets.py +0 -0
  114. {qulab-2.9.10 → qulab-2.10.1}/setup.cfg +0 -0
  115. {qulab-2.9.10 → qulab-2.10.1}/setup.py +0 -0
  116. {qulab-2.9.10 → qulab-2.10.1}/src/qulab.h +0 -0
  117. {qulab-2.9.10 → qulab-2.10.1}/tests/test_kad.py +0 -0
  118. {qulab-2.9.10 → qulab-2.10.1}/tests/test_scan.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuLab
3
- Version: 2.9.10
3
+ Version: 2.10.1
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuLab
3
- Version: 2.9.10
3
+ Version: 2.10.1
4
4
  Summary: contral instruments and manage data
5
5
  Author-email: feihoo87 <feihoo87@gmail.com>
6
6
  Maintainer-email: feihoo87 <feihoo87@gmail.com>
@@ -18,6 +18,7 @@ qulab/version.py
18
18
  qulab/cli/__init__.py
19
19
  qulab/cli/commands.py
20
20
  qulab/cli/config.py
21
+ qulab/cli/decorators.py
21
22
  qulab/executor/__init__.py
22
23
  qulab/executor/analyze.py
23
24
  qulab/executor/cli.py
@@ -109,5 +110,6 @@ qulab/visualization/qdat.py
109
110
  qulab/visualization/rot3d.py
110
111
  qulab/visualization/widgets.py
111
112
  src/qulab.h
113
+ tests/test_expression.py
112
114
  tests/test_kad.py
113
115
  tests/test_scan.py
@@ -11,7 +11,10 @@ CONFIG_PATH = os.path.expanduser("~/.qulab.ini")
11
11
  ENV_PREFIX = "QULAB_"
12
12
 
13
13
 
14
- def _get_config_value(option_name, type_cast=str, command_name=None):
14
+ def _get_config_value(option_name,
15
+ type_cast=str,
16
+ command_name=None,
17
+ default=None):
15
18
  """支持命令专属配置的优先级获取"""
16
19
  # 构造环境变量名
17
20
  if command_name:
@@ -47,34 +50,90 @@ def _get_config_value(option_name, type_cast=str, command_name=None):
47
50
  return os.path.expanduser(config_value)
48
51
  return type_cast(config_value)
49
52
 
50
- return None # 交给 Click 处理默认值
51
-
52
-
53
- def get_config_value(option_name, type_cast=str, command_name=None):
54
- value = _get_config_value(option_name, type_cast, command_name)
53
+ return default # 交给 Click 处理默认值
54
+
55
+
56
+ def get_config_value(option_name,
57
+ type_cast=str,
58
+ command_name=None,
59
+ default=None):
60
+ """
61
+ 获取配置值,支持命令专属配置的优先级获取
62
+
63
+ 优先级:
64
+ 1. 命令行参数
65
+ 2. 配置文件专属配置
66
+ 3. 配置文件公共配置
67
+ 4. 环境变量专属配置
68
+ 5. 环境变量公共配置
69
+ 6. 默认值
70
+ 7. Click 处理默认值
71
+ 8. None
72
+
73
+ Parameters
74
+ ----------
75
+ option_name : str
76
+ 配置项名称
77
+ type_cast : type
78
+ 转换类型
79
+ command_name : str
80
+ 命令名称
81
+ 如果为 None,则不使用命令专属配置
82
+ default : any
83
+ 默认值
84
+ 如果为 None,则不使用默认值
85
+
86
+ Returns
87
+ -------
88
+ any
89
+ 配置值
90
+ 如果没有找到配置值,则返回 None
91
+ """
92
+ value = _get_config_value(option_name,
93
+ type_cast,
94
+ command_name,
95
+ default=default)
55
96
  if value is None and command_name is not None:
56
- return _get_config_value(option_name, type_cast)
97
+ return _get_config_value(option_name, type_cast, default=default)
57
98
  return value
58
99
 
59
100
 
60
- def log_options(func):
61
- """通用配置装饰器(所有命令共用)"""
101
+ def log_options(func=None, command_name=None):
102
+ """通用配置装饰器(所有命令共用)
103
+
104
+ 添加 --debug, --log, --debug-log, --quiet 选项
105
+ 1. --debug: 是否开启调试模式
106
+ 2. --log: 日志文件路径
107
+ 3. --debug-log: 调试日志文件路径
108
+ 4. --quiet: 是否静默输出
109
+ """
110
+
111
+ if isinstance(func, str):
112
+ return functools.partial(log_options, command_name=func)
113
+ if func is None:
114
+ return functools.partial(log_options, command_name=command_name)
62
115
 
63
116
  @click.option("--debug",
64
117
  is_flag=True,
65
- default=get_config_value("debug", bool),
118
+ default=get_config_value("debug",
119
+ bool,
120
+ command_name=command_name),
66
121
  help=f"Enable debug mode")
67
122
  @click.option("--log",
68
123
  type=click.Path(),
69
- default=lambda: get_config_value("log", Path),
124
+ default=lambda: get_config_value(
125
+ "log", Path, command_name=command_name),
70
126
  help=f"Log file path")
71
127
  @click.option("--debug-log",
72
128
  type=click.Path(),
73
- default=lambda: get_config_value("debug_log", Path),
129
+ default=lambda: get_config_value(
130
+ "debug_log", Path, command_name=command_name),
74
131
  help=f"Debug log file path")
75
132
  @click.option("--quiet",
76
133
  is_flag=True,
77
- default=get_config_value("quiet", bool),
134
+ default=get_config_value("quiet",
135
+ bool,
136
+ command_name=command_name),
78
137
  help=f"Disable log output")
79
138
  @functools.wraps(func)
80
139
  def wrapper(*args, **kwargs):
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import functools
3
+ import inspect
4
+
5
+
6
+ def async_command(func=None):
7
+ """
8
+ Decorator to mark a function as an asynchronous command.
9
+ """
10
+ if func is None:
11
+ return async_command
12
+
13
+ @functools.wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ return run(func, *args, **kwargs)
16
+
17
+ return wrapper
18
+
19
+
20
+ def run(main, *args, **kwds):
21
+ if inspect.iscoroutinefunction(main):
22
+ try:
23
+ import uvloop
24
+ uvloop.run(main(*args, **kwds))
25
+ except ImportError:
26
+ asyncio.run(main(*args, **kwds))
27
+ else:
28
+ main(*args, **kwds)
@@ -9,6 +9,7 @@ import click
9
9
  from loguru import logger
10
10
 
11
11
  from ..cli.config import get_config_value, log_options
12
+ from ..cli.decorators import async_command
12
13
  from .load import (WorkflowType, find_unreferenced_workflows, get_entries,
13
14
  load_workflow, make_graph)
14
15
  from .schedule import CalibrationFailedError
@@ -165,7 +166,8 @@ def get(key, api):
165
166
  @click.option('--freeze', is_flag=True, help='Freeze the config table.')
166
167
  @log_options
167
168
  @command_option('run')
168
- def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
169
+ @async_command
170
+ async def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
169
171
  """
170
172
  Run a workflow.
171
173
 
@@ -204,29 +206,33 @@ def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
204
206
  if no_dependents:
205
207
  if hasattr(wf, 'entries'):
206
208
  for entry in get_entries(wf, code):
207
- run_workflow(entry,
208
- code,
209
- data,
210
- plot=plot,
211
- freeze=freeze)
209
+ await run_workflow(entry,
210
+ code,
211
+ data,
212
+ plot=plot,
213
+ freeze=freeze)
212
214
  else:
213
- run_workflow(wf, code, data, plot=plot, freeze=freeze)
215
+ await run_workflow(wf,
216
+ code,
217
+ data,
218
+ plot=plot,
219
+ freeze=freeze)
214
220
  else:
215
221
  if hasattr(wf, 'entries'):
216
222
  for entry in get_entries(wf, code):
217
- maintain_workflow(entry,
218
- code,
219
- data,
220
- run=True,
221
- plot=plot,
222
- freeze=freeze)
223
+ await maintain_workflow(entry,
224
+ code,
225
+ data,
226
+ run=True,
227
+ plot=plot,
228
+ freeze=freeze)
223
229
  else:
224
- maintain_workflow(wf,
225
- code,
226
- data,
227
- run=True,
228
- plot=plot,
229
- freeze=freeze)
230
+ await maintain_workflow(wf,
231
+ code,
232
+ data,
233
+ run=True,
234
+ plot=plot,
235
+ freeze=freeze)
230
236
  break
231
237
  except CalibrationFailedError as e:
232
238
  if i == retry - 1:
@@ -241,7 +247,8 @@ def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
241
247
  @click.option('--plot', '-p', is_flag=True, help='Plot the report.')
242
248
  @log_options
243
249
  @command_option('maintain')
244
- def maintain(workflow, code, data, api, retry, plot):
250
+ @async_command
251
+ async def maintain(workflow, code, data, api, retry, plot):
245
252
  """
246
253
  Maintain a workflow.
247
254
 
@@ -275,19 +282,19 @@ def maintain(workflow, code, data, api, retry, plot):
275
282
  try:
276
283
  if hasattr(wf, 'entries'):
277
284
  for entry in get_entries(wf, code):
278
- maintain_workflow(entry,
279
- code,
280
- data,
281
- run=False,
282
- plot=plot,
283
- freeze=False)
285
+ await maintain_workflow(entry,
286
+ code,
287
+ data,
288
+ run=False,
289
+ plot=plot,
290
+ freeze=False)
284
291
  else:
285
- maintain_workflow(wf,
286
- code,
287
- data,
288
- run=False,
289
- plot=plot,
290
- freeze=False)
292
+ await maintain_workflow(wf,
293
+ code,
294
+ data,
295
+ run=False,
296
+ plot=plot,
297
+ freeze=False)
291
298
  break
292
299
  except CalibrationFailedError as e:
293
300
  if i == retry - 1:
@@ -301,7 +308,8 @@ def maintain(workflow, code, data, api, retry, plot):
301
308
  @click.option('--plot', '-p', is_flag=True, help='Plot the report.')
302
309
  @log_options
303
310
  @command_option('reproduce')
304
- def reproduce(report_id, code, data, api, plot):
311
+ @async_command
312
+ async def reproduce(report_id, code, data, api, plot):
305
313
  """
306
314
  Reproduce a report.
307
315
 
@@ -335,6 +343,6 @@ def reproduce(report_id, code, data, api, plot):
335
343
  cfg = transform.export_config()
336
344
  transform.clear_config()
337
345
  transform.update_config(r.config)
338
- run_workflow(wf, code, data, plot=plot, freeze=True)
346
+ await run_workflow(wf, code, data, plot=plot, freeze=True)
339
347
  transform.clear_config()
340
348
  transform.update_config(cfg)
@@ -135,10 +135,14 @@ def verify_dependence_key(workflow: str | tuple[str, dict[str, Any]]
135
135
  if not isinstance(workflow, tuple) or len(workflow) not in [2, 3]:
136
136
  raise ValueError(f"Invalid workflow: {workflow}")
137
137
 
138
- if len(workflow) == 2:
138
+ if len(workflow) == 2 and isinstance(workflow[1], str):
139
139
  file_name, mapping = workflow
140
140
  if not Path(file_name).exists():
141
141
  raise FileNotFoundError(f"File not found: {file_name}")
142
+ elif len(workflow) == 2 and isinstance(workflow[1], dict):
143
+ template_path, mapping = workflow
144
+ if not (Path(base_path) / template_path).exists():
145
+ raise FileNotFoundError(f"File not found: {template_path}")
142
146
  elif len(workflow) == 3:
143
147
  template_path, target_path, mapping = workflow
144
148
  if not (Path(base_path) / template_path).exists():
@@ -127,18 +127,25 @@ def check_state(workflow: WorkflowType, code_path: str | Path,
127
127
 
128
128
 
129
129
  @logger.catch()
130
- def call_plot(node: WorkflowType, report: Report, check=False):
130
+ async def call_plot(node: WorkflowType, report: Report, check=False):
131
131
  if hasattr(node, 'plot') and callable(node.plot):
132
- node.plot(report)
132
+ if inspect.iscoroutinefunction(node.plot):
133
+ await node.plot(report, check=check)
134
+ else:
135
+ node.plot(report)
133
136
 
134
137
 
135
- def call_check(workflow: WorkflowType, session_id: str, state_path: Path):
138
+ async def call_check(workflow: WorkflowType, session_id: str,
139
+ state_path: Path):
136
140
  report = get_cache(session_id, (workflow.__workflow_id__, 'check'))
137
141
  if report is not None:
138
142
  logger.debug(f'Cache hit for "{workflow.__workflow_id__}:check"')
139
143
  return report
140
144
 
141
- data = workflow.check()
145
+ if inspect.iscoroutinefunction(workflow.check):
146
+ data = await workflow.check()
147
+ else:
148
+ data = workflow.check()
142
149
  if not is_pickleable(data):
143
150
  raise TypeError(
144
151
  f'"{workflow.__workflow_id__}" : "check" return not pickleable data'
@@ -159,13 +166,17 @@ def call_check(workflow: WorkflowType, session_id: str, state_path: Path):
159
166
  return report
160
167
 
161
168
 
162
- def call_calibrate(workflow: WorkflowType, session_id: str, state_path: Path):
169
+ async def call_calibrate(workflow: WorkflowType, session_id: str,
170
+ state_path: Path):
163
171
  report = get_cache(session_id, (workflow.__workflow_id__, 'calibrate'))
164
172
  if report is not None:
165
173
  logger.debug(f'Cache hit for "{workflow.__workflow_id__}:calibrate"')
166
174
  return report
167
175
 
168
- data = workflow.calibrate()
176
+ if inspect.iscoroutinefunction(workflow.calibrate):
177
+ data = await workflow.calibrate()
178
+ else:
179
+ data = workflow.calibrate()
169
180
  if not is_pickleable(data):
170
181
  raise TypeError(
171
182
  f'"{workflow.__workflow_id__}" : "calibrate" return not pickleable data'
@@ -186,12 +197,15 @@ def call_calibrate(workflow: WorkflowType, session_id: str, state_path: Path):
186
197
  return report
187
198
 
188
199
 
189
- def call_check_analyzer(node: WorkflowType,
190
- report: Report,
191
- history: Report | None,
192
- state_path: Path,
193
- plot=False) -> Report:
194
- report = node.check_analyze(report, history=history)
200
+ async def call_check_analyzer(node: WorkflowType,
201
+ report: Report,
202
+ history: Report | None,
203
+ state_path: Path,
204
+ plot=False) -> Report:
205
+ if inspect.iscoroutinefunction(node.check_analyze):
206
+ report = await node.check_analyze(report, history=history)
207
+ else:
208
+ report = node.check_analyze(report, history=history)
195
209
  veryfy_analyzed_report(report, node.__workflow_id__, "check_analyze")
196
210
  report.fully_calibrated = False
197
211
  if report.in_spec:
@@ -211,26 +225,29 @@ def call_check_analyzer(node: WorkflowType,
211
225
  return report
212
226
 
213
227
 
214
- def call_analyzer(node: WorkflowType,
215
- report: Report,
216
- history: Report | None,
217
- state_path: Path,
218
- plot=False) -> Report:
219
-
220
- report = node.analyze(report, history=history)
228
+ async def call_analyzer(node: WorkflowType,
229
+ report: Report,
230
+ history: Report | None,
231
+ state_path: Path,
232
+ plot=False) -> Report:
233
+ if inspect.iscoroutinefunction(node.analyze):
234
+ report = await node.analyze(report, history=history)
235
+ else:
236
+ report = node.analyze(report, history=history)
221
237
  veryfy_analyzed_report(report, node.__workflow_id__, "analyze")
222
238
  if hasattr(node, 'oracle') and callable(node.oracle):
223
239
  logger.debug(
224
240
  f'"{node.__workflow_id__}" has oracle method, calling ...')
225
- report = call_oracle(node, report, history)
241
+ report = await call_oracle(node, report, history)
226
242
  report.fully_calibrated = True
227
243
  save_report(node.__workflow_id__, report, state_path, overwrite=True)
228
244
  if plot:
229
- call_plot(node, report)
245
+ await call_plot(node, report)
230
246
  return report
231
247
 
232
248
 
233
- def call_oracle(node: WorkflowType, report: Report, history: Report | None):
249
+ async def call_oracle(node: WorkflowType, report: Report,
250
+ history: Report | None):
234
251
  sig = inspect.signature(node.oracle)
235
252
  try:
236
253
  if 'history' in sig.parameters and 'system_state' in sig.parameters:
@@ -244,6 +261,8 @@ def call_oracle(node: WorkflowType, report: Report, history: Report | None):
244
261
  system_state=get_heads(report.base_path))
245
262
  else:
246
263
  report = node.oracle(report)
264
+ if inspect.isawaitable(report):
265
+ report = await report
247
266
  except Exception as e:
248
267
  logger.exception(e)
249
268
  report.oracle = {}
@@ -259,8 +278,8 @@ def call_oracle(node: WorkflowType, report: Report, history: Report | None):
259
278
  return report
260
279
 
261
280
 
262
- def check_data(workflow: WorkflowType, state_path: str | Path, plot: bool,
263
- session_id: str) -> Report:
281
+ async def check_data(workflow: WorkflowType, state_path: str | Path,
282
+ plot: bool, session_id: str) -> Report:
264
283
  """
265
284
  check data answers two questions:
266
285
  Is the parameter associated with this cal in spec,
@@ -299,52 +318,56 @@ def check_data(workflow: WorkflowType, state_path: str | Path, plot: bool,
299
318
  logger.debug(
300
319
  f'Checking "{workflow.__workflow_id__}" with "check" method ...')
301
320
 
302
- report = call_check(workflow, session_id, state_path)
321
+ report = await call_check(workflow, session_id, state_path)
303
322
 
304
323
  logger.debug(f'Checked "{workflow.__workflow_id__}" !')
305
- report = call_check_analyzer(workflow,
306
- report,
307
- history,
308
- state_path,
309
- plot=plot)
324
+ report = await call_check_analyzer(workflow,
325
+ report,
326
+ history,
327
+ state_path,
328
+ plot=plot)
310
329
  else:
311
330
  logger.debug(
312
331
  f'Checking "{workflow.__workflow_id__}" with "calibrate" method ...'
313
332
  )
314
333
 
315
- report = call_calibrate(workflow, session_id, state_path)
334
+ report = await call_calibrate(workflow, session_id, state_path)
316
335
 
317
336
  logger.debug(f'Calibrated "{workflow.__workflow_id__}" !')
318
- report = call_analyzer(workflow,
319
- report,
320
- history,
321
- state_path,
322
- plot=plot)
337
+ report = await call_analyzer(workflow,
338
+ report,
339
+ history,
340
+ state_path,
341
+ plot=plot)
323
342
  return report
324
343
 
325
344
 
326
- def calibrate(workflow: WorkflowType, state_path: str | Path, plot: bool,
327
- session_id: str) -> Report:
345
+ async def calibrate(workflow: WorkflowType, state_path: str | Path, plot: bool,
346
+ session_id: str) -> Report:
328
347
  history = find_report(workflow.__workflow_id__, state_path)
329
348
 
330
349
  logger.debug(f'Calibrating "{workflow.__workflow_id__}" ...')
331
350
 
332
- report = call_calibrate(workflow, session_id, state_path)
351
+ report = await call_calibrate(workflow, session_id, state_path)
333
352
 
334
353
  logger.debug(f'Calibrated "{workflow.__workflow_id__}" !')
335
354
 
336
- report = call_analyzer(workflow, report, history, state_path, plot=plot)
355
+ report = await call_analyzer(workflow,
356
+ report,
357
+ history,
358
+ state_path,
359
+ plot=plot)
337
360
  return report
338
361
 
339
362
 
340
- def diagnose(workflow: WorkflowType, code_path: str | Path,
341
- state_path: str | Path, plot: bool, session_id: str):
363
+ async def diagnose(workflow: WorkflowType, code_path: str | Path,
364
+ state_path: str | Path, plot: bool, session_id: str):
342
365
  '''
343
366
  Returns: True if node or dependent recalibrated.
344
367
  '''
345
368
  logger.debug(f'diagnose "{workflow.__workflow_id__}"')
346
369
  # check_data
347
- report = check_data(workflow, state_path, plot, session_id)
370
+ report = await check_data(workflow, state_path, plot, session_id)
348
371
  # in spec case
349
372
  if report.in_spec:
350
373
  logger.debug(
@@ -357,7 +380,7 @@ def diagnose(workflow: WorkflowType, code_path: str | Path,
357
380
  logger.debug(
358
381
  f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
359
382
  recalibrated = [
360
- diagnose(n, code_path, state_path, plot, session_id)
383
+ await diagnose(n, code_path, state_path, plot, session_id)
361
384
  for n in get_dependents(workflow, code_path)
362
385
  ]
363
386
  if not any(recalibrated):
@@ -386,7 +409,7 @@ def diagnose(workflow: WorkflowType, code_path: str | Path,
386
409
  else:
387
410
  logger.error(f'Never reach: recalibrate "{workflow.__workflow_id__}"')
388
411
 
389
- report = calibrate(workflow, state_path, plot, session_id)
412
+ report = await calibrate(workflow, state_path, plot, session_id)
390
413
  if report.bad_data or not report.in_spec:
391
414
  obey_the_oracle(report, state_path)
392
415
  raise CalibrationFailedError(
@@ -398,13 +421,13 @@ def diagnose(workflow: WorkflowType, code_path: str | Path,
398
421
 
399
422
 
400
423
  @logger.catch(reraise=True)
401
- def maintain(workflow: WorkflowType,
402
- code_path: str | Path,
403
- state_path: str | Path,
404
- session_id: str | None = None,
405
- run: bool = False,
406
- plot: bool = False,
407
- freeze: bool = False):
424
+ async def maintain(workflow: WorkflowType,
425
+ code_path: str | Path,
426
+ state_path: str | Path,
427
+ session_id: str | None = None,
428
+ run: bool = False,
429
+ plot: bool = False,
430
+ freeze: bool = False):
408
431
  if session_id is None:
409
432
  session_id = uuid.uuid4().hex
410
433
  logger.debug(f'run "{workflow.__workflow_id__}"'
@@ -414,13 +437,13 @@ def maintain(workflow: WorkflowType,
414
437
  logger.debug(
415
438
  f'maintain "{n.__workflow_id__}" because it is depended by "{workflow.__workflow_id__}"'
416
439
  )
417
- maintain(n,
418
- code_path,
419
- state_path,
420
- session_id,
421
- run=False,
422
- plot=plot,
423
- freeze=freeze)
440
+ await maintain(n,
441
+ code_path,
442
+ state_path,
443
+ session_id,
444
+ run=False,
445
+ plot=plot,
446
+ freeze=freeze)
424
447
  else:
425
448
  logger.debug(
426
449
  f'"{workflow.__workflow_id__}": All dependents maintained')
@@ -430,7 +453,7 @@ def maintain(workflow: WorkflowType,
430
453
  f'"{workflow.__workflow_id__}": In spec, no need to maintain')
431
454
  return
432
455
  # check_data
433
- report = check_data(workflow, state_path, plot, session_id)
456
+ report = await check_data(workflow, state_path, plot, session_id)
434
457
  if report.in_spec:
435
458
  if not run:
436
459
  logger.debug(
@@ -443,13 +466,13 @@ def maintain(workflow: WorkflowType,
443
466
  logger.debug(
444
467
  f'diagnose "{n.__workflow_id__}" because of "{workflow.__workflow_id__}" bad data'
445
468
  )
446
- diagnose(n, code_path, state_path, plot, session_id)
469
+ await diagnose(n, code_path, state_path, plot, session_id)
447
470
  else:
448
471
  logger.debug(
449
472
  f'"{workflow.__workflow_id__}": All dependents diagnosed')
450
473
  # calibrate
451
474
  logger.debug(f'recalibrate "{workflow.__workflow_id__}"')
452
- report = calibrate(workflow, state_path, plot, session_id)
475
+ report = await calibrate(workflow, state_path, plot, session_id)
453
476
  if report.bad_data or not report.in_spec:
454
477
  if not freeze:
455
478
  obey_the_oracle(report, state_path)
@@ -465,14 +488,14 @@ def maintain(workflow: WorkflowType,
465
488
 
466
489
 
467
490
  @logger.catch(reraise=True)
468
- def run(workflow: WorkflowType,
469
- code_path: str | Path,
470
- state_path: str | Path,
471
- plot: bool = False,
472
- freeze: bool = False):
491
+ async def run(workflow: WorkflowType,
492
+ code_path: str | Path,
493
+ state_path: str | Path,
494
+ plot: bool = False,
495
+ freeze: bool = False):
473
496
  session_id = uuid.uuid4().hex
474
497
  logger.debug(f'run "{workflow.__workflow_id__}" without dependences.')
475
- report = calibrate(workflow, state_path, plot, session_id=session_id)
498
+ report = await calibrate(workflow, state_path, plot, session_id=session_id)
476
499
  if report.bad_data or not report.in_spec:
477
500
  if not freeze:
478
501
  obey_the_oracle(report, state_path)
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  from pathlib import Path
2
3
 
3
4
  from ..cli.config import get_config_value
@@ -155,7 +156,7 @@ def oracle(report: Report,
155
156
  """
156
157
 
157
158
 
158
- def debug_analyze(
159
+ async def debug_analyze(
159
160
  report_index: int,
160
161
  code_path: str | Path = get_config_value('code', Path),
161
162
  data_path: str | Path = get_config_value('data', Path),
@@ -170,7 +171,8 @@ def debug_analyze(
170
171
  if wf is None:
171
172
  raise ValueError(f'Invalid workflow: {workflow}')
172
173
  if hasattr(wf, '__QULAB_TEMPLATE__'):
173
- template_mtime = (Path(code_path) / wf.__QULAB_TEMPLATE__).stat().st_mtime
174
+ template_mtime = (Path(code_path) /
175
+ wf.__QULAB_TEMPLATE__).stat().st_mtime
174
176
  if template_mtime > wf.__mtime__:
175
177
  for k in dir(wf):
176
178
  if k.startswith('__VAR_') and len(k) == len('__VAR_17fb4dde'):
@@ -182,6 +184,11 @@ def debug_analyze(
182
184
  code_path)
183
185
 
184
186
  report = wf.analyze(report, report.previous)
187
+ if inspect.isawaitable(report):
188
+ report = await report
185
189
  if hasattr(wf, 'plot'):
186
- wf.plot(report)
190
+ if inspect.iscoroutinefunction(wf.plot):
191
+ await wf.plot(report)
192
+ else:
193
+ wf.plot(report)
187
194
  return report