diff options
author | morpheus65535 <[email protected]> | 2022-11-07 13:06:49 -0500 |
---|---|---|
committer | morpheus65535 <[email protected]> | 2022-11-07 13:08:27 -0500 |
commit | bbe2483e21c2c1549ceeed16f021f9581b899f70 (patch) | |
tree | bcc2bef2f55789ec6e6c64809c07fb4f4d3d9c86 /libs/flask_socketio | |
parent | 708fbfcd8ec0620647975be39a1f6acbbf08f767 (diff) | |
download | bazarr-bbe2483e21c2c1549ceeed16f021f9581b899f70.tar.gz bazarr-bbe2483e21c2c1549ceeed16f021f9581b899f70.zip |
Updated vendored dependencies.
Diffstat (limited to 'libs/flask_socketio')
-rw-r--r-- | libs/flask_socketio/__init__.py | 157 | ||||
-rw-r--r-- | libs/flask_socketio/test_client.py | 57 |
2 files changed, 158 insertions, 56 deletions
diff --git a/libs/flask_socketio/__init__.py b/libs/flask_socketio/__init__.py index ef7b9e616..a3dab4566 100644 --- a/libs/flask_socketio/__init__.py +++ b/libs/flask_socketio/__init__.py @@ -16,7 +16,7 @@ if gevent_socketio_found: sys.exit(1) import flask -from flask import _request_ctx_stack, has_request_context, json as flask_json +from flask import has_request_context, json as flask_json from flask.sessions import SessionMixin import socketio from socketio.exceptions import ConnectionRefusedError # noqa: F401 @@ -197,8 +197,8 @@ class SocketIO(object): self.manage_session = self.server_options.pop('manage_session', self.manage_session) - if 'client_manager' not in self.server_options: - url = self.server_options.pop('message_queue', None) + if 'client_manager' not in kwargs: + url = self.server_options.get('message_queue', None) channel = self.server_options.pop('channel', 'flask-socketio') write_only = app is None if url: @@ -416,9 +416,9 @@ class SocketIO(object): :param args: A dictionary with the JSON data to send as payload. :param namespace: The namespace under which the message is to be sent. Defaults to the global namespace. - :param to: Send the message to all the users in the given room. If - this parameter is not included, the event is sent to all - connected users. + :param to: Send the message to all the users in the given room, or to + the user with the given session ID. If this parameter is not + included, the event is sent to all connected users. :param include_self: ``True`` to include the sender when broadcasting or addressing a room, or ``False`` to send to everyone but the sender. @@ -434,7 +434,7 @@ class SocketIO(object): only be used when addressing an individual client. """ namespace = kwargs.pop('namespace', '/') - to = kwargs.pop('to', kwargs.pop('room', None)) + to = kwargs.pop('to', None) or kwargs.pop('room', None) include_self = kwargs.pop('include_self', True) skip_sid = kwargs.pop('skip_sid', None) if not include_self and not skip_sid: @@ -460,6 +460,41 @@ class SocketIO(object): self.server.emit(event, *args, namespace=namespace, to=to, skip_sid=skip_sid, callback=callback, **kwargs) + def call(self, event, *args, **kwargs): # pragma: no cover + """Emit a SocketIO event and wait for the response. + + This method issues an emit with a callback and waits for the callback + to be invoked by the client before returning. If the callback isn’t + invoked before the timeout, then a TimeoutError exception is raised. If + the Socket.IO connection drops during the wait, this method still waits + until the specified timeout. Example:: + + def get_status(client, data): + status = call('status', {'data': data}, to=client) + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the global namespace. + :param to: The session ID of the recipient client. + :param timeout: The waiting timeout. If the timeout is reached before + the client acknowledges the event, then a + ``TimeoutError`` exception is raised. The default is 60 + seconds. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always + leave this parameter with its default value of + ``False``. + """ + namespace = kwargs.pop('namespace', '/') + to = kwargs.pop('to', None) or kwargs.pop('room', None) + return self.server.call(event, *args, namespace=namespace, to=to, + **kwargs) + def send(self, data, json=False, namespace=None, to=None, callback=None, include_self=True, skip_sid=None, **kwargs): """Send a server-generated SocketIO message. @@ -475,9 +510,9 @@ class SocketIO(object): otherwise. :param namespace: The namespace under which the message is to be sent. Defaults to the global namespace. - :param to: Send the message only to the users in the given room. If - this parameter is not included, the message is sent to all - connected users. + :param to: Send the message to all the users in the given room, or to + the user with the given session ID. If this parameter is not + included, the event is sent to all connected users. :param include_self: ``True`` to include the sender when broadcasting or addressing a room, or ``False`` to send to everyone but the sender. @@ -536,6 +571,10 @@ class SocketIO(object): Defaults to ``True`` in debug mode, ``False`` in normal mode. Unused when the threading async mode is used. + :param allow_unsafe_werkzeug: Set to ``True`` to allow the use of the + Werkzeug web server in a production + setting. Default is ``False``. Set to + ``True`` at your own risk. :param kwargs: Additional web server options. The web server options are specific to the server used in each of the supported async modes. Note that options provided here will @@ -593,6 +632,20 @@ class SocketIO(object): from werkzeug._internal import _log _log('warning', 'WebSocket transport not available. Install ' 'simple-websocket for improved performance.') + allow_unsafe_werkzeug = kwargs.pop('allow_unsafe_werkzeug', + False) + if not sys.stdin or not sys.stdin.isatty(): # pragma: no cover + if not allow_unsafe_werkzeug: + raise RuntimeError('The Werkzeug web server is not ' + 'designed to run in production. Pass ' + 'allow_unsafe_werkzeug=True to the ' + 'run() method to disable this error.') + else: + from werkzeug._internal import _log + _log('warning', ('Werkzeug appears to be used in a ' + 'production deployment. Consider ' + 'switching to a production web server ' + 'instead.')) app.run(host=host, port=port, threaded=True, use_reloader=use_reloader, **reloader_options, **kwargs) elif self.server.eio.async_mode == 'eventlet': @@ -690,9 +743,9 @@ class SocketIO(object): :param args: arguments to pass to the function. :param kwargs: keyword arguments to pass to the function. - This function returns an object compatible with the `Thread` class in - the Python standard library. The `start()` method on this object is - already called by this function. + This function returns an object that represents the background task, + on which the ``join()`` methond can be invoked to wait for the task to + complete. """ return self.server.start_background_task(target, *args, **kwargs) @@ -744,6 +797,14 @@ class SocketIO(object): if 'saved_session' not in environ: environ['saved_session'] = _ManagedSession(flask.session) session_obj = environ['saved_session'] + if hasattr(flask, 'globals') and \ + hasattr(flask.globals, 'request_ctx'): + # update session for Flask >= 2.2 + ctx = flask.globals.request_ctx._get_current_object() + else: # pragma: no cover + # update session for Flask < 2.2 + ctx = flask._request_ctx_stack.top + ctx.session = session_obj else: # let Flask handle the user session # for cookie based sessions, this effectively freezes the @@ -751,7 +812,6 @@ class SocketIO(object): # for server-side sessions, this allows HTTP and Socket.IO to # share the session, with both having read/write access to it session_obj = flask.session._get_current_object() - _request_ctx_stack.top.session = session_obj flask.request.sid = sid flask.request.namespace = namespace flask.request.event = {'message': message, 'args': args} @@ -802,9 +862,10 @@ def emit(event, *args, **kwargs): acknowledgement. :param broadcast: ``True`` to send the message to all clients, or ``False`` to only reply to the sender of the originating event. - :param to: Send the message to all the users in the given room. If this - argument is not set and ``broadcast`` is ``False``, then the - message is sent only to the originating user. + :param to: Send the message to all the users in the given room, or to the + user with the given session ID. If this argument is not set and + ``broadcast`` is ``False``, then the message is sent only to the + originating user. :param include_self: ``True`` to include the sender when broadcasting or addressing a room, or ``False`` to send to everyone but the sender. @@ -827,7 +888,7 @@ def emit(event, *args, **kwargs): namespace = flask.request.namespace callback = kwargs.get('callback') broadcast = kwargs.get('broadcast') - to = kwargs.pop('to', kwargs.pop('room', None)) + to = kwargs.pop('to', None) or kwargs.pop('room', None) if to is None and not broadcast: to = flask.request.sid include_self = kwargs.get('include_self', True) @@ -840,6 +901,53 @@ def emit(event, *args, **kwargs): callback=callback, ignore_queue=ignore_queue) +def call(event, *args, **kwargs): # pragma: no cover + """Emit a SocketIO event and wait for the response. + + This function issues an emit with a callback and waits for the callback to + be invoked by the client before returning. If the callback isn’t invoked + before the timeout, then a TimeoutError exception is raised. If the + Socket.IO connection drops during the wait, this method still waits until + the specified timeout. Example:: + + def get_status(client, data): + status = call('status', {'data': data}, to=client) + + :param event: The name of the user event to emit. + :param args: A dictionary with the JSON data to send as payload. + :param namespace: The namespace under which the message is to be sent. + Defaults to the namespace used by the originating event. + A ``'/'`` can be used to explicitly specify the global + namespace. + :param to: The session ID of the recipient client. If this argument is not + given, the event is sent to the originating client. + :param timeout: The waiting timeout. If the timeout is reached before the + client acknowledges the event, then a ``TimeoutError`` + exception is raised. The default is 60 seconds. + :param ignore_queue: Only used when a message queue is configured. If + set to ``True``, the event is emitted to the + client directly, without going through the queue. + This is more efficient, but only works when a + single server process is used, or when there is a + single addressee. It is recommended to always leave + this parameter with its default value of ``False``. + """ + if 'namespace' in kwargs: + namespace = kwargs['namespace'] + else: + namespace = flask.request.namespace + to = kwargs.pop('to', None) or kwargs.pop('room', None) + if to is None: + to = flask.request.sid + timeout = kwargs.get('timeout', 60) + ignore_queue = kwargs.get('ignore_queue', False) + + socketio = flask.current_app.extensions['socketio'] + return socketio.call(event, *args, namespace=namespace, to=to, + include_self=False, skip_sid=None, + ignore_queue=ignore_queue, timeout=timeout) + + def send(message, **kwargs): """Send a SocketIO message. @@ -859,9 +967,10 @@ def send(message, **kwargs): :param broadcast: ``True`` to send the message to all connected clients, or ``False`` to only reply to the sender of the originating event. - :param to: Send the message to all the users in the given room. If this - argument is not set and ``broadcast`` is ``False``, then the - message is sent only to the originating user. + :param to: Send the message to all the users in the given room, or to the + user with the given session ID. If this argument is not set and + ``broadcast`` is ``False``, then the message is sent only to the + originating user. :param include_self: ``True`` to include the sender when broadcasting or addressing a room, or ``False`` to send to everyone but the sender. @@ -885,7 +994,7 @@ def send(message, **kwargs): namespace = flask.request.namespace callback = kwargs.get('callback') broadcast = kwargs.get('broadcast') - to = kwargs.pop('to', kwargs.pop('room', None)) + to = kwargs.pop('to', None) or kwargs.pop('room', None) if to is None and not broadcast: to = flask.request.sid include_self = kwargs.get('include_self', True) @@ -910,7 +1019,7 @@ def join_room(room, sid=None, namespace=None): username = session['username'] room = data['room'] join_room(room) - send(username + ' has entered the room.', room=room) + send(username + ' has entered the room.', to=room) :param room: The name of the room to join. :param sid: The session id of the client. If not provided, the client is @@ -935,7 +1044,7 @@ def leave_room(room, sid=None, namespace=None): username = session['username'] room = data['room'] leave_room(room) - send(username + ' has left the room.', room=room) + send(username + ' has left the room.', to=room) :param room: The name of the room to leave. :param sid: The session id of the client. If not provided, the client is diff --git a/libs/flask_socketio/test_client.py b/libs/flask_socketio/test_client.py index f0f5b8f8b..6ae6a9b34 100644 --- a/libs/flask_socketio/test_client.py +++ b/libs/flask_socketio/test_client.py @@ -24,8 +24,7 @@ class SocketIOTestClient(object): cookies set in HTTP routes accessible from Socket.IO events. """ - queue = {} - acks = {} + clients = {} def __init__(self, app, socketio, namespace=None, query_string=None, headers=None, auth=None, flask_test_client=None): @@ -38,35 +37,37 @@ class SocketIOTestClient(object): pkt = packet.Packet(encoded_packet=epkt[0]) for att in epkt[1:]: pkt.add_attachment(att) + client = self.clients.get(eio_sid) + if not client: + return if pkt.packet_type == packet.EVENT or \ pkt.packet_type == packet.BINARY_EVENT: - if eio_sid not in self.queue: - self.queue[eio_sid] = [] if pkt.data[0] == 'message' or pkt.data[0] == 'json': - self.queue[eio_sid].append({ + client.queue.append({ 'name': pkt.data[0], 'args': pkt.data[1], 'namespace': pkt.namespace or '/'}) else: - self.queue[eio_sid].append({ + client.queue.append({ 'name': pkt.data[0], 'args': pkt.data[1:], 'namespace': pkt.namespace or '/'}) elif pkt.packet_type == packet.ACK or \ pkt.packet_type == packet.BINARY_ACK: - self.acks[eio_sid] = {'args': pkt.data, - 'namespace': pkt.namespace or '/'} + client.acks = {'args': pkt.data, + 'namespace': pkt.namespace or '/'} elif pkt.packet_type in [packet.DISCONNECT, packet.CONNECT_ERROR]: - self.connected[pkt.namespace or '/'] = False + client.connected[pkt.namespace or '/'] = False self.app = app self.flask_test_client = flask_test_client self.eio_sid = uuid.uuid4().hex - self.acks[self.eio_sid] = None - self.queue[self.eio_sid] = [] + self.clients[self.eio_sid] = self self.callback_counter = 0 self.socketio = socketio self.connected = {} + self.queue = [] + self.acks = None socketio.server._send_packet = _mock_send_packet socketio.server.environ[self.eio_sid] = {} socketio.server.async_handlers = False # easier to test when @@ -116,9 +117,7 @@ class SocketIOTestClient(object): self.flask_test_client.cookie_jar.inject_wsgi(environ) self.socketio.server._handle_eio_connect(self.eio_sid, environ) pkt = packet.Packet(packet.CONNECT, auth, namespace=namespace) - with self.app.app_context(): - self.socketio.server._handle_eio_message(self.eio_sid, - pkt.encode()) + self.socketio.server._handle_eio_message(self.eio_sid, pkt.encode()) sid = self.socketio.server.manager.sid_from_eio_sid(self.eio_sid, namespace) if sid: @@ -133,9 +132,7 @@ class SocketIOTestClient(object): if not self.is_connected(namespace): raise RuntimeError('not connected') pkt = packet.Packet(packet.DISCONNECT, namespace=namespace) - with self.app.app_context(): - self.socketio.server._handle_eio_message(self.eio_sid, - pkt.encode()) + self.socketio.server._handle_eio_message(self.eio_sid, pkt.encode()) del self.connected[namespace or '/'] def emit(self, event, *args, **kwargs): @@ -163,17 +160,15 @@ class SocketIOTestClient(object): id = self.callback_counter pkt = packet.Packet(packet.EVENT, data=[event] + list(args), namespace=namespace, id=id) - with self.app.app_context(): - encoded_pkt = pkt.encode() - if isinstance(encoded_pkt, list): - for epkt in encoded_pkt: - self.socketio.server._handle_eio_message(self.eio_sid, - epkt) - else: - self.socketio.server._handle_eio_message(self.eio_sid, - encoded_pkt) - ack = self.acks.pop(self.eio_sid, None) - if ack is not None: + encoded_pkt = pkt.encode() + if isinstance(encoded_pkt, list): + for epkt in encoded_pkt: + self.socketio.server._handle_eio_message(self.eio_sid, epkt) + else: + self.socketio.server._handle_eio_message(self.eio_sid, encoded_pkt) + if self.acks is not None: + ack = self.acks + self.acks = None return ack['args'][0] if len(ack['args']) == 1 \ else ack['args'] @@ -213,8 +208,6 @@ class SocketIOTestClient(object): if not self.is_connected(namespace): raise RuntimeError('not connected') namespace = namespace or '/' - r = [pkt for pkt in self.queue[self.eio_sid] - if pkt['namespace'] == namespace] - self.queue[self.eio_sid] = [ - pkt for pkt in self.queue[self.eio_sid] if pkt not in r] + r = [pkt for pkt in self.queue if pkt['namespace'] == namespace] + self.queue = [pkt for pkt in self.queue if pkt not in r] return r |