QuLab 2.4.14__cp310-cp310-win_amd64.whl → 2.4.16__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: QuLab
3
- Version: 2.4.14
3
+ Version: 2.4.16
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,11 +1,13 @@
1
1
  qulab/__init__.py,sha256=vkFybY8YSsQilYdThPRD83-btPAR41sy_WCXiM-6mME,141
2
- qulab/__main__.py,sha256=vUnluugYF1cH2UA44odFW1Z4AKecBZIfAihvsbhhSe0,629
2
+ qulab/__main__.py,sha256=g9iBs8xxX6Yik7cmgllQkpBN8C4JNoZVsEOyCCLCyFU,63
3
3
  qulab/dicttree.py,sha256=ZoSJVWK4VMqfzj42gPb_n5RqLlM6K1Me0WmLIfLEYf8,14195
4
- qulab/fun.cp310-win_amd64.pyd,sha256=UYlwuWIM6wHQzBVChDCW2SoTcIq6GeLlXU0eyeYi-P0,31232
5
- qulab/version.py,sha256=IPQCIOITfbX8l3dhIiuTYy89V7yOtf70dflfUCn6Dgk,22
4
+ qulab/fun.cp310-win_amd64.pyd,sha256=boOBNmUeX_ZxH-eKN4KgO7ZLyj-99F091uVZkxXKc08,31232
5
+ qulab/version.py,sha256=rVOnxEJVvl0ZWvCblBzVBGcp0VALbV_koS5W-UB9zfk,22
6
+ qulab/cli/__init__.py,sha256=6xd2eYw32k1NmfAuYSu__1kaP12Oz1QVqwbkYXdWno4,588
7
+ qulab/cli/config.py,sha256=gvMObzaVJ-xTJ1GmhP3sATayfDasYlijX1krSGeccNY,3071
6
8
  qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
7
- qulab/executor/__main__.py,sha256=qgZcdmx1wBZInHf6ionpuuZytIJnaDje9WRv-a8o2OM,5870
8
- qulab/executor/load.py,sha256=WNpJmHAxXaQKPkmycsqbqeuAuiudNi-4GscmBKfdKlw,9713
9
+ qulab/executor/cli.py,sha256=LRSb5-J8Tn7dj-fxwCp3OP_6YUgPRoY-f8O18sbWG8I,5164
10
+ qulab/executor/load.py,sha256=yTLoWOtTvmCTuYFZ6qFsjBM_CvsrKgmUkBSHHz3223A,10899
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=Uo1-dB1YE9IN_A9tuaOs9ZG3S5dKDQ_l98iD2Wbxp
89
91
  qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
90
92
  qulab/visualization/rot3d.py,sha256=jGHJcqj1lEWBUV-W4GUGONGacqjrYvuFoFCwPse5h1Y,757
91
93
  qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
92
- QuLab-2.4.14.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
93
- QuLab-2.4.14.dist-info/METADATA,sha256=37tzAZ4vAtg_Jf8j4qWlL6uXf8DmPfztQdyfQ0TUoTk,3804
94
- QuLab-2.4.14.dist-info/WHEEL,sha256=rzGfZgUcGeKSgIHGYMuqg4xE4VPHxnaldXH6BG0zjVk,101
95
- QuLab-2.4.14.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
96
- QuLab-2.4.14.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
97
- QuLab-2.4.14.dist-info/RECORD,,
94
+ QuLab-2.4.16.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
95
+ QuLab-2.4.16.dist-info/METADATA,sha256=800PogLCuT2uN9w6hfEuVv5sOrzXlr3AobBHeiruLrQ,3804
96
+ QuLab-2.4.16.dist-info/WHEEL,sha256=rzGfZgUcGeKSgIHGYMuqg4xE4VPHxnaldXH6BG0zjVk,101
97
+ QuLab-2.4.16.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
98
+ QuLab-2.4.16.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
99
+ QuLab-2.4.16.dist-info/RECORD,,
qulab/__main__.py CHANGED
@@ -1,32 +1,4 @@
1
- import click
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
@@ -15,6 +15,7 @@ from .storage import Result
15
15
 
16
16
  class SetConfigWorkflow():
17
17
  __timeout__ = None
18
+ __mtime__ = 0
18
19
 
19
20
  def __init__(self, key):
20
21
  self.key = key
@@ -119,6 +120,17 @@ def is_workflow(module: ModuleType) -> bool:
119
120
  return False
120
121
 
121
122
 
123
+ def is_template(path: str | Path) -> bool:
124
+ path = Path(path)
125
+ if path.name == 'template.py':
126
+ return True
127
+ if path.name.endswith('_template.py'):
128
+ return True
129
+ if 'templates' in path.parts:
130
+ return True
131
+ return False
132
+
133
+
122
134
  def find_unreferenced_workflows(path: str) -> list[str]:
123
135
  root = Path(path).resolve()
124
136
  workflows = []
@@ -128,8 +140,7 @@ def find_unreferenced_workflows(path: str) -> list[str]:
128
140
  for file_path in root.rglob("*.py"):
129
141
  if file_path.name == "__init__.py":
130
142
  continue
131
- if file_path.name == 'template.py' or file_path.name.endswith(
132
- "_template.py"):
143
+ if is_template(file_path):
133
144
  continue
134
145
  try:
135
146
  rel_path = file_path.relative_to(root)
@@ -188,14 +199,18 @@ def find_unreferenced_workflows(path: str) -> list[str]:
188
199
  def load_workflow_from_file(file_name: str,
189
200
  base_path: str | Path,
190
201
  package='workflows') -> WorkflowType:
191
- if file_name.startswith('cfg:'):
192
- return SetConfigWorkflow(file_name[4:])
193
202
  base_path = Path(base_path)
194
203
  path = Path(file_name)
204
+ if not (base_path / path).exists():
205
+ raise FileNotFoundError(f"File not found: {base_path / path}")
195
206
  module_name = f"{package}.{'.'.join([*path.parts[:-1], path.stem])}"
196
207
  spec = spec_from_file_location(module_name, base_path / path)
197
208
  module = module_from_spec(spec)
198
209
  spec.loader.exec_module(module)
210
+ module.__mtime__ = (base_path / path).stat().st_mtime
211
+
212
+ if hasattr(module, 'entries'):
213
+ return module
199
214
 
200
215
  if not hasattr(module, '__timeout__'):
201
216
  module.__timeout__ = None
@@ -209,17 +224,20 @@ def load_workflow_from_file(file_name: str,
209
224
  return module
210
225
 
211
226
 
212
- def load_workflow_from_template(file_name: str,
227
+ def load_workflow_from_template(template_path: str,
213
228
  mappping: dict[str, str],
214
229
  base_path: str | Path,
215
- subtitle: str | None = None,
216
- package='workflows') -> WorkflowType:
230
+ target_path: str | None = None,
231
+ package='workflows',
232
+ mtime: float = 0) -> WorkflowType:
217
233
  base_path = Path(base_path)
218
- path = Path(file_name)
234
+ path = Path(template_path)
219
235
 
220
236
  with open(base_path / path) as f:
221
237
  content = f.read()
222
238
 
239
+ mtime = max(Path(template_path).stat().st_mtime, mtime)
240
+
223
241
  def replace(text):
224
242
  """
225
243
  将给定文本中的所有 _D_("var") 替换为 ${var}。
@@ -239,41 +257,51 @@ def load_workflow_from_template(file_name: str,
239
257
  content = template.substitute(mappping)
240
258
 
241
259
  hash_str = hashlib.md5(pickle.dumps(mappping)).hexdigest()[:8]
242
- if subtitle is None:
260
+ if target_path is None:
243
261
  if path.stem == 'template':
244
262
  path = path.parent / f'tmp{hash_str}.py'
245
- else:
263
+ elif path.stem.endswith('_template'):
246
264
  path = path.parent / path.stem.replace('_template',
247
265
  f'_tmp{hash_str}.py')
248
- else:
249
- if path.stem == 'template':
250
- path = path.parent / f'{subtitle}.py'
251
266
  else:
252
- path = path.parent / path.stem.replace('_template',
253
- f'_{subtitle}.py')
267
+ path = path.parent / f'{path.stem}_tmp{hash_str}.py'
268
+ else:
269
+ path = target_path
254
270
 
255
271
  file = base_path / path
256
272
  if not file.exists():
273
+ file.parent.mkdir(parents=True, exist_ok=True)
257
274
  with open(file, 'w') as f:
258
275
  f.write(content)
276
+ else:
277
+ if file.stat().st_mtime < mtime:
278
+ with open(file, 'w') as f:
279
+ f.write(content)
280
+ else:
281
+ if file.read_text() != content:
282
+ logger.warning(
283
+ f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
284
+ )
259
285
 
260
286
  module = load_workflow_from_file(str(path), base_path, package)
287
+ module.__mtime__ = max(mtime, module.__mtime__)
261
288
 
262
289
  return module
263
290
 
264
291
 
265
292
  def load_workflow(workflow: str | tuple[str, dict],
266
293
  base_path: str | Path,
267
- package='workflows') -> WorkflowType:
294
+ package='workflows',
295
+ mtime: float = 0) -> WorkflowType:
268
296
  if isinstance(workflow, tuple):
269
297
  if len(workflow) == 2:
270
298
  file_name, mapping = workflow
271
299
  w = load_workflow_from_template(file_name, mapping, base_path,
272
- None, package)
300
+ None, package, mtime)
273
301
  elif len(workflow) == 3:
274
- file_name, subtitle, mapping = workflow
275
- w = load_workflow_from_template(file_name, mapping, base_path,
276
- subtitle, package)
302
+ template_path, target_path, mapping = workflow
303
+ w = load_workflow_from_template(template_path, mapping, base_path,
304
+ target_path, package, mtime)
277
305
  else:
278
306
  raise ValueError(f"Invalid workflow: {workflow}")
279
307
  w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
@@ -293,4 +321,14 @@ def load_workflow(workflow: str | tuple[str, dict],
293
321
 
294
322
  def get_dependents(workflow: WorkflowType,
295
323
  code_path: str | Path) -> list[WorkflowType]:
296
- return [load_workflow(n, code_path) for n in workflow.depends()[0]]
324
+ return [
325
+ load_workflow(n, code_path, mtime=workflow.__mtime__)
326
+ for n in workflow.depends()[0]
327
+ ]
328
+
329
+
330
+ def get_entries(workflow: WorkflowType, code_path: str | Path) -> WorkflowType:
331
+ return [
332
+ load_workflow(n, code_path, mtime=workflow.__mtime__)
333
+ for n in workflow.entries()
334
+ ]
Binary file
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.4.14"
1
+ __version__ = "2.4.16"
@@ -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