QuLab 2.0.2__cp312-cp312-win_amd64.whl → 2.0.4__cp312-cp312-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-2.0.2.dist-info → QuLab-2.0.4.dist-info}/METADATA +2 -1
- {QuLab-2.0.2.dist-info → QuLab-2.0.4.dist-info}/RECORD +14 -13
- qulab/__main__.py +2 -0
- qulab/fun.cp312-win_amd64.pyd +0 -0
- qulab/scan/query_record.py +0 -1
- qulab/scan/recorder.py +109 -48
- qulab/scan/scan.py +411 -308
- qulab/scan/server.py +106 -0
- qulab/scan/utils.py +80 -34
- qulab/version.py +1 -1
- {QuLab-2.0.2.dist-info → QuLab-2.0.4.dist-info}/LICENSE +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.4.dist-info}/WHEEL +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.4.dist-info}/entry_points.txt +0 -0
- {QuLab-2.0.2.dist-info → QuLab-2.0.4.dist-info}/top_level.txt +0 -0
qulab/scan/server.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import pickle
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .scan import Scan
|
|
8
|
+
import click
|
|
9
|
+
import dill
|
|
10
|
+
import numpy as np
|
|
11
|
+
import zmq
|
|
12
|
+
from loguru import logger
|
|
13
|
+
|
|
14
|
+
from qulab.sys.rpc.zmq_socket import ZMQContextManager
|
|
15
|
+
|
|
16
|
+
pool = {}
|
|
17
|
+
|
|
18
|
+
class Request():
|
|
19
|
+
__slots__ = ['sock', 'identity', 'msg', 'method']
|
|
20
|
+
|
|
21
|
+
def __init__(self, sock, identity, msg):
|
|
22
|
+
self.sock = sock
|
|
23
|
+
self.identity = identity
|
|
24
|
+
self.msg = pickle.loads(msg)
|
|
25
|
+
self.method = self.msg.get('method', '')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def reply(req, resp):
|
|
29
|
+
await req.sock.send_multipart([req.identity, pickle.dumps(resp)])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@logger.catch
|
|
33
|
+
async def handle(request: Request):
|
|
34
|
+
|
|
35
|
+
msg = request.msg
|
|
36
|
+
|
|
37
|
+
match request.method:
|
|
38
|
+
case 'ping':
|
|
39
|
+
await reply(request, 'pong')
|
|
40
|
+
case 'submit':
|
|
41
|
+
description = dill.loads(msg['description'])
|
|
42
|
+
task = Scan()
|
|
43
|
+
task.description = description
|
|
44
|
+
task.start()
|
|
45
|
+
pool[task.id] = task
|
|
46
|
+
await reply(request, task.id)
|
|
47
|
+
case 'get_record_id':
|
|
48
|
+
task = pool.get(msg['id'])
|
|
49
|
+
for _ in range(10):
|
|
50
|
+
if task.record:
|
|
51
|
+
await reply(request, task.record.id)
|
|
52
|
+
break
|
|
53
|
+
await asyncio.sleep(1)
|
|
54
|
+
else:
|
|
55
|
+
await reply(request, None)
|
|
56
|
+
case _:
|
|
57
|
+
logger.error(f"Unknown method: {msg['method']}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def _handle(request: Request):
|
|
61
|
+
try:
|
|
62
|
+
await handle(request)
|
|
63
|
+
except:
|
|
64
|
+
await reply(request, 'error')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def serv(port):
|
|
68
|
+
logger.info('Server starting.')
|
|
69
|
+
async with ZMQContextManager(zmq.ROUTER, bind=f"tcp://*:{port}") as sock:
|
|
70
|
+
logger.info('Server started.')
|
|
71
|
+
while True:
|
|
72
|
+
identity, msg = await sock.recv_multipart()
|
|
73
|
+
req = Request(sock, identity, msg)
|
|
74
|
+
asyncio.create_task(_handle(req))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def watch(port, timeout=1):
|
|
78
|
+
with ZMQContextManager(zmq.DEALER,
|
|
79
|
+
connect=f"tcp://127.0.0.1:{port}") as sock:
|
|
80
|
+
sock.setsockopt(zmq.LINGER, 0)
|
|
81
|
+
while True:
|
|
82
|
+
try:
|
|
83
|
+
sock.send_pyobj({"method": "ping"})
|
|
84
|
+
if sock.poll(int(1000 * timeout)):
|
|
85
|
+
sock.recv()
|
|
86
|
+
else:
|
|
87
|
+
raise asyncio.TimeoutError()
|
|
88
|
+
except (zmq.error.ZMQError, asyncio.TimeoutError):
|
|
89
|
+
return asyncio.create_task(serv(port))
|
|
90
|
+
await asyncio.sleep(timeout)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
async def main(port, timeout=1):
|
|
94
|
+
task = await watch(port=port, timeout=timeout)
|
|
95
|
+
await task
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@click.command()
|
|
99
|
+
@click.option('--port', default=6788, help='Port of the server.')
|
|
100
|
+
@click.option('--timeout', default=1, help='Timeout of ping.')
|
|
101
|
+
def server(port, timeout):
|
|
102
|
+
asyncio.run(main(port, timeout))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
server()
|
qulab/scan/utils.py
CHANGED
|
@@ -1,37 +1,83 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import asyncio
|
|
1
3
|
import inspect
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from .expression import Env, Expression
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_valid_identifier(s: str) -> bool:
|
|
10
|
+
"""
|
|
11
|
+
Check if a string is a valid identifier.
|
|
12
|
+
"""
|
|
13
|
+
try:
|
|
14
|
+
ast.parse(f"f({s}=0)")
|
|
15
|
+
return True
|
|
16
|
+
except SyntaxError:
|
|
17
|
+
return False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def async_next(aiter):
|
|
21
|
+
try:
|
|
22
|
+
if hasattr(aiter, '__anext__'):
|
|
23
|
+
return await aiter.__anext__()
|
|
24
|
+
else:
|
|
25
|
+
return next(aiter)
|
|
26
|
+
except StopIteration:
|
|
27
|
+
raise StopAsyncIteration from None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def async_zip(*aiters):
|
|
31
|
+
aiters = [
|
|
32
|
+
ait.__aiter__() if hasattr(ait, '__aiter__') else iter(ait)
|
|
33
|
+
for ait in aiters
|
|
34
|
+
]
|
|
35
|
+
try:
|
|
36
|
+
while True:
|
|
37
|
+
# 使用 asyncio.gather 等待所有异步生成器返回下一个元素
|
|
38
|
+
result = await asyncio.gather(*(async_next(ait) for ait in aiters))
|
|
39
|
+
yield tuple(result)
|
|
40
|
+
except StopAsyncIteration:
|
|
41
|
+
# 当任一异步生成器耗尽时停止迭代
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def call_function(func: Callable | Expression, variables: dict[str,
|
|
46
|
+
Any]):
|
|
47
|
+
if isinstance(func, Expression):
|
|
48
|
+
env = Env()
|
|
49
|
+
for name in func.symbols():
|
|
50
|
+
if name in variables:
|
|
51
|
+
if inspect.isawaitable(variables[name]):
|
|
52
|
+
variables[name] = await variables[name]
|
|
53
|
+
env.variables[name] = variables[name]
|
|
54
|
+
else:
|
|
55
|
+
raise ValueError(f'{name} is not provided.')
|
|
56
|
+
return func.eval(env)
|
|
57
|
+
|
|
16
58
|
try:
|
|
17
|
-
|
|
18
|
-
arg.result() if isinstance(arg, Future) else arg for arg in args
|
|
19
|
-
]
|
|
20
|
-
kw = {
|
|
21
|
-
k: v.result() if isinstance(v, Future) else v
|
|
22
|
-
for k, v in kw.items()
|
|
23
|
-
}
|
|
24
|
-
return func(*args, **kw)
|
|
59
|
+
sig = inspect.signature(func)
|
|
25
60
|
except:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
return func()
|
|
62
|
+
args = []
|
|
63
|
+
for name, param in sig.parameters.items():
|
|
64
|
+
if param.kind == param.POSITIONAL_OR_KEYWORD:
|
|
65
|
+
if name in variables:
|
|
66
|
+
if inspect.isawaitable(variables[name]):
|
|
67
|
+
variables[name] = await variables[name]
|
|
68
|
+
args.append(variables[name])
|
|
69
|
+
elif param.default is not param.empty:
|
|
70
|
+
args.append(param.default)
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f'parameter {name} is not provided.')
|
|
73
|
+
elif param.kind == param.VAR_POSITIONAL:
|
|
74
|
+
raise ValueError('not support VAR_POSITIONAL')
|
|
75
|
+
elif param.kind == param.VAR_KEYWORD:
|
|
76
|
+
ret = func(**variables)
|
|
77
|
+
if inspect.isawaitable(ret):
|
|
78
|
+
ret = await ret
|
|
79
|
+
return ret
|
|
80
|
+
ret = func(*args)
|
|
81
|
+
if inspect.isawaitable(ret):
|
|
82
|
+
ret = await ret
|
|
83
|
+
return ret
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|