QuLab 2.0.2__cp312-cp312-macosx_10_9_universal2.whl → 2.0.4__cp312-cp312-macosx_10_9_universal2.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/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 concurrent.futures import Future
3
-
4
-
5
- def call_func_with_kwds(func, args, kwds, log=None):
6
- funcname = getattr(func, '__name__', repr(func))
7
- sig = inspect.signature(func)
8
- for p in sig.parameters.values():
9
- if p.kind == p.VAR_KEYWORD:
10
- return func(*args, **kwds)
11
- kw = {
12
- k: v
13
- for k, v in kwds.items()
14
- if k in list(sig.parameters.keys())[len(args):]
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
- args = [
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
- if log:
27
- log.exception(f'Call {funcname} with {args} and {kw}')
28
- raise
29
- finally:
30
- if log:
31
- log.debug(f'Call {funcname} with {args} and {kw}')
32
-
33
-
34
- def try_to_call(x, args, kwds, log=None):
35
- if callable(x):
36
- return call_func_with_kwds(x, args, kwds, log)
37
- return x
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.2"
1
+ __version__ = "2.0.4"
File without changes
File without changes