QuLab 2.9.9__cp310-cp310-macosx_10_9_universal2.whl → 2.10.0__cp310-cp310-macosx_10_9_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qulab/cli/config.py +72 -13
- qulab/cli/decorators.py +28 -0
- qulab/executor/cli.py +42 -34
- qulab/executor/load.py +13 -12
- qulab/executor/schedule.py +91 -68
- qulab/executor/utils.py +10 -3
- qulab/fun.cpython-310-darwin.so +0 -0
- qulab/scan/expression.py +187 -34
- qulab/scan/server.py +18 -104
- qulab/version.py +1 -1
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/METADATA +1 -1
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/RECORD +16 -15
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/WHEEL +0 -0
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/entry_points.txt +0 -0
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/licenses/LICENSE +0 -0
- {qulab-2.9.9.dist-info → qulab-2.10.0.dist-info}/top_level.txt +0 -0
qulab/cli/config.py
CHANGED
@@ -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,
|
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
|
51
|
-
|
52
|
-
|
53
|
-
def get_config_value(option_name,
|
54
|
-
|
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",
|
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(
|
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(
|
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",
|
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):
|
qulab/cli/decorators.py
ADDED
@@ -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)
|
qulab/executor/cli.py
CHANGED
@@ -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
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
209
|
+
await run_workflow(entry,
|
210
|
+
code,
|
211
|
+
data,
|
212
|
+
plot=plot,
|
213
|
+
freeze=freeze)
|
212
214
|
else:
|
213
|
-
run_workflow(wf,
|
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
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
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)
|
qulab/executor/load.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
import
|
1
|
+
import atexit
|
2
2
|
import inspect
|
3
3
|
import pickle
|
4
|
-
import
|
4
|
+
import tempfile
|
5
5
|
import warnings
|
6
6
|
from importlib.util import module_from_spec, spec_from_file_location
|
7
7
|
from pathlib import Path
|
@@ -333,17 +333,18 @@ def load_workflow_from_file(file_name: str,
|
|
333
333
|
def load_workflow_from_source_code(workflow_id: str,
|
334
334
|
source_code: str,
|
335
335
|
package='workflows') -> WorkflowType:
|
336
|
-
|
337
|
-
module_name = f"{package}.{'.'.join([*
|
336
|
+
workflow_path = Path(workflow_id)
|
337
|
+
module_name = f"{package}.{'.'.join([*workflow_path.parts[:-1], workflow_path.stem])}"
|
338
|
+
|
339
|
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.py').name
|
340
|
+
with open(temp_file, 'w') as f:
|
341
|
+
f.write(source_code)
|
342
|
+
|
343
|
+
atexit.register(lambda f=temp_file: Path(f).unlink(missing_ok=True))
|
338
344
|
|
339
|
-
|
340
|
-
module =
|
341
|
-
|
342
|
-
sys.modules[module_name] = module
|
343
|
-
# 将源代码编译成字节码
|
344
|
-
code = compile(source_code, '<string>', 'exec')
|
345
|
-
# 在模块的命名空间中执行字节码
|
346
|
-
exec(code, module.__dict__)
|
345
|
+
spec = spec_from_file_location(module_name, temp_file)
|
346
|
+
module = module_from_spec(spec)
|
347
|
+
spec.loader.exec_module(module)
|
347
348
|
|
348
349
|
module.__source__ = source_code
|
349
350
|
module.__mtime__ = 0
|
qulab/executor/schedule.py
CHANGED
@@ -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
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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,
|
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,
|
263
|
-
|
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
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
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
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
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
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
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
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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)
|
qulab/executor/utils.py
CHANGED
@@ -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) /
|
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
|
190
|
+
if inspect.iscoroutinefunction(wf.plot):
|
191
|
+
await wf.plot(report)
|
192
|
+
else:
|
193
|
+
wf.plot(report)
|
187
194
|
return report
|
qulab/fun.cpython-310-darwin.so
CHANGED
Binary file
|
qulab/scan/expression.py
CHANGED
@@ -4,42 +4,154 @@ import operator
|
|
4
4
|
|
5
5
|
import numpy as np
|
6
6
|
from pyparsing import (CaselessKeyword, Combine, Forward, Group, Keyword,
|
7
|
-
Literal, Optional, ParserElement,
|
8
|
-
|
9
|
-
|
10
|
-
stringStart)
|
7
|
+
Literal, Optional, ParserElement, ParseResults,
|
8
|
+
Suppress, Word, alphanums, alphas, delimitedList,
|
9
|
+
infixNotation, nums, oneOf, opAssoc, pyparsing_common,
|
10
|
+
restOfLine, srange, stringEnd, stringStart)
|
11
11
|
from scipy import special
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
# 启用 Packrat 优化以提高解析效率
|
14
|
+
ParserElement.enablePackrat()
|
15
|
+
|
16
|
+
# 定义符号(括号、方括号、花括号、点号等)的 Suppress 版
|
17
|
+
LPAREN, RPAREN = map(Suppress, "()")
|
18
|
+
LBRACK, RBRACK = map(Suppress, "[]")
|
19
|
+
LBRACE, RBRACE = map(Suppress, "{}")
|
20
|
+
DOT = Suppress(".")
|
21
|
+
COMMA = Suppress(",")
|
22
|
+
|
23
|
+
# 数字定义:整数(十进制、八进制、十六进制)和浮点数
|
24
|
+
INT = (Combine(Word("123456789", nums))
|
25
|
+
| Literal("0")).setParseAction(lambda t: int(t[0]))
|
26
|
+
OCT = Combine("0" + Word("01234567")).setParseAction(lambda t: int(t[0], 8))
|
27
|
+
HEX = Combine("0x" + Word("0123456789abcdefABCDEF")).setParseAction(
|
20
28
|
lambda t: int(t[0], 16))
|
21
|
-
FLOAT = Combine(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
FLOAT = Combine(
|
30
|
+
Word(nums) + '.' + Word(nums) | '.' + Word(nums) | Word(nums) + '.'
|
31
|
+
| Word(nums) + (Literal('e') | Literal('E')) +
|
32
|
+
Word("+-" + nums)).setParseAction(lambda t: float(t[0]))
|
33
|
+
|
34
|
+
|
35
|
+
# 定义标识符,转换为 Symbol 对象(在 Expression 类中已定义)
|
36
|
+
def symbol_parse_action(t):
|
37
|
+
return Symbol(t[0])
|
38
|
+
|
39
|
+
|
40
|
+
SYMBOL = Word(alphas, alphanums + "_").setParseAction(symbol_parse_action)
|
41
|
+
|
42
|
+
#------------------------------------------------------------------------------
|
43
|
+
# 定义运算表达式的解析动作转换函数
|
44
|
+
|
45
|
+
# 一元运算符映射(注意:此处 ! 使用逻辑非 operator.not_)
|
46
|
+
unary_ops = {
|
47
|
+
'+': operator.pos,
|
48
|
+
'-': operator.neg,
|
49
|
+
'~': operator.invert,
|
50
|
+
'!': operator.not_
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
def unary_parse_action(tokens: ParseResults) -> Expression:
|
55
|
+
# tokens 形如:[['-', operand]]
|
56
|
+
op, operand = tokens[0]
|
57
|
+
# operand 已经是 Expression 对象(或常量),构造 UnaryExpression
|
58
|
+
return UnaryExpression(operand, unary_ops[op])
|
59
|
+
|
60
|
+
|
61
|
+
# 二元运算符映射
|
62
|
+
binary_ops = {
|
63
|
+
'+': operator.add,
|
64
|
+
'-': operator.sub,
|
65
|
+
'*': operator.mul,
|
66
|
+
'/': operator.truediv,
|
67
|
+
'//': operator.floordiv,
|
68
|
+
'%': operator.mod,
|
69
|
+
'**': operator.pow,
|
70
|
+
'@': operator.matmul,
|
71
|
+
'<<': operator.lshift,
|
72
|
+
'>>': operator.rshift,
|
73
|
+
'&': operator.and_,
|
74
|
+
'^': operator.xor,
|
75
|
+
'|': operator.or_,
|
76
|
+
'<': operator.lt,
|
77
|
+
'<=': operator.le,
|
78
|
+
'>': operator.gt,
|
79
|
+
'>=': operator.ge,
|
80
|
+
'==': operator.eq,
|
81
|
+
'!=': operator.ne
|
82
|
+
}
|
83
|
+
|
84
|
+
|
85
|
+
def binary_parse_action(tokens: ParseResults) -> Expression:
|
86
|
+
# tokens[0] 为形如:[operand1, op, operand2, op, operand3, ...]
|
87
|
+
t = tokens[0]
|
88
|
+
expr = t[0]
|
89
|
+
for i in range(1, len(t), 2):
|
90
|
+
op = t[i]
|
91
|
+
right = t[i + 1]
|
92
|
+
expr = BinaryExpression(expr, right, binary_ops[op])
|
93
|
+
return expr
|
36
94
|
|
37
|
-
atom << (INT | OCT | HEX | FLOAT | SYMBOL | (LPAREN + expr + RPAREN) |
|
38
|
-
(LBRACK + expr + RBRACK) | (LBRACE + expr + RBRACE) | (MINUS + atom) |
|
39
|
-
(PLUS + atom) | (TILDE + atom) | (BANG + atom) | (nums + DOT + nums))
|
40
95
|
|
41
|
-
|
42
|
-
|
96
|
+
expr = Forward()
|
97
|
+
#------------------------------------------------------------------------------
|
98
|
+
# 构造基元表达式:包括数值、标识符、括号内表达式
|
99
|
+
atom = (
|
100
|
+
FLOAT | INT | OCT | HEX | SYMBOL |
|
101
|
+
(LPAREN + expr + RPAREN) # 注意:后面我们将使用递归定义 expr
|
102
|
+
)
|
103
|
+
|
104
|
+
|
105
|
+
# 为支持函数调用和属性访问,构造后缀表达式:
|
106
|
+
# 例如: func(x,y).attr
|
107
|
+
def parse_function_call(expr_obj):
|
108
|
+
# 参数列表可能为空,或者用逗号分隔的表达式列表
|
109
|
+
arg_list = Optional(delimitedList(expr), default=[])
|
110
|
+
return LPAREN + Group(arg_list) + RPAREN
|
111
|
+
|
112
|
+
|
113
|
+
postfix = Forward()
|
114
|
+
# 后缀可以是函数调用,也可以是属性访问
|
115
|
+
postfix_operation = ((parse_function_call(atom)
|
116
|
+
).setParseAction(lambda t: ('CALL', t[0].asList())) |
|
117
|
+
(DOT + SYMBOL).setParseAction(lambda t: ('ATTR', t[0])))
|
118
|
+
|
119
|
+
|
120
|
+
# 定义 factor:先解析 atom,然后再依次处理后缀操作
|
121
|
+
def attach_postfix(tokens: ParseResults) -> Expression:
|
122
|
+
# tokens[0] 为初始的 atom 对象
|
123
|
+
expr_obj = tokens[0]
|
124
|
+
# 遍历后缀操作序列
|
125
|
+
for op, arg in tokens[1:]:
|
126
|
+
if op == 'CALL':
|
127
|
+
# 对于函数调用,arg 是参数列表,调用 __call__ 运算符(由 Expression.__call__ 实现)
|
128
|
+
expr_obj = expr_obj(*arg)
|
129
|
+
elif op == 'ATTR':
|
130
|
+
# 对于属性访问,用 ObjectMethod 构造
|
131
|
+
expr_obj = ObjectMethod(expr_obj, '__getattr__', arg)
|
132
|
+
return expr_obj
|
133
|
+
|
134
|
+
|
135
|
+
# 将 atom 与后缀操作连接
|
136
|
+
postfix << (atom + Optional(
|
137
|
+
(postfix_operation[...]))).setParseAction(attach_postfix)
|
138
|
+
|
139
|
+
#------------------------------------------------------------------------------
|
140
|
+
# 现在构造整个表达式解析器,利用 infixNotation 建立运算符优先级
|
141
|
+
expr <<= infixNotation(
|
142
|
+
postfix,
|
143
|
+
[
|
144
|
+
(oneOf('! ~ + -'), 1, opAssoc.RIGHT, unary_parse_action),
|
145
|
+
# 指数运算,右结合
|
146
|
+
(Literal("**"), 2, opAssoc.RIGHT, binary_parse_action),
|
147
|
+
(oneOf('* / // % @'), 2, opAssoc.LEFT, binary_parse_action),
|
148
|
+
(oneOf('+ -'), 2, opAssoc.LEFT, binary_parse_action),
|
149
|
+
(oneOf('<< >>'), 2, opAssoc.LEFT, binary_parse_action),
|
150
|
+
(oneOf('&'), 2, opAssoc.LEFT, binary_parse_action),
|
151
|
+
(oneOf('^'), 2, opAssoc.LEFT, binary_parse_action),
|
152
|
+
(oneOf('|'), 2, opAssoc.LEFT, binary_parse_action),
|
153
|
+
(oneOf('< <= > >= == !='), 2, opAssoc.LEFT, binary_parse_action),
|
154
|
+
])
|
43
155
|
|
44
156
|
ConstType = (int, float, complex)
|
45
157
|
_empty = object()
|
@@ -91,7 +203,8 @@ class Env():
|
|
91
203
|
}
|
92
204
|
|
93
205
|
def __contains__(self, key):
|
94
|
-
return key in self.consts or key in self.variables
|
206
|
+
return (key in self.consts or key in self.variables
|
207
|
+
or key in self.functions or key in self.refs)
|
95
208
|
|
96
209
|
def __getitem__(self, key):
|
97
210
|
if key in self.consts:
|
@@ -107,6 +220,8 @@ class Env():
|
|
107
220
|
def __setitem__(self, key, value):
|
108
221
|
if key in self.consts:
|
109
222
|
raise KeyError(f"Key {key:r} is const")
|
223
|
+
if key in self.functions:
|
224
|
+
raise KeyError(f"Key {key:r} is function")
|
110
225
|
elif isinstance(value, Ref):
|
111
226
|
self.create_ref(key, value.name)
|
112
227
|
elif key in self.refs:
|
@@ -117,6 +232,8 @@ class Env():
|
|
117
232
|
def __delitem__(self, key):
|
118
233
|
if key in self.consts:
|
119
234
|
raise KeyError(f"Key {key:r} is const")
|
235
|
+
if key in self.functions:
|
236
|
+
raise KeyError(f"Key {key:r} is function")
|
120
237
|
elif key in self.refs:
|
121
238
|
del self[self.refs[key]]
|
122
239
|
else:
|
@@ -576,8 +693,14 @@ class ObjectMethod(Expression):
|
|
576
693
|
if isinstance(obj, Expression) or any(
|
577
694
|
isinstance(x, Expression) for x in args):
|
578
695
|
return ObjectMethod(obj, self.method, *args)
|
696
|
+
elif self.method == '__getattr__':
|
697
|
+
print(f"getattr {obj} {args}")
|
698
|
+
return getattr(
|
699
|
+
obj, *[a.name if isinstance(a, Symbol) else a for a in args])
|
579
700
|
else:
|
580
|
-
return getattr(obj, self.method)(*
|
701
|
+
return getattr(obj, self.method)(*[
|
702
|
+
a.value(env) if isinstance(a, Expression) else a for a in args
|
703
|
+
])
|
581
704
|
|
582
705
|
def __repr__(self):
|
583
706
|
if self.method == '__call__':
|
@@ -604,7 +727,11 @@ class Symbol(Expression):
|
|
604
727
|
|
605
728
|
def eval(self, env):
|
606
729
|
if self.name in env:
|
607
|
-
|
730
|
+
value = env[self.name]
|
731
|
+
if isinstance(value, Expression):
|
732
|
+
return value.eval(env)
|
733
|
+
else:
|
734
|
+
return value
|
608
735
|
else:
|
609
736
|
return self
|
610
737
|
|
@@ -644,3 +771,29 @@ sign = Symbol('sign')
|
|
644
771
|
heaviside = Symbol('heaviside')
|
645
772
|
erf = Symbol('erf')
|
646
773
|
erfc = Symbol('erfc')
|
774
|
+
|
775
|
+
|
776
|
+
def calc(exp: str | Expression, **kwargs) -> Expression:
|
777
|
+
"""
|
778
|
+
Calculate the expression.
|
779
|
+
|
780
|
+
Parameters
|
781
|
+
----------
|
782
|
+
exp : str | Expression
|
783
|
+
The expression to be calculated.
|
784
|
+
env : Env, optional
|
785
|
+
The environment to be used for the calculation. Default is _default_env.
|
786
|
+
**kwargs : dict
|
787
|
+
Additional arguments to be passed to the expression.
|
788
|
+
|
789
|
+
Returns
|
790
|
+
-------
|
791
|
+
Expression
|
792
|
+
The calculated expression.
|
793
|
+
"""
|
794
|
+
env = Env()
|
795
|
+
for k, v in kwargs.items():
|
796
|
+
env[k] = v
|
797
|
+
if isinstance(exp, str):
|
798
|
+
exp = expr.parseString(exp, parseAll=True)[0]
|
799
|
+
return exp.eval(env)
|
qulab/scan/server.py
CHANGED
@@ -14,6 +14,7 @@ from loguru import logger
|
|
14
14
|
|
15
15
|
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
16
16
|
|
17
|
+
from ..cli.config import get_config_value, log_options
|
17
18
|
from .curd import (create_cell, create_config, create_notebook, get_config,
|
18
19
|
query_record, remove_tags, tag, update_tags)
|
19
20
|
from .models import Cell, Notebook
|
@@ -411,123 +412,36 @@ async def serv(port,
|
|
411
412
|
last_flush_time = time.time()
|
412
413
|
|
413
414
|
|
414
|
-
async def main(port,
|
415
|
-
|
416
|
-
|
417
|
-
timeout=1,
|
418
|
-
buffer=1024,
|
419
|
-
interval=60,
|
420
|
-
log='stderr',
|
421
|
-
no_watch=True,
|
422
|
-
debug=False):
|
423
|
-
if no_watch:
|
424
|
-
logger.remove()
|
425
|
-
if debug:
|
426
|
-
level = 'DEBUG'
|
427
|
-
else:
|
428
|
-
level = 'INFO'
|
429
|
-
if log == 'stderr':
|
430
|
-
logger.add(sys.stderr, level=level)
|
431
|
-
elif log == 'stdout':
|
432
|
-
logger.add(sys.stdout, level=level)
|
433
|
-
else:
|
434
|
-
logger.add(sys.stderr, level=level)
|
435
|
-
logger.add(log, level=level)
|
436
|
-
logger.debug(f"logging level: {level}")
|
437
|
-
logger.info('Server starting...')
|
438
|
-
await serv(port, datapath, url, buffer * 1024 * 1024, interval)
|
439
|
-
else:
|
440
|
-
process = None
|
441
|
-
|
442
|
-
while True:
|
443
|
-
try:
|
444
|
-
with ZMQContextManager(
|
445
|
-
zmq.DEALER, connect=f"tcp://127.0.0.1:{port}") as sock:
|
446
|
-
sock.setsockopt(zmq.LINGER, 0)
|
447
|
-
sock.send_pyobj({"method": "ping"})
|
448
|
-
logger.debug('ping.')
|
449
|
-
if sock.poll(int(1000 * timeout)):
|
450
|
-
sock.recv()
|
451
|
-
logger.debug('recv pong.')
|
452
|
-
else:
|
453
|
-
logger.debug('timeout.')
|
454
|
-
raise asyncio.TimeoutError()
|
455
|
-
except (zmq.error.ZMQError, asyncio.TimeoutError):
|
456
|
-
if process is not None:
|
457
|
-
logger.debug(
|
458
|
-
f'killing process... PID={process.pid}, returncode={process.returncode}'
|
459
|
-
)
|
460
|
-
process.kill()
|
461
|
-
logger.debug(
|
462
|
-
f'killed process. PID={process.pid}, returncode={process.returncode}'
|
463
|
-
)
|
464
|
-
cmd = [
|
465
|
-
sys.executable,
|
466
|
-
"-m",
|
467
|
-
"qulab",
|
468
|
-
"server",
|
469
|
-
"--port",
|
470
|
-
f"{port}",
|
471
|
-
"--datapath",
|
472
|
-
f"{datapath}",
|
473
|
-
"--url",
|
474
|
-
f"{url}",
|
475
|
-
"--timeout",
|
476
|
-
f"{timeout}",
|
477
|
-
"--buffer",
|
478
|
-
f"{buffer}",
|
479
|
-
"--interval",
|
480
|
-
f"{interval}",
|
481
|
-
"--log",
|
482
|
-
f"{log}",
|
483
|
-
]
|
484
|
-
if url:
|
485
|
-
cmd.extend(['--url', url])
|
486
|
-
if debug:
|
487
|
-
cmd.append('--debug')
|
488
|
-
cmd.append("--no-watch")
|
489
|
-
logger.debug(f"starting process: {' '.join(cmd)}")
|
490
|
-
process = subprocess.Popen(cmd, cwd=os.getcwd())
|
491
|
-
logger.debug(
|
492
|
-
f'process started. PID={process.pid}, returncode={process.returncode}'
|
493
|
-
)
|
494
|
-
|
495
|
-
# Capture and log the output
|
496
|
-
# stdout, stderr = process.communicate(timeout=5)
|
497
|
-
# if stdout:
|
498
|
-
# logger.info(f'Server stdout: {stdout.decode()}')
|
499
|
-
# if stderr:
|
500
|
-
# logger.error(f'Server stderr: {stderr.decode()}')
|
501
|
-
|
502
|
-
await asyncio.sleep(5)
|
503
|
-
await asyncio.sleep(timeout)
|
415
|
+
async def main(port, datapath, url, buffer=1024, interval=60):
|
416
|
+
logger.info('Server starting...')
|
417
|
+
await serv(port, datapath, url, buffer * 1024 * 1024, interval)
|
504
418
|
|
505
419
|
|
506
420
|
@click.command()
|
507
421
|
@click.option('--port',
|
508
|
-
default=
|
422
|
+
default=get_config_value('port',
|
423
|
+
int,
|
424
|
+
command_name='server',
|
425
|
+
default=6789),
|
509
426
|
help='Port of the server.')
|
510
|
-
@click.option('--datapath',
|
427
|
+
@click.option('--datapath',
|
428
|
+
default=get_config_value('data',
|
429
|
+
Path,
|
430
|
+
command_name='server',
|
431
|
+
default=Path.home() / 'qulab' / 'data'),
|
432
|
+
help='Path of the data.')
|
511
433
|
@click.option('--url', default='sqlite', help='URL of the database.')
|
512
|
-
@click.option('--timeout', default=1, help='Timeout of ping.')
|
513
434
|
@click.option('--buffer', default=1024, help='Buffer size (MB).')
|
514
435
|
@click.option('--interval',
|
515
436
|
default=60,
|
516
437
|
help='Interval of flush cache, in unit of second.')
|
517
|
-
@
|
518
|
-
|
519
|
-
@click.option('--debug', is_flag=True, help='Debug mode.')
|
520
|
-
def server(port, datapath, url, timeout, buffer, interval, log, no_watch,
|
521
|
-
debug):
|
438
|
+
@log_options(command_name='server')
|
439
|
+
def server(port, datapath, url, buffer, interval):
|
522
440
|
try:
|
523
441
|
import uvloop
|
524
|
-
uvloop.run(
|
525
|
-
main(port, Path(datapath), url, timeout, buffer, interval, log,
|
526
|
-
True, debug))
|
442
|
+
uvloop.run(main(port, Path(datapath), url, buffer, interval))
|
527
443
|
except ImportError:
|
528
|
-
asyncio.run(
|
529
|
-
main(port, Path(datapath), url, timeout, buffer, interval, log,
|
530
|
-
True, debug))
|
444
|
+
asyncio.run(main(port, Path(datapath), url, buffer, interval))
|
531
445
|
|
532
446
|
|
533
447
|
if __name__ == "__main__":
|
qulab/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "2.
|
1
|
+
__version__ = "2.10.0"
|
@@ -1,22 +1,23 @@
|
|
1
1
|
qulab/__init__.py,sha256=fL8FSMGk4vzTMXLxWgNHpd1fNWau4yujSWjPEPqudos,367
|
2
2
|
qulab/__main__.py,sha256=fjaRSL_uUjNIzBGNgjlGswb9TJ2VD5qnkZHW3hItrD4,68
|
3
3
|
qulab/dicttree.py,sha256=tRRMpGZYVOLw0TEByE3_2Ss8FdOmzuGL9e1DWbs8qoY,13684
|
4
|
-
qulab/fun.cpython-310-darwin.so,sha256=
|
4
|
+
qulab/fun.cpython-310-darwin.so,sha256=63ufLELOb5Qais4CFu5O_kWXOezOpdLOhCi4LBK9MP0,126864
|
5
5
|
qulab/typing.py,sha256=vg62sGqxuD9CI5677ejlzAmf2fVdAESZCQjAE_xSxPg,69
|
6
6
|
qulab/utils.py,sha256=BdLdlfjpe6m6gSeONYmpAKTTqxDaYHNk4exlz8kZxTg,2982
|
7
|
-
qulab/version.py,sha256
|
7
|
+
qulab/version.py,sha256=-wrxKlVMUOYQC2baw-827pV-q4STQnQkmi5VCiGpO64,22
|
8
8
|
qulab/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
qulab/cli/commands.py,sha256=F1LATmSC9lOAdnrOTMK7DRjETCEcOmMsocovWRyjWTc,597
|
10
|
-
qulab/cli/config.py,sha256=
|
10
|
+
qulab/cli/config.py,sha256=fWeWHiX-Oxodvfv4g0B-UFisJlYcF1Hv6V1fKib2mOM,5447
|
11
|
+
qulab/cli/decorators.py,sha256=66lzX1FtGG3C0qheLOjbKquLitnivXgmWyEG2ywRvKY,598
|
11
12
|
qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
|
12
13
|
qulab/executor/analyze.py,sha256=4Hau5LrKUdpweh7W94tcG4ahgxucHOevbM0hm57T7zE,5649
|
13
|
-
qulab/executor/cli.py,sha256=
|
14
|
-
qulab/executor/load.py,sha256=
|
15
|
-
qulab/executor/schedule.py,sha256=
|
14
|
+
qulab/executor/cli.py,sha256=JhaOvMfLHOFe8HdQhGeqU3Lj3z0mJXLXJhzIwmAsaso,12251
|
15
|
+
qulab/executor/load.py,sha256=6QLlATu2KyGY2nrWLFTVxDE4DwoCXx3WNx72QR_LZB0,18715
|
16
|
+
qulab/executor/schedule.py,sha256=PmNRvs41DXKC2teHj5XGblpUEKLrsZpgPqk2K-opA-g,20009
|
16
17
|
qulab/executor/storage.py,sha256=8K73KGLAVgchJdtd4rKHXkr1CQOJORWH-Gi57w8IYsw,21081
|
17
18
|
qulab/executor/template.py,sha256=_HEtsUQ5_jSujCw8FBDAK1PRTMRCa4iD4DduHIpjo3c,10569
|
18
19
|
qulab/executor/transform.py,sha256=rk4CLIKVjGRaFzi5FVSgadUxAKKVLSopEHZCaAzDwDg,3435
|
19
|
-
qulab/executor/utils.py,sha256=
|
20
|
+
qulab/executor/utils.py,sha256=S254JDTKVA4Fnu9lslNBos6vEssLWn8zbMjhmLP-Gi8,6434
|
20
21
|
qulab/monitor/__init__.py,sha256=nTHelnDpxRS_fl_B38TsN0njgq8eVTEz9IAnN3NbDlM,42
|
21
22
|
qulab/monitor/__main__.py,sha256=w3yUcqq195LzSnXTkQcuC1RSFRhy4oQ_PEBmucXguME,97
|
22
23
|
qulab/monitor/config.py,sha256=fQ5JcsMApKc1UwANEnIvbDQZl8uYW0tle92SaYtX9lI,744
|
@@ -29,13 +30,13 @@ qulab/monitor/qt_compat.py,sha256=OK71_JSO_iyXjRIKHANmaK4Lx4bILUzmXI-mwKg3QeI,78
|
|
29
30
|
qulab/monitor/toolbar.py,sha256=WEag6cxAtEsOLL14XvM7pSE56EA3MO188_JuprNjdBs,7948
|
30
31
|
qulab/scan/__init__.py,sha256=ZX4WsvqYxvJeHLgGSrtJoAnVU94gxY7EHKMxYooMERg,130
|
31
32
|
qulab/scan/curd.py,sha256=ZcwO5SKO95OcDgpUPDHcK5FW_BrJbUzuBk71UG_pqnM,6888
|
32
|
-
qulab/scan/expression.py,sha256=
|
33
|
+
qulab/scan/expression.py,sha256=Mk9-hzmTzDszM0p6J_FlrewzQerOBWhRv5_iFApA768,24978
|
33
34
|
qulab/scan/models.py,sha256=JFofv-RH0gpS3--M6izXiQg7iGkS9a_em2DwhS5kTTk,17626
|
34
35
|
qulab/scan/optimize.py,sha256=VT9TpuqDkG7FdJJkYy60r5Pfrmjvfu5i36Ru6XvIiTI,2561
|
35
36
|
qulab/scan/query.py,sha256=-5uHMhXSyGovK1oy_uUbGVEbRFzaMBkP78ZMNfI3jD0,11809
|
36
37
|
qulab/scan/record.py,sha256=yIHPANf6nuBXy8Igf-dMtGJ7wuFTLYlBaaAUc0AzwyU,21280
|
37
38
|
qulab/scan/scan.py,sha256=iXvbnXLZvHa4v88MlYiZ_LYubEB7ZfbX7OiFTMhl_1o,39479
|
38
|
-
qulab/scan/server.py,sha256=
|
39
|
+
qulab/scan/server.py,sha256=QhUo-TxdTb_PDBd4sDRiI5L1WHmuEOAYEmUiFhjNqc4,16995
|
39
40
|
qulab/scan/space.py,sha256=OQLepiNNP5TNYMHXeVFT59lL5n4soPmnMoMy_o9EHt0,6696
|
40
41
|
qulab/scan/utils.py,sha256=SzJ_c4cLZJzERZr_CJO1_4juOxjfwCpU2K1mkc1PWGM,6124
|
41
42
|
qulab/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -98,9 +99,9 @@ qulab/visualization/plot_seq.py,sha256=UWTS6p9nfX_7B8ehcYo6UnSTUCjkBsNU9jiOeW2ca
|
|
98
99
|
qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
|
99
100
|
qulab/visualization/rot3d.py,sha256=lMrEJlRLwYe6NMBlGkKYpp_V9CTipOAuDy6QW_cQK00,734
|
100
101
|
qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
|
101
|
-
qulab-2.
|
102
|
-
qulab-2.
|
103
|
-
qulab-2.
|
104
|
-
qulab-2.
|
105
|
-
qulab-2.
|
106
|
-
qulab-2.
|
102
|
+
qulab-2.10.0.dist-info/licenses/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
|
103
|
+
qulab-2.10.0.dist-info/METADATA,sha256=zgYXM6nfKcUtCQlhiOXvYpUAmrrQko9kO_7syyfjkCo,3753
|
104
|
+
qulab-2.10.0.dist-info/WHEEL,sha256=qnIvK8L8kOW9FTrzifubcmNsZckgNfGDNKzGG7FMEdE,114
|
105
|
+
qulab-2.10.0.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
|
106
|
+
qulab-2.10.0.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
107
|
+
qulab-2.10.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|