Add IAdapterRegistry.subscribed and Components.rebuildUtilityRegistryFromLocalCache
Fixes #230
This commit is contained in:
parent
dd69666aae
commit
50c73de0a8
12
CHANGES.rst
12
CHANGES.rst
|
@ -24,6 +24,18 @@
|
|||
to fix the reference counting issue mentioned above, as well as to
|
||||
update the data structures when custom data types have changed.
|
||||
|
||||
- Add the interface method ``IAdapterRegistry.subscribed()`` and
|
||||
implementation ``BaseAdapterRegistry.subscribed()`` for querying
|
||||
directly registered subscribers. See `issue 230
|
||||
<https://github.com/zopefoundation/zope.interface/issues/230>`_.
|
||||
|
||||
- Add the maintenance method
|
||||
``Components.rebuildUtilityRegistryFromLocalCache()``. Most users
|
||||
will not need this, but it can be useful if the ``Components.utilities``
|
||||
registry is suspected to be out of sync with the ``Components``
|
||||
object itself (this might happen to persistent ``Components``
|
||||
implementations in the face of bugs).
|
||||
|
||||
5.2.0 (2020-11-05)
|
||||
==================
|
||||
|
||||
|
|
|
@ -430,6 +430,26 @@ class BaseAdapterRegistry(object):
|
|||
|
||||
self.changed(self)
|
||||
|
||||
def subscribed(self, required, provided, subscriber):
|
||||
required = tuple([_convert_None_to_Interface(r) for r in required])
|
||||
name = u''
|
||||
order = len(required)
|
||||
byorder = self._subscribers
|
||||
if len(byorder) <= order:
|
||||
return None
|
||||
|
||||
components = byorder[order]
|
||||
key = required + (provided,)
|
||||
|
||||
for k in key:
|
||||
d = components.get(k)
|
||||
if d is None:
|
||||
return None
|
||||
components = d
|
||||
|
||||
subscribers = components.get(name, ())
|
||||
return subscriber if subscriber in subscribers else None
|
||||
|
||||
def allSubscriptions(self):
|
||||
"""
|
||||
Yields tuples ``(required, provided, value)`` for all the
|
||||
|
|
|
@ -997,11 +997,35 @@ class IAdapterRegistry(Interface):
|
|||
Subscribers have no names.
|
||||
"""
|
||||
|
||||
def subscriptions(required, provided):
|
||||
"""Get a sequence of subscribers
|
||||
def subscribed(required, provided, subscriber):
|
||||
"""
|
||||
Check whether the object *subscriber* is registered directly
|
||||
with this object via a previous
|
||||
call to ``subscribe(required, provided, subscriber)``.
|
||||
|
||||
Subscribers for a **sequence** of *required* interfaces, and a *provided*
|
||||
interface are returned.
|
||||
If the *subscriber*, or one equal to it, has been subscribed,
|
||||
for the given *required* sequence and *provided* interface,
|
||||
return that object. (This does not guarantee whether the *subscriber*
|
||||
itself is returned, or an object equal to it.)
|
||||
|
||||
If it has not, return ``None``.
|
||||
|
||||
Unlike :meth:`subscriptions`, this method won't retrieve
|
||||
components registered for more specific required interfaces or
|
||||
less specific provided interfaces.
|
||||
|
||||
.. versionadded:: 5.3.0
|
||||
"""
|
||||
|
||||
def subscriptions(required, provided):
|
||||
"""
|
||||
Get a sequence of subscribers.
|
||||
|
||||
Subscribers for a sequence of *required* interfaces, and a *provided*
|
||||
interface are returned. This takes into account subscribers
|
||||
registered with this object, as well as those registered with
|
||||
base adapter registries in the resolution order, and interfaces that
|
||||
extend *provided*.
|
||||
|
||||
.. versionchanged:: 5.1.1
|
||||
Correct the method signature to remove the ``name`` parameter.
|
||||
|
@ -1009,7 +1033,26 @@ class IAdapterRegistry(Interface):
|
|||
"""
|
||||
|
||||
def subscribers(objects, provided):
|
||||
"""Get a sequence of subscription adapters
|
||||
"""
|
||||
Get a sequence of subscription **adapters**.
|
||||
|
||||
This is like :meth:`subscriptions`, but calls the returned
|
||||
subscribers with *objects* (and optionally returns the results
|
||||
of those calls), instead of returning the subscribers directly.
|
||||
|
||||
:param objects: A sequence of objects; they will be used to
|
||||
determine the *required* argument to :meth:`subscriptions`.
|
||||
:param provided: A single interface, or ``None``, to pass
|
||||
as the *provided* parameter to :meth:`subscriptions`.
|
||||
If an interface is given, the results of calling each returned
|
||||
subscriber with the the *objects* are collected and returned
|
||||
from this method; each result should be an object implementing
|
||||
the *provided* interface. If ``None``, the resulting subscribers
|
||||
are still called, but the results are ignored.
|
||||
:return: A sequence of the results of calling the subscribers
|
||||
if *provided* is not ``None``. If there are no registered
|
||||
subscribers, or *provided* is ``None``, this will be an empty
|
||||
sequence.
|
||||
|
||||
.. versionchanged:: 5.1.1
|
||||
Correct the method signature to remove the ``name`` parameter.
|
||||
|
|
|
@ -505,6 +505,72 @@ class Components(object):
|
|||
def handle(self, *objects):
|
||||
self.adapters.subscribers(objects, None)
|
||||
|
||||
def rebuildUtilityRegistryFromLocalCache(self, rebuild=False):
|
||||
"""
|
||||
Emergency maintenance method to rebuild the ``.utilities``
|
||||
registry from the local copy maintained in this object, or
|
||||
detect the need to do so.
|
||||
|
||||
Most users will never need to call this, but it can be helpful
|
||||
in the event of suspected corruption.
|
||||
|
||||
By default, this method only checks for corruption. To make it
|
||||
actually rebuild the registry, pass `True` for *rebuild*.
|
||||
|
||||
:param bool rebuild: If set to `True` (not the default),
|
||||
this method will actually register and subscribe utilities
|
||||
in the registry as needed to synchronize with the local cache.
|
||||
|
||||
:return: A dictionary that's meant as diagnostic data. The keys
|
||||
and values may change over time. When called with a false *rebuild*,
|
||||
the keys ``"needed_registered"`` and ``"needed_subscribed`` will be
|
||||
non-zero if any corruption was detected, but that will not be corrected.
|
||||
|
||||
.. versionadded:: 5.3.0
|
||||
"""
|
||||
regs = dict(self._utility_registrations)
|
||||
utils = self.utilities
|
||||
needed_registered = 0
|
||||
did_not_register = 0
|
||||
needed_subscribed = 0
|
||||
did_not_subscribe = 0
|
||||
|
||||
|
||||
# Avoid the expensive change process during this; we'll call
|
||||
# it once at the end if needed.
|
||||
assert 'changed' not in utils.__dict__
|
||||
utils.changed = lambda _: None
|
||||
|
||||
if rebuild:
|
||||
register = utils.register
|
||||
subscribe = utils.subscribe
|
||||
else:
|
||||
register = subscribe = lambda *args: None
|
||||
|
||||
try:
|
||||
for (provided, name), (value, _info, _factory) in regs.items():
|
||||
if utils.registered((), provided, name) != value:
|
||||
register((), provided, name, value)
|
||||
needed_registered += 1
|
||||
else:
|
||||
did_not_register += 1
|
||||
|
||||
if utils.subscribed((), provided, value) is None:
|
||||
needed_subscribed += 1
|
||||
subscribe((), provided, value)
|
||||
else:
|
||||
did_not_subscribe += 1
|
||||
finally:
|
||||
del utils.changed
|
||||
if rebuild and (needed_subscribed or needed_registered):
|
||||
utils.changed(utils)
|
||||
|
||||
return {
|
||||
'needed_registered': needed_registered,
|
||||
'did_not_register': did_not_register,
|
||||
'needed_subscribed': needed_subscribed,
|
||||
'did_not_subscribe': did_not_subscribe
|
||||
}
|
||||
|
||||
def _getName(component):
|
||||
try:
|
||||
|
|
|
@ -621,6 +621,29 @@ class BaseAdapterRegistryTests(unittest.TestCase):
|
|||
self.assertEqual(len(registry._subscribers), 0)
|
||||
self.assertEqual(registry._provided, PT())
|
||||
|
||||
def test_subscribed_empty(self):
|
||||
registry = self._makeOne()
|
||||
self.assertIsNone(registry.subscribed([None], None, ''))
|
||||
subscribed = list(registry.allSubscriptions())
|
||||
self.assertEqual(subscribed, [])
|
||||
|
||||
def test_subscribed_non_empty_miss(self):
|
||||
IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() # pylint:disable=unused-variable
|
||||
registry = self._makeOne()
|
||||
registry.subscribe([IB1], IF0, 'A1')
|
||||
# Mismatch required
|
||||
self.assertIsNone(registry.subscribed([IB2], IF0, ''))
|
||||
# Mismatch provided
|
||||
self.assertIsNone(registry.subscribed([IB1], IF1, ''))
|
||||
# Mismatch value
|
||||
self.assertIsNone(registry.subscribed([IB1], IF0, ''))
|
||||
|
||||
def test_subscribed_non_empty_hit(self):
|
||||
IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() # pylint:disable=unused-variable
|
||||
registry = self._makeOne()
|
||||
registry.subscribe([IB0], IF0, 'A1')
|
||||
self.assertEqual(registry.subscribed([IB0], IF0, 'A1'), 'A1')
|
||||
|
||||
def test_unsubscribe_w_None_after_multiple(self):
|
||||
IB0, IB1, IB2, IB3, IB4, IF0, IF1, IR0, IR1 = _makeInterfaces() # pylint:disable=unused-variable
|
||||
registry = self._makeOne()
|
||||
|
|
|
@ -2351,6 +2351,92 @@ class ComponentsTests(unittest.TestCase):
|
|||
def test_register_unregister_nonequal_objects_provided(self):
|
||||
self.test_register_unregister_identical_objects_provided(identical=False)
|
||||
|
||||
def test_rebuildUtilityRegistryFromLocalCache(self):
|
||||
class IFoo(Interface):
|
||||
"Does nothing"
|
||||
|
||||
class UtilityImplementingFoo(object):
|
||||
"Does nothing"
|
||||
|
||||
comps = self._makeOne()
|
||||
|
||||
for i in range(30):
|
||||
comps.registerUtility(UtilityImplementingFoo(), IFoo, name=u'%s' % (i,))
|
||||
|
||||
orig_generation = comps.utilities._generation
|
||||
|
||||
orig_adapters = comps.utilities._adapters
|
||||
self.assertEqual(len(orig_adapters), 1)
|
||||
self.assertEqual(len(orig_adapters[0]), 1)
|
||||
self.assertEqual(len(orig_adapters[0][IFoo]), 30)
|
||||
|
||||
orig_subscribers = comps.utilities._subscribers
|
||||
self.assertEqual(len(orig_subscribers), 1)
|
||||
self.assertEqual(len(orig_subscribers[0]), 1)
|
||||
self.assertEqual(len(orig_subscribers[0][IFoo]), 1)
|
||||
self.assertEqual(len(orig_subscribers[0][IFoo][u'']), 30)
|
||||
|
||||
# Blow a bunch of them away, creating artificial corruption
|
||||
new_adapters = comps.utilities._adapters = type(orig_adapters)()
|
||||
new_adapters.append({})
|
||||
d = new_adapters[0][IFoo] = {}
|
||||
for name in range(10):
|
||||
name = type(u'')(str(name))
|
||||
d[name] = orig_adapters[0][IFoo][name]
|
||||
|
||||
self.assertNotEqual(orig_adapters, new_adapters)
|
||||
|
||||
new_subscribers = comps.utilities._subscribers = type(orig_subscribers)()
|
||||
new_subscribers.append({})
|
||||
d = new_subscribers[0][IFoo] = {}
|
||||
d[u''] = ()
|
||||
|
||||
for name in range(5, 12): # 12 - 5 = 7
|
||||
name = type(u'')(str(name))
|
||||
comp = orig_adapters[0][IFoo][name]
|
||||
d[u''] += (comp,)
|
||||
|
||||
# We can preflight (by default) and nothing changes
|
||||
rebuild_results_preflight = comps.rebuildUtilityRegistryFromLocalCache()
|
||||
|
||||
self.assertEqual(comps.utilities._generation, orig_generation)
|
||||
self.assertEqual(rebuild_results_preflight, {
|
||||
'did_not_register': 10,
|
||||
'needed_registered': 20,
|
||||
|
||||
'did_not_subscribe': 7,
|
||||
'needed_subscribed': 23,
|
||||
})
|
||||
|
||||
# Now for real
|
||||
rebuild_results = comps.rebuildUtilityRegistryFromLocalCache(rebuild=True)
|
||||
|
||||
# The generation only got incremented once
|
||||
self.assertEqual(comps.utilities._generation, orig_generation + 1)
|
||||
# The result was the same
|
||||
self.assertEqual(rebuild_results_preflight, rebuild_results)
|
||||
self.assertEqual(new_adapters, orig_adapters)
|
||||
self.assertEqual(
|
||||
len(new_subscribers[0][IFoo][u'']),
|
||||
len(orig_subscribers[0][IFoo][u'']))
|
||||
|
||||
for orig_subscriber in orig_subscribers[0][IFoo][u'']:
|
||||
self.assertIn(orig_subscriber, new_subscribers[0][IFoo][u''])
|
||||
|
||||
# Preflighting, rebuilding again produce no changes.
|
||||
preflight_after = comps.rebuildUtilityRegistryFromLocalCache()
|
||||
self.assertEqual(preflight_after, {
|
||||
'did_not_register': 30,
|
||||
'needed_registered': 0,
|
||||
|
||||
'did_not_subscribe': 30,
|
||||
'needed_subscribed': 0,
|
||||
})
|
||||
|
||||
rebuild_after = comps.rebuildUtilityRegistryFromLocalCache(rebuild=True)
|
||||
self.assertEqual(rebuild_after, preflight_after)
|
||||
self.assertEqual(comps.utilities._generation, orig_generation + 1)
|
||||
|
||||
|
||||
class UnhashableComponentsTests(ComponentsTests):
|
||||
|
||||
|
|
Loading…
Reference in New Issue