QuLab 2.4.9__cp311-cp311-win_amd64.whl → 2.4.12__cp311-cp311-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.9
3
+ Version: 2.4.12
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,12 +1,12 @@
1
1
  qulab/__init__.py,sha256=vkFybY8YSsQilYdThPRD83-btPAR41sy_WCXiM-6mME,141
2
- qulab/__main__.py,sha256=sQ1z8lk9Q4pq7X9lEzLcY8t6Izr6uxQXO0t3qnTUxEs,575
2
+ qulab/__main__.py,sha256=vUnluugYF1cH2UA44odFW1Z4AKecBZIfAihvsbhhSe0,629
3
3
  qulab/dicttree.py,sha256=ZoSJVWK4VMqfzj42gPb_n5RqLlM6K1Me0WmLIfLEYf8,14195
4
- qulab/fun.cp311-win_amd64.pyd,sha256=X2kr9Eo5djmRQorj7qerQb8phqUI5OTLcoDsdszjCnY,31744
5
- qulab/version.py,sha256=85DENkQDPFKjENaBuXBqtMA83jQapfZmg5ANqmiCkYc,21
4
+ qulab/fun.cp311-win_amd64.pyd,sha256=qtM2pxcTw_kvHrb4iIkjhrrfsW-Jl0bBNdSz5-I6GIE,31744
5
+ qulab/version.py,sha256=3Ac_4-IUz5KK-UOcxuXzEVTZr4p9bV93jrLgO3Au_zA,22
6
6
  qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
7
- qulab/executor/__main__.py,sha256=1Mg5fJ0dYSWQnP6wgTa0s6U_WH7NT2lCnCQOHyx_I7c,2430
8
- qulab/executor/load.py,sha256=jZHkEzFF8ufFAIajkRDKmHYYJxbxI3RcEbAFIbl9VQ0,6208
9
- qulab/executor/schedule.py,sha256=koVlSw7ejoScW6RBm6HDbzIJZcGPckTGFQDVYaG1Kj8,9563
7
+ qulab/executor/__main__.py,sha256=qgZcdmx1wBZInHf6ionpuuZytIJnaDje9WRv-a8o2OM,5870
8
+ qulab/executor/load.py,sha256=GSRrP2UXOmTgg5VDTmZy_y4xXk91H3Sd5SEX-tOEBqY,8689
9
+ qulab/executor/schedule.py,sha256=UOace0Gpt9gR0xe1BoNSUfeHxZynaUS0BFHZhmfnB30,11849
10
10
  qulab/executor/storage.py,sha256=M66Q5_Uc5MMfc_QAuuaaexwAz7wxBPMkeleB5nRpQmI,4621
11
11
  qulab/executor/transform.py,sha256=inaOn6eqCs22ZZ0xAQl8s8YCoEACaXSwFNNu7jqdwAk,2148
12
12
  qulab/executor/utils.py,sha256=n3uCSKh-qdDFFeNvOpj7_es2_B4AaC-ASAlV9gPmSO0,3086
@@ -89,9 +89,9 @@ qulab/visualization/plot_seq.py,sha256=Uo1-dB1YE9IN_A9tuaOs9ZG3S5dKDQ_l98iD2Wbxp
89
89
  qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
90
90
  qulab/visualization/rot3d.py,sha256=jGHJcqj1lEWBUV-W4GUGONGacqjrYvuFoFCwPse5h1Y,757
91
91
  qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
92
- QuLab-2.4.9.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
93
- QuLab-2.4.9.dist-info/METADATA,sha256=AhSxsNn3xk_-YQ6pH1jA4fev09EnUxIdTdxi9PY-owg,3803
94
- QuLab-2.4.9.dist-info/WHEEL,sha256=yNnHoQL2GZYIUXm9YvoaBpFjGlUoK9qq9oqYeudrWlE,101
95
- QuLab-2.4.9.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
96
- QuLab-2.4.9.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
97
- QuLab-2.4.9.dist-info/RECORD,,
92
+ QuLab-2.4.12.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
93
+ QuLab-2.4.12.dist-info/METADATA,sha256=SHtCS_P4r8VmZXrsFopdlOZDbdEvgdKejBsJzB4xHJ8,3804
94
+ QuLab-2.4.12.dist-info/WHEEL,sha256=yNnHoQL2GZYIUXm9YvoaBpFjGlUoK9qq9oqYeudrWlE,101
95
+ QuLab-2.4.12.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
96
+ QuLab-2.4.12.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
97
+ QuLab-2.4.12.dist-info/RECORD,,
qulab/__main__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import click
2
2
 
3
- from .executor.__main__ import create, maintain, run
3
+ from .executor.__main__ import create, get, maintain, run, set
4
4
  from .monitor.__main__ import main as monitor
5
5
  from .scan.server import server
6
6
  from .sys.net.cli import dht
@@ -25,6 +25,8 @@ cli.add_command(server)
25
25
  cli.add_command(maintain)
26
26
  cli.add_command(run)
27
27
  cli.add_command(create)
28
+ cli.add_command(set)
29
+ cli.add_command(get)
28
30
 
29
31
  if __name__ == '__main__':
30
32
  cli()
@@ -1,15 +1,68 @@
1
+ import functools
1
2
  import importlib
3
+ import os
4
+ import sys
2
5
  from pathlib import Path
3
6
 
4
7
  import click
8
+ from loguru import logger
5
9
 
6
- from .load import find_unreferenced_workflows
10
+ from .load import find_unreferenced_workflows, load_workflow
7
11
  from .schedule import maintain as maintain_workflow
8
12
  from .schedule import run as run_workflow
9
13
  from .transform import set_config_api
10
14
  from .utils import workflow_template
11
15
 
12
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
+
13
66
  @click.group()
14
67
  def cli():
15
68
  pass
@@ -17,7 +70,7 @@ def cli():
17
70
 
18
71
  @click.command()
19
72
  @click.argument('workflow')
20
- @click.option('--code', '-c', default=None)
73
+ @click.option('--code', '-c', default=None, help='The path of the code.')
21
74
  def create(workflow, code):
22
75
  """
23
76
  Create a new workflow file.
@@ -26,6 +79,7 @@ def create(workflow, code):
26
79
  code = Path.cwd()
27
80
 
28
81
  fname = Path(code) / f'{workflow}'
82
+ fname = Path(os.path.expanduser(fname))
29
83
  if fname.exists():
30
84
  click.echo(f'{workflow} already exists')
31
85
  return
@@ -38,13 +92,50 @@ def create(workflow, code):
38
92
  click.echo(f'{workflow} created')
39
93
 
40
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
+
41
128
  @click.command()
42
129
  @click.argument('workflow')
43
- @click.option('--code', '-c', default=None)
44
- @click.option('--data', '-d', default=None)
45
- @click.option('--api', '-a', default=None)
46
- @click.option('--plot', '-p', is_flag=True)
47
- @click.option('--no-dependents', '-n', is_flag=True)
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
48
139
  def run(workflow, code, data, api, plot, no_dependents):
49
140
  """
50
141
  Run a workflow.
@@ -57,18 +148,26 @@ def run(workflow, code, data, api, plot, no_dependents):
57
148
  if data is None:
58
149
  data = Path(code) / 'logs'
59
150
 
151
+ code = Path(os.path.expanduser(code))
152
+ data = Path(os.path.expanduser(data))
153
+
60
154
  if no_dependents:
61
- run_workflow(workflow, code, data, plot=plot)
155
+ run_workflow(load_workflow(workflow, code), code, data, plot=plot)
62
156
  else:
63
- maintain_workflow(workflow, code, data, run=True, plot=plot)
157
+ maintain_workflow(load_workflow(workflow, code),
158
+ code,
159
+ data,
160
+ run=True,
161
+ plot=plot)
64
162
 
65
163
 
66
164
  @click.command()
67
165
  @click.argument('workflow')
68
- @click.option('--code', '-c', default=None)
69
- @click.option('--data', '-d', default=None)
70
- @click.option('--api', '-a', default=None)
71
- @click.option('--plot', '-p', is_flag=True)
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
72
171
  def maintain(workflow, code, data, api, plot):
73
172
  """
74
173
  Maintain a workflow.
@@ -81,12 +180,21 @@ def maintain(workflow, code, data, api, plot):
81
180
  if data is None:
82
181
  data = Path(code) / 'logs'
83
182
 
84
- maintain_workflow(workflow, code, data, run=False, plot=plot)
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)
85
191
 
86
192
 
87
193
  cli.add_command(maintain)
88
194
  cli.add_command(run)
89
195
  cli.add_command(create)
196
+ cli.add_command(set)
197
+ cli.add_command(get)
90
198
 
91
199
  if __name__ == '__main__':
92
200
  cli()
qulab/executor/load.py CHANGED
@@ -1,10 +1,14 @@
1
+ import hashlib
1
2
  import inspect
3
+ import pickle
4
+ import re
5
+ import string
2
6
  import warnings
3
7
  from importlib.util import module_from_spec, spec_from_file_location
4
8
  from pathlib import Path
5
9
  from types import ModuleType
6
10
 
7
- import loguru
11
+ from loguru import logger
8
12
 
9
13
  from .storage import Result
10
14
 
@@ -124,12 +128,15 @@ def find_unreferenced_workflows(path: str) -> list[str]:
124
128
  for file_path in root.rglob("*.py"):
125
129
  if file_path.name == "__init__.py":
126
130
  continue
131
+ if file_path.name.endswith("_template.py") or re.match(
132
+ r'.*_tmp_[0-9a-fA-F]{8}.py', file_path.name):
133
+ continue
127
134
  try:
128
135
  rel_path = file_path.relative_to(root)
129
136
  except ValueError:
130
137
  continue
131
138
 
132
- module = load_workflow(str(rel_path), root)
139
+ module = load_workflow_from_module(str(rel_path), root)
133
140
 
134
141
  if is_workflow(module):
135
142
  rel_str = str(rel_path)
@@ -140,7 +147,7 @@ def find_unreferenced_workflows(path: str) -> list[str]:
140
147
 
141
148
  # Check dependencies for each workflow module
142
149
  for rel_str in workflows:
143
- module = load_workflow(rel_str, root)
150
+ module = load_workflow_from_module(rel_str, root)
144
151
 
145
152
  depends_func = getattr(module, "depends", None)
146
153
  if depends_func and callable(depends_func):
@@ -150,17 +157,15 @@ def find_unreferenced_workflows(path: str) -> list[str]:
150
157
  )
151
158
  continue
152
159
  try:
153
- depends_list = depends_func()
160
+ depends_list = depends_func()[0]
154
161
  except Exception as e:
155
162
  warnings.warn(f"Error calling depends() in {rel_str}: {e}")
156
163
  continue
157
164
 
158
165
  if not isinstance(depends_list, list) or not all(
159
- isinstance(item, str) for item in depends_list
160
- ):
166
+ isinstance(item, str) for item in depends_list):
161
167
  warnings.warn(
162
- f"depends() in {rel_str} did not return a list of strings"
163
- )
168
+ f"depends() in {rel_str} did not return a list of strings")
164
169
  continue
165
170
 
166
171
  for dep in depends_list:
@@ -178,9 +183,9 @@ def find_unreferenced_workflows(path: str) -> list[str]:
178
183
  return unreferenced
179
184
 
180
185
 
181
- def load_workflow(file_name: str,
182
- base_path: str | Path,
183
- package='workflows') -> WorkflowType:
186
+ def load_workflow_from_module(file_name: str,
187
+ base_path: str | Path,
188
+ package='workflows') -> WorkflowType:
184
189
  if file_name.startswith('cfg:'):
185
190
  return SetConfigWorkflow(file_name[4:])
186
191
  base_path = Path(base_path)
@@ -200,3 +205,60 @@ def load_workflow(file_name: str,
200
205
  verify_check_method(module)
201
206
 
202
207
  return module
208
+
209
+
210
+ def load_workflow_from_template(file_name: str,
211
+ mappping: dict[str, str],
212
+ base_path: str | Path,
213
+ subtitle: str | None = None,
214
+ package='workflows') -> WorkflowType:
215
+ base_path = Path(base_path)
216
+ path = Path(file_name)
217
+
218
+ with open(base_path / path) as f:
219
+ content = f.read()
220
+ template = string.Template(content)
221
+ content = template.substitute(mappping)
222
+
223
+ hash_str = hashlib.md5(pickle.dumps(mappping)).hexdigest()[:8]
224
+ if subtitle is None:
225
+ path = path.parent / path.stem.replace('_template',
226
+ f'_tmp{hash_str}.py')
227
+ else:
228
+ path = path.parent / path.stem.replace('_template', f'_{subtitle}.py')
229
+
230
+ with open(base_path / path, 'w') as f:
231
+ f.write(content)
232
+
233
+ module = load_workflow_from_module(str(path), base_path, package)
234
+
235
+ return module
236
+
237
+
238
+ def load_workflow(workflow: str | tuple[str, dict],
239
+ base_path: str | Path,
240
+ package='workflows') -> WorkflowType:
241
+ if isinstance(workflow, tuple):
242
+ if len(workflow) == 2:
243
+ file_name, mapping = workflow
244
+ w = load_workflow_from_template(file_name, mapping, base_path,
245
+ None, package)
246
+ elif len(workflow) == 3:
247
+ file_name, subtitle, mapping = workflow
248
+ w = load_workflow_from_template(file_name, mapping, base_path,
249
+ subtitle, package)
250
+ else:
251
+ raise ValueError(f"Invalid workflow: {workflow}")
252
+ w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
253
+ elif isinstance(workflow, str):
254
+ if workflow.startswith('cfg:'):
255
+ key = workflow[4:]
256
+ w = SetConfigWorkflow(key)
257
+ w.__workflow_id__ = workflow
258
+ else:
259
+ w = load_workflow_from_module(workflow, base_path, package)
260
+ w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
261
+ else:
262
+ raise TypeError(f"Invalid workflow: {workflow}")
263
+
264
+ return w
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from loguru import logger
7
7
 
8
8
  from . import transform
9
- from .load import load_workflow
9
+ from .load import WorkflowType, load_workflow
10
10
  from .storage import (Result, find_result, get_head, renew_result,
11
11
  revoke_result, save_result)
12
12
 
@@ -15,7 +15,7 @@ class CalibrationFailedError(Exception):
15
15
  pass
16
16
 
17
17
 
18
- def check_state(workflow: str, code_path: str | Path,
18
+ def check_state(workflow: WorkflowType, code_path: str | Path,
19
19
  state_path: str | Path) -> bool:
20
20
  """
21
21
  check state should report a pass if and only if the following are satisfied:
@@ -26,35 +26,42 @@ def check_state(workflow: str, code_path: str | Path,
26
26
  4. All dependencies pass check state.
27
27
  """
28
28
  logger.debug(f'check_state: "{workflow}"')
29
- result = find_result(workflow, state_path)
29
+ result = find_result(workflow.__workflow_id__, state_path)
30
30
  if not result:
31
- logger.debug(f'check_state failed: No history found for "{workflow}"')
31
+ logger.debug(
32
+ f'check_state failed: No history found for "{workflow.__workflow_id__}"'
33
+ )
32
34
  return False
33
- node = load_workflow(workflow, code_path)
34
- if hasattr(node, 'check_state') and callable(node.check_state):
35
- logger.debug(
36
- f'check_state: "{workflow}" has custom check_state method')
37
- return node.check_state(result)
38
- if node.__timeout__ is not None and datetime.now(
39
- ) > result.checked_time + timedelta(seconds=node.__timeout__):
40
- logger.debug(f'check_state failed: "{workflow}" has expired')
35
+ if hasattr(workflow, 'check_state') and callable(workflow.check_state):
36
+ logger.debug(
37
+ f'check_state: "{workflow.__workflow_id__}" has custom check_state method'
38
+ )
39
+ return workflow.check_state(result)
40
+ if workflow.__timeout__ is not None and datetime.now(
41
+ ) > result.checked_time + timedelta(seconds=workflow.__timeout__):
42
+ logger.debug(
43
+ f'check_state failed: "{workflow.__workflow_id__}" has expired')
41
44
  return False
42
45
  if not result.in_spec:
43
- logger.debug(f'check_state failed: "{workflow}" is out of spec')
46
+ logger.debug(
47
+ f'check_state failed: "{workflow.__workflow_id__}" is out of spec')
44
48
  return False
45
49
  if result.bad_data:
46
- logger.debug(f'check_state failed: "{workflow}" has bad data')
50
+ logger.debug(
51
+ f'check_state failed: "{workflow.__workflow_id__}" has bad data')
47
52
  return False
48
53
  for n in get_dependents(workflow, code_path):
49
- r = find_result(n, state_path)
54
+ r = find_result(n.__workflow_id__, state_path)
50
55
  if r is None or r.checked_time > result.checked_time:
51
56
  logger.debug(
52
- f'check_state failed: "{workflow}" has outdated dependencies')
57
+ f'check_state failed: "{workflow.__workflow_id__}" has outdated dependencies'
58
+ )
53
59
  return False
54
60
  for n in get_dependents(workflow, code_path):
55
61
  if not check_state(n, code_path, state_path):
56
62
  logger.debug(
57
- f'check_state failed: "{workflow}" has bad dependencies')
63
+ f'check_state failed: "{workflow.__workflow_id__}" has bad dependencies'
64
+ )
58
65
  return False
59
66
  return True
60
67
 
@@ -83,118 +90,153 @@ def call_plot(node, result, check=False):
83
90
 
84
91
 
85
92
  @functools.lru_cache(maxsize=128)
86
- def check_data(workflow: str, code_path: str | Path, state_path: str | Path,
87
- plot: bool, session_id: str) -> Result:
93
+ def check_data(workflow: WorkflowType, code_path: str | Path,
94
+ state_path: str | Path, plot: bool, session_id: str) -> Result:
88
95
  """
89
96
  check data answers two questions:
90
97
  Is the parameter associated with this cal in spec,
91
98
  and is the cal scan working as expected?
92
99
  """
93
- node = load_workflow(workflow, code_path)
94
- history = find_result(workflow, state_path)
100
+ history = find_result(workflow.__workflow_id__, state_path)
95
101
 
96
102
  if history is None:
97
- logger.debug(f'No history found for "{workflow}"')
103
+ logger.debug(f'No history found for "{workflow.__workflow_id__}"')
98
104
  result = Result()
99
105
  result.in_spec = False
100
106
  result.bad_data = False
101
107
  return result
102
108
 
103
109
  if history.bad_data:
104
- logger.debug(f'History found for "{workflow}", but bad data')
110
+ logger.debug(
111
+ f'History found for "{workflow.__workflow_id__}", but bad data')
105
112
  return history
106
113
  if not history.in_spec:
107
- logger.debug(f'History found for "{workflow}", but out of spec')
114
+ logger.debug(
115
+ f'History found for "{workflow.__workflow_id__}", but out of spec')
108
116
  return history
109
117
 
110
- logger.debug(f'History found for "{workflow}", but has expired')
118
+ logger.debug(
119
+ f'History found for "{workflow.__workflow_id__}", but has expired')
111
120
 
112
- if hasattr(node, 'check') and callable(node.check) and hasattr(
113
- node, 'check_analyze') and callable(node.check_analyze):
121
+ if hasattr(workflow, 'check') and callable(workflow.check) and hasattr(
122
+ workflow, 'check_analyze') and callable(workflow.check_analyze):
114
123
  logger.debug(f'Checking "{workflow}" with "check" method ...')
115
- data = node.check()
124
+ data = workflow.check()
116
125
  result = Result()
117
126
  result.data = data
118
- save_result(workflow, result, state_path)
127
+ save_result(workflow.__workflow_id__, result, state_path)
119
128
 
120
129
  logger.debug(f'Checked "{workflow}" !')
121
- result = call_analyzer(node, data, history, check=True, plot=plot)
130
+ result = call_analyzer(workflow, data, history, check=True, plot=plot)
122
131
  if result.in_spec:
123
- logger.debug(f'"{workflow}": checked in spec, renewing result')
124
- renew_result(workflow, state_path)
132
+ logger.debug(
133
+ f'"{workflow.__workflow_id__}": checked in spec, renewing result'
134
+ )
135
+ renew_result(workflow.__workflow_id__, state_path)
125
136
  else:
126
- logger.debug(f'"{workflow}": checked out of spec, revoking result')
127
- revoke_result(workflow, state_path)
137
+ logger.debug(
138
+ f'"{workflow.__workflow_id__}": checked out of spec, revoking result'
139
+ )
140
+ revoke_result(workflow.__workflow_id__, state_path)
128
141
  else:
129
- logger.debug(f'Checking "{workflow}" with "calibrate" method ...')
130
- data = node.calibrate()
142
+ logger.debug(
143
+ f'Checking "{workflow.__workflow_id__}" with "calibrate" method ...'
144
+ )
145
+ data = workflow.calibrate()
131
146
  result = Result()
132
147
  result.data = data
133
- save_result(workflow, result, state_path)
148
+ save_result(workflow.__workflow_id__, result, state_path)
134
149
 
135
150
  logger.debug(f'Calibrated "{workflow}" !')
136
- result = call_analyzer(node, data, history, check=False, plot=plot)
137
- save_result(workflow, result, state_path,
138
- get_head(workflow, state_path))
151
+ result = call_analyzer(workflow, data, history, check=False, plot=plot)
152
+ save_result(workflow.__workflow_id__, result, state_path,
153
+ get_head(workflow.__workflow_id__, state_path))
139
154
 
140
155
  return result
141
156
 
142
157
 
143
158
  @functools.lru_cache(maxsize=128)
144
- def calibrate(workflow, code_path: str | Path, state_path: str | Path,
145
- plot: bool, session_id: str) -> Result:
146
- node = load_workflow(workflow, code_path)
147
- history = find_result(workflow, state_path)
159
+ def calibrate(workflow: WorkflowType, code_path: str | Path,
160
+ state_path: str | Path, plot: bool, session_id: str) -> Result:
161
+ history = find_result(workflow.__workflow_id__, state_path)
148
162
 
149
- logger.debug(f'Calibrating "{workflow}" ...')
150
- data = node.calibrate()
163
+ logger.debug(f'Calibrating "{workflow.__workflow_id__}" ...')
164
+ data = workflow.calibrate()
151
165
  result = Result()
152
166
  result.data = data
153
- save_result(workflow, result, state_path)
154
- logger.debug(f'Calibrated "{workflow}" !')
155
- result = call_analyzer(node, data, history, check=False, plot=plot)
156
- save_result(workflow, result, state_path, get_head(workflow, state_path))
167
+ save_result(workflow.__workflow_id__, result, state_path)
168
+ logger.debug(f'Calibrated "{workflow.__workflow_id__}" !')
169
+ result = call_analyzer(workflow, data, history, check=False, plot=plot)
170
+ save_result(workflow.__workflow_id__, result, state_path,
171
+ get_head(workflow.__workflow_id__, state_path))
157
172
  return result
158
173
 
159
174
 
160
- def diagnose(node, code_path: str | Path, state_path: str | Path, plot: bool,
161
- session_id: str):
175
+ def diagnose(workflow: WorkflowType, code_path: str | Path,
176
+ state_path: str | Path, plot: bool, session_id: str):
162
177
  '''
163
178
  Returns: True if node or dependent recalibrated.
164
179
  '''
165
- logger.debug(f'diagnose "{node}"')
180
+ logger.debug(f'diagnose "{workflow.__workflow_id__}"')
166
181
  # check_data
167
- result = check_data(node, code_path, state_path, plot, session_id)
182
+ result = check_data(workflow, code_path, state_path, plot, session_id)
168
183
  # in spec case
169
184
  if result.in_spec:
170
- logger.debug(f'"{node}": Checked! In spec, no need to diagnose')
185
+ logger.debug(
186
+ f'"{workflow.__workflow_id__}": Checked! In spec, no need to diagnose'
187
+ )
171
188
  return False
172
189
  # bad data case
173
190
  recalibrated = []
174
191
  if result.bad_data:
192
+ logger.debug(
193
+ f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
175
194
  recalibrated = [
176
195
  diagnose(n, code_path, state_path, plot, session_id)
177
- for n in get_dependents(node, code_path)
196
+ for n in get_dependents(workflow, code_path)
178
197
  ]
179
- if not any(recalibrated) and recalibrated:
180
- logger.debug(f'"{node}": no dependents recalibrated.')
181
- return False
198
+ if not any(recalibrated):
199
+ if result.bad_data:
200
+ raise CalibrationFailedError(
201
+ f'"{workflow.__workflow_id__}": bad data but no dependents recalibrated.'
202
+ )
203
+ logger.debug(
204
+ f'"{workflow.__workflow_id__}": no dependents recalibrated.')
182
205
  # calibrate
183
- logger.debug(f'recalibrate "{node}" because some dependents recalibrated')
184
- result = calibrate(node, code_path, state_path, plot, session_id)
206
+ if any(recalibrated):
207
+ logger.debug(
208
+ f'recalibrate "{workflow.__workflow_id__}" because some dependents recalibrated.'
209
+ )
210
+ elif not result.in_spec and not result.bad_data:
211
+ logger.debug(
212
+ f'recalibrate "{workflow.__workflow_id__}" because out of spec.')
213
+ elif result.in_spec:
214
+ logger.error(
215
+ f'Never reach: recalibrate "{workflow.__workflow_id__}" because in spec.'
216
+ )
217
+ elif result.bad_data:
218
+ logger.error(
219
+ f'Never reach: recalibrate "{workflow.__workflow_id__}" because bad data.'
220
+ )
221
+ else:
222
+ logger.error(f'Never reach: recalibrate "{workflow.__workflow_id__}"')
223
+
224
+ result = calibrate(workflow, code_path, state_path, plot, session_id)
185
225
  if result.bad_data or not result.in_spec:
186
226
  raise CalibrationFailedError(
187
- f'"{node}": All dependents passed, but calibration failed!')
227
+ f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
228
+ )
188
229
  transform.update_parameters(result)
189
230
  return True
190
231
 
191
232
 
192
- def get_dependents(workflow: str, code_path: str | Path) -> list[str]:
193
- return [n for n in load_workflow(workflow, code_path).depends()[0]]
233
+ def get_dependents(workflow: WorkflowType,
234
+ code_path: str | Path) -> list[WorkflowType]:
235
+ return [load_workflow(n, code_path) for n in workflow.depends()[0]]
194
236
 
195
237
 
196
- #@logger.catch(reraise=True)
197
- def maintain(node,
238
+ @logger.catch(reraise=True)
239
+ def maintain(workflow: WorkflowType,
198
240
  code_path: str | Path,
199
241
  state_path: str | Path,
200
242
  session_id: str | None = None,
@@ -202,48 +244,61 @@ def maintain(node,
202
244
  plot: bool = False):
203
245
  if session_id is None:
204
246
  session_id = uuid.uuid4().hex
205
- logger.debug(f'run "{node}"' if run else f'maintain "{node}"')
247
+ logger.debug(f'run "{workflow.__workflow_id__}"'
248
+ if run else f'maintain "{workflow.__workflow_id__}"')
206
249
  # recursive maintain
207
- for n in get_dependents(node, code_path):
208
- logger.debug(f'maintain "{n}" because it is depended by "{node}"')
250
+ for n in get_dependents(workflow, code_path):
251
+ logger.debug(
252
+ f'maintain "{n.__workflow_id__}" because it is depended by "{workflow.__workflow_id__}"'
253
+ )
209
254
  maintain(n, code_path, state_path, session_id, run=False, plot=plot)
210
255
  else:
211
- logger.debug(f'"{node}": All dependents maintained')
256
+ logger.debug(
257
+ f'"{workflow.__workflow_id__}": All dependents maintained')
212
258
  # check_state
213
- if check_state(node, code_path, state_path) and not run:
214
- logger.debug(f'"{node}": In spec, no need to maintain')
259
+ if check_state(workflow, code_path, state_path) and not run:
260
+ logger.debug(
261
+ f'"{workflow.__workflow_id__}": In spec, no need to maintain')
215
262
  return
216
263
  # check_data
217
- result = check_data(node, code_path, state_path, plot, session_id)
264
+ result = check_data(workflow, code_path, state_path, plot, session_id)
218
265
  if result.in_spec:
219
266
  if not run:
220
- logger.debug(f'"{node}": In spec, no need to maintain')
267
+ logger.debug(
268
+ f'"{workflow.__workflow_id__}": In spec, no need to maintain')
221
269
  return
222
270
  elif result.bad_data:
223
- logger.debug(f'"{node}": Bad data, diagnosing dependents')
224
- for n in get_dependents(node, code_path):
225
- logger.debug(f'diagnose "{n}" because of "{node}" bad data')
271
+ logger.debug(
272
+ f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
273
+ for n in get_dependents(workflow, code_path):
274
+ logger.debug(
275
+ f'diagnose "{n.__workflow_id__}" because of "{workflow.__workflow_id__}" bad data'
276
+ )
226
277
  diagnose(n, code_path, state_path, plot, session_id)
227
278
  else:
228
- logger.debug(f'"{node}": All dependents diagnosed')
279
+ logger.debug(
280
+ f'"{workflow.__workflow_id__}": All dependents diagnosed')
229
281
  # calibrate
230
- logger.debug(f'recalibrate "{node}"')
231
- result = calibrate(node, code_path, state_path, plot, session_id)
282
+ logger.debug(f'recalibrate "{workflow.__workflow_id__}"')
283
+ result = calibrate(workflow, code_path, state_path, plot, session_id)
232
284
  if result.bad_data or not result.in_spec:
233
285
  raise CalibrationFailedError(
234
- f'"{node}": All dependents passed, but calibration failed!')
286
+ f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
287
+ )
235
288
  transform.update_parameters(result)
236
289
  return
237
290
 
238
291
 
239
- def run(node,
292
+ @logger.catch(reraise=True)
293
+ def run(workflow: WorkflowType,
240
294
  code_path: str | Path,
241
295
  state_path: str | Path,
242
296
  plot: bool = False):
243
- logger.debug(f'run "{node}" without dependences.')
244
- result = calibrate(node, code_path, state_path, plot)
297
+ logger.debug(f'run "{workflow.__workflow_id__}" without dependences.')
298
+ result = calibrate(workflow, code_path, state_path, plot)
245
299
  if result.bad_data or not result.in_spec:
246
300
  raise CalibrationFailedError(
247
- f'"{node}": All dependents passed, but calibration failed!')
301
+ f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
302
+ )
248
303
  transform.update_parameters(result)
249
304
  return
Binary file
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.4.9"
1
+ __version__ = "2.4.12"
File without changes