QuLab 2.7.17__cp310-cp310-win_amd64.whl → 2.7.19__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.
qulab/executor/load.py CHANGED
@@ -17,6 +17,7 @@ from .template import (TemplateKeyError, TemplateTypeError, decode_mapping,
17
17
  class SetConfigWorkflow():
18
18
  __timeout__ = None
19
19
  __mtime__ = 0
20
+ __source__ = ''
20
21
 
21
22
  def __init__(self, key):
22
23
  self.key = key
@@ -309,6 +310,8 @@ def load_workflow_from_file(file_name: str,
309
310
  module = module_from_spec(spec)
310
311
  spec.loader.exec_module(module)
311
312
  module.__mtime__ = (base_path / path).stat().st_mtime
313
+ source_code = (base_path / path).read_text()
314
+ module.__source__ = source_code
312
315
 
313
316
  if hasattr(module, 'entries'):
314
317
  verify_entries(module, base_path)
@@ -336,11 +339,11 @@ def load_workflow_from_template(template_path: str,
336
339
  path = Path(template_path)
337
340
 
338
341
  with open(base_path / path) as f:
339
- content = f.read()
342
+ template = f.read()
340
343
 
341
344
  mtime = max((base_path / template_path).stat().st_mtime, mtime)
342
345
 
343
- content, hash_str = inject_mapping(content, mapping, str(path))
346
+ content, hash_str = inject_mapping(template, mapping, str(path))
344
347
 
345
348
  if target_path is None:
346
349
  if path.stem == 'template':
@@ -354,22 +357,19 @@ def load_workflow_from_template(template_path: str,
354
357
  path = target_path
355
358
 
356
359
  file = base_path / path
357
- if not file.exists():
360
+ if not file.exists() or file.stat().st_mtime < mtime:
358
361
  file.parent.mkdir(parents=True, exist_ok=True)
359
362
  with open(file, 'w') as f:
360
363
  f.write(content)
361
- else:
362
- if file.stat().st_mtime < mtime:
363
- with open(file, 'w') as f:
364
- f.write(content)
365
- else:
366
- if file.read_text() != content:
367
- logger.warning(
368
- f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
369
- )
364
+ elif file.read_text() != content:
365
+ logger.warning(
366
+ f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
367
+ )
370
368
 
371
369
  module = load_workflow_from_file(str(path), base_path, package)
372
370
  module.__mtime__ = max(mtime, module.__mtime__)
371
+ if module.__source__ == content:
372
+ module.__source__ = template, mapping, str(template_path)
373
373
 
374
374
  return module
375
375
 
@@ -70,16 +70,6 @@ def veryfy_analyzed_report(report: Report, script: str, method: str):
70
70
  )
71
71
 
72
72
 
73
- def get_source(workflow: WorkflowType, code_path: str | Path) -> str:
74
- if isinstance(code_path, str):
75
- code_path = Path(code_path)
76
- try:
77
- with open(code_path / workflow.__workflow_id__, 'r') as f:
78
- return f.read()
79
- except:
80
- return ''
81
-
82
-
83
73
  def check_state(workflow: WorkflowType, code_path: str | Path,
84
74
  state_path: str | Path) -> bool:
85
75
  """
@@ -160,8 +150,7 @@ def call_check(workflow: WorkflowType, session_id: str, state_path: Path):
160
150
  heads=get_heads(state_path),
161
151
  previous_path=get_head(workflow.__workflow_id__,
162
152
  state_path),
163
- script_path=save_item(get_source(workflow, state_path),
164
- state_path))
153
+ script_path=save_item(workflow.__source__, state_path))
165
154
 
166
155
  save_report(workflow.__workflow_id__, report, state_path)
167
156
 
@@ -187,8 +176,7 @@ def call_calibrate(workflow: WorkflowType, session_id: str, state_path: Path):
187
176
  heads=get_heads(state_path),
188
177
  previous_path=get_head(workflow.__workflow_id__,
189
178
  state_path),
190
- script_path=save_item(get_source(workflow, state_path),
191
- state_path))
179
+ script_path=save_item(workflow.__source__, state_path))
192
180
 
193
181
  save_report(workflow.__workflow_id__, report, state_path)
194
182
 
@@ -286,8 +274,7 @@ def check_data(workflow: WorkflowType, state_path: str | Path, plot: bool,
286
274
  heads=get_heads(state_path),
287
275
  previous_path=get_head(workflow.__workflow_id__,
288
276
  state_path),
289
- script_path=save_item(get_source(workflow, state_path),
290
- state_path))
277
+ script_path=save_item(workflow.__source__, state_path))
291
278
  report.in_spec = False
292
279
  report.bad_data = False
293
280
  return report
qulab/executor/storage.py CHANGED
@@ -36,6 +36,20 @@ class Report():
36
36
  config_path: Path | None = field(default=None, repr=False)
37
37
  script_path: Path | None = field(default=None, repr=False)
38
38
 
39
+ def __getstate__(self):
40
+ state = self.__dict__.copy()
41
+ state.pop('base_path')
42
+ for k in ['path', 'previous_path', 'config_path', 'script_path']:
43
+ if state[k] is not None:
44
+ state[k] = str(state[k])
45
+ return state
46
+
47
+ def __setstate__(self, state):
48
+ for k in ['path', 'previous_path', 'config_path', 'script_path']:
49
+ if state[k] is not None:
50
+ state[k] = Path(state[k])
51
+ self.__dict__.update(state)
52
+
39
53
  @property
40
54
  def previous(self):
41
55
  if self.previous_path is not None and self.base_path is not None:
@@ -84,7 +98,9 @@ class Report():
84
98
  @property
85
99
  def script(self):
86
100
  if self.script_path is not None and self.base_path is not None:
87
- return load_item(self.script_path, self.base_path)
101
+ source = load_item(self.script_path, self.base_path)
102
+ if isinstance(source, str):
103
+ return source
88
104
  else:
89
105
  return None
90
106
 
Binary file
qulab/utils.py CHANGED
@@ -1,31 +1,95 @@
1
+ import shlex
1
2
  import subprocess
2
3
  import sys
4
+ import time
3
5
 
6
+ import click
4
7
 
5
- def run_detached_with_terminal(executable_path):
8
+
9
+ def run_detached(executable_path):
6
10
  """
7
- 启动可执行文件并在新终端窗口中保持运行,Python退出后进程仍存在。
8
- 适用于Windows、Linux和macOS
11
+ 启动可执行文件并完全分离(优先用 tmux/screen),无需额外终端窗口
12
+ 支持 Windows、Linux macOS
9
13
  """
10
14
  try:
11
- if sys.platform == 'win32':
12
- # Windows:使用start命令启动新cmd窗口
13
- cmd = f'start cmd /k "{executable_path}"'
14
- subprocess.Popen(cmd, shell=True)
15
- elif sys.platform == 'darwin':
16
- # macOS:通过AppleScript在Terminal中执行命令
17
- escaped_path = executable_path.replace('"', r'\"')
18
- script = f'tell application "Terminal" to do script "{escaped_path}"'
19
- subprocess.Popen(['osascript', '-e', script],
20
- start_new_session=True)
21
- else:
22
- # Linux:尝试gnome-terminal或xterm
23
- try:
24
- subprocess.Popen(['gnome-terminal', '--', executable_path],
25
- start_new_session=True)
26
- except FileNotFoundError:
27
- subprocess.Popen(['xterm', '-e', executable_path],
28
- start_new_session=True)
15
+ if sys.platform == 'win32' or not _unix_detach_with_tmux_or_screen(
16
+ executable_path):
17
+ # 回退到带终端窗口的方案
18
+ run_detached_with_terminal(executable_path)
19
+
29
20
  except Exception as e:
30
- print(f"启动失败: {e}")
21
+ click.echo(f"启动失败: {e}")
31
22
  sys.exit(1)
23
+
24
+
25
+ def _windows_start(executable_path):
26
+ """Windows 弹窗启动方案"""
27
+ subprocess.Popen(f'start cmd /k "{executable_path}"',
28
+ shell=True,
29
+ creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
30
+
31
+
32
+ def _unix_detach_with_tmux_or_screen(executable_path):
33
+ """Unix 后台分离方案(无窗口)"""
34
+ safe_path = shlex.quote(executable_path)
35
+ session_name = f"qulab_{int(time.time())}"
36
+
37
+ # 尝试 tmux
38
+ if _check_command_exists("tmux"):
39
+ command = [
40
+ "tmux",
41
+ "new-session",
42
+ "-d",
43
+ "-s",
44
+ session_name,
45
+ safe_path + " ; tmux wait-for -S finished", # 等待命令结束
46
+ ";",
47
+ "tmux",
48
+ "wait-for",
49
+ "finished" # 防止进程立即退出
50
+ ]
51
+ subprocess.Popen(" ".join(command), shell=True, start_new_session=True)
52
+ click.echo(f"已启动 tmux 会话: {session_name}")
53
+ click.echo(f"你可以使用 `tmux attach -t {session_name}` 来查看输出")
54
+ return True
55
+
56
+ # 尝试 screen
57
+ elif _check_command_exists("screen"):
58
+ command = ["screen", "-dmS", session_name, safe_path]
59
+ subprocess.Popen(command, start_new_session=True)
60
+ click.echo(f"已启动 screen 会话: {session_name}")
61
+ click.echo(f"你可以使用 `screen -r {session_name}` 来查看输出")
62
+ return True
63
+
64
+ return False
65
+
66
+
67
+ def run_detached_with_terminal(executable_path):
68
+ """回退到带终端窗口的方案"""
69
+ safe_path = shlex.quote(executable_path)
70
+ if sys.platform == 'win32':
71
+ _windows_start(executable_path)
72
+ elif sys.platform == 'darwin':
73
+ script = f'tell app "Terminal" to do script "{safe_path}"'
74
+ subprocess.Popen(["osascript", "-e", script], start_new_session=True)
75
+ else:
76
+ try:
77
+ subprocess.Popen(["gnome-terminal", "--", "sh", "-c", safe_path],
78
+ start_new_session=True)
79
+ except FileNotFoundError:
80
+ subprocess.Popen(["xterm", "-e", safe_path],
81
+ start_new_session=True)
82
+
83
+
84
+ def _check_command_exists(cmd):
85
+ """检查命令行工具是否存在"""
86
+ try:
87
+ subprocess.check_output(["which", cmd], stderr=subprocess.DEVNULL)
88
+ return True
89
+ except:
90
+ return False
91
+
92
+
93
+ # 示例用法
94
+ if __name__ == '__main__':
95
+ run_detached("/path/to/your/program")
qulab/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.7.17"
1
+ __version__ = "2.7.19"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: QuLab
3
- Version: 2.7.17
3
+ Version: 2.7.19
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,18 +1,18 @@
1
1
  qulab/__init__.py,sha256=RZme5maBSMZpP6ckXymqZpo2sRYttwEpTYCIzIvys1c,292
2
2
  qulab/__main__.py,sha256=FL4YsGZL1jEtmcPc5WbleArzhOHLMsWl7OH3O-1d1ss,72
3
3
  qulab/dicttree.py,sha256=ZoSJVWK4VMqfzj42gPb_n5RqLlM6K1Me0WmLIfLEYf8,14195
4
- qulab/fun.cp310-win_amd64.pyd,sha256=aW3Q6amEbw2lCPp67AN5jrmGUDfppFKpLSKf-12PKeM,31744
4
+ qulab/fun.cp310-win_amd64.pyd,sha256=oUlIAuuUIquUS0LoxQs8OWRxq6VIKkj4u_GIR_CtalU,31744
5
5
  qulab/typing.py,sha256=PRtwbCHWY2ROKK8GHq4Bo8llXrIGo6xC73DrQf7S9os,71
6
- qulab/utils.py,sha256=UyZNPIyvis5t2MJBkXXLO5EmYP3mQZbt87zmYAHgoyk,1291
7
- qulab/version.py,sha256=PehmFwZNf5Rm2Y2666_iNgcOM1EyacdqNZMNt-3-wKg,22
6
+ qulab/utils.py,sha256=kSy_tQRLDdlMwk7XhGhg75JadNHbDkau5vYcfOlJG_4,3088
7
+ qulab/version.py,sha256=l3aREioriZeXEjFmLR4Dbu7Nj408gevOUOElKwDjjD4,22
8
8
  qulab/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  qulab/cli/commands.py,sha256=6xd2eYw32k1NmfAuYSu__1kaP12Oz1QVqwbkYXdWno4,588
10
10
  qulab/cli/config.py,sha256=7h3k0K8FYHhI6LVWt8BoDdKrX2ApFDBAUAUuXhHwst4,3799
11
11
  qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
12
12
  qulab/executor/cli.py,sha256=z8W1RivKdABQSOGy2viNUvG73QvOBpE9gSKjw45vSVA,9794
13
- qulab/executor/load.py,sha256=_Zn4wtGGD8pBZwOfbJaaFVAr8riY2Bkhi67lX9Jy-hA,17427
14
- qulab/executor/schedule.py,sha256=yiFSDMdboVjQzgPMl2nq5ts23whrsw7XxadIooy-zx0,19044
15
- qulab/executor/storage.py,sha256=gI6g28BmKKEZ_Pl-hFwvpiOj3mF8Su-yjj3hfMXs1VY,11630
13
+ qulab/executor/load.py,sha256=YndvzagvWR8Sg6WHZ-gP-Of0FrFOyh_E_a3VXsjDf1Q,17502
14
+ qulab/executor/schedule.py,sha256=0BV5LGxhqdIlGwW6-o5_5mljAtdtL1La8EDNBFi8pzU,18585
15
+ qulab/executor/storage.py,sha256=jFUXYcBFyH2vVzrSzmXtyLtEgIMRczfk27Yb95pV5JM,12217
16
16
  qulab/executor/template.py,sha256=bKMoOBPfa3XMgTfGHQK6pDTswH1vcIjnopaWE3UKpP0,7726
17
17
  qulab/executor/transform.py,sha256=BDx0c4nqTHMAOLVqju0Ydd91uxNm6EpVIfssjZse0bI,2284
18
18
  qulab/executor/utils.py,sha256=l_b0y2kMwYKyyXeFtoblPYwKNU-wiFQ9PMo9QlWl9wE,6213
@@ -97,9 +97,9 @@ qulab/visualization/plot_seq.py,sha256=Uo1-dB1YE9IN_A9tuaOs9ZG3S5dKDQ_l98iD2Wbxp
97
97
  qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
98
98
  qulab/visualization/rot3d.py,sha256=jGHJcqj1lEWBUV-W4GUGONGacqjrYvuFoFCwPse5h1Y,757
99
99
  qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
100
- qulab-2.7.17.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
101
- qulab-2.7.17.dist-info/METADATA,sha256=nYTpeWgGuq2cvV9u5Be4OfVWCceSiTGo4MvxP1jJVvY,3804
102
- qulab-2.7.17.dist-info/WHEEL,sha256=gMSPBFjPynvgFG2ofmeEoM1Uf9iH_GsJd2JBy-bxpiQ,101
103
- qulab-2.7.17.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
104
- qulab-2.7.17.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
105
- qulab-2.7.17.dist-info/RECORD,,
100
+ qulab-2.7.19.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
101
+ qulab-2.7.19.dist-info/METADATA,sha256=De5Wd5mlNaRcr3jnjjS0R2O-_6tfd3Jj-4slwKIXmIc,3804
102
+ qulab-2.7.19.dist-info/WHEEL,sha256=H5gvOYEhGgVYizXMDNmO6CnZIviGx48geV3Lbzu7OZg,101
103
+ qulab-2.7.19.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
104
+ qulab-2.7.19.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
105
+ qulab-2.7.19.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-cp310-win_amd64
5
5