QuLab 2.10.10__cp313-cp313-macosx_10_13_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/__init__.py +33 -0
- qulab/__main__.py +4 -0
- qulab/cli/__init__.py +0 -0
- qulab/cli/commands.py +30 -0
- qulab/cli/config.py +170 -0
- qulab/cli/decorators.py +28 -0
- qulab/dicttree.py +523 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/analyze.py +188 -0
- qulab/executor/cli.py +434 -0
- qulab/executor/load.py +563 -0
- qulab/executor/registry.py +185 -0
- qulab/executor/schedule.py +543 -0
- qulab/executor/storage.py +615 -0
- qulab/executor/template.py +259 -0
- qulab/executor/utils.py +194 -0
- qulab/expression.py +827 -0
- qulab/fun.cpython-313-darwin.so +0 -0
- qulab/monitor/__init__.py +1 -0
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/config.py +41 -0
- qulab/monitor/dataset.py +77 -0
- qulab/monitor/event_queue.py +54 -0
- qulab/monitor/mainwindow.py +234 -0
- qulab/monitor/monitor.py +115 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +2 -0
- qulab/scan/curd.py +221 -0
- qulab/scan/models.py +554 -0
- qulab/scan/optimize.py +76 -0
- qulab/scan/query.py +387 -0
- qulab/scan/record.py +603 -0
- qulab/scan/scan.py +1166 -0
- qulab/scan/server.py +450 -0
- qulab/scan/space.py +213 -0
- qulab/scan/utils.py +234 -0
- qulab/storage/__init__.py +0 -0
- qulab/storage/__main__.py +51 -0
- qulab/storage/backend/__init__.py +0 -0
- qulab/storage/backend/redis.py +204 -0
- qulab/storage/base_dataset.py +352 -0
- qulab/storage/chunk.py +60 -0
- qulab/storage/dataset.py +127 -0
- qulab/storage/file.py +273 -0
- qulab/storage/models/__init__.py +22 -0
- qulab/storage/models/base.py +4 -0
- qulab/storage/models/config.py +28 -0
- qulab/storage/models/file.py +89 -0
- qulab/storage/models/ipy.py +58 -0
- qulab/storage/models/models.py +88 -0
- qulab/storage/models/record.py +161 -0
- qulab/storage/models/report.py +22 -0
- qulab/storage/models/tag.py +93 -0
- qulab/storage/storage.py +95 -0
- qulab/sys/__init__.py +2 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +255 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +79 -0
- qulab/sys/drivers/FakeInstrument.py +68 -0
- qulab/sys/drivers/__init__.py +0 -0
- qulab/sys/ipy_events.py +125 -0
- qulab/sys/net/__init__.py +0 -0
- qulab/sys/net/bencoder.py +205 -0
- qulab/sys/net/cli.py +169 -0
- qulab/sys/net/dhcp.py +543 -0
- qulab/sys/net/dhcpd.py +176 -0
- qulab/sys/net/kad.py +1142 -0
- qulab/sys/net/kcp.py +192 -0
- qulab/sys/net/nginx.py +194 -0
- qulab/sys/progress.py +190 -0
- qulab/sys/rpc/__init__.py +0 -0
- qulab/sys/rpc/client.py +0 -0
- qulab/sys/rpc/exceptions.py +96 -0
- qulab/sys/rpc/msgpack.py +1052 -0
- qulab/sys/rpc/msgpack.pyi +41 -0
- qulab/sys/rpc/router.py +35 -0
- qulab/sys/rpc/rpc.py +412 -0
- qulab/sys/rpc/serialize.py +139 -0
- qulab/sys/rpc/server.py +29 -0
- qulab/sys/rpc/socket.py +29 -0
- qulab/sys/rpc/utils.py +25 -0
- qulab/sys/rpc/worker.py +0 -0
- qulab/sys/rpc/zmq_socket.py +227 -0
- qulab/tools/__init__.py +0 -0
- qulab/tools/connection_helper.py +39 -0
- qulab/typing.py +2 -0
- qulab/utils.py +95 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +464 -0
- qulab/visualization/plot_circ.py +319 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +242 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/rot3d.py +23 -0
- qulab/visualization/widgets.py +86 -0
- qulab-2.10.10.dist-info/METADATA +110 -0
- qulab-2.10.10.dist-info/RECORD +107 -0
- qulab-2.10.10.dist-info/WHEEL +5 -0
- qulab-2.10.10.dist-info/entry_points.txt +2 -0
- qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
- qulab-2.10.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
__version__: str
|
4
|
+
|
5
|
+
version: tuple[int, int, int]
|
6
|
+
|
7
|
+
def pack(obj, fp, **options) -> None: ...
|
8
|
+
def packb(obj, **options) -> bytes: ...
|
9
|
+
def dump(obj, fp, **options) -> None: ...
|
10
|
+
def dumps(obj, **options) -> bytes: ...
|
11
|
+
|
12
|
+
def unpackb(s: bytes | bytearray, **options) -> Any: ...
|
13
|
+
def unpack(fp, **options) -> Any: ...
|
14
|
+
def loads(s: bytes | bytearray, **options) -> Any: ...
|
15
|
+
def load(fp, **options) -> Any: ...
|
16
|
+
|
17
|
+
class Ext:
|
18
|
+
type: int
|
19
|
+
data: bytes
|
20
|
+
def __init__(self, type: int, data: bytes) -> None: ...
|
21
|
+
def __eq__(self, other) -> bool: ...
|
22
|
+
def __ne__(self, other) -> bool: ...
|
23
|
+
def __hash__(self) -> int: ...
|
24
|
+
|
25
|
+
class InvalidString(bytes): ...
|
26
|
+
|
27
|
+
def ext_serializable(ext_type: int): ...
|
28
|
+
|
29
|
+
class PackException(Exception): ...
|
30
|
+
class UnpackException(Exception): ...
|
31
|
+
class UnsupportedTypeException(PackException): ...
|
32
|
+
class InsufficientDataException(UnpackException): ...
|
33
|
+
class InvalidStringException(UnpackException): ...
|
34
|
+
class UnsupportedTimestampException(UnpackException): ...
|
35
|
+
class ReservedCodeException(UnpackException): ...
|
36
|
+
class UnhashableKeyException(UnpackException): ...
|
37
|
+
class DuplicateKeyException(UnpackException): ...
|
38
|
+
KeyNotPrimitiveException = UnhashableKeyException
|
39
|
+
KeyDuplicateException = DuplicateKeyException
|
40
|
+
|
41
|
+
compatibility: bool
|
qulab/sys/rpc/router.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
import asyncio
|
2
|
+
import random
|
3
|
+
|
4
|
+
import zmq
|
5
|
+
import zmq.asyncio
|
6
|
+
|
7
|
+
|
8
|
+
async def handle_client(socket, identity, message):
|
9
|
+
print(f"Received request from {identity}: {message.decode()}")
|
10
|
+
# 随机延时 0 到 3 秒
|
11
|
+
await asyncio.sleep(random.uniform(0, 3))
|
12
|
+
task_id = random.randint(1000, 9999) # 随机生成一个任务 ID
|
13
|
+
await socket.send_multipart([identity, f"Task ID: {task_id}".encode()])
|
14
|
+
print(f"Sent Task ID {task_id} to {identity}")
|
15
|
+
|
16
|
+
|
17
|
+
async def server():
|
18
|
+
context = zmq.asyncio.Context()
|
19
|
+
socket = context.socket(zmq.ROUTER)
|
20
|
+
socket.bind("tcp://*:5555")
|
21
|
+
|
22
|
+
while True:
|
23
|
+
try:
|
24
|
+
identity, message = await socket.recv_multipart()
|
25
|
+
asyncio.create_task(handle_client(socket, identity, message))
|
26
|
+
except Exception as e:
|
27
|
+
print(f"An error occurred: {e}")
|
28
|
+
break
|
29
|
+
|
30
|
+
socket.close()
|
31
|
+
context.term()
|
32
|
+
|
33
|
+
|
34
|
+
if __name__ == "__main__":
|
35
|
+
asyncio.run(server())
|
qulab/sys/rpc/rpc.py
ADDED
@@ -0,0 +1,412 @@
|
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
import inspect
|
4
|
+
import logging
|
5
|
+
import platform
|
6
|
+
import struct
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from collections.abc import Awaitable
|
9
|
+
|
10
|
+
from ...version import __version__
|
11
|
+
from .exceptions import RPCError, RPCServerError, RPCTimeout
|
12
|
+
from .serialize import pack, unpack
|
13
|
+
from .utils import acceptArg
|
14
|
+
|
15
|
+
if platform.system() == "Windows":
|
16
|
+
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
17
|
+
|
18
|
+
log = logging.getLogger(__name__) # pylint: disable=invalid-name
|
19
|
+
|
20
|
+
msgIDFormat = struct.Struct("!IIQ")
|
21
|
+
|
22
|
+
__msgIndex = 1024
|
23
|
+
|
24
|
+
|
25
|
+
def nextMsgID(clientID, sessionID=0):
|
26
|
+
global __msgIndex
|
27
|
+
__msgIndex += 1
|
28
|
+
return msgIDFormat.pack(clientID, sessionID, __msgIndex)
|
29
|
+
|
30
|
+
|
31
|
+
def parseMsgID(msgID):
|
32
|
+
"""
|
33
|
+
return: (clientID, sessionID, msgIndex)
|
34
|
+
"""
|
35
|
+
return msgIDFormat.unpack(msgID)
|
36
|
+
|
37
|
+
|
38
|
+
# message type
|
39
|
+
|
40
|
+
RPC_REQUEST = b'\x01'
|
41
|
+
RPC_RESPONSE = b'\x02'
|
42
|
+
RPC_PING = b'\x03'
|
43
|
+
RPC_PONG = b'\x04'
|
44
|
+
RPC_CANCEL = b'\x05'
|
45
|
+
RPC_SHUTDOWN = b'\x06'
|
46
|
+
RPC_CONNECT = b'\x07'
|
47
|
+
RPC_WELCOME = b'\x08'
|
48
|
+
|
49
|
+
RPC_MSGIDSIZE = msgIDFormat.size
|
50
|
+
|
51
|
+
|
52
|
+
class RPCMixin(ABC):
|
53
|
+
info = {}
|
54
|
+
|
55
|
+
def start(self):
|
56
|
+
pass
|
57
|
+
|
58
|
+
def stop(self):
|
59
|
+
pass
|
60
|
+
|
61
|
+
def close(self):
|
62
|
+
pass
|
63
|
+
|
64
|
+
@property
|
65
|
+
@abstractmethod
|
66
|
+
def loop(self):
|
67
|
+
"""
|
68
|
+
Event loop.
|
69
|
+
"""
|
70
|
+
|
71
|
+
@abstractmethod
|
72
|
+
async def sendto(self, data, address):
|
73
|
+
"""
|
74
|
+
Send message to address.
|
75
|
+
"""
|
76
|
+
|
77
|
+
__rpc_handlers = {
|
78
|
+
RPC_CONNECT: 'on_connect',
|
79
|
+
RPC_WELCOME: 'on_welcome',
|
80
|
+
RPC_PING: 'on_ping',
|
81
|
+
RPC_PONG: 'on_pong',
|
82
|
+
RPC_REQUEST: 'on_request',
|
83
|
+
RPC_RESPONSE: 'on_response',
|
84
|
+
RPC_CANCEL: 'on_cancel',
|
85
|
+
RPC_SHUTDOWN: 'on_shutdown',
|
86
|
+
}
|
87
|
+
|
88
|
+
def parseData(self, data):
|
89
|
+
msg_type, msg = data[:1], data[1:]
|
90
|
+
if msg_type in [RPC_PING, RPC_PONG, RPC_CONNECT, RPC_WELCOME]:
|
91
|
+
return msg_type, msg
|
92
|
+
elif msg_type in [RPC_REQUEST, RPC_RESPONSE, RPC_CANCEL, RPC_SHUTDOWN]:
|
93
|
+
msgID, msg = msg[:RPC_MSGIDSIZE], msg[RPC_MSGIDSIZE:]
|
94
|
+
return msg_type, msgID, msg
|
95
|
+
# elif msg_type in [RPC_LONGREQUEST, RPC_LONGRESPONSE]:
|
96
|
+
# msgID, sessionID, msg = msg[:20], msg[20:40], msg[40:]
|
97
|
+
# return msg_type, msgID, sessionID, msg
|
98
|
+
else:
|
99
|
+
raise RPCError(f'Unkown message type {msg_type}.')
|
100
|
+
|
101
|
+
def handle(self, source, data):
|
102
|
+
"""
|
103
|
+
Handle received data.
|
104
|
+
|
105
|
+
Should be called whenever received data from outside.
|
106
|
+
"""
|
107
|
+
msg_type, *args = self.parseData(data)
|
108
|
+
log.debug(f'received request {msg_type} from {source}')
|
109
|
+
handler = self.__rpc_handlers.get(msg_type, None)
|
110
|
+
if handler is not None:
|
111
|
+
getattr(self, handler)(source, *args)
|
112
|
+
else:
|
113
|
+
log.error(f'No handler found for request {msg_type} from {source}')
|
114
|
+
|
115
|
+
async def pong(self, addr):
|
116
|
+
await self.sendto(RPC_PONG, addr)
|
117
|
+
|
118
|
+
def on_ping(self, source, msg):
|
119
|
+
log.debug(f"received ping from {source}")
|
120
|
+
asyncio.ensure_future(self.pong(source), loop=self.loop)
|
121
|
+
|
122
|
+
|
123
|
+
class RPCClientMixin(RPCMixin):
|
124
|
+
_client_defualt_timeout = 10
|
125
|
+
__pending = None
|
126
|
+
__clientID = 1
|
127
|
+
|
128
|
+
@property
|
129
|
+
def pending(self):
|
130
|
+
if self.__pending is None:
|
131
|
+
self.__pending = {}
|
132
|
+
return self.__pending
|
133
|
+
|
134
|
+
@property
|
135
|
+
def clientID(self):
|
136
|
+
return self.__clientID
|
137
|
+
|
138
|
+
def createPending(self, addr, msgID, timeout=1, cancelRemote=True):
|
139
|
+
"""
|
140
|
+
Create a future for request, wait response before timeout.
|
141
|
+
"""
|
142
|
+
fut = self.loop.create_future()
|
143
|
+
self.pending[msgID] = (fut,
|
144
|
+
self.loop.call_later(timeout,
|
145
|
+
self.cancelPending, addr,
|
146
|
+
msgID, cancelRemote))
|
147
|
+
|
148
|
+
def clean(fut, msgID=msgID):
|
149
|
+
if msgID in self.pending:
|
150
|
+
del self.pending[msgID]
|
151
|
+
|
152
|
+
fut.add_done_callback(clean)
|
153
|
+
|
154
|
+
return fut
|
155
|
+
|
156
|
+
def cancelPending(self, addr, msgID, cancelRemote):
|
157
|
+
"""
|
158
|
+
Give up when request timeout and try to cancel remote task.
|
159
|
+
"""
|
160
|
+
if msgID in self.pending:
|
161
|
+
fut, timeout = self.pending[msgID]
|
162
|
+
if cancelRemote:
|
163
|
+
self.cancelRemoteTask(addr, msgID)
|
164
|
+
if not fut.done():
|
165
|
+
fut.set_exception(
|
166
|
+
RPCTimeout(
|
167
|
+
f'Node({self.info}): Wait response from {addr} timeout.'
|
168
|
+
))
|
169
|
+
|
170
|
+
def cancelRemoteTask(self, addr, msgID):
|
171
|
+
"""
|
172
|
+
Try to cancel remote task.
|
173
|
+
"""
|
174
|
+
asyncio.ensure_future(self.sendto(RPC_CANCEL + msgID, addr),
|
175
|
+
loop=self.loop)
|
176
|
+
|
177
|
+
def close(self):
|
178
|
+
self.stop()
|
179
|
+
for fut, timeout in list(self.pending.values()):
|
180
|
+
fut.cancel()
|
181
|
+
timeout.cancel()
|
182
|
+
self.pending.clear()
|
183
|
+
|
184
|
+
def setTimeout(self, timeout=10):
|
185
|
+
self._client_defualt_timeout = timeout
|
186
|
+
|
187
|
+
def remoteCall(self, addr, methodNane, sessionID=0, args=(), kw={}):
|
188
|
+
if 'timeout' in kw:
|
189
|
+
timeout = kw['timeout']
|
190
|
+
else:
|
191
|
+
timeout = self._client_defualt_timeout
|
192
|
+
msg = pack((methodNane, args, kw))
|
193
|
+
msgID = nextMsgID(self.clientID, sessionID)
|
194
|
+
asyncio.ensure_future(self.request(addr, msgID, msg), loop=self.loop)
|
195
|
+
return self.createPending(addr, msgID, timeout)
|
196
|
+
|
197
|
+
async def connect(self, addr, authkey=b"", timeout=None):
|
198
|
+
if timeout is None:
|
199
|
+
timeout = self._client_defualt_timeout
|
200
|
+
await self.sendto(RPC_CONNECT + authkey, addr)
|
201
|
+
fut = self.createPending(addr, addr, timeout, False)
|
202
|
+
msgID = await fut
|
203
|
+
clientID, *_ = parseMsgID(msgID)
|
204
|
+
if clientID < 1024:
|
205
|
+
raise RPCError(f'Connect {addr} fail')
|
206
|
+
self.__clientID = clientID
|
207
|
+
|
208
|
+
async def ping(self, addr, timeout=1):
|
209
|
+
await self.sendto(RPC_PING, addr)
|
210
|
+
fut = self.createPending(addr, addr, timeout, False)
|
211
|
+
try:
|
212
|
+
return await fut
|
213
|
+
except RPCTimeout:
|
214
|
+
return False
|
215
|
+
|
216
|
+
async def request(self, address, msgID, msg):
|
217
|
+
log.debug(f'send request {address}, {msgID.hex()}, {msg}')
|
218
|
+
await self.sendto(RPC_REQUEST + msgID + msg, address)
|
219
|
+
|
220
|
+
async def shutdown(self, address, roleAuth):
|
221
|
+
await self.sendto(RPC_SHUTDOWN + nextMsgID(self.clientID) + roleAuth,
|
222
|
+
address)
|
223
|
+
|
224
|
+
def on_welcome(self, source, msg):
|
225
|
+
if source in self.pending:
|
226
|
+
fut, timeout = self.pending[source]
|
227
|
+
timeout.cancel()
|
228
|
+
if not fut.done():
|
229
|
+
fut.set_result(msg)
|
230
|
+
|
231
|
+
def on_pong(self, source, msg):
|
232
|
+
log.debug(f"received pong from {source}")
|
233
|
+
if source in self.pending:
|
234
|
+
fut, timeout = self.pending[source]
|
235
|
+
timeout.cancel()
|
236
|
+
if not fut.done():
|
237
|
+
fut.set_result(True)
|
238
|
+
|
239
|
+
def on_response(self, source, msgID, msg):
|
240
|
+
"""
|
241
|
+
Client side.
|
242
|
+
"""
|
243
|
+
if msgID not in self.pending:
|
244
|
+
return
|
245
|
+
fut, timeout = self.pending[msgID]
|
246
|
+
timeout.cancel()
|
247
|
+
try:
|
248
|
+
result = unpack(msg)
|
249
|
+
except Exception as e:
|
250
|
+
fut.set_exception(e)
|
251
|
+
return
|
252
|
+
if not fut.done():
|
253
|
+
if isinstance(result, Exception):
|
254
|
+
fut.set_exception(result)
|
255
|
+
else:
|
256
|
+
fut.set_result(result)
|
257
|
+
|
258
|
+
|
259
|
+
class RPCServerMixin(RPCMixin):
|
260
|
+
__tasks = None
|
261
|
+
__sessions = None
|
262
|
+
__nextClientID = 1024
|
263
|
+
__nextSessionID = 1024
|
264
|
+
|
265
|
+
def info(self):
|
266
|
+
return {'version': __version__}
|
267
|
+
|
268
|
+
@property
|
269
|
+
def nextClientID(self):
|
270
|
+
self.__nextClientID += 1
|
271
|
+
return self.__nextClientID
|
272
|
+
|
273
|
+
@property
|
274
|
+
def nextSessionID(self):
|
275
|
+
self.__nextSessionID += 1
|
276
|
+
return self.__nextSessionID
|
277
|
+
|
278
|
+
@property
|
279
|
+
def tasks(self):
|
280
|
+
if self.__tasks is None:
|
281
|
+
self.__tasks = {}
|
282
|
+
return self.__tasks
|
283
|
+
|
284
|
+
@property
|
285
|
+
def sessions(self):
|
286
|
+
if self.__sessions is None:
|
287
|
+
self.__sessions = {}
|
288
|
+
return self.__sessions
|
289
|
+
|
290
|
+
def createTask(self, msgID, coro, timeout=0):
|
291
|
+
"""
|
292
|
+
Create a new task for msgID.
|
293
|
+
"""
|
294
|
+
if timeout > 0:
|
295
|
+
coro = asyncio.wait_for(coro, timeout)
|
296
|
+
task = asyncio.ensure_future(coro, loop=self.loop)
|
297
|
+
self.tasks[msgID] = task
|
298
|
+
|
299
|
+
def clean(fut, msgID=msgID):
|
300
|
+
if msgID in self.tasks:
|
301
|
+
del self.tasks[msgID]
|
302
|
+
|
303
|
+
task.add_done_callback(clean)
|
304
|
+
|
305
|
+
def cancelTask(self, msgID):
|
306
|
+
"""
|
307
|
+
Cancel the task for msgID.
|
308
|
+
"""
|
309
|
+
if msgID in self.tasks:
|
310
|
+
self.tasks[msgID].cancel()
|
311
|
+
|
312
|
+
def createSession(self, clientID, obj):
|
313
|
+
sessionID = self.nextSessionID
|
314
|
+
self.sessions[(clientID, sessionID)] = obj
|
315
|
+
return sessionID
|
316
|
+
|
317
|
+
def removeSession(self, clientID, sessionID):
|
318
|
+
del self.sessions[(clientID, sessionID)]
|
319
|
+
|
320
|
+
def close(self):
|
321
|
+
self.stop()
|
322
|
+
for task in list(self.tasks.values()):
|
323
|
+
task.cancel()
|
324
|
+
self.tasks.clear()
|
325
|
+
|
326
|
+
def _unpack_request(self, msg):
|
327
|
+
try:
|
328
|
+
method, args, kw = unpack(msg)
|
329
|
+
except:
|
330
|
+
raise RPCError("Could not read packet: %r" % msg)
|
331
|
+
return method, args, kw
|
332
|
+
|
333
|
+
@property
|
334
|
+
def executor(self):
|
335
|
+
return None
|
336
|
+
|
337
|
+
@abstractmethod
|
338
|
+
def getRequestHandler(self, methodNane, source, msgID, args=(), kw={}):
|
339
|
+
"""
|
340
|
+
Get suitable handler for request.
|
341
|
+
|
342
|
+
You should implement this method yourself.
|
343
|
+
"""
|
344
|
+
|
345
|
+
def processResult(self, result, method, msgID):
|
346
|
+
return result
|
347
|
+
|
348
|
+
async def handle_request(self, source, msgID, method, args, kw):
|
349
|
+
"""
|
350
|
+
Handle a request from source.
|
351
|
+
"""
|
352
|
+
try:
|
353
|
+
func = self.getRequestHandler(method, source=source, msgID=msgID)
|
354
|
+
result = await self.callMethod(func, *args, **kw)
|
355
|
+
result = self.processResult(result, method, msgID)
|
356
|
+
except (RPCError, RPCServerError) as e:
|
357
|
+
result = e
|
358
|
+
except Exception as e:
|
359
|
+
result = RPCServerError.make(e)
|
360
|
+
msg = pack(result)
|
361
|
+
await self.response(source, msgID, msg)
|
362
|
+
|
363
|
+
async def callMethod(self, func, *args, **kw):
|
364
|
+
if 'timeout' in kw and not acceptArg(func, 'timeout'):
|
365
|
+
del kw['timeout']
|
366
|
+
if inspect.iscoroutinefunction(func):
|
367
|
+
result = await func(*args, **kw)
|
368
|
+
else:
|
369
|
+
result = await self.loop.run_in_executor(
|
370
|
+
self.executor, functools.partial(func, *args, **kw))
|
371
|
+
if isinstance(result, Awaitable):
|
372
|
+
result = await result
|
373
|
+
return result
|
374
|
+
|
375
|
+
async def response(self, address, msgID, msg):
|
376
|
+
log.debug(f'send response {address}, {msgID.hex()}, {msg}')
|
377
|
+
await self.sendto(RPC_RESPONSE + msgID + msg, address)
|
378
|
+
|
379
|
+
def on_connect(self, source, msg):
|
380
|
+
log.debug(f"connect from {source}")
|
381
|
+
if self.auth(source, msg):
|
382
|
+
clientID = self.nextClientID
|
383
|
+
else:
|
384
|
+
clientID = 0
|
385
|
+
msg = pack(self.info())
|
386
|
+
asyncio.ensure_future(
|
387
|
+
self.sendto(
|
388
|
+
RPC_WELCOME + nextMsgID(clientID), # + msg,
|
389
|
+
source),
|
390
|
+
loop=self.loop)
|
391
|
+
|
392
|
+
def on_request(self, source, msgID, msg):
|
393
|
+
"""
|
394
|
+
Received a request from source.
|
395
|
+
"""
|
396
|
+
method, args, kw = self._unpack_request(msg)
|
397
|
+
self.createTask(msgID,
|
398
|
+
self.handle_request(source, msgID, method, args, kw),
|
399
|
+
timeout=kw.get('timeout', 0))
|
400
|
+
|
401
|
+
def on_shutdown(self, source, msgID, roleAuth):
|
402
|
+
if self.is_admin(source, roleAuth):
|
403
|
+
raise SystemExit(0)
|
404
|
+
|
405
|
+
def auth(self, source, authkey):
|
406
|
+
return True
|
407
|
+
|
408
|
+
def is_admin(self, source, roleAuth):
|
409
|
+
return True
|
410
|
+
|
411
|
+
def on_cancel(self, source, msgID, msg):
|
412
|
+
self.cancelTask(msgID)
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import pickle
|
2
|
+
import zlib
|
3
|
+
from typing import Any, Callable, TypeVar
|
4
|
+
|
5
|
+
import msgpack
|
6
|
+
|
7
|
+
__index = 0
|
8
|
+
__pack_handlers = {}
|
9
|
+
__unpack_handlers = {}
|
10
|
+
__compress_level = zlib.Z_NO_COMPRESSION
|
11
|
+
|
12
|
+
cls = TypeVar('cls')
|
13
|
+
|
14
|
+
|
15
|
+
def compress_level(level: int) -> None:
|
16
|
+
"""
|
17
|
+
Set compress level
|
18
|
+
|
19
|
+
Args:
|
20
|
+
level: int
|
21
|
+
An integer from 0 to 9 controlling the level of compression;
|
22
|
+
1 is fastest and produces the least compression, 9 is slowest
|
23
|
+
and produces the most. 0 is no compression. The default value
|
24
|
+
is 0.
|
25
|
+
"""
|
26
|
+
global __compress_level
|
27
|
+
__compress_level = level
|
28
|
+
|
29
|
+
|
30
|
+
def register(cls: type,
|
31
|
+
encode: Callable[[cls], bytes] = pickle.dumps,
|
32
|
+
decode: Callable[[bytes], cls] = pickle.loads) -> None:
|
33
|
+
"""
|
34
|
+
Register a serializable type
|
35
|
+
|
36
|
+
Args:
|
37
|
+
cls: type
|
38
|
+
encode: Callable
|
39
|
+
translate an object of type `cls` into `bytes`
|
40
|
+
default: pickle.dumps
|
41
|
+
decode: Callable
|
42
|
+
translate `bytes` to an object of type `cls`
|
43
|
+
default: pickle.loads
|
44
|
+
"""
|
45
|
+
global __index
|
46
|
+
__index += 1
|
47
|
+
t = __index
|
48
|
+
__pack_handlers[cls] = (t, encode)
|
49
|
+
__unpack_handlers[t] = decode
|
50
|
+
|
51
|
+
|
52
|
+
def default(obj: Any) -> msgpack.ExtType:
|
53
|
+
for cls, (t, encode) in __pack_handlers.items():
|
54
|
+
if isinstance(obj, cls):
|
55
|
+
return msgpack.ExtType(t, encode(obj))
|
56
|
+
else:
|
57
|
+
raise TypeError("Unknown type: %r" % (obj, ))
|
58
|
+
|
59
|
+
|
60
|
+
def ext_hook(code: int, data: bytes) -> msgpack.ExtType:
|
61
|
+
for c, decode in __unpack_handlers.items():
|
62
|
+
if code == c:
|
63
|
+
return decode(data)
|
64
|
+
else:
|
65
|
+
return msgpack.ExtType(code, data)
|
66
|
+
|
67
|
+
|
68
|
+
def pack(obj: Any) -> bytes:
|
69
|
+
"""
|
70
|
+
Serialize
|
71
|
+
"""
|
72
|
+
return msgpack.packb(obj, default=default, use_bin_type=True)
|
73
|
+
|
74
|
+
|
75
|
+
def unpack(buff: bytes) -> Any:
|
76
|
+
"""
|
77
|
+
Unserialize
|
78
|
+
"""
|
79
|
+
return msgpack.unpackb(buff, ext_hook=ext_hook, raw=False)
|
80
|
+
|
81
|
+
|
82
|
+
def packz(obj: Any) -> bytes:
|
83
|
+
"""
|
84
|
+
Serialize and compress.
|
85
|
+
"""
|
86
|
+
return zlib.compress(pack(obj), level=__compress_level)
|
87
|
+
|
88
|
+
|
89
|
+
def unpackz(buff: bytes) -> Any:
|
90
|
+
"""
|
91
|
+
Decompress and unserialize.
|
92
|
+
"""
|
93
|
+
return unpack(zlib.decompress(buff))
|
94
|
+
|
95
|
+
|
96
|
+
def encode_excepion(e: Exception) -> bytes:
|
97
|
+
e.__traceback__ = None
|
98
|
+
return pickle.dumps(e)
|
99
|
+
|
100
|
+
|
101
|
+
register(Exception, encode_excepion)
|
102
|
+
|
103
|
+
|
104
|
+
try:
|
105
|
+
import numpy as np
|
106
|
+
|
107
|
+
dtypes = [
|
108
|
+
np.bool_, np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16,
|
109
|
+
np.uint32, np.uint64, np.float16, np.float32, np.float64, np.complex64,
|
110
|
+
np.complex128
|
111
|
+
]
|
112
|
+
|
113
|
+
_dtype_map1 = {t: i for i, t in enumerate(dtypes)}
|
114
|
+
_dtype_map2 = {i: t for i, t in enumerate(dtypes)}
|
115
|
+
|
116
|
+
def encode_ndarray(x: np.ndarray) -> bytes:
|
117
|
+
dtype = x.dtype
|
118
|
+
if isinstance(dtype, np.dtype):
|
119
|
+
dtype = dtype.type
|
120
|
+
if x.flags['F_CONTIGUOUS']:
|
121
|
+
x = x.T
|
122
|
+
T = True
|
123
|
+
else:
|
124
|
+
T = False
|
125
|
+
return pack((_dtype_map1[dtype], x.shape, T, x.data))
|
126
|
+
|
127
|
+
def decode_ndarray(buff: bytes) -> np.ndarray:
|
128
|
+
t, shape, T, buff = unpack(buff)
|
129
|
+
x = np.ndarray(shape, dtype=_dtype_map2[t], buffer=buff, order='C')
|
130
|
+
if T:
|
131
|
+
x = x.T
|
132
|
+
return x
|
133
|
+
|
134
|
+
register(np.ndarray, encode_ndarray, decode_ndarray)
|
135
|
+
|
136
|
+
except:
|
137
|
+
pass
|
138
|
+
|
139
|
+
__all__ = ['compress_level', 'register', 'pack', 'unpack', 'packz', 'unpackz']
|
qulab/sys/rpc/server.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
from ..net.kcp import listen
|
2
|
+
|
3
|
+
|
4
|
+
class Peer:
|
5
|
+
|
6
|
+
def __init__(self, addr, handler, **kwargs):
|
7
|
+
self.addr = addr
|
8
|
+
self.handler = handler
|
9
|
+
self.kwargs = kwargs
|
10
|
+
|
11
|
+
async def run(self):
|
12
|
+
conv = self.kwargs.get('conv', 0)
|
13
|
+
ttl = self.kwargs.get('ttl', 60)
|
14
|
+
async with listen(self.addr, conv, ttl, **self.kwargs) as server:
|
15
|
+
async for conn in server:
|
16
|
+
await self.handler(conn)
|
17
|
+
|
18
|
+
def __call__(self, handler):
|
19
|
+
self.handler = handler
|
20
|
+
return self
|
21
|
+
|
22
|
+
def __await__(self):
|
23
|
+
return self.run().__await__()
|
24
|
+
|
25
|
+
def __aenter__(self):
|
26
|
+
return self
|
27
|
+
|
28
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
29
|
+
pass
|
qulab/sys/rpc/socket.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
from ..net.kcp import listen
|
2
|
+
|
3
|
+
|
4
|
+
class Peer:
|
5
|
+
|
6
|
+
def __init__(self, addr, handler, **kwargs):
|
7
|
+
self.addr = addr
|
8
|
+
self.handler = handler
|
9
|
+
self.kwargs = kwargs
|
10
|
+
|
11
|
+
async def run(self):
|
12
|
+
conv = self.kwargs.get('conv', 0)
|
13
|
+
ttl = self.kwargs.get('ttl', 60)
|
14
|
+
async with listen(self.addr, conv, ttl, **self.kwargs) as server:
|
15
|
+
async for conn in server:
|
16
|
+
await self.handler(conn)
|
17
|
+
|
18
|
+
def __call__(self, handler):
|
19
|
+
self.handler = handler
|
20
|
+
return self
|
21
|
+
|
22
|
+
def __await__(self):
|
23
|
+
return self.run().__await__()
|
24
|
+
|
25
|
+
def __aenter__(self):
|
26
|
+
return self
|
27
|
+
|
28
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
29
|
+
pass
|
qulab/sys/rpc/utils.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
import inspect
|
2
|
+
import os
|
3
|
+
from hashlib import sha1
|
4
|
+
|
5
|
+
|
6
|
+
def acceptArg(f, name, keyword=True):
|
7
|
+
"""
|
8
|
+
Test if argument is acceptable by function.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
f: callable
|
12
|
+
function
|
13
|
+
name: str
|
14
|
+
argument name
|
15
|
+
"""
|
16
|
+
sig = inspect.signature(f)
|
17
|
+
for param in sig.parameters.values():
|
18
|
+
if param.name == name and param.kind != param.VAR_POSITIONAL:
|
19
|
+
return True
|
20
|
+
elif param.kind == param.VAR_KEYWORD:
|
21
|
+
return True
|
22
|
+
elif param.kind == param.VAR_POSITIONAL and not keyword:
|
23
|
+
return True
|
24
|
+
return False
|
25
|
+
|