Browse Source

- Added ignore_interceptor decorator and namespace attribute.

- Fixed list indexing bug.
- Updated docs.
pull/45/head
Robert Schindler 9 years ago
parent
commit
10ef1e5ed5
  1. 32
      docs/index.rst
  2. 5
      socketio/__init__.py
  3. 1
      socketio/namespace.py
  4. 12
      socketio/server.py
  5. 18
      socketio/util.py
  6. 34
      tests/test_server.py

32
docs/index.rst

@ -252,6 +252,9 @@ instance includes versions of several of the methods in the
:class:`socketio.Server` class that default to the proper namespace when the
``namespace`` argument is not given.
Note: if an event has a handler in a class-based namespace, and also a
decorator-based function handler, the standalone function handler is invoked.
Event handler interceptors
--------------------------
@ -307,9 +310,9 @@ Example:
::
from socketio import interceptor, server
import socketio
class MyInterceptor(interceptor.Interceptor):
class MyInterceptor(socketio.Interceptor):
def ignore_for(self, event, namespace):
# This interceptor won't be applied to connect and disconnect
# event handlers.
@ -326,7 +329,7 @@ Example:
def after_event(self, event, namespace, args):
args[0] = "I faked the response!"
sio = socketio.server.Server()
sio = socketio.Server()
@sio.on("/foo")
def foo(sid, data):
@ -342,7 +345,10 @@ handlers.
Interceptors can be added to a ``Namespace`` object as well by inserting
them into its ``interceptors`` ``list`` attribute. They are applied
after the server-wide interceptors to every event handler defined in that
``Namespace`` object.
``Namespace`` object. By adding interceptors to a namespace's
``ignore_middleware`` ``list`` attribute, you can remove interceptors
that have been added server-wide for that particular namespace. The
order in wich interceptors are added to the ignore list doesn't matter.
There is also a decorator available to add a interceptor to a specific
handler only. Given the interceptor class from above, it would be used
@ -350,30 +356,28 @@ as follows:
::
from socketio import util
sio = socketio.server.Server()
@sio.on("/foo")
@util.apply_interceptor(MyInterceptor)
@socketio.apply_interceptor(MyInterceptor)
def foo(sid, data):
print("foo executed")
return "foo executed"
Middlewares added by the decorator are treated as if they were added
Interceptors added by the decorator are treated as if they were added
*after* the server-wide and namespace-wide interceptors. Naturally,
decorators are applied from bottom to top. Hence the following will
first add ``MW1`` and then ``MW2``.
::
@util.apply_interceptor(MW2)
@util.apply_interceptor(MW1)
@socketio.apply_interceptor(MW2)
@socketio.apply_interceptor(MW1)
def foo(sid):
# ...
Note: if an event has a handler in a class-based namespace, and also a
decorator-based function handler, the standalone function handler is invoked.
A ``socketio.ignore_middleware`` decorator is available as well. By
decorating an event handler with it, you can remove interceptors that
have been added server-wide or namespace-wide for that particular event
handler. The order this decorator is applied in doesn't matter.
Using a Message Queue
---------------------

5
socketio/__init__.py

@ -6,7 +6,8 @@ from .pubsub_manager import PubSubManager
from .kombu_manager import KombuManager
from .redis_manager import RedisManager
from .server import Server
from .util import apply_interceptor
from .util import apply_interceptor, ignore_interceptor
__all__ = [Interceptor, Middleware, Namespace, Server, BaseManager,
PubSubManager, KombuManager, RedisManager, apply_interceptor]
PubSubManager, KombuManager, RedisManager,
apply_interceptor, ignore_interceptor]

1
socketio/namespace.py

@ -14,6 +14,7 @@ class Namespace(object):
self.name = namespace or '/'
self.server = None
self.interceptors = []
self.ignore_interceptors = []
def _set_server(self, server):
self.server = server

12
socketio/server.py

@ -452,9 +452,17 @@ class Server(object):
handler = self.handlers[namespace][event]
elif namespace in self.namespace_handlers:
ns = self.namespace_handlers[namespace]
interceptors.extend(ns.interceptors)
handler = ns._get_event_handler(event)
if handler is not None:
for interceptor in ns.ignore_interceptors:
while interceptor in interceptors:
interceptors.remove(interceptor)
interceptors.extend(ns.interceptors)
if handler is not None:
for interceptor in getattr(
handler, '_sio_ignore_interceptors', []):
while interceptor in interceptors:
interceptors.remove(interceptor)
interceptors.extend(getattr(handler, '_sio_interceptors', []))
handler = self._apply_interceptors(interceptors, event, namespace,
handler)
@ -504,11 +512,11 @@ class Server(object):
try:
# Apply before_event methods.
for interceptor in _interceptors:
interceptors_processed += 1
result = interceptor.before_event(
event, namespace, args)
if result is not None:
break
interceptors_processed += 1
if result is None:
# call the real event handler
result = handler(*args)

18
socketio/util.py

@ -14,3 +14,21 @@ def apply_interceptor(interceptor):
handler._sio_interceptors.append(interceptor)
return handler
return wrapper
def ignore_interceptor(interceptor):
"""Returns a decorator for event handlers that ignores the given
interceptor for the handler decorated with it.
:param interceptor: The interceptor to ignore
Ensure that you only add well-behaving decorators after this one
(meaning such that preserve attributes) because you'll loose them
otherwise.
"""
def wrapper(handler):
if not hasattr(handler, '_sio_ignore_interceptors'):
handler._sio_ignore_interceptors = []
handler._sio_ignore_interceptors.append(interceptor)
return handler
return wrapper

34
tests/test_server.py

@ -62,7 +62,6 @@ class TestServer(unittest.TestCase):
mw2 = MW(s)
mw3 = MW(s)
mw4 = MW(s)
mw4.ignore_for = mock.MagicMock(side_effect=[True] + 100 * [False])
mw4.before_event = mock.MagicMock(side_effect=['x'] + 100 * [None])
mw4.after_event = mock.MagicMock(side_effect=['x'] + 100 * [None])
@ -72,6 +71,7 @@ class TestServer(unittest.TestCase):
@namespace.Namespace.event_name('foo bar')
@util.apply_interceptor(mw4)
@util.ignore_interceptor(mw3)
def some_name(self, sid):
pass
@ -97,37 +97,39 @@ class TestServer(unittest.TestCase):
self.assertEqual(mw4.before_event.call_count, 0)
self.assertEqual(mw4.after_event.call_count, 0)
# only mw1 and mw3 should run completely, mw4 is enabled but ignored
# only mw1 and mw4 should run, mw3 is ignored and mw4
# before_event returns "x" and starts after_event processing.
s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 2)
self.assertEqual(mw1.after_event.call_count, 2)
self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 2)
self.assertEqual(mw3.after_event.call_count, 2)
self.assertEqual(mw4.before_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 1)
self.assertEqual(mw4.after_event.call_count, 0)
# again, this time mw4 before + after should be triggered as well
# again, this time mw4 after should be triggered as well and abort
# ing "x"
s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 3)
self.assertEqual(mw1.after_event.call_count, 2)
self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 3)
self.assertEqual(mw3.after_event.call_count, 2)
self.assertEqual(mw4.before_event.call_count, 1)
self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw4.after_event.call_count, 1)
# again, this time mw4 before + after_event should be triggered
# again, this time mw1 and mw4 should run completely
s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 4)
self.assertEqual(mw1.after_event.call_count, 3)
self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 4)
self.assertEqual(mw3.after_event.call_count, 3)
self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 3)
self.assertEqual(mw4.after_event.call_count, 2)
# only mw1 and mw2 should run completely
@ -136,9 +138,9 @@ class TestServer(unittest.TestCase):
self.assertEqual(mw1.after_event.call_count, 4)
self.assertEqual(mw2.before_event.call_count, 1)
self.assertEqual(mw2.after_event.call_count, 1)
self.assertEqual(mw3.before_event.call_count, 4)
self.assertEqual(mw3.after_event.call_count, 3)
self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 3)
self.assertEqual(mw4.after_event.call_count, 2)
def test_on_bad_event_name(self, eio):

Loading…
Cancel
Save