QuLab 2.4.14__cp311-cp311-macosx_10_9_universal2.whl → 2.4.15__cp311-cp311-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-2.4.14.dist-info → QuLab-2.4.15.dist-info}/METADATA +1 -1
- {QuLab-2.4.14.dist-info → QuLab-2.4.15.dist-info}/RECORD +13 -11
- qulab/__main__.py +1 -29
- qulab/cli/__init__.py +29 -0
- qulab/cli/config.py +88 -0
- qulab/executor/cli.py +178 -0
- qulab/executor/load.py +40 -17
- qulab/fun.cpython-311-darwin.so +0 -0
- qulab/version.py +1 -1
- qulab/executor/__main__.py +0 -200
- {QuLab-2.4.14.dist-info → QuLab-2.4.15.dist-info}/LICENSE +0 -0
- {QuLab-2.4.14.dist-info → QuLab-2.4.15.dist-info}/WHEEL +0 -0
- {QuLab-2.4.14.dist-info → QuLab-2.4.15.dist-info}/entry_points.txt +0 -0
- {QuLab-2.4.14.dist-info → QuLab-2.4.15.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
qulab/__init__.py,sha256=P-Mx2p4TVmL91SoxoeXcj8Qm0x4xUf5Q_FLk0Yc_gIQ,138
|
|
2
|
-
qulab/__main__.py,sha256=
|
|
2
|
+
qulab/__main__.py,sha256=JgErYkiskih8Y6oRwowALtR-rwQhAAdqOYWjQraRIPI,59
|
|
3
3
|
qulab/dicttree.py,sha256=tRRMpGZYVOLw0TEByE3_2Ss8FdOmzuGL9e1DWbs8qoY,13684
|
|
4
|
-
qulab/fun.cpython-311-darwin.so,sha256=
|
|
5
|
-
qulab/version.py,sha256=
|
|
4
|
+
qulab/fun.cpython-311-darwin.so,sha256=1cFTXrPgr32EX6kyYVdMM2CTag-1MMpy0D4atRhisPA,126848
|
|
5
|
+
qulab/version.py,sha256=QW2MBz1JeuFtoyMh-qAewXiVGQGFzZAiBx69V3wIX5U,22
|
|
6
|
+
qulab/cli/__init__.py,sha256=tgDIkkeIoasQXAifJZ6NU8jDgpNgb2a-B0C4nF0evrE,559
|
|
7
|
+
qulab/cli/config.py,sha256=QksTYD3-RBYBG8xnKLAh7EXrNOwxr128XXr30ZTFW6A,2983
|
|
6
8
|
qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
|
|
7
|
-
qulab/executor/
|
|
8
|
-
qulab/executor/load.py,sha256=
|
|
9
|
+
qulab/executor/cli.py,sha256=LRSb5-J8Tn7dj-fxwCp3OP_6YUgPRoY-f8O18sbWG8I,5164
|
|
10
|
+
qulab/executor/load.py,sha256=4qQrw9O5dmWYYwxWt15z7uc6B3YuRB7xKgKi0g8-YtA,10418
|
|
9
11
|
qulab/executor/schedule.py,sha256=SfMZys8WdmIvfW5fPM42g-F6XwewTdpC0-puhf5TA54,11721
|
|
10
12
|
qulab/executor/storage.py,sha256=M66Q5_Uc5MMfc_QAuuaaexwAz7wxBPMkeleB5nRpQmI,4621
|
|
11
13
|
qulab/executor/transform.py,sha256=inaOn6eqCs22ZZ0xAQl8s8YCoEACaXSwFNNu7jqdwAk,2148
|
|
@@ -89,9 +91,9 @@ qulab/visualization/plot_seq.py,sha256=UWTS6p9nfX_7B8ehcYo6UnSTUCjkBsNU9jiOeW2ca
|
|
|
89
91
|
qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
|
|
90
92
|
qulab/visualization/rot3d.py,sha256=lMrEJlRLwYe6NMBlGkKYpp_V9CTipOAuDy6QW_cQK00,734
|
|
91
93
|
qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
|
|
92
|
-
QuLab-2.4.
|
|
93
|
-
QuLab-2.4.
|
|
94
|
-
QuLab-2.4.
|
|
95
|
-
QuLab-2.4.
|
|
96
|
-
QuLab-2.4.
|
|
97
|
-
QuLab-2.4.
|
|
94
|
+
QuLab-2.4.15.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
|
|
95
|
+
QuLab-2.4.15.dist-info/METADATA,sha256=EgZbZvkhzL5UTZFxqJ2yHVir2RSvAkmSCNd_nUgitdE,3699
|
|
96
|
+
QuLab-2.4.15.dist-info/WHEEL,sha256=K10eKSN6_vzvMOgXxWbVOQNR7Orfl6gBTCpCI8bcYx4,114
|
|
97
|
+
QuLab-2.4.15.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
|
|
98
|
+
QuLab-2.4.15.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
|
99
|
+
QuLab-2.4.15.dist-info/RECORD,,
|
qulab/__main__.py
CHANGED
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
from .executor.__main__ import create, get, maintain, run, set
|
|
4
|
-
from .monitor.__main__ import main as monitor
|
|
5
|
-
from .scan.server import server
|
|
6
|
-
from .sys.net.cli import dht
|
|
7
|
-
from .visualization.__main__ import plot
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@click.group()
|
|
11
|
-
def cli():
|
|
12
|
-
pass
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@cli.command()
|
|
16
|
-
def hello():
|
|
17
|
-
"""Print hello world."""
|
|
18
|
-
click.echo('hello, world')
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
cli.add_command(monitor)
|
|
22
|
-
cli.add_command(plot)
|
|
23
|
-
cli.add_command(dht)
|
|
24
|
-
cli.add_command(server)
|
|
25
|
-
cli.add_command(maintain)
|
|
26
|
-
cli.add_command(run)
|
|
27
|
-
cli.add_command(create)
|
|
28
|
-
cli.add_command(set)
|
|
29
|
-
cli.add_command(get)
|
|
1
|
+
from .cli import cli
|
|
30
2
|
|
|
31
3
|
if __name__ == '__main__':
|
|
32
4
|
cli()
|
qulab/cli/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from ..executor.cli import create, get, maintain, run, set
|
|
4
|
+
from ..monitor.__main__ import main as monitor
|
|
5
|
+
from ..scan.server import server
|
|
6
|
+
from ..sys.net.cli import dht
|
|
7
|
+
from ..visualization.__main__ import plot
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def cli():
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@cli.command()
|
|
16
|
+
def hello():
|
|
17
|
+
"""Print hello world."""
|
|
18
|
+
click.echo('hello, world')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
cli.add_command(monitor)
|
|
22
|
+
cli.add_command(plot)
|
|
23
|
+
cli.add_command(dht)
|
|
24
|
+
cli.add_command(server)
|
|
25
|
+
cli.add_command(maintain)
|
|
26
|
+
cli.add_command(run)
|
|
27
|
+
cli.add_command(create)
|
|
28
|
+
cli.add_command(set)
|
|
29
|
+
cli.add_command(get)
|
qulab/cli/config.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import functools
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
CONFIG_PATH = os.path.expanduser("~/.qulab.ini")
|
|
11
|
+
ENV_PREFIX = "QULAB_"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_config_value(option_name, type_cast=str, command_name=None):
|
|
15
|
+
"""支持命令专属配置的优先级获取"""
|
|
16
|
+
# 构造环境变量名
|
|
17
|
+
if command_name:
|
|
18
|
+
env_var = f"{ENV_PREFIX}{command_name.upper()}_{option_name.upper()}"
|
|
19
|
+
config_section = command_name
|
|
20
|
+
else:
|
|
21
|
+
env_var = f"{ENV_PREFIX}{option_name.upper()}"
|
|
22
|
+
config_section = "common"
|
|
23
|
+
|
|
24
|
+
# 1. 检查环境变量
|
|
25
|
+
if env_value := os.getenv(env_var):
|
|
26
|
+
if type_cast is bool:
|
|
27
|
+
return env_value.lower() in ("true", "1", "yes")
|
|
28
|
+
if "path" in option_name:
|
|
29
|
+
return os.path.expanduser(env_value)
|
|
30
|
+
return type_cast(env_value)
|
|
31
|
+
|
|
32
|
+
# 2. 检查配置文件
|
|
33
|
+
config = configparser.ConfigParser()
|
|
34
|
+
# 先加载默认配置防止段不存在
|
|
35
|
+
config.read_dict({config_section: {}})
|
|
36
|
+
if Path(CONFIG_PATH).exists():
|
|
37
|
+
config.read(CONFIG_PATH)
|
|
38
|
+
|
|
39
|
+
# 从对应配置段读取
|
|
40
|
+
if config.has_section(config_section):
|
|
41
|
+
if config_value := config.get(config_section,
|
|
42
|
+
option_name,
|
|
43
|
+
fallback=None):
|
|
44
|
+
if type_cast is bool:
|
|
45
|
+
return config_value.lower() in ("true", "1", "yes")
|
|
46
|
+
if "path" in option_name:
|
|
47
|
+
return os.path.expanduser(config_value)
|
|
48
|
+
return type_cast(config_value)
|
|
49
|
+
|
|
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)
|
|
55
|
+
if value is None and command_name is not None:
|
|
56
|
+
return _get_config_value(option_name, type_cast)
|
|
57
|
+
return value
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def log_options(func):
|
|
61
|
+
"""通用配置装饰器(所有命令共用)"""
|
|
62
|
+
|
|
63
|
+
@click.option("--debug",
|
|
64
|
+
is_flag=True,
|
|
65
|
+
default=lambda: get_config_value("debug", bool) or False,
|
|
66
|
+
help=f"Enable debug mode")
|
|
67
|
+
@click.option("--log",
|
|
68
|
+
type=click.Path(),
|
|
69
|
+
default=lambda: get_config_value("log"),
|
|
70
|
+
help=f"Log file path")
|
|
71
|
+
@functools.wraps(func)
|
|
72
|
+
def wrapper(*args, log=None, debug=False, **kwargs):
|
|
73
|
+
if log is None and not debug:
|
|
74
|
+
logger.remove()
|
|
75
|
+
logger.add(sys.stderr, level='INFO')
|
|
76
|
+
elif log is None and debug:
|
|
77
|
+
logger.remove()
|
|
78
|
+
logger.add(sys.stderr, level='DEBUG')
|
|
79
|
+
elif log is not None and not debug:
|
|
80
|
+
logger.configure(handlers=[dict(sink=log, level='INFO')])
|
|
81
|
+
elif log is not None and debug:
|
|
82
|
+
logger.configure(handlers=[
|
|
83
|
+
dict(sink=log, level='DEBUG'),
|
|
84
|
+
dict(sink=sys.stderr, level='DEBUG')
|
|
85
|
+
])
|
|
86
|
+
return func(*args, **kwargs)
|
|
87
|
+
|
|
88
|
+
return wrapper
|
qulab/executor/cli.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from ..cli.config import ENV_PREFIX, get_config_value, log_options
|
|
11
|
+
from .load import find_unreferenced_workflows, get_entries, load_workflow
|
|
12
|
+
from .schedule import maintain as maintain_workflow
|
|
13
|
+
from .schedule import run as run_workflow
|
|
14
|
+
from .transform import set_config_api
|
|
15
|
+
from .utils import workflow_template
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def command_option(command_name):
|
|
19
|
+
"""命令专属配置装饰器工厂"""
|
|
20
|
+
|
|
21
|
+
def decorator(func):
|
|
22
|
+
|
|
23
|
+
@click.option(
|
|
24
|
+
'--code',
|
|
25
|
+
'-c',
|
|
26
|
+
default=lambda: get_config_value("code", str, command_name),
|
|
27
|
+
help='The path of the code.')
|
|
28
|
+
@click.option(
|
|
29
|
+
'--data',
|
|
30
|
+
'-d',
|
|
31
|
+
default=lambda: get_config_value("data", str, command_name),
|
|
32
|
+
help='The path of the data.')
|
|
33
|
+
@click.option(
|
|
34
|
+
'--api',
|
|
35
|
+
'-a',
|
|
36
|
+
default=lambda: get_config_value("api", str, command_name),
|
|
37
|
+
help='The modlule name of the api.')
|
|
38
|
+
@functools.wraps(func)
|
|
39
|
+
def wrapper(*args, **kwargs):
|
|
40
|
+
return func(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
return decorator
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@click.command()
|
|
48
|
+
@click.argument('workflow')
|
|
49
|
+
@click.option('--code',
|
|
50
|
+
'-c',
|
|
51
|
+
default=lambda: get_config_value("code", str, 'create'),
|
|
52
|
+
help='The path of the code.')
|
|
53
|
+
def create(workflow, code):
|
|
54
|
+
"""
|
|
55
|
+
Create a new workflow file.
|
|
56
|
+
"""
|
|
57
|
+
if code is None:
|
|
58
|
+
code = Path.cwd()
|
|
59
|
+
|
|
60
|
+
fname = Path(code) / f'{workflow}'
|
|
61
|
+
fname = Path(os.path.expanduser(fname))
|
|
62
|
+
if fname.exists():
|
|
63
|
+
click.echo(f'{workflow} already exists')
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
fname.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
deps = find_unreferenced_workflows(code)
|
|
68
|
+
|
|
69
|
+
with open(fname, 'w') as f:
|
|
70
|
+
f.write(workflow_template(list(deps)))
|
|
71
|
+
click.echo(f'{workflow} created')
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@click.command()
|
|
75
|
+
@click.argument('key')
|
|
76
|
+
@click.argument('value', type=str)
|
|
77
|
+
@click.option('--api',
|
|
78
|
+
'-a',
|
|
79
|
+
default=lambda: get_config_value("api", str, 'set'),
|
|
80
|
+
help='The modlule name of the api.')
|
|
81
|
+
def set(key, value, api):
|
|
82
|
+
"""
|
|
83
|
+
Set a config.
|
|
84
|
+
"""
|
|
85
|
+
from . import transform
|
|
86
|
+
if api is not None:
|
|
87
|
+
api = importlib.import_module(api)
|
|
88
|
+
set_config_api(api.query_config, api.update_config)
|
|
89
|
+
try:
|
|
90
|
+
value = eval(value)
|
|
91
|
+
except:
|
|
92
|
+
pass
|
|
93
|
+
transform.update_config({key: value})
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@click.command()
|
|
97
|
+
@click.argument('key')
|
|
98
|
+
@click.option('--api',
|
|
99
|
+
'-a',
|
|
100
|
+
default=lambda: get_config_value("api", str, 'get'),
|
|
101
|
+
help='The modlule name of the api.')
|
|
102
|
+
def get(key, api):
|
|
103
|
+
"""
|
|
104
|
+
Get a config.
|
|
105
|
+
"""
|
|
106
|
+
from . import transform
|
|
107
|
+
if api is not None:
|
|
108
|
+
api = importlib.import_module(api)
|
|
109
|
+
set_config_api(api.query_config, api.update_config)
|
|
110
|
+
click.echo(transform.query_config(key))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@click.command()
|
|
114
|
+
@click.argument('workflow')
|
|
115
|
+
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
116
|
+
@click.option('--no-dependents',
|
|
117
|
+
'-n',
|
|
118
|
+
is_flag=True,
|
|
119
|
+
help='Do not run dependents.')
|
|
120
|
+
@log_options
|
|
121
|
+
@command_option('run')
|
|
122
|
+
def run(workflow, code, data, api, plot, no_dependents):
|
|
123
|
+
"""
|
|
124
|
+
Run a workflow.
|
|
125
|
+
"""
|
|
126
|
+
if api is not None:
|
|
127
|
+
api = importlib.import_module(api)
|
|
128
|
+
set_config_api(api.query_config, api.update_config)
|
|
129
|
+
if code is None:
|
|
130
|
+
code = Path.cwd()
|
|
131
|
+
if data is None:
|
|
132
|
+
data = Path(code) / 'logs'
|
|
133
|
+
|
|
134
|
+
code = Path(os.path.expanduser(code))
|
|
135
|
+
data = Path(os.path.expanduser(data))
|
|
136
|
+
|
|
137
|
+
wf = load_workflow(workflow, code)
|
|
138
|
+
|
|
139
|
+
if no_dependents:
|
|
140
|
+
if hasattr(wf, 'entries'):
|
|
141
|
+
for entry in get_entries(wf, code):
|
|
142
|
+
run_workflow(entry, code, data, plot=plot)
|
|
143
|
+
else:
|
|
144
|
+
run_workflow(wf, code, data, plot=plot)
|
|
145
|
+
else:
|
|
146
|
+
if hasattr(wf, 'entries'):
|
|
147
|
+
for entry in get_entries(wf, code):
|
|
148
|
+
maintain_workflow(entry, code, data, run=True, plot=plot)
|
|
149
|
+
else:
|
|
150
|
+
maintain_workflow(wf, code, data, run=True, plot=plot)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@click.command()
|
|
154
|
+
@click.argument('workflow')
|
|
155
|
+
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
156
|
+
@log_options
|
|
157
|
+
@command_option('maintain')
|
|
158
|
+
def maintain(workflow, code, data, api, plot):
|
|
159
|
+
"""
|
|
160
|
+
Maintain a workflow.
|
|
161
|
+
"""
|
|
162
|
+
if api is not None:
|
|
163
|
+
api = importlib.import_module(api)
|
|
164
|
+
set_config_api(api.query_config, api.update_config)
|
|
165
|
+
if code is None:
|
|
166
|
+
code = Path.cwd()
|
|
167
|
+
if data is None:
|
|
168
|
+
data = Path(code) / 'logs'
|
|
169
|
+
|
|
170
|
+
code = Path(os.path.expanduser(code))
|
|
171
|
+
data = Path(os.path.expanduser(data))
|
|
172
|
+
|
|
173
|
+
wf = load_workflow(workflow, code)
|
|
174
|
+
if hasattr(wf, 'entries'):
|
|
175
|
+
for entry in get_entries(wf, code):
|
|
176
|
+
maintain_workflow(entry, code, data, run=False, plot=plot)
|
|
177
|
+
else:
|
|
178
|
+
maintain_workflow(wf, code, data, run=False, plot=plot)
|
qulab/executor/load.py
CHANGED
|
@@ -119,6 +119,17 @@ def is_workflow(module: ModuleType) -> bool:
|
|
|
119
119
|
return False
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
def is_template(path: str | Path) -> bool:
|
|
123
|
+
path = Path(path)
|
|
124
|
+
if path.name == 'template.py':
|
|
125
|
+
return True
|
|
126
|
+
if path.name.endswith('_template.py'):
|
|
127
|
+
return True
|
|
128
|
+
if 'templates' in path.parts:
|
|
129
|
+
return True
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
122
133
|
def find_unreferenced_workflows(path: str) -> list[str]:
|
|
123
134
|
root = Path(path).resolve()
|
|
124
135
|
workflows = []
|
|
@@ -128,8 +139,7 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
128
139
|
for file_path in root.rglob("*.py"):
|
|
129
140
|
if file_path.name == "__init__.py":
|
|
130
141
|
continue
|
|
131
|
-
if file_path
|
|
132
|
-
"_template.py"):
|
|
142
|
+
if is_template(file_path):
|
|
133
143
|
continue
|
|
134
144
|
try:
|
|
135
145
|
rel_path = file_path.relative_to(root)
|
|
@@ -188,8 +198,6 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
188
198
|
def load_workflow_from_file(file_name: str,
|
|
189
199
|
base_path: str | Path,
|
|
190
200
|
package='workflows') -> WorkflowType:
|
|
191
|
-
if file_name.startswith('cfg:'):
|
|
192
|
-
return SetConfigWorkflow(file_name[4:])
|
|
193
201
|
base_path = Path(base_path)
|
|
194
202
|
path = Path(file_name)
|
|
195
203
|
module_name = f"{package}.{'.'.join([*path.parts[:-1], path.stem])}"
|
|
@@ -197,6 +205,9 @@ def load_workflow_from_file(file_name: str,
|
|
|
197
205
|
module = module_from_spec(spec)
|
|
198
206
|
spec.loader.exec_module(module)
|
|
199
207
|
|
|
208
|
+
if hasattr(module, 'entries'):
|
|
209
|
+
return module
|
|
210
|
+
|
|
200
211
|
if not hasattr(module, '__timeout__'):
|
|
201
212
|
module.__timeout__ = None
|
|
202
213
|
|
|
@@ -209,13 +220,13 @@ def load_workflow_from_file(file_name: str,
|
|
|
209
220
|
return module
|
|
210
221
|
|
|
211
222
|
|
|
212
|
-
def load_workflow_from_template(
|
|
223
|
+
def load_workflow_from_template(template_path: str,
|
|
213
224
|
mappping: dict[str, str],
|
|
214
225
|
base_path: str | Path,
|
|
215
|
-
|
|
226
|
+
target_path: str | None = None,
|
|
216
227
|
package='workflows') -> WorkflowType:
|
|
217
228
|
base_path = Path(base_path)
|
|
218
|
-
path = Path(
|
|
229
|
+
path = Path(template_path)
|
|
219
230
|
|
|
220
231
|
with open(base_path / path) as f:
|
|
221
232
|
content = f.read()
|
|
@@ -239,23 +250,31 @@ def load_workflow_from_template(file_name: str,
|
|
|
239
250
|
content = template.substitute(mappping)
|
|
240
251
|
|
|
241
252
|
hash_str = hashlib.md5(pickle.dumps(mappping)).hexdigest()[:8]
|
|
242
|
-
if
|
|
253
|
+
if target_path is None:
|
|
243
254
|
if path.stem == 'template':
|
|
244
255
|
path = path.parent / f'tmp{hash_str}.py'
|
|
245
|
-
|
|
256
|
+
elif path.stem.endswith('_template'):
|
|
246
257
|
path = path.parent / path.stem.replace('_template',
|
|
247
258
|
f'_tmp{hash_str}.py')
|
|
248
|
-
else:
|
|
249
|
-
if path.stem == 'template':
|
|
250
|
-
path = path.parent / f'{subtitle}.py'
|
|
251
259
|
else:
|
|
252
|
-
path = path.parent / path.stem.
|
|
253
|
-
|
|
260
|
+
path = path.parent / f'{path.stem}_tmp{hash_str}.py'
|
|
261
|
+
else:
|
|
262
|
+
path = target_path
|
|
254
263
|
|
|
255
264
|
file = base_path / path
|
|
256
265
|
if not file.exists():
|
|
266
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
257
267
|
with open(file, 'w') as f:
|
|
258
268
|
f.write(content)
|
|
269
|
+
else:
|
|
270
|
+
if file.stat().st_mtime < Path(template_path).stat().st_mtime:
|
|
271
|
+
with open(file, 'w') as f:
|
|
272
|
+
f.write(content)
|
|
273
|
+
else:
|
|
274
|
+
if file.read_text() != content:
|
|
275
|
+
logger.warning(
|
|
276
|
+
f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
|
|
277
|
+
)
|
|
259
278
|
|
|
260
279
|
module = load_workflow_from_file(str(path), base_path, package)
|
|
261
280
|
|
|
@@ -271,9 +290,9 @@ def load_workflow(workflow: str | tuple[str, dict],
|
|
|
271
290
|
w = load_workflow_from_template(file_name, mapping, base_path,
|
|
272
291
|
None, package)
|
|
273
292
|
elif len(workflow) == 3:
|
|
274
|
-
|
|
275
|
-
w = load_workflow_from_template(
|
|
276
|
-
|
|
293
|
+
template_path, target_path, mapping = workflow
|
|
294
|
+
w = load_workflow_from_template(template_path, mapping, base_path,
|
|
295
|
+
target_path, package)
|
|
277
296
|
else:
|
|
278
297
|
raise ValueError(f"Invalid workflow: {workflow}")
|
|
279
298
|
w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
|
|
@@ -294,3 +313,7 @@ def load_workflow(workflow: str | tuple[str, dict],
|
|
|
294
313
|
def get_dependents(workflow: WorkflowType,
|
|
295
314
|
code_path: str | Path) -> list[WorkflowType]:
|
|
296
315
|
return [load_workflow(n, code_path) for n in workflow.depends()[0]]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_entries(workflow: WorkflowType, code_path: str | Path) -> WorkflowType:
|
|
319
|
+
return [load_workflow(n, code_path) for n in workflow.entries()]
|
qulab/fun.cpython-311-darwin.so
CHANGED
|
Binary file
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.4.
|
|
1
|
+
__version__ = "2.4.15"
|
qulab/executor/__main__.py
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import importlib
|
|
3
|
-
import os
|
|
4
|
-
import sys
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
import click
|
|
8
|
-
from loguru import logger
|
|
9
|
-
|
|
10
|
-
from .load import find_unreferenced_workflows, load_workflow
|
|
11
|
-
from .schedule import maintain as maintain_workflow
|
|
12
|
-
from .schedule import run as run_workflow
|
|
13
|
-
from .transform import set_config_api
|
|
14
|
-
from .utils import workflow_template
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def load_config():
|
|
18
|
-
import yaml
|
|
19
|
-
|
|
20
|
-
config_paths = [
|
|
21
|
-
Path.home() / ".myapp/config.yaml", # 用户主目录
|
|
22
|
-
Path("config.yaml") # 当前目录
|
|
23
|
-
]
|
|
24
|
-
for path in config_paths:
|
|
25
|
-
if path.exists():
|
|
26
|
-
with open(path) as f:
|
|
27
|
-
return yaml.safe_load(f)
|
|
28
|
-
return {"defaults": {"log": "default.log", "debug": False}}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_config_value(option_name):
|
|
32
|
-
# 1. 尝试从环境变量读取
|
|
33
|
-
env_value = os.environ.get(f"MYAPP_{option_name.upper()}")
|
|
34
|
-
if env_value:
|
|
35
|
-
return env_value
|
|
36
|
-
|
|
37
|
-
# 2. 尝试从配置文件读取
|
|
38
|
-
config = load_config()
|
|
39
|
-
return config["defaults"].get(option_name)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def log_options(func):
|
|
43
|
-
|
|
44
|
-
@click.option("--debug", is_flag=True, help="Enable debug mode.")
|
|
45
|
-
@click.option("--log", type=str, help="Log file path.")
|
|
46
|
-
@functools.wraps(func) # 保持函数元信息
|
|
47
|
-
def wrapper(*args, log=None, debug=False, **kwargs):
|
|
48
|
-
if log is None and not debug:
|
|
49
|
-
logger.remove()
|
|
50
|
-
logger.add(sys.stderr, level='INFO')
|
|
51
|
-
elif log is None and debug:
|
|
52
|
-
logger.remove()
|
|
53
|
-
logger.add(sys.stderr, level='DEBUG')
|
|
54
|
-
elif log is not None and not debug:
|
|
55
|
-
logger.configure(handlers=[dict(sink=log, level='INFO')])
|
|
56
|
-
elif log is not None and debug:
|
|
57
|
-
logger.configure(handlers=[
|
|
58
|
-
dict(sink=log, level='DEBUG'),
|
|
59
|
-
dict(sink=sys.stderr, level='DEBUG')
|
|
60
|
-
])
|
|
61
|
-
return func(*args, **kwargs)
|
|
62
|
-
|
|
63
|
-
return wrapper
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@click.group()
|
|
67
|
-
def cli():
|
|
68
|
-
pass
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@click.command()
|
|
72
|
-
@click.argument('workflow')
|
|
73
|
-
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
74
|
-
def create(workflow, code):
|
|
75
|
-
"""
|
|
76
|
-
Create a new workflow file.
|
|
77
|
-
"""
|
|
78
|
-
if code is None:
|
|
79
|
-
code = Path.cwd()
|
|
80
|
-
|
|
81
|
-
fname = Path(code) / f'{workflow}'
|
|
82
|
-
fname = Path(os.path.expanduser(fname))
|
|
83
|
-
if fname.exists():
|
|
84
|
-
click.echo(f'{workflow} already exists')
|
|
85
|
-
return
|
|
86
|
-
|
|
87
|
-
fname.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
-
deps = find_unreferenced_workflows(code)
|
|
89
|
-
|
|
90
|
-
with open(fname, 'w') as f:
|
|
91
|
-
f.write(workflow_template(list(deps)))
|
|
92
|
-
click.echo(f'{workflow} created')
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
@click.command()
|
|
96
|
-
@click.argument('key')
|
|
97
|
-
@click.argument('value', type=str)
|
|
98
|
-
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
99
|
-
def set(key, value, api):
|
|
100
|
-
"""
|
|
101
|
-
Set a config.
|
|
102
|
-
"""
|
|
103
|
-
from . import transform
|
|
104
|
-
if api is not None:
|
|
105
|
-
api = importlib.import_module(api)
|
|
106
|
-
set_config_api(api.query_config, api.update_config)
|
|
107
|
-
try:
|
|
108
|
-
value = eval(value)
|
|
109
|
-
except:
|
|
110
|
-
pass
|
|
111
|
-
transform.update_config({key: value})
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@click.command()
|
|
115
|
-
@click.argument('key')
|
|
116
|
-
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
117
|
-
def get(key, api):
|
|
118
|
-
"""
|
|
119
|
-
Get a config.
|
|
120
|
-
"""
|
|
121
|
-
from . import transform
|
|
122
|
-
if api is not None:
|
|
123
|
-
api = importlib.import_module(api)
|
|
124
|
-
set_config_api(api.query_config, api.update_config)
|
|
125
|
-
click.echo(transform.query_config(key))
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@click.command()
|
|
129
|
-
@click.argument('workflow')
|
|
130
|
-
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
131
|
-
@click.option('--data', '-d', default=None, help='The path of the data.')
|
|
132
|
-
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
133
|
-
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
134
|
-
@click.option('--no-dependents',
|
|
135
|
-
'-n',
|
|
136
|
-
is_flag=True,
|
|
137
|
-
help='Do not run dependents.')
|
|
138
|
-
@log_options
|
|
139
|
-
def run(workflow, code, data, api, plot, no_dependents):
|
|
140
|
-
"""
|
|
141
|
-
Run a workflow.
|
|
142
|
-
"""
|
|
143
|
-
if api is not None:
|
|
144
|
-
api = importlib.import_module(api)
|
|
145
|
-
set_config_api(api.query_config, api.update_config)
|
|
146
|
-
if code is None:
|
|
147
|
-
code = Path.cwd()
|
|
148
|
-
if data is None:
|
|
149
|
-
data = Path(code) / 'logs'
|
|
150
|
-
|
|
151
|
-
code = Path(os.path.expanduser(code))
|
|
152
|
-
data = Path(os.path.expanduser(data))
|
|
153
|
-
|
|
154
|
-
if no_dependents:
|
|
155
|
-
run_workflow(load_workflow(workflow, code), code, data, plot=plot)
|
|
156
|
-
else:
|
|
157
|
-
maintain_workflow(load_workflow(workflow, code),
|
|
158
|
-
code,
|
|
159
|
-
data,
|
|
160
|
-
run=True,
|
|
161
|
-
plot=plot)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
@click.command()
|
|
165
|
-
@click.argument('workflow')
|
|
166
|
-
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
167
|
-
@click.option('--data', '-d', default=None, help='The path of the data.')
|
|
168
|
-
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
169
|
-
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
170
|
-
@log_options
|
|
171
|
-
def maintain(workflow, code, data, api, plot):
|
|
172
|
-
"""
|
|
173
|
-
Maintain a workflow.
|
|
174
|
-
"""
|
|
175
|
-
if api is not None:
|
|
176
|
-
api = importlib.import_module(api)
|
|
177
|
-
set_config_api(api.query_config, api.update_config)
|
|
178
|
-
if code is None:
|
|
179
|
-
code = Path.cwd()
|
|
180
|
-
if data is None:
|
|
181
|
-
data = Path(code) / 'logs'
|
|
182
|
-
|
|
183
|
-
code = Path(os.path.expanduser(code))
|
|
184
|
-
data = Path(os.path.expanduser(data))
|
|
185
|
-
|
|
186
|
-
maintain_workflow(load_workflow(workflow, code),
|
|
187
|
-
code,
|
|
188
|
-
data,
|
|
189
|
-
run=False,
|
|
190
|
-
plot=plot)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
cli.add_command(maintain)
|
|
194
|
-
cli.add_command(run)
|
|
195
|
-
cli.add_command(create)
|
|
196
|
-
cli.add_command(set)
|
|
197
|
-
cli.add_command(get)
|
|
198
|
-
|
|
199
|
-
if __name__ == '__main__':
|
|
200
|
-
cli()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|