codekingpro/portable-devtools
114k
1import asyncio2from socketio import AsyncClient3from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError4 5 6class AsyncSimpleClient:7 """A Socket.IO client.8 9 This class implements a simple, yet fully compliant Socket.IO web client10 with support for websocket and long-polling transports.11 12 The positional and keyword arguments given in the constructor are passed13 to the underlying :func:`socketio.AsyncClient` object.14 """15 def __init__(self, *args, **kwargs):16 self.client_args = args17 self.client_kwargs = kwargs18 self.client = None19 self.namespace = '/'20 self.connected_event = asyncio.Event()21 self.connected = False22 self.input_event = asyncio.Event()23 self.input_buffer = []24 25 async def connect(self, url, headers={}, auth=None, transports=None,26 namespace='/', socketio_path='socket.io',27 wait_timeout=5):28 """Connect to a Socket.IO server.29 30 :param url: The URL of the Socket.IO server. It can include custom31 query string parameters if required by the server. If a32 function is provided, the client will invoke it to obtain33 the URL each time a connection or reconnection is34 attempted.35 :param headers: A dictionary with custom headers to send with the36 connection request. If a function is provided, the37 client will invoke it to obtain the headers dictionary38 each time a connection or reconnection is attempted.39 :param auth: Authentication data passed to the server with the40 connection request, normally a dictionary with one or41 more string key/value pairs. If a function is provided,42 the client will invoke it to obtain the authentication43 data each time a connection or reconnection is attempted.44 :param transports: The list of allowed transports. Valid transports45 are ``'polling'`` and ``'websocket'``. If not46 given, the polling transport is connected first,47 then an upgrade to websocket is attempted.48 :param namespace: The namespace to connect to as a string. If not49 given, the default namespace ``/`` is used.50 :param socketio_path: The endpoint where the Socket.IO server is51 installed. The default value is appropriate for52 most cases.53 :param wait_timeout: How long the client should wait for the54 connection. The default is 5 seconds.55 56 Note: this method is a coroutine.57 """58 if self.connected:59 raise RuntimeError('Already connected')60 self.namespace = namespace61 self.input_buffer = []62 self.input_event.clear()63 self.client = AsyncClient(*self.client_args, **self.client_kwargs)64 65 @self.client.event(namespace=self.namespace)66 def connect(): # pragma: no cover67 self.connected = True68 self.connected_event.set()69 70 @self.client.event(namespace=self.namespace)71 def disconnect(): # pragma: no cover72 self.connected_event.clear()73 74 @self.client.event(namespace=self.namespace)75 def __disconnect_final(): # pragma: no cover76 self.connected = False77 self.connected_event.set()78 79 @self.client.on('*', namespace=self.namespace)80 def on_event(event, *args): # pragma: no cover81 self.input_buffer.append([event, *args])82 self.input_event.set()83 84 await self.client.connect(85 url, headers=headers, auth=auth, transports=transports,86 namespaces=[namespace], socketio_path=socketio_path,87 wait_timeout=wait_timeout)88 89 @property90 def sid(self):91 """The session ID received from the server.92 93 The session ID is not guaranteed to remain constant throughout the life94 of the connection, as reconnections can cause it to change.95 """96 return self.client.get_sid(self.namespace) if self.client else None97 98 @property99 def transport(self):100 """The name of the transport currently in use.101 102 The transport is returned as a string and can be one of ``polling``103 and ``websocket``.104 """105 return self.client.transport if self.client else ''106 107 async def emit(self, event, data=None):108 """Emit an event to the server.109 110 :param event: The event name. It can be any string. The event names111 ``'connect'``, ``'message'`` and ``'disconnect'`` are112 reserved and should not be used.113 :param data: The data to send to the server. Data can be of114 type ``str``, ``bytes``, ``list`` or ``dict``. To send115 multiple arguments, use a tuple where each element is of116 one of the types indicated above.117 118 Note: this method is a coroutine.119 120 This method schedules the event to be sent out and returns, without121 actually waiting for its delivery. In cases where the client needs to122 ensure that the event was received, :func:`socketio.SimpleClient.call`123 should be used instead.124 """125 while True:126 await self.connected_event.wait()127 if not self.connected:128 raise DisconnectedError()129 try:130 return await self.client.emit(event, data,131 namespace=self.namespace)132 except SocketIOError:133 pass134 135 async def call(self, event, data=None, timeout=60):136 """Emit an event to the server and wait for a response.137 138 This method issues an emit and waits for the server to provide a139 response or acknowledgement. If the response does not arrive before the140 timeout, then a ``TimeoutError`` exception is raised.141 142 :param event: The event name. It can be any string. The event names143 ``'connect'``, ``'message'`` and ``'disconnect'`` are144 reserved and should not be used.145 :param data: The data to send to the server. Data can be of146 type ``str``, ``bytes``, ``list`` or ``dict``. To send147 multiple arguments, use a tuple where each element is of148 one of the types indicated above.149 :param timeout: The waiting timeout. If the timeout is reached before150 the server acknowledges the event, then a151 ``TimeoutError`` exception is raised.152 153 Note: this method is a coroutine.154 """155 while True:156 await self.connected_event.wait()157 if not self.connected:158 raise DisconnectedError()159 try:160 return await self.client.call(event, data,161 namespace=self.namespace,162 timeout=timeout)163 except SocketIOError:164 pass165 166 async def receive(self, timeout=None):167 """Wait for an event from the server.168 169 :param timeout: The waiting timeout. If the timeout is reached before170 the server acknowledges the event, then a171 ``TimeoutError`` exception is raised.172 173 Note: this method is a coroutine.174 175 The return value is a list with the event name as the first element. If176 the server included arguments with the event, they are returned as177 additional list elements.178 """179 while not self.input_buffer:180 try:181 await asyncio.wait_for(self.connected_event.wait(),182 timeout=timeout)183 except asyncio.TimeoutError: # pragma: no cover184 raise TimeoutError()185 if not self.connected:186 raise DisconnectedError()187 try:188 await asyncio.wait_for(self.input_event.wait(),189 timeout=timeout)190 except asyncio.TimeoutError:191 raise TimeoutError()192 self.input_event.clear()193 return self.input_buffer.pop(0)194 195 async def disconnect(self):196 """Disconnect from the server.197 198 Note: this method is a coroutine.199 """200 if self.connected:201 await self.client.disconnect()202 self.client = None203 self.connected = False204 205 async def __aenter__(self):206 return self207 208 async def __aexit__(self, exc_type, exc_val, exc_tb):209 await self.disconnect()210 