Merge "callback: priority to specify calling order"
This commit is contained in:
commit
f46d20bc7d
|
@ -11,12 +11,14 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
import itertools
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import reflection
|
from oslo_utils import reflection
|
||||||
|
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
from neutron_lib.callbacks import exceptions
|
from neutron_lib.callbacks import exceptions
|
||||||
|
from neutron_lib.callbacks import priority_group
|
||||||
from neutron_lib.db import utils as db_utils
|
from neutron_lib.db import utils as db_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -28,7 +30,8 @@ class CallbacksManager(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
def subscribe(self, callback, resource, event):
|
def subscribe(self, callback, resource, event,
|
||||||
|
priority=priority_group.PRIORITY_DEFAULT):
|
||||||
"""Subscribe callback for a resource event.
|
"""Subscribe callback for a resource event.
|
||||||
|
|
||||||
The same callback may register for more than one event.
|
The same callback may register for more than one event.
|
||||||
|
@ -36,23 +39,40 @@ class CallbacksManager(object):
|
||||||
:param callback: the callback. It must raise or return a boolean.
|
:param callback: the callback. It must raise or return a boolean.
|
||||||
:param resource: the resource. It must be a valid resource.
|
:param resource: the resource. It must be a valid resource.
|
||||||
:param event: the event. It must be a valid event.
|
:param event: the event. It must be a valid event.
|
||||||
|
:param priority: the priority. Callbacks are sorted by priority
|
||||||
|
to be called. Smaller one is called earlier.
|
||||||
"""
|
"""
|
||||||
LOG.debug("Subscribe: %(callback)s %(resource)s %(event)s",
|
LOG.debug("Subscribe: %(callback)s %(resource)s %(event)s "
|
||||||
{'callback': callback, 'resource': resource, 'event': event})
|
"%(priority)d",
|
||||||
|
{'callback': callback, 'resource': resource, 'event': event,
|
||||||
|
'priority': priority})
|
||||||
|
|
||||||
callback_id = _get_id(callback)
|
callback_id = _get_id(callback)
|
||||||
try:
|
callbacks_list = self._callbacks[resource].setdefault(event, [])
|
||||||
self._callbacks[resource][event][callback_id] = callback
|
for pc_pair in callbacks_list:
|
||||||
except KeyError:
|
if pc_pair[0] == priority:
|
||||||
# Initialize the registry for unknown resources and/or events
|
pri_callbacks = pc_pair[1]
|
||||||
# prior to enlisting the callback.
|
break
|
||||||
self._callbacks[resource][event] = {}
|
else:
|
||||||
self._callbacks[resource][event][callback_id] = callback
|
pri_callbacks = {}
|
||||||
|
callbacks_list.append((priority, pri_callbacks))
|
||||||
|
callbacks_list.sort(key=lambda x: x[0])
|
||||||
|
pri_callbacks[callback_id] = callback
|
||||||
|
|
||||||
# We keep a copy of callbacks to speed the unsubscribe operation.
|
# We keep a copy of callbacks to speed the unsubscribe operation.
|
||||||
if callback_id not in self._index:
|
if callback_id not in self._index:
|
||||||
self._index[callback_id] = collections.defaultdict(set)
|
self._index[callback_id] = collections.defaultdict(set)
|
||||||
self._index[callback_id][resource].add(event)
|
self._index[callback_id][resource].add(event)
|
||||||
|
|
||||||
|
def _del_callback(self, callbacks_list, callback_id):
|
||||||
|
for pc_pair in callbacks_list:
|
||||||
|
pri_callbacks = pc_pair[1]
|
||||||
|
if callback_id in pri_callbacks:
|
||||||
|
del pri_callbacks[callback_id]
|
||||||
|
if not pri_callbacks:
|
||||||
|
callbacks_list.remove(pc_pair)
|
||||||
|
break
|
||||||
|
|
||||||
def unsubscribe(self, callback, resource, event):
|
def unsubscribe(self, callback, resource, event):
|
||||||
"""Unsubscribe callback from the registry.
|
"""Unsubscribe callback from the registry.
|
||||||
|
|
||||||
|
@ -68,7 +88,7 @@ class CallbacksManager(object):
|
||||||
LOG.debug("Callback %s not found", callback_id)
|
LOG.debug("Callback %s not found", callback_id)
|
||||||
return
|
return
|
||||||
if resource and event:
|
if resource and event:
|
||||||
del self._callbacks[resource][event][callback_id]
|
self._del_callback(self._callbacks[resource][event], callback_id)
|
||||||
self._index[callback_id][resource].discard(event)
|
self._index[callback_id][resource].discard(event)
|
||||||
if not self._index[callback_id][resource]:
|
if not self._index[callback_id][resource]:
|
||||||
del self._index[callback_id][resource]
|
del self._index[callback_id][resource]
|
||||||
|
@ -88,7 +108,8 @@ class CallbacksManager(object):
|
||||||
if callback_id:
|
if callback_id:
|
||||||
if resource in self._index[callback_id]:
|
if resource in self._index[callback_id]:
|
||||||
for event in self._index[callback_id][resource]:
|
for event in self._index[callback_id][resource]:
|
||||||
del self._callbacks[resource][event][callback_id]
|
self._del_callback(self._callbacks[resource][event],
|
||||||
|
callback_id)
|
||||||
del self._index[callback_id][resource]
|
del self._index[callback_id][resource]
|
||||||
if not self._index[callback_id]:
|
if not self._index[callback_id]:
|
||||||
del self._index[callback_id]
|
del self._index[callback_id]
|
||||||
|
@ -103,7 +124,8 @@ class CallbacksManager(object):
|
||||||
if callback_id:
|
if callback_id:
|
||||||
for resource, resource_events in self._index[callback_id].items():
|
for resource, resource_events in self._index[callback_id].items():
|
||||||
for event in resource_events:
|
for event in resource_events:
|
||||||
del self._callbacks[resource][event][callback_id]
|
self._del_callback(self._callbacks[resource][event],
|
||||||
|
callback_id)
|
||||||
del self._index[callback_id]
|
del self._index[callback_id]
|
||||||
|
|
||||||
def publish(self, resource, event, trigger, payload=None):
|
def publish(self, resource, event, trigger, payload=None):
|
||||||
|
@ -162,7 +184,11 @@ class CallbacksManager(object):
|
||||||
def _notify_loop(self, resource, event, trigger, **kwargs):
|
def _notify_loop(self, resource, event, trigger, **kwargs):
|
||||||
"""The notification loop."""
|
"""The notification loop."""
|
||||||
errors = []
|
errors = []
|
||||||
callbacks = list(self._callbacks[resource].get(event, {}).items())
|
# NOTE(yamahata): Since callback may unsubscribe it,
|
||||||
|
# convert iterator to list to avoid runtime error.
|
||||||
|
callbacks = list(itertools.chain(
|
||||||
|
*[pri_callbacks.items() for (priority, pri_callbacks)
|
||||||
|
in self._callbacks[resource].get(event, [])]))
|
||||||
LOG.debug("Notify callbacks %s for %s, %s",
|
LOG.debug("Notify callbacks %s for %s, %s",
|
||||||
[c[0] for c in callbacks], resource, event)
|
[c[0] for c in callbacks], resource, event)
|
||||||
# TODO(armax): consider using a GreenPile
|
# TODO(armax): consider using a GreenPile
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Copyright (c) 2018 Intel Corporation.
|
||||||
|
# Copyright (c) 2018 Isaku Yamahata <isaku.yamahata at intel com>
|
||||||
|
# <isaku.yamahata at gmail com>
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(yamahata):smallest one is called first.
|
||||||
|
# we are using a big enough number for default that can
|
||||||
|
# be manipulated to increase or decrease the priorities.
|
||||||
|
# for all the callbacks, to reduce the priority division
|
||||||
|
# can be used for reducing that number for higher priority.
|
||||||
|
# each resources would want to define their own symbolic values for their use.
|
||||||
|
|
||||||
|
PRIORITY_DEFAULT = 55550000
|
||||||
|
|
||||||
|
|
||||||
|
# For l3 plugin and flavor
|
||||||
|
PRIORITY_ROUTER_EXTENDED_ATTRIBUTE = PRIORITY_DEFAULT - 100
|
||||||
|
# DEFAULT is reserved for third party which may not know priority yet
|
||||||
|
PRIORITY_ROUTER_DEFAULT = PRIORITY_DEFAULT
|
||||||
|
PRIORITY_ROUTER_CONTROLLER = PRIORITY_DEFAULT + 100
|
||||||
|
PRIORITY_ROUTER_DRIVER = PRIORITY_DEFAULT + 200
|
|
@ -14,6 +14,7 @@ import collections
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
from neutron_lib.callbacks import manager
|
from neutron_lib.callbacks import manager
|
||||||
|
from neutron_lib.callbacks import priority_group
|
||||||
|
|
||||||
|
|
||||||
# TODO(armax): consider adding locking
|
# TODO(armax): consider adding locking
|
||||||
|
@ -31,8 +32,9 @@ def _get_callback_manager():
|
||||||
return _CALLBACK_MANAGER
|
return _CALLBACK_MANAGER
|
||||||
|
|
||||||
|
|
||||||
def subscribe(callback, resource, event):
|
def subscribe(callback, resource, event,
|
||||||
_get_callback_manager().subscribe(callback, resource, event)
|
priority=priority_group.PRIORITY_DEFAULT):
|
||||||
|
_get_callback_manager().subscribe(callback, resource, event, priority)
|
||||||
|
|
||||||
|
|
||||||
def unsubscribe(callback, resource, event):
|
def unsubscribe(callback, resource, event):
|
||||||
|
@ -61,7 +63,7 @@ def clear():
|
||||||
_get_callback_manager().clear()
|
_get_callback_manager().clear()
|
||||||
|
|
||||||
|
|
||||||
def receives(resource, events):
|
def receives(resource, events, priority=priority_group.PRIORITY_DEFAULT):
|
||||||
"""Use to decorate methods on classes before initialization.
|
"""Use to decorate methods on classes before initialization.
|
||||||
|
|
||||||
Any classes that use this must themselves be decorated with the
|
Any classes that use this must themselves be decorated with the
|
||||||
|
@ -72,7 +74,7 @@ def receives(resource, events):
|
||||||
|
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
for e in events:
|
for e in events:
|
||||||
_REGISTERED_CLASS_METHODS[f].append((resource, e))
|
_REGISTERED_CLASS_METHODS[f].append((resource, e, priority))
|
||||||
return f
|
return f
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
@ -112,9 +114,9 @@ def has_registry_receivers(klass):
|
||||||
func = getattr(unbound_method, 'im_func', unbound_method)
|
func = getattr(unbound_method, 'im_func', unbound_method)
|
||||||
if func not in _REGISTERED_CLASS_METHODS:
|
if func not in _REGISTERED_CLASS_METHODS:
|
||||||
continue
|
continue
|
||||||
for resource, event in _REGISTERED_CLASS_METHODS[func]:
|
for resource, event, priority in _REGISTERED_CLASS_METHODS[func]:
|
||||||
# subscribe the bound method
|
# subscribe the bound method
|
||||||
subscribe(getattr(instance, name), resource, event)
|
subscribe(getattr(instance, name), resource, event, priority)
|
||||||
setattr(instance, '_DECORATED_METHODS_SUBSCRIBED', True)
|
setattr(instance, '_DECORATED_METHODS_SUBSCRIBED', True)
|
||||||
return instance
|
return instance
|
||||||
klass.__new__ = replacement_new
|
klass.__new__ = replacement_new
|
||||||
|
|
|
@ -20,9 +20,15 @@ from oslotest import base
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
from neutron_lib.callbacks import exceptions
|
from neutron_lib.callbacks import exceptions
|
||||||
from neutron_lib.callbacks import manager
|
from neutron_lib.callbacks import manager
|
||||||
|
from neutron_lib.callbacks import priority_group
|
||||||
from neutron_lib.callbacks import resources
|
from neutron_lib.callbacks import resources
|
||||||
|
|
||||||
|
|
||||||
|
PRI_HIGH = 0
|
||||||
|
PRI_MED = 5000
|
||||||
|
PRI_LOW = 10000
|
||||||
|
|
||||||
|
|
||||||
class ObjectWithCallback(object):
|
class ObjectWithCallback(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -101,8 +107,12 @@ class CallBacksManagerTestCase(base.BaseTestCase):
|
||||||
callback_2, resources.PORT, events.BEFORE_CREATE)
|
callback_2, resources.PORT, events.BEFORE_CREATE)
|
||||||
self.assertEqual(2, len(self.manager._index))
|
self.assertEqual(2, len(self.manager._index))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
2,
|
1,
|
||||||
len(self.manager._callbacks[resources.PORT][events.BEFORE_CREATE]))
|
len(self.manager._callbacks[resources.PORT][events.BEFORE_CREATE]))
|
||||||
|
self.assertEqual(
|
||||||
|
2,
|
||||||
|
len(self.manager._callbacks
|
||||||
|
[resources.PORT][events.BEFORE_CREATE][0][1]))
|
||||||
|
|
||||||
def test_unsubscribe_during_iteration(self):
|
def test_unsubscribe_during_iteration(self):
|
||||||
unsub = lambda r, e, *a, **k: self.manager.unsubscribe(unsub, r, e)
|
unsub = lambda r, e, *a, **k: self.manager.unsubscribe(unsub, r, e)
|
||||||
|
@ -157,12 +167,17 @@ class CallBacksManagerTestCase(base.BaseTestCase):
|
||||||
self.manager.subscribe(
|
self.manager.subscribe(
|
||||||
callback_2, resources.PORT, events.BEFORE_DELETE)
|
callback_2, resources.PORT, events.BEFORE_DELETE)
|
||||||
self.manager.unsubscribe_by_resource(callback_1, resources.PORT)
|
self.manager.unsubscribe_by_resource(callback_1, resources.PORT)
|
||||||
self.assertNotIn(
|
self.assertEqual(
|
||||||
callback_id_1,
|
0,
|
||||||
self.manager._callbacks[resources.PORT][events.BEFORE_CREATE])
|
len(self.manager._callbacks
|
||||||
|
[resources.PORT][events.BEFORE_CREATE]))
|
||||||
|
self.assertEqual(
|
||||||
|
1,
|
||||||
|
len(self.manager._callbacks[resources.PORT][events.BEFORE_DELETE]))
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
callback_id_2,
|
callback_id_2,
|
||||||
self.manager._callbacks[resources.PORT][events.BEFORE_DELETE])
|
self.manager._callbacks
|
||||||
|
[resources.PORT][events.BEFORE_DELETE][0][1])
|
||||||
self.assertNotIn(callback_id_1, self.manager._index)
|
self.assertNotIn(callback_id_1, self.manager._index)
|
||||||
|
|
||||||
def test_unsubscribe_all(self):
|
def test_unsubscribe_all(self):
|
||||||
|
@ -266,6 +281,52 @@ class CallBacksManagerTestCase(base.BaseTestCase):
|
||||||
self.assertEqual(0, len(self.manager._callbacks))
|
self.assertEqual(0, len(self.manager._callbacks))
|
||||||
self.assertEqual(0, len(self.manager._index))
|
self.assertEqual(0, len(self.manager._index))
|
||||||
|
|
||||||
|
def test_callback_priority(self):
|
||||||
|
pri_first = priority_group.PRIORITY_DEFAULT - 100
|
||||||
|
pri_last = priority_group.PRIORITY_DEFAULT + 100
|
||||||
|
# lowest priority value should be first in the _callbacks
|
||||||
|
self.manager.subscribe(callback_1, 'my-resource', 'my-event')
|
||||||
|
self.manager.subscribe(callback_2, 'my-resource',
|
||||||
|
'my-event', pri_last)
|
||||||
|
self.manager.subscribe(callback_3, 'my-resource',
|
||||||
|
'my-event', pri_first)
|
||||||
|
callbacks = self.manager._callbacks['my-resource']['my-event']
|
||||||
|
# callbacks should be sorted based on priority for resource and event
|
||||||
|
self.assertEqual(3, len(callbacks))
|
||||||
|
self.assertEqual(pri_first, callbacks[0][0])
|
||||||
|
self.assertEqual(priority_group.PRIORITY_DEFAULT, callbacks[1][0])
|
||||||
|
self.assertEqual(pri_last, callbacks[2][0])
|
||||||
|
|
||||||
|
@mock.patch('neutron_lib.callbacks.manager.CallbacksManager._del_callback')
|
||||||
|
def test_del_callback_called_on_unsubscribe(self, mock_cb):
|
||||||
|
self.manager.subscribe(callback_1, 'my-resource', 'my-event')
|
||||||
|
callback_id = self.manager._find(callback_1)
|
||||||
|
callbacks = self.manager._callbacks['my-resource']['my-event']
|
||||||
|
self.assertEqual(1, len(callbacks))
|
||||||
|
self.manager.unsubscribe(callback_1, 'my-resource', 'my-event')
|
||||||
|
mock_cb.assert_called_once_with(callbacks, callback_id)
|
||||||
|
|
||||||
|
@mock.patch("neutron_lib.callbacks.manager.LOG")
|
||||||
|
def test_callback_order(self, _logger):
|
||||||
|
self.manager.subscribe(callback_1, 'my-resource', 'my-event', PRI_MED)
|
||||||
|
self.manager.subscribe(callback_2, 'my-resource', 'my-event', PRI_HIGH)
|
||||||
|
self.manager.subscribe(callback_3, 'my-resource', 'my-event', PRI_LOW)
|
||||||
|
self.assertEqual(
|
||||||
|
3, len(self.manager._callbacks['my-resource']['my-event']))
|
||||||
|
self.manager.unsubscribe(callback_3, 'my-resource', 'my-event')
|
||||||
|
self.manager.notify('my-resource', 'my-event', mock.ANY)
|
||||||
|
# callback_3 should be deleted and not executed
|
||||||
|
self.assertEqual(
|
||||||
|
2, len(self.manager._callbacks['my-resource']['my-event']))
|
||||||
|
self.assertEqual(0, callback_3.counter)
|
||||||
|
# executed callbacks should have counter incremented
|
||||||
|
self.assertEqual(1, callback_2.counter)
|
||||||
|
self.assertEqual(1, callback_1.counter)
|
||||||
|
callback_ids = _logger.debug.mock_calls[4][1][1]
|
||||||
|
# callback_2 should be first in exceution as it has higher priority
|
||||||
|
self.assertEqual(callback_id_2, callback_ids[0])
|
||||||
|
self.assertEqual(callback_id_1, callback_ids[1])
|
||||||
|
|
||||||
@mock.patch("neutron_lib.callbacks.manager.LOG")
|
@mock.patch("neutron_lib.callbacks.manager.LOG")
|
||||||
def test__notify_loop_skip_log_errors(self, _logger):
|
def test__notify_loop_skip_log_errors(self, _logger):
|
||||||
self.manager.subscribe(
|
self.manager.subscribe(
|
||||||
|
|
|
@ -18,10 +18,13 @@ import testtools
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
|
from neutron_lib.callbacks import priority_group
|
||||||
from neutron_lib.callbacks import registry
|
from neutron_lib.callbacks import registry
|
||||||
from neutron_lib.callbacks import resources
|
from neutron_lib.callbacks import resources
|
||||||
from neutron_lib import fixture
|
from neutron_lib import fixture
|
||||||
|
|
||||||
|
PRI_CALLBACK = 20
|
||||||
|
|
||||||
|
|
||||||
@registry.has_registry_receivers
|
@registry.has_registry_receivers
|
||||||
class ObjectWithDecoratedCallback(object):
|
class ObjectWithDecoratedCallback(object):
|
||||||
|
@ -51,7 +54,7 @@ class AnotherObjectWithDecoratedCallback(ObjectWithDecoratedCallback,
|
||||||
super(AnotherObjectWithDecoratedCallback, self).__init__()
|
super(AnotherObjectWithDecoratedCallback, self).__init__()
|
||||||
self.counter2 = 0
|
self.counter2 = 0
|
||||||
|
|
||||||
@registry.receives(resources.NETWORK, [events.AFTER_DELETE])
|
@registry.receives(resources.NETWORK, [events.AFTER_DELETE], PRI_CALLBACK)
|
||||||
def callback2(self, *args, **kwargs):
|
def callback2(self, *args, **kwargs):
|
||||||
self.counter2 += 1
|
self.counter2 += 1
|
||||||
|
|
||||||
|
@ -89,10 +92,14 @@ class CallBacksManagerTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def test_object_inheriting_others_no_double_subscribe(self):
|
def test_object_inheriting_others_no_double_subscribe(self):
|
||||||
with mock.patch.object(registry, 'subscribe') as sub:
|
with mock.patch.object(registry, 'subscribe') as sub:
|
||||||
AnotherObjectWithDecoratedCallback()
|
callback = AnotherObjectWithDecoratedCallback()
|
||||||
# there are 3 methods (2 in parent and one in child) and 1
|
# there are 3 methods (2 in parent and one in child) and 1
|
||||||
# subscribes to 2 events, so we expect 4 subscribes
|
# subscribes to 2 events, so we expect 4 subscribes
|
||||||
|
priority_call = [mock.call(
|
||||||
|
callback.callback2,
|
||||||
|
resources.NETWORK, events.AFTER_DELETE, PRI_CALLBACK)]
|
||||||
self.assertEqual(4, len(sub.mock_calls))
|
self.assertEqual(4, len(sub.mock_calls))
|
||||||
|
sub.assert_has_calls(priority_call)
|
||||||
|
|
||||||
def test_new_inheritance_not_broken(self):
|
def test_new_inheritance_not_broken(self):
|
||||||
self.assertTrue(AnotherObjectWithDecoratedCallback().new_called)
|
self.assertTrue(AnotherObjectWithDecoratedCallback().new_called)
|
||||||
|
@ -117,7 +124,14 @@ class TestCallbackRegistryDispatching(base.BaseTestCase):
|
||||||
def test_subscribe(self):
|
def test_subscribe(self):
|
||||||
registry.subscribe(my_callback, 'my-resource', 'my-event')
|
registry.subscribe(my_callback, 'my-resource', 'my-event')
|
||||||
self.callback_manager.subscribe.assert_called_with(
|
self.callback_manager.subscribe.assert_called_with(
|
||||||
my_callback, 'my-resource', 'my-event')
|
my_callback, 'my-resource', 'my-event',
|
||||||
|
priority_group.PRIORITY_DEFAULT)
|
||||||
|
|
||||||
|
def test_subscribe_explicit_priority(self):
|
||||||
|
registry.subscribe(my_callback, 'my-resource', 'my-event',
|
||||||
|
PRI_CALLBACK)
|
||||||
|
self.callback_manager.subscribe.assert_called_with(
|
||||||
|
my_callback, 'my-resource', 'my-event', PRI_CALLBACK)
|
||||||
|
|
||||||
def test_unsubscribe(self):
|
def test_unsubscribe(self):
|
||||||
registry.unsubscribe(my_callback, 'my-resource', 'my-event')
|
registry.unsubscribe(my_callback, 'my-resource', 'my-event')
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Introduced priority to callback subscription. An integer value can be
|
||||||
|
associated with each callback so that callbacks can be executed in
|
||||||
|
specified order for same resources and events. Every callback will have
|
||||||
|
priority value by default. To execute callbacks in specified order, priorities
|
||||||
|
should be defined explicitly, lower priority value would be executed first.
|
Loading…
Reference in New Issue