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 :class:`socketio.Server` class that default to the proper namespace when the
``namespace`` argument is not given. ``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 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): def ignore_for(self, event, namespace):
# This interceptor won't be applied to connect and disconnect # This interceptor won't be applied to connect and disconnect
# event handlers. # event handlers.
@ -326,7 +329,7 @@ Example:
def after_event(self, event, namespace, args): def after_event(self, event, namespace, args):
args[0] = "I faked the response!" args[0] = "I faked the response!"
sio = socketio.server.Server() sio = socketio.Server()
@sio.on("/foo") @sio.on("/foo")
def foo(sid, data): def foo(sid, data):
@ -342,7 +345,10 @@ handlers.
Interceptors can be added to a ``Namespace`` object as well by inserting Interceptors can be added to a ``Namespace`` object as well by inserting
them into its ``interceptors`` ``list`` attribute. They are applied them into its ``interceptors`` ``list`` attribute. They are applied
after the server-wide interceptors to every event handler defined in that 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 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 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") @sio.on("/foo")
@util.apply_interceptor(MyInterceptor) @socketio.apply_interceptor(MyInterceptor)
def foo(sid, data): def foo(sid, data):
print("foo executed") print("foo executed")
return "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, *after* the server-wide and namespace-wide interceptors. Naturally,
decorators are applied from bottom to top. Hence the following will decorators are applied from bottom to top. Hence the following will
first add ``MW1`` and then ``MW2``. first add ``MW1`` and then ``MW2``.
:: ::
@util.apply_interceptor(MW2) @socketio.apply_interceptor(MW2)
@util.apply_interceptor(MW1) @socketio.apply_interceptor(MW1)
def foo(sid): def foo(sid):
# ... # ...
Note: if an event has a handler in a class-based namespace, and also a A ``socketio.ignore_middleware`` decorator is available as well. By
decorator-based function handler, the standalone function handler is invoked. 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 Using a Message Queue
--------------------- ---------------------

5
socketio/__init__.py

@ -6,7 +6,8 @@ from .pubsub_manager import PubSubManager
from .kombu_manager import KombuManager from .kombu_manager import KombuManager
from .redis_manager import RedisManager from .redis_manager import RedisManager
from .server import Server from .server import Server
from .util import apply_interceptor from .util import apply_interceptor, ignore_interceptor
__all__ = [Interceptor, Middleware, Namespace, Server, BaseManager, __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.name = namespace or '/'
self.server = None self.server = None
self.interceptors = [] self.interceptors = []
self.ignore_interceptors = []
def _set_server(self, server): def _set_server(self, server):
self.server = server self.server = server

12
socketio/server.py

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

18
socketio/util.py

@ -14,3 +14,21 @@ def apply_interceptor(interceptor):
handler._sio_interceptors.append(interceptor) handler._sio_interceptors.append(interceptor)
return handler return handler
return wrapper 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) mw2 = MW(s)
mw3 = MW(s) mw3 = MW(s)
mw4 = 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.before_event = mock.MagicMock(side_effect=['x'] + 100 * [None])
mw4.after_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') @namespace.Namespace.event_name('foo bar')
@util.apply_interceptor(mw4) @util.apply_interceptor(mw4)
@util.ignore_interceptor(mw3)
def some_name(self, sid): def some_name(self, sid):
pass pass
@ -97,37 +97,39 @@ class TestServer(unittest.TestCase):
self.assertEqual(mw4.before_event.call_count, 0) self.assertEqual(mw4.before_event.call_count, 0)
self.assertEqual(mw4.after_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') s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 2) self.assertEqual(mw1.before_event.call_count, 2)
self.assertEqual(mw1.after_event.call_count, 2) self.assertEqual(mw1.after_event.call_count, 2)
self.assertEqual(mw2.before_event.call_count, 0) self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0) self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 2) self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 2) self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 0) self.assertEqual(mw4.before_event.call_count, 1)
self.assertEqual(mw4.after_event.call_count, 0) 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') s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 3) self.assertEqual(mw1.before_event.call_count, 3)
self.assertEqual(mw1.after_event.call_count, 2) self.assertEqual(mw1.after_event.call_count, 2)
self.assertEqual(mw2.before_event.call_count, 0) self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0) self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 3) self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 2) self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 1) self.assertEqual(mw4.before_event.call_count, 2)
self.assertEqual(mw4.after_event.call_count, 1) 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') s._trigger_event('foo bar', '/ns', '123')
self.assertEqual(mw1.before_event.call_count, 4) self.assertEqual(mw1.before_event.call_count, 4)
self.assertEqual(mw1.after_event.call_count, 3) self.assertEqual(mw1.after_event.call_count, 3)
self.assertEqual(mw2.before_event.call_count, 0) self.assertEqual(mw2.before_event.call_count, 0)
self.assertEqual(mw2.after_event.call_count, 0) self.assertEqual(mw2.after_event.call_count, 0)
self.assertEqual(mw3.before_event.call_count, 4) self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 3) self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 2) self.assertEqual(mw4.before_event.call_count, 3)
self.assertEqual(mw4.after_event.call_count, 2) self.assertEqual(mw4.after_event.call_count, 2)
# only mw1 and mw2 should run completely # 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(mw1.after_event.call_count, 4)
self.assertEqual(mw2.before_event.call_count, 1) self.assertEqual(mw2.before_event.call_count, 1)
self.assertEqual(mw2.after_event.call_count, 1) self.assertEqual(mw2.after_event.call_count, 1)
self.assertEqual(mw3.before_event.call_count, 4) self.assertEqual(mw3.before_event.call_count, 1)
self.assertEqual(mw3.after_event.call_count, 3) self.assertEqual(mw3.after_event.call_count, 1)
self.assertEqual(mw4.before_event.call_count, 2) self.assertEqual(mw4.before_event.call_count, 3)
self.assertEqual(mw4.after_event.call_count, 2) self.assertEqual(mw4.after_event.call_count, 2)
def test_on_bad_event_name(self, eio): def test_on_bad_event_name(self, eio):

Loading…
Cancel
Save