aboutsummaryrefslogtreecommitdiffhomepage
path: root/libs/flask_socketio
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2022-11-07 13:06:49 -0500
committermorpheus65535 <[email protected]>2022-11-07 13:08:27 -0500
commitbbe2483e21c2c1549ceeed16f021f9581b899f70 (patch)
treebcc2bef2f55789ec6e6c64809c07fb4f4d3d9c86 /libs/flask_socketio
parent708fbfcd8ec0620647975be39a1f6acbbf08f767 (diff)
downloadbazarr-bbe2483e21c2c1549ceeed16f021f9581b899f70.tar.gz
bazarr-bbe2483e21c2c1549ceeed16f021f9581b899f70.zip
Updated vendored dependencies.
Diffstat (limited to 'libs/flask_socketio')
-rw-r--r--libs/flask_socketio/__init__.py157
-rw-r--r--libs/flask_socketio/test_client.py57
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