QuLab 2.7.19__cp310-cp310-win_amd64.whl → 2.8.0__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/cli.py +5 -0
- qulab/executor/storage.py +323 -142
- qulab/fun.cp310-win_amd64.pyd +0 -0
- qulab/utils.py +7 -7
- qulab/version.py +1 -1
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/METADATA +1 -1
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/RECORD +11 -11
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/WHEEL +1 -1
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/LICENSE +0 -0
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/entry_points.txt +0 -0
- {qulab-2.7.19.dist-info → qulab-2.8.0.dist-info}/top_level.txt +0 -0
qulab/executor/cli.py
CHANGED
@@ -2,6 +2,7 @@ import functools
|
|
2
2
|
import graphlib
|
3
3
|
import importlib
|
4
4
|
import os
|
5
|
+
import sys
|
5
6
|
from pathlib import Path
|
6
7
|
|
7
8
|
import click
|
@@ -68,6 +69,10 @@ def command_option(command_name):
|
|
68
69
|
help='The path of the bootstrap.')
|
69
70
|
@functools.wraps(func)
|
70
71
|
def wrapper(*args, **kwargs):
|
72
|
+
if 'code' in kwargs and kwargs['code'] is not None:
|
73
|
+
code = os.path.expanduser(kwargs['code'])
|
74
|
+
if code not in sys.path:
|
75
|
+
sys.path.insert(0, code)
|
71
76
|
bootstrap = kwargs.pop('bootstrap')
|
72
77
|
if bootstrap is not None:
|
73
78
|
boot(bootstrap)
|
qulab/executor/storage.py
CHANGED
@@ -2,6 +2,7 @@ import hashlib
|
|
2
2
|
import lzma
|
3
3
|
import pickle
|
4
4
|
import uuid
|
5
|
+
import zipfile
|
5
6
|
from dataclasses import dataclass, field
|
6
7
|
from datetime import datetime, timedelta
|
7
8
|
from functools import lru_cache
|
@@ -10,6 +11,22 @@ from typing import Any, Literal
|
|
10
11
|
|
11
12
|
from loguru import logger
|
12
13
|
|
14
|
+
try:
|
15
|
+
from paramiko import SSHClient
|
16
|
+
from paramiko.ssh_exception import SSHException
|
17
|
+
except:
|
18
|
+
class SSHClient:
|
19
|
+
|
20
|
+
def __init__(self):
|
21
|
+
raise ImportError("Can't import paramiko, ssh support will be disabled.")
|
22
|
+
|
23
|
+
def __enter__(self):
|
24
|
+
return self
|
25
|
+
|
26
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
13
30
|
from ..cli.config import get_config_value
|
14
31
|
|
15
32
|
__current_config_cache = None
|
@@ -43,7 +60,7 @@ class Report():
|
|
43
60
|
if state[k] is not None:
|
44
61
|
state[k] = str(state[k])
|
45
62
|
return state
|
46
|
-
|
63
|
+
|
47
64
|
def __setstate__(self, state):
|
48
65
|
for k in ['path', 'previous_path', 'config_path', 'script_path']:
|
49
66
|
if state[k] is not None:
|
@@ -101,6 +118,17 @@ class Report():
|
|
101
118
|
source = load_item(self.script_path, self.base_path)
|
102
119
|
if isinstance(source, str):
|
103
120
|
return source
|
121
|
+
else:
|
122
|
+
from .template import inject_mapping
|
123
|
+
return inject_mapping(*source)[0]
|
124
|
+
else:
|
125
|
+
return None
|
126
|
+
|
127
|
+
@property
|
128
|
+
def template_source(self):
|
129
|
+
if self.script_path is not None and self.base_path is not None:
|
130
|
+
source = load_item(self.script_path, self.base_path)
|
131
|
+
return source
|
104
132
|
else:
|
105
133
|
return None
|
106
134
|
|
@@ -113,106 +141,6 @@ def random_path(base: Path) -> Path:
|
|
113
141
|
return path
|
114
142
|
|
115
143
|
|
116
|
-
def save_config_key_history(key: str, report: Report,
|
117
|
-
base_path: str | Path) -> int:
|
118
|
-
global __current_config_cache
|
119
|
-
base_path = Path(base_path) / 'state'
|
120
|
-
base_path.mkdir(parents=True, exist_ok=True)
|
121
|
-
|
122
|
-
if __current_config_cache is None:
|
123
|
-
if (base_path / 'parameters.pkl').exists():
|
124
|
-
with open(base_path / 'parameters.pkl', 'rb') as f:
|
125
|
-
__current_config_cache = pickle.load(f)
|
126
|
-
else:
|
127
|
-
__current_config_cache = {}
|
128
|
-
|
129
|
-
__current_config_cache[
|
130
|
-
key] = report.data, report.calibrated_time, report.checked_time
|
131
|
-
|
132
|
-
with open(base_path / 'parameters.pkl', 'wb') as f:
|
133
|
-
pickle.dump(__current_config_cache, f)
|
134
|
-
return 0
|
135
|
-
|
136
|
-
|
137
|
-
def find_config_key_history(key: str, base_path: str | Path) -> Report | None:
|
138
|
-
global __current_config_cache
|
139
|
-
base_path = Path(base_path) / 'state'
|
140
|
-
if __current_config_cache is None:
|
141
|
-
if (base_path / 'parameters.pkl').exists():
|
142
|
-
with open(base_path / 'parameters.pkl', 'rb') as f:
|
143
|
-
__current_config_cache = pickle.load(f)
|
144
|
-
else:
|
145
|
-
__current_config_cache = {}
|
146
|
-
|
147
|
-
if key in __current_config_cache:
|
148
|
-
value, calibrated_time, checked_time = __current_config_cache.get(
|
149
|
-
key, None)
|
150
|
-
report = Report(
|
151
|
-
workflow=f'cfg:{key}',
|
152
|
-
bad_data=False,
|
153
|
-
in_spec=True,
|
154
|
-
fully_calibrated=True,
|
155
|
-
parameters={key: value},
|
156
|
-
data=value,
|
157
|
-
calibrated_time=calibrated_time,
|
158
|
-
checked_time=checked_time,
|
159
|
-
)
|
160
|
-
return report
|
161
|
-
return None
|
162
|
-
|
163
|
-
|
164
|
-
def save_report(workflow: str,
|
165
|
-
report: Report,
|
166
|
-
base_path: str | Path,
|
167
|
-
overwrite: bool = False,
|
168
|
-
refresh_heads: bool = True) -> int:
|
169
|
-
if workflow.startswith("cfg:"):
|
170
|
-
return save_config_key_history(workflow[4:], report, base_path)
|
171
|
-
|
172
|
-
logger.debug(
|
173
|
-
f'Saving report for "{workflow}", {report.in_spec=}, {report.bad_data=}, {report.fully_calibrated=}'
|
174
|
-
)
|
175
|
-
base_path = Path(base_path)
|
176
|
-
try:
|
177
|
-
buf = lzma.compress(pickle.dumps(report))
|
178
|
-
except:
|
179
|
-
raise ValueError(f"Can't pickle report for {workflow}")
|
180
|
-
if overwrite:
|
181
|
-
path = report.path
|
182
|
-
if path is None:
|
183
|
-
raise ValueError("Report path is None, can't overwrite.")
|
184
|
-
with open(base_path / 'reports' / path, "rb") as f:
|
185
|
-
index = int.from_bytes(f.read(8), 'big')
|
186
|
-
report.index = index
|
187
|
-
else:
|
188
|
-
path = random_path(base_path / 'reports')
|
189
|
-
(base_path / 'reports' / path).parent.mkdir(parents=True,
|
190
|
-
exist_ok=True)
|
191
|
-
report.path = path
|
192
|
-
report.index = create_index("report",
|
193
|
-
base_path,
|
194
|
-
context=str(path),
|
195
|
-
width=35)
|
196
|
-
with open(base_path / 'reports' / path, "wb") as f:
|
197
|
-
f.write(report.index.to_bytes(8, 'big'))
|
198
|
-
f.write(buf)
|
199
|
-
if refresh_heads:
|
200
|
-
set_head(workflow, path, base_path)
|
201
|
-
return report.index
|
202
|
-
|
203
|
-
|
204
|
-
def load_report(path: str | Path, base_path: str | Path) -> Report | None:
|
205
|
-
base_path = Path(base_path)
|
206
|
-
path = base_path / 'reports' / path
|
207
|
-
|
208
|
-
with open(base_path / 'reports' / path, "rb") as f:
|
209
|
-
index = int.from_bytes(f.read(8), 'big')
|
210
|
-
report = pickle.loads(lzma.decompress(f.read()))
|
211
|
-
report.base_path = base_path
|
212
|
-
report.index = index
|
213
|
-
return report
|
214
|
-
|
215
|
-
|
216
144
|
def find_report(
|
217
145
|
workflow: str, base_path: str | Path = get_config_value("data", Path)
|
218
146
|
) -> Report | None:
|
@@ -252,6 +180,26 @@ def revoke_report(workflow: str, report: Report | None, base_path: str | Path):
|
|
252
180
|
refresh_heads=True)
|
253
181
|
|
254
182
|
|
183
|
+
def get_report_by_index(
|
184
|
+
index: int, base_path: str | Path = get_config_value("data", Path)
|
185
|
+
) -> Report | None:
|
186
|
+
try:
|
187
|
+
path = query_index("report", base_path, index)
|
188
|
+
return load_report(path, base_path)
|
189
|
+
except:
|
190
|
+
raise
|
191
|
+
return None
|
192
|
+
|
193
|
+
|
194
|
+
def get_head(workflow: str, base_path: str | Path) -> Path | None:
|
195
|
+
return get_heads(base_path).get(workflow, None)
|
196
|
+
|
197
|
+
|
198
|
+
#########################################################################
|
199
|
+
## Basic Write API ##
|
200
|
+
#########################################################################
|
201
|
+
|
202
|
+
|
255
203
|
def set_head(workflow: str, path: Path, base_path: str | Path):
|
256
204
|
base_path = Path(base_path)
|
257
205
|
base_path.mkdir(parents=True, exist_ok=True)
|
@@ -265,24 +213,44 @@ def set_head(workflow: str, path: Path, base_path: str | Path):
|
|
265
213
|
pickle.dump(heads, f)
|
266
214
|
|
267
215
|
|
268
|
-
def
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
return None
|
276
|
-
|
216
|
+
def save_report(workflow: str,
|
217
|
+
report: Report,
|
218
|
+
base_path: str | Path,
|
219
|
+
overwrite: bool = False,
|
220
|
+
refresh_heads: bool = True) -> int:
|
221
|
+
if workflow.startswith("cfg:"):
|
222
|
+
return save_config_key_history(workflow[4:], report, base_path)
|
277
223
|
|
278
|
-
|
224
|
+
logger.debug(
|
225
|
+
f'Saving report for "{workflow}", {report.in_spec=}, {report.bad_data=}, {report.fully_calibrated=}'
|
226
|
+
)
|
279
227
|
base_path = Path(base_path)
|
280
228
|
try:
|
281
|
-
|
282
|
-
heads = pickle.load(f)
|
283
|
-
return heads
|
229
|
+
buf = lzma.compress(pickle.dumps(report))
|
284
230
|
except:
|
285
|
-
|
231
|
+
raise ValueError(f"Can't pickle report for {workflow}")
|
232
|
+
if overwrite:
|
233
|
+
path = report.path
|
234
|
+
if path is None:
|
235
|
+
raise ValueError("Report path is None, can't overwrite.")
|
236
|
+
with open(base_path / 'reports' / path, "rb") as f:
|
237
|
+
index = int.from_bytes(f.read(8), 'big')
|
238
|
+
report.index = index
|
239
|
+
else:
|
240
|
+
path = random_path(base_path / 'reports')
|
241
|
+
(base_path / 'reports' / path).parent.mkdir(parents=True,
|
242
|
+
exist_ok=True)
|
243
|
+
report.path = path
|
244
|
+
report.index = create_index("report",
|
245
|
+
base_path,
|
246
|
+
context=str(path),
|
247
|
+
width=35)
|
248
|
+
with open(base_path / 'reports' / path, "wb") as f:
|
249
|
+
f.write(report.index.to_bytes(8, 'big'))
|
250
|
+
f.write(buf)
|
251
|
+
if refresh_heads:
|
252
|
+
set_head(workflow, path, base_path)
|
253
|
+
return report.index
|
286
254
|
|
287
255
|
|
288
256
|
def create_index(name: str,
|
@@ -318,27 +286,6 @@ def create_index(name: str,
|
|
318
286
|
return index
|
319
287
|
|
320
288
|
|
321
|
-
@lru_cache(maxsize=4096)
|
322
|
-
def query_index(name: str, base_path: str | Path, index: int):
|
323
|
-
path = Path(base_path) / "index" / name
|
324
|
-
width = int(path.with_suffix('.width').read_text())
|
325
|
-
|
326
|
-
with path.with_suffix('.idx').open("r") as f:
|
327
|
-
f.seek(index * (width + 1))
|
328
|
-
context = f.read(width)
|
329
|
-
return context.rstrip()
|
330
|
-
|
331
|
-
|
332
|
-
def get_report_by_index(
|
333
|
-
index: int, base_path: str | Path = get_config_value("data", Path)
|
334
|
-
) -> Report | None:
|
335
|
-
try:
|
336
|
-
path = query_index("report", base_path, index)
|
337
|
-
return load_report(path, base_path)
|
338
|
-
except:
|
339
|
-
return None
|
340
|
-
|
341
|
-
|
342
289
|
def save_item(item, data_path):
|
343
290
|
salt = 0
|
344
291
|
buf = pickle.dumps(item)
|
@@ -361,10 +308,244 @@ def save_item(item, data_path):
|
|
361
308
|
return str(item_id)
|
362
309
|
|
363
310
|
|
311
|
+
def save_config_key_history(key: str, report: Report,
|
312
|
+
base_path: str | Path) -> int:
|
313
|
+
global __current_config_cache
|
314
|
+
base_path = Path(base_path) / 'state'
|
315
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
316
|
+
|
317
|
+
if __current_config_cache is None:
|
318
|
+
if (base_path / 'parameters.pkl').exists():
|
319
|
+
with open(base_path / 'parameters.pkl', 'rb') as f:
|
320
|
+
__current_config_cache = pickle.load(f)
|
321
|
+
else:
|
322
|
+
__current_config_cache = {}
|
323
|
+
|
324
|
+
__current_config_cache[
|
325
|
+
key] = report.data, report.calibrated_time, report.checked_time
|
326
|
+
|
327
|
+
with open(base_path / 'parameters.pkl', 'wb') as f:
|
328
|
+
pickle.dump(__current_config_cache, f)
|
329
|
+
return 0
|
330
|
+
|
331
|
+
|
332
|
+
#########################################################################
|
333
|
+
## Basic Read API ##
|
334
|
+
#########################################################################
|
335
|
+
|
336
|
+
|
337
|
+
def load_report(path: str | Path, base_path: str | Path) -> Report | None:
|
338
|
+
if isinstance(base_path, str) and base_path.startswith('ssh '):
|
339
|
+
with SSHClient() as client:
|
340
|
+
cfg, base_path = _pase_ssh_config(base_path[4:])
|
341
|
+
client.load_system_host_keys()
|
342
|
+
client.connect(**cfg)
|
343
|
+
return load_report_from_scp(path, base_path, client)
|
344
|
+
|
345
|
+
base_path = Path(base_path)
|
346
|
+
if zipfile.is_zipfile(base_path):
|
347
|
+
return load_report_from_zipfile(path, base_path)
|
348
|
+
|
349
|
+
path = base_path / 'reports' / path
|
350
|
+
|
351
|
+
with open(base_path / 'reports' / path, "rb") as f:
|
352
|
+
index = int.from_bytes(f.read(8), 'big')
|
353
|
+
report = pickle.loads(lzma.decompress(f.read()))
|
354
|
+
report.base_path = base_path
|
355
|
+
report.index = index
|
356
|
+
return report
|
357
|
+
|
358
|
+
|
359
|
+
def get_heads(base_path: str | Path) -> Path | None:
|
360
|
+
if isinstance(base_path, str) and base_path.startswith('ssh '):
|
361
|
+
with SSHClient() as client:
|
362
|
+
cfg, base_path = _pase_ssh_config(base_path[4:])
|
363
|
+
client.load_system_host_keys()
|
364
|
+
client.connect(**cfg)
|
365
|
+
return get_heads_from_scp(base_path, client)
|
366
|
+
|
367
|
+
base_path = Path(base_path)
|
368
|
+
if zipfile.is_zipfile(base_path):
|
369
|
+
return get_heads_from_zipfile(base_path)
|
370
|
+
try:
|
371
|
+
with open(base_path / "heads", "rb") as f:
|
372
|
+
heads = pickle.load(f)
|
373
|
+
return heads
|
374
|
+
except:
|
375
|
+
return {}
|
376
|
+
|
377
|
+
|
378
|
+
@lru_cache(maxsize=4096)
|
379
|
+
def query_index(name: str, base_path: str | Path, index: int):
|
380
|
+
if isinstance(base_path, str) and base_path.startswith('ssh '):
|
381
|
+
with SSHClient() as client:
|
382
|
+
cfg, base_path = _pase_ssh_config(base_path[4:])
|
383
|
+
client.load_system_host_keys()
|
384
|
+
client.connect(**cfg)
|
385
|
+
return query_index_from_scp(name, base_path, client, index)
|
386
|
+
|
387
|
+
base_path = Path(base_path)
|
388
|
+
if zipfile.is_zipfile(base_path):
|
389
|
+
return query_index_from_zipfile(name, base_path, index)
|
390
|
+
path = Path(base_path) / "index" / name
|
391
|
+
width = int(path.with_suffix('.width').read_text())
|
392
|
+
|
393
|
+
with path.with_suffix('.idx').open("r") as f:
|
394
|
+
f.seek(index * (width + 1))
|
395
|
+
context = f.read(width)
|
396
|
+
return context.rstrip()
|
397
|
+
|
398
|
+
|
364
399
|
@lru_cache(maxsize=4096)
|
365
|
-
def load_item(id,
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
400
|
+
def load_item(id, base_path):
|
401
|
+
if isinstance(base_path, str) and base_path.startswith('ssh '):
|
402
|
+
with SSHClient() as client:
|
403
|
+
cfg, base_path = _pase_ssh_config(base_path[4:])
|
404
|
+
client.load_system_host_keys()
|
405
|
+
client.connect(**cfg)
|
406
|
+
buf = load_item_buf_from_scp(id, base_path, client)
|
407
|
+
else:
|
408
|
+
base_path = Path(base_path)
|
409
|
+
if zipfile.is_zipfile(base_path):
|
410
|
+
buf = load_item_buf_from_zipfile(id, base_path)
|
411
|
+
else:
|
412
|
+
path = Path(base_path) / 'items' / id
|
413
|
+
with open(path, 'rb') as f:
|
414
|
+
buf = f.read()
|
415
|
+
item = pickle.loads(lzma.decompress(buf))
|
416
|
+
return item
|
417
|
+
|
418
|
+
|
419
|
+
def find_config_key_history(key: str, base_path: str | Path) -> Report | None:
|
420
|
+
global __current_config_cache
|
421
|
+
base_path = Path(base_path) / 'state'
|
422
|
+
if __current_config_cache is None:
|
423
|
+
if (base_path / 'parameters.pkl').exists():
|
424
|
+
with open(base_path / 'parameters.pkl', 'rb') as f:
|
425
|
+
__current_config_cache = pickle.load(f)
|
426
|
+
else:
|
427
|
+
__current_config_cache = {}
|
428
|
+
|
429
|
+
if key in __current_config_cache:
|
430
|
+
value, calibrated_time, checked_time = __current_config_cache.get(
|
431
|
+
key, None)
|
432
|
+
report = Report(
|
433
|
+
workflow=f'cfg:{key}',
|
434
|
+
bad_data=False,
|
435
|
+
in_spec=True,
|
436
|
+
fully_calibrated=True,
|
437
|
+
parameters={key: value},
|
438
|
+
data=value,
|
439
|
+
calibrated_time=calibrated_time,
|
440
|
+
checked_time=checked_time,
|
441
|
+
)
|
442
|
+
return report
|
443
|
+
return None
|
444
|
+
|
445
|
+
|
446
|
+
#########################################################################
|
447
|
+
## Zipfile support ##
|
448
|
+
#########################################################################
|
449
|
+
|
450
|
+
|
451
|
+
def load_report_from_zipfile(path: str | Path,
|
452
|
+
base_path: str | Path) -> Report | None:
|
453
|
+
path = Path(path)
|
454
|
+
with zipfile.ZipFile(base_path) as zf:
|
455
|
+
path = '/'.join(path.parts)
|
456
|
+
with zf.open(f"{base_path.stem}/reports/{path}") as f:
|
457
|
+
index = int.from_bytes(f.read(8), 'big')
|
458
|
+
report = pickle.loads(lzma.decompress(f.read()))
|
459
|
+
report.base_path = base_path
|
460
|
+
report.index = index
|
461
|
+
return report
|
462
|
+
|
463
|
+
|
464
|
+
def get_heads_from_zipfile(base_path: str | Path) -> Path | None:
|
465
|
+
with zipfile.ZipFile(base_path) as zf:
|
466
|
+
with zf.open(f"{base_path.stem}/heads") as f:
|
467
|
+
heads = pickle.load(f)
|
468
|
+
return heads
|
469
|
+
|
470
|
+
|
471
|
+
def query_index_from_zipfile(name: str, base_path: str | Path, index: int):
|
472
|
+
with zipfile.ZipFile(base_path) as zf:
|
473
|
+
with zf.open(f"{base_path.stem}/index/{name}.width") as f:
|
474
|
+
width = int(f.read().decode())
|
475
|
+
with zf.open(f"{base_path.stem}/index/{name}.idx") as f:
|
476
|
+
f.seek(index * (width + 1))
|
477
|
+
context = f.read(width).decode()
|
478
|
+
return context.rstrip()
|
479
|
+
|
480
|
+
|
481
|
+
def load_item_buf_from_zipfile(id, base_path):
|
482
|
+
with zipfile.ZipFile(base_path) as zf:
|
483
|
+
with zf.open(f"{base_path.stem}/items/{id}") as f:
|
484
|
+
return f.read()
|
485
|
+
|
486
|
+
|
487
|
+
#########################################################################
|
488
|
+
## SCP support ##
|
489
|
+
#########################################################################
|
490
|
+
|
491
|
+
|
492
|
+
def _pase_ssh_config(config: str):
|
493
|
+
config = config.split()
|
494
|
+
base_path = ' '.join(config[4:])
|
495
|
+
return {
|
496
|
+
'hostname': config[0],
|
497
|
+
'port': int(config[1]),
|
498
|
+
'username': config[2],
|
499
|
+
'key_filename': config[3]
|
500
|
+
}, Path(base_path)
|
501
|
+
|
502
|
+
|
503
|
+
def load_report_from_scp(path: str | Path, base_path: Path,
|
504
|
+
client: SSHClient) -> Report:
|
505
|
+
try:
|
506
|
+
path = Path(path)
|
507
|
+
with client.open_sftp() as sftp:
|
508
|
+
with sftp.open(str(Path(base_path) / 'reports' / path), 'rb') as f:
|
509
|
+
index = int.from_bytes(f.read(8), 'big')
|
510
|
+
report = pickle.loads(lzma.decompress(f.read()))
|
511
|
+
report.base_path = path
|
512
|
+
report.index = index
|
513
|
+
return report
|
514
|
+
except SSHException:
|
515
|
+
raise ValueError(f"Can't load report from {path}")
|
516
|
+
|
517
|
+
|
518
|
+
def get_heads_from_scp(base_path: Path, client: SSHClient) -> Path | None:
|
519
|
+
try:
|
520
|
+
with client.open_sftp() as sftp:
|
521
|
+
with sftp.open(str(Path(base_path) / 'heads'), 'rb') as f:
|
522
|
+
heads = pickle.load(f)
|
523
|
+
return heads
|
524
|
+
except SSHException:
|
525
|
+
return None
|
526
|
+
|
527
|
+
|
528
|
+
def query_index_from_scp(name: str, base_path: Path, client: SSHClient,
|
529
|
+
index: int):
|
530
|
+
try:
|
531
|
+
with client.open_sftp() as sftp:
|
532
|
+
s = str(Path(base_path) / 'index' / f'{name}.width')
|
533
|
+
with sftp.open(s, 'rb') as f:
|
534
|
+
width = int(f.read().decode())
|
535
|
+
with sftp.open(str(base_path / 'index' / f'{name}.idx'),
|
536
|
+
'rb') as f:
|
537
|
+
f.seek(index * (width + 1))
|
538
|
+
context = f.read(width).decode()
|
539
|
+
return context.rstrip()
|
540
|
+
except SSHException:
|
541
|
+
return None
|
542
|
+
|
543
|
+
|
544
|
+
def load_item_buf_from_scp(id: str, base_path: Path, client: SSHClient):
|
545
|
+
try:
|
546
|
+
with client.open_sftp() as sftp:
|
547
|
+
with sftp.open(str(Path(base_path) / 'items' / str(id)),
|
548
|
+
'rb') as f:
|
549
|
+
return f.read()
|
550
|
+
except SSHException:
|
551
|
+
return None
|
qulab/fun.cp310-win_amd64.pyd
CHANGED
Binary file
|
qulab/utils.py
CHANGED
@@ -42,7 +42,7 @@ def _unix_detach_with_tmux_or_screen(executable_path):
|
|
42
42
|
"-d",
|
43
43
|
"-s",
|
44
44
|
session_name,
|
45
|
-
|
45
|
+
executable_path + " ; tmux wait-for -S finished", # 等待命令结束
|
46
46
|
";",
|
47
47
|
"tmux",
|
48
48
|
"wait-for",
|
@@ -55,7 +55,7 @@ def _unix_detach_with_tmux_or_screen(executable_path):
|
|
55
55
|
|
56
56
|
# 尝试 screen
|
57
57
|
elif _check_command_exists("screen"):
|
58
|
-
command = ["screen", "-dmS", session_name,
|
58
|
+
command = ["screen", "-dmS", session_name, executable_path]
|
59
59
|
subprocess.Popen(command, start_new_session=True)
|
60
60
|
click.echo(f"已启动 screen 会话: {session_name}")
|
61
61
|
click.echo(f"你可以使用 `screen -r {session_name}` 来查看输出")
|
@@ -66,18 +66,18 @@ def _unix_detach_with_tmux_or_screen(executable_path):
|
|
66
66
|
|
67
67
|
def run_detached_with_terminal(executable_path):
|
68
68
|
"""回退到带终端窗口的方案"""
|
69
|
-
safe_path = shlex.quote(executable_path)
|
70
69
|
if sys.platform == 'win32':
|
71
70
|
_windows_start(executable_path)
|
72
71
|
elif sys.platform == 'darwin':
|
73
|
-
script = f'tell app "Terminal" to do script "{
|
72
|
+
script = f'tell app "Terminal" to do script "{executable_path}"'
|
74
73
|
subprocess.Popen(["osascript", "-e", script], start_new_session=True)
|
75
74
|
else:
|
76
75
|
try:
|
77
|
-
subprocess.Popen(
|
78
|
-
|
76
|
+
subprocess.Popen(
|
77
|
+
["gnome-terminal", "--", "sh", "-c", executable_path],
|
78
|
+
start_new_session=True)
|
79
79
|
except FileNotFoundError:
|
80
|
-
subprocess.Popen(["xterm", "-e",
|
80
|
+
subprocess.Popen(["xterm", "-e", executable_path],
|
81
81
|
start_new_session=True)
|
82
82
|
|
83
83
|
|
qulab/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "2.
|
1
|
+
__version__ = "2.8.0"
|
@@ -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=
|
4
|
+
qulab/fun.cp310-win_amd64.pyd,sha256=IFR7znN7dCxfNQ3OHZgTsi1G8b8cSlyLacwV_oGBWnI,31744
|
5
5
|
qulab/typing.py,sha256=PRtwbCHWY2ROKK8GHq4Bo8llXrIGo6xC73DrQf7S9os,71
|
6
|
-
qulab/utils.py,sha256=
|
7
|
-
qulab/version.py,sha256=
|
6
|
+
qulab/utils.py,sha256=65N2Xj7kqRsQ4epoLNY6tL-i5ts6Wk8YuJYee3Te6zI,3077
|
7
|
+
qulab/version.py,sha256=HoslYmXXy3WAQMEXd5jNDwMD2rL7bVPI5DzutSN5GNc,21
|
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
|
-
qulab/executor/cli.py,sha256=
|
12
|
+
qulab/executor/cli.py,sha256=8d-8bRWZ5lmsMtjASsl1zu1rV-syeAESMNVthvIQxlo,10018
|
13
13
|
qulab/executor/load.py,sha256=YndvzagvWR8Sg6WHZ-gP-Of0FrFOyh_E_a3VXsjDf1Q,17502
|
14
14
|
qulab/executor/schedule.py,sha256=0BV5LGxhqdIlGwW6-o5_5mljAtdtL1La8EDNBFi8pzU,18585
|
15
|
-
qulab/executor/storage.py,sha256=
|
15
|
+
qulab/executor/storage.py,sha256=2YWUxYis8QlYLhzvvwoS2Wmyb9UYaojEh6X20ICePWI,19014
|
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.
|
101
|
-
qulab-2.
|
102
|
-
qulab-2.
|
103
|
-
qulab-2.
|
104
|
-
qulab-2.
|
105
|
-
qulab-2.
|
100
|
+
qulab-2.8.0.dist-info/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
|
101
|
+
qulab-2.8.0.dist-info/METADATA,sha256=7GHqBOgAMQ_zxJaGPbsc6AbwGMQ7IObgjC9Dh5k7ggM,3803
|
102
|
+
qulab-2.8.0.dist-info/WHEEL,sha256=H72wNgFePEN0L06A2Z11ydRFbMa6Lsr93VFntInNpxE,101
|
103
|
+
qulab-2.8.0.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
|
104
|
+
qulab-2.8.0.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
105
|
+
qulab-2.8.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|