1##############################################################################
2#
3# Copyright (c) 2006 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE.
12#
13##############################################################################
14"""Basic components support
15"""
16from collections import defaultdict
17
18try:
19    from zope.event import notify
20except ImportError: # pragma: no cover
21    def notify(*arg, **kw): pass
22
23from zope.interface.interfaces import ISpecification
24from zope.interface.interfaces import ComponentLookupError
25from zope.interface.interfaces import IAdapterRegistration
26from zope.interface.interfaces import IComponents
27from zope.interface.interfaces import IHandlerRegistration
28from zope.interface.interfaces import ISubscriptionAdapterRegistration
29from zope.interface.interfaces import IUtilityRegistration
30from zope.interface.interfaces import Registered
31from zope.interface.interfaces import Unregistered
32
33from zope.interface.interface import Interface
34from zope.interface.declarations import implementedBy
35from zope.interface.declarations import implementer
36from zope.interface.declarations import implementer_only
37from zope.interface.declarations import providedBy
38from zope.interface.adapter import AdapterRegistry
39from zope.interface._compat import CLASS_TYPES
40from zope.interface._compat import STRING_TYPES
41
42__all__ = [
43    # Components is public API, but
44    # the *Registration classes are just implementations
45    # of public interfaces.
46    'Components',
47]
48
49class _UnhashableComponentCounter(object):
50    # defaultdict(int)-like object for unhashable components
51
52    def __init__(self, otherdict):
53        # [(component, count)]
54        self._data = [item for item in otherdict.items()]
55
56    def __getitem__(self, key):
57        for component, count in self._data:
58            if component == key:
59                return count
60        return 0
61
62    def __setitem__(self, component, count):
63        for i, data in enumerate(self._data):
64            if data[0] == component:
65                self._data[i] = component, count
66                return
67        self._data.append((component, count))
68
69    def __delitem__(self, component):
70        for i, data in enumerate(self._data):
71            if data[0] == component:
72                del self._data[i]
73                return
74        raise KeyError(component) # pragma: no cover
75
76def _defaultdict_int():
77    return defaultdict(int)
78
79class _UtilityRegistrations(object):
80
81    def __init__(self, utilities, utility_registrations):
82        # {provided -> {component: count}}
83        self._cache = defaultdict(_defaultdict_int)
84        self._utilities = utilities
85        self._utility_registrations = utility_registrations
86
87        self.__populate_cache()
88
89    def __populate_cache(self):
90        for ((p, _), data) in iter(self._utility_registrations.items()):
91            component = data[0]
92            self.__cache_utility(p, component)
93
94    def __cache_utility(self, provided, component):
95        try:
96            self._cache[provided][component] += 1
97        except TypeError:
98            # The component is not hashable, and we have a dict. Switch to a strategy
99            # that doesn't use hashing.
100            prov = self._cache[provided] = _UnhashableComponentCounter(self._cache[provided])
101            prov[component] += 1
102
103    def __uncache_utility(self, provided, component):
104        provided = self._cache[provided]
105        # It seems like this line could raise a TypeError if component isn't
106        # hashable and we haven't yet switched to _UnhashableComponentCounter. However,
107        # we can't actually get in that situation. In order to get here, we would
108        # have had to cache the utility already which would have switched
109        # the datastructure if needed.
110        count = provided[component]
111        count -= 1
112        if count == 0:
113            del provided[component]
114        else:
115            provided[component] = count
116        return count > 0
117
118    def _is_utility_subscribed(self, provided, component):
119        try:
120            return self._cache[provided][component] > 0
121        except TypeError:
122            # Not hashable and we're still using a dict
123            return False
124
125    def registerUtility(self, provided, name, component, info, factory):
126        subscribed = self._is_utility_subscribed(provided, component)
127
128        self._utility_registrations[(provided, name)] = component, info, factory
129        self._utilities.register((), provided, name, component)
130
131        if not subscribed:
132            self._utilities.subscribe((), provided, component)
133
134        self.__cache_utility(provided, component)
135
136    def unregisterUtility(self, provided, name, component):
137        del self._utility_registrations[(provided, name)]
138        self._utilities.unregister((), provided, name)
139
140        subscribed = self.__uncache_utility(provided, component)
141
142        if not subscribed:
143            self._utilities.unsubscribe((), provided, component)
144
145
146@implementer(IComponents)
147class Components(object):
148
149    _v_utility_registrations_cache = None
150
151    def __init__(self, name='', bases=()):
152        # __init__ is used for test cleanup as well as initialization.
153        # XXX add a separate API for test cleanup.
154        assert isinstance(name, STRING_TYPES)
155        self.__name__ = name
156        self._init_registries()
157        self._init_registrations()
158        self.__bases__ = tuple(bases)
159        self._v_utility_registrations_cache = None
160
161    def __repr__(self):
162        return "<%s %s>" % (self.__class__.__name__, self.__name__)
163
164    def __reduce__(self):
165        # Mimic what a persistent.Persistent object does and elide
166        # _v_ attributes so that they don't get saved in ZODB.
167        # This allows us to store things that cannot be pickled in such
168        # attributes.
169        reduction = super(Components, self).__reduce__()
170        # (callable, args, state, listiter, dictiter)
171        # We assume the state is always a dict; the last three items
172        # are technically optional and can be missing or None.
173        filtered_state = {k: v for k, v in reduction[2].items()
174                          if not k.startswith('_v_')}
175        reduction = list(reduction)
176        reduction[2] = filtered_state
177        return tuple(reduction)
178
179    def _init_registries(self):
180        # Subclasses have never been required to call this method
181        # if they override it, merely to fill in these two attributes.
182        self.adapters = AdapterRegistry()
183        self.utilities = AdapterRegistry()
184
185    def _init_registrations(self):
186        self._utility_registrations = {}
187        self._adapter_registrations = {}
188        self._subscription_registrations = []
189        self._handler_registrations = []
190
191    @property
192    def _utility_registrations_cache(self):
193        # We use a _v_ attribute internally so that data aren't saved in ZODB,
194        # because this object cannot be pickled.
195        cache = self._v_utility_registrations_cache
196        if (cache is None
197            or cache._utilities is not self.utilities
198            or cache._utility_registrations is not self._utility_registrations):
199            cache = self._v_utility_registrations_cache = _UtilityRegistrations(
200                self.utilities,
201                self._utility_registrations)
202        return cache
203
204    def _getBases(self):
205        # Subclasses might override
206        return self.__dict__.get('__bases__', ())
207
208    def _setBases(self, bases):
209        # Subclasses might override
210        self.adapters.__bases__ = tuple([
211            base.adapters for base in bases])
212        self.utilities.__bases__ = tuple([
213            base.utilities for base in bases])
214        self.__dict__['__bases__'] = tuple(bases)
215
216    __bases__ = property(
217        lambda self: self._getBases(),
218        lambda self, bases: self._setBases(bases),
219        )
220
221    def registerUtility(self, component=None, provided=None, name=u'',
222                        info=u'', event=True, factory=None):
223        if factory:
224            if component:
225                raise TypeError("Can't specify factory and component.")
226            component = factory()
227
228        if provided is None:
229            provided = _getUtilityProvided(component)
230
231        if name == u'':
232            name = _getName(component)
233
234        reg = self._utility_registrations.get((provided, name))
235        if reg is not None:
236            if reg[:2] == (component, info):
237                # already registered
238                return
239            self.unregisterUtility(reg[0], provided, name)
240
241        self._utility_registrations_cache.registerUtility(
242            provided, name, component, info, factory)
243
244        if event:
245            notify(Registered(
246                UtilityRegistration(self, provided, name, component, info,
247                                    factory)
248                ))
249
250    def unregisterUtility(self, component=None, provided=None, name=u'',
251                          factory=None):
252        if factory:
253            if component:
254                raise TypeError("Can't specify factory and component.")
255            component = factory()
256
257        if provided is None:
258            if component is None:
259                raise TypeError("Must specify one of component, factory and "
260                                "provided")
261            provided = _getUtilityProvided(component)
262
263        old = self._utility_registrations.get((provided, name))
264        if (old is None) or ((component is not None) and
265                             (component != old[0])):
266            return False
267
268        if component is None:
269            component = old[0]
270
271        # Note that component is now the old thing registered
272        self._utility_registrations_cache.unregisterUtility(
273            provided, name, component)
274
275        notify(Unregistered(
276            UtilityRegistration(self, provided, name, component, *old[1:])
277            ))
278
279        return True
280
281    def registeredUtilities(self):
282        for ((provided, name), data
283             ) in iter(self._utility_registrations.items()):
284            yield UtilityRegistration(self, provided, name, *data)
285
286    def queryUtility(self, provided, name=u'', default=None):
287        return self.utilities.lookup((), provided, name, default)
288
289    def getUtility(self, provided, name=u''):
290        utility = self.utilities.lookup((), provided, name)
291        if utility is None:
292            raise ComponentLookupError(provided, name)
293        return utility
294
295    def getUtilitiesFor(self, interface):
296        for name, utility in self.utilities.lookupAll((), interface):
297            yield name, utility
298
299    def getAllUtilitiesRegisteredFor(self, interface):
300        return self.utilities.subscriptions((), interface)
301
302    def registerAdapter(self, factory, required=None, provided=None,
303                        name=u'', info=u'', event=True):
304        if provided is None:
305            provided = _getAdapterProvided(factory)
306        required = _getAdapterRequired(factory, required)
307        if name == u'':
308            name = _getName(factory)
309        self._adapter_registrations[(required, provided, name)
310                                    ] = factory, info
311        self.adapters.register(required, provided, name, factory)
312
313        if event:
314            notify(Registered(
315                AdapterRegistration(self, required, provided, name,
316                                    factory, info)
317                ))
318
319
320    def unregisterAdapter(self, factory=None,
321                          required=None, provided=None, name=u'',
322                          ):
323        if provided is None:
324            if factory is None:
325                raise TypeError("Must specify one of factory and provided")
326            provided = _getAdapterProvided(factory)
327
328        if (required is None) and (factory is None):
329            raise TypeError("Must specify one of factory and required")
330
331        required = _getAdapterRequired(factory, required)
332        old = self._adapter_registrations.get((required, provided, name))
333        if (old is None) or ((factory is not None) and
334                             (factory != old[0])):
335            return False
336
337        del self._adapter_registrations[(required, provided, name)]
338        self.adapters.unregister(required, provided, name)
339
340        notify(Unregistered(
341            AdapterRegistration(self, required, provided, name,
342                                *old)
343            ))
344
345        return True
346
347    def registeredAdapters(self):
348        for ((required, provided, name), (component, info)
349             ) in iter(self._adapter_registrations.items()):
350            yield AdapterRegistration(self, required, provided, name,
351                                      component, info)
352
353    def queryAdapter(self, object, interface, name=u'', default=None):
354        return self.adapters.queryAdapter(object, interface, name, default)
355
356    def getAdapter(self, object, interface, name=u''):
357        adapter = self.adapters.queryAdapter(object, interface, name)
358        if adapter is None:
359            raise ComponentLookupError(object, interface, name)
360        return adapter
361
362    def queryMultiAdapter(self, objects, interface, name=u'',
363                          default=None):
364        return self.adapters.queryMultiAdapter(
365            objects, interface, name, default)
366
367    def getMultiAdapter(self, objects, interface, name=u''):
368        adapter = self.adapters.queryMultiAdapter(objects, interface, name)
369        if adapter is None:
370            raise ComponentLookupError(objects, interface, name)
371        return adapter
372
373    def getAdapters(self, objects, provided):
374        for name, factory in self.adapters.lookupAll(
375            list(map(providedBy, objects)),
376            provided):
377            adapter = factory(*objects)
378            if adapter is not None:
379                yield name, adapter
380
381    def registerSubscriptionAdapter(self,
382                                    factory, required=None, provided=None,
383                                    name=u'', info=u'',
384                                    event=True):
385        if name:
386            raise TypeError("Named subscribers are not yet supported")
387        if provided is None:
388            provided = _getAdapterProvided(factory)
389        required = _getAdapterRequired(factory, required)
390        self._subscription_registrations.append(
391            (required, provided, name, factory, info)
392            )
393        self.adapters.subscribe(required, provided, factory)
394
395        if event:
396            notify(Registered(
397                SubscriptionRegistration(self, required, provided, name,
398                                         factory, info)
399                ))
400
401    def registeredSubscriptionAdapters(self):
402        for data in self._subscription_registrations:
403            yield SubscriptionRegistration(self, *data)
404
405    def unregisterSubscriptionAdapter(self, factory=None,
406                          required=None, provided=None, name=u'',
407                          ):
408        if name:
409            raise TypeError("Named subscribers are not yet supported")
410        if provided is None:
411            if factory is None:
412                raise TypeError("Must specify one of factory and provided")
413            provided = _getAdapterProvided(factory)
414
415        if (required is None) and (factory is None):
416            raise TypeError("Must specify one of factory and required")
417
418        required = _getAdapterRequired(factory, required)
419
420        if factory is None:
421            new = [(r, p, n, f, i)
422                   for (r, p, n, f, i)
423                   in self._subscription_registrations
424                   if not (r == required and p == provided)
425                   ]
426        else:
427            new = [(r, p, n, f, i)
428                   for (r, p, n, f, i)
429                   in self._subscription_registrations
430                   if not (r == required and p == provided and f == factory)
431                   ]
432
433        if len(new) == len(self._subscription_registrations):
434            return False
435
436
437        self._subscription_registrations[:] = new
438        self.adapters.unsubscribe(required, provided, factory)
439
440        notify(Unregistered(
441            SubscriptionRegistration(self, required, provided, name,
442                                     factory, '')
443            ))
444
445        return True
446
447    def subscribers(self, objects, provided):
448        return self.adapters.subscribers(objects, provided)
449
450    def registerHandler(self,
451                        factory, required=None,
452                        name=u'', info=u'',
453                        event=True):
454        if name:
455            raise TypeError("Named handlers are not yet supported")
456        required = _getAdapterRequired(factory, required)
457        self._handler_registrations.append(
458            (required, name, factory, info)
459            )
460        self.adapters.subscribe(required, None, factory)
461
462        if event:
463            notify(Registered(
464                HandlerRegistration(self, required, name, factory, info)
465                ))
466
467    def registeredHandlers(self):
468        for data in self._handler_registrations:
469            yield HandlerRegistration(self, *data)
470
471    def unregisterHandler(self, factory=None, required=None, name=u''):
472        if name:
473            raise TypeError("Named subscribers are not yet supported")
474
475        if (required is None) and (factory is None):
476            raise TypeError("Must specify one of factory and required")
477
478        required = _getAdapterRequired(factory, required)
479
480        if factory is None:
481            new = [(r, n, f, i)
482                   for (r, n, f, i)
483                   in self._handler_registrations
484                   if r != required
485                   ]
486        else:
487            new = [(r, n, f, i)
488                   for (r, n, f, i)
489                   in self._handler_registrations
490                   if not (r == required and f == factory)
491                   ]
492
493        if len(new) == len(self._handler_registrations):
494            return False
495
496        self._handler_registrations[:] = new
497        self.adapters.unsubscribe(required, None, factory)
498
499        notify(Unregistered(
500            HandlerRegistration(self, required, name, factory, '')
501            ))
502
503        return True
504
505    def handle(self, *objects):
506        self.adapters.subscribers(objects, None)
507
508    def rebuildUtilityRegistryFromLocalCache(self, rebuild=False):
509        """
510        Emergency maintenance method to rebuild the ``.utilities``
511        registry from the local copy maintained in this object, or
512        detect the need to do so.
513
514        Most users will never need to call this, but it can be helpful
515        in the event of suspected corruption.
516
517        By default, this method only checks for corruption. To make it
518        actually rebuild the registry, pass `True` for *rebuild*.
519
520        :param bool rebuild: If set to `True` (not the default),
521           this method will actually register and subscribe utilities
522           in the registry as needed to synchronize with the local cache.
523
524        :return: A dictionary that's meant as diagnostic data. The keys
525           and values may change over time. When called with a false *rebuild*,
526           the keys ``"needed_registered"`` and ``"needed_subscribed"`` will be
527           non-zero if any corruption was detected, but that will not be corrected.
528
529        .. versionadded:: 5.3.0
530        """
531        regs = dict(self._utility_registrations)
532        utils = self.utilities
533        needed_registered = 0
534        did_not_register = 0
535        needed_subscribed = 0
536        did_not_subscribe = 0
537
538
539        # Avoid the expensive change process during this; we'll call
540        # it once at the end if needed.
541        assert 'changed' not in utils.__dict__
542        utils.changed = lambda _: None
543
544        if rebuild:
545            register = utils.register
546            subscribe = utils.subscribe
547        else:
548            register = subscribe = lambda *args: None
549
550        try:
551            for (provided, name), (value, _info, _factory) in regs.items():
552                if utils.registered((), provided, name) != value:
553                    register((), provided, name, value)
554                    needed_registered += 1
555                else:
556                    did_not_register += 1
557
558                if utils.subscribed((), provided, value) is None:
559                    needed_subscribed += 1
560                    subscribe((), provided, value)
561                else:
562                    did_not_subscribe += 1
563        finally:
564            del utils.changed
565            if rebuild and (needed_subscribed or needed_registered):
566                utils.changed(utils)
567
568        return {
569            'needed_registered': needed_registered,
570            'did_not_register': did_not_register,
571            'needed_subscribed': needed_subscribed,
572            'did_not_subscribe': did_not_subscribe
573        }
574
575def _getName(component):
576    try:
577        return component.__component_name__
578    except AttributeError:
579        return u''
580
581def _getUtilityProvided(component):
582    provided = list(providedBy(component))
583    if len(provided) == 1:
584        return provided[0]
585    raise TypeError(
586        "The utility doesn't provide a single interface "
587        "and no provided interface was specified.")
588
589def _getAdapterProvided(factory):
590    provided = list(implementedBy(factory))
591    if len(provided) == 1:
592        return provided[0]
593    raise TypeError(
594        "The adapter factory doesn't implement a single interface "
595        "and no provided interface was specified.")
596
597def _getAdapterRequired(factory, required):
598    if required is None:
599        try:
600            required = factory.__component_adapts__
601        except AttributeError:
602            raise TypeError(
603                "The adapter factory doesn't have a __component_adapts__ "
604                "attribute and no required specifications were specified"
605                )
606    elif ISpecification.providedBy(required):
607        raise TypeError("the required argument should be a list of "
608                        "interfaces, not a single interface")
609
610    result = []
611    for r in required:
612        if r is None:
613            r = Interface
614        elif not ISpecification.providedBy(r):
615            if isinstance(r, CLASS_TYPES):
616                r = implementedBy(r)
617            else:
618                raise TypeError("Required specification must be a "
619                                "specification or class, not %r" % type(r)
620                                )
621        result.append(r)
622    return tuple(result)
623
624
625@implementer(IUtilityRegistration)
626class UtilityRegistration(object):
627
628    def __init__(self, registry, provided, name, component, doc, factory=None):
629        (self.registry, self.provided, self.name, self.component, self.info,
630         self.factory
631         ) = registry, provided, name, component, doc, factory
632
633    def __repr__(self):
634        return '%s(%r, %s, %r, %s, %r, %r)' % (
635                self.__class__.__name__,
636                self.registry,
637                getattr(self.provided, '__name__', None), self.name,
638                getattr(self.component, '__name__', repr(self.component)),
639                self.factory, self.info,
640                )
641
642    def __hash__(self):
643        return id(self)
644
645    def __eq__(self, other):
646        return repr(self) == repr(other)
647
648    def __ne__(self, other):
649        return repr(self) != repr(other)
650
651    def __lt__(self, other):
652        return repr(self) < repr(other)
653
654    def __le__(self, other):
655        return repr(self) <= repr(other)
656
657    def __gt__(self, other):
658        return repr(self) > repr(other)
659
660    def __ge__(self, other):
661        return repr(self) >= repr(other)
662
663@implementer(IAdapterRegistration)
664class AdapterRegistration(object):
665
666    def __init__(self, registry, required, provided, name, component, doc):
667        (self.registry, self.required, self.provided, self.name,
668         self.factory, self.info
669         ) = registry, required, provided, name, component, doc
670
671    def __repr__(self):
672        return '%s(%r, %s, %s, %r, %s, %r)' % (
673            self.__class__.__name__,
674            self.registry,
675            '[' + ", ".join([r.__name__ for r in self.required]) + ']',
676            getattr(self.provided, '__name__', None), self.name,
677            getattr(self.factory, '__name__', repr(self.factory)), self.info,
678            )
679
680    def __hash__(self):
681        return id(self)
682
683    def __eq__(self, other):
684        return repr(self) == repr(other)
685
686    def __ne__(self, other):
687        return repr(self) != repr(other)
688
689    def __lt__(self, other):
690        return repr(self) < repr(other)
691
692    def __le__(self, other):
693        return repr(self) <= repr(other)
694
695    def __gt__(self, other):
696        return repr(self) > repr(other)
697
698    def __ge__(self, other):
699        return repr(self) >= repr(other)
700
701@implementer_only(ISubscriptionAdapterRegistration)
702class SubscriptionRegistration(AdapterRegistration):
703    pass
704
705
706@implementer_only(IHandlerRegistration)
707class HandlerRegistration(AdapterRegistration):
708
709    def __init__(self, registry, required, name, handler, doc):
710        (self.registry, self.required, self.name, self.handler, self.info
711         ) = registry, required, name, handler, doc
712
713    @property
714    def factory(self):
715        return self.handler
716
717    provided = None
718
719    def __repr__(self):
720        return '%s(%r, %s, %r, %s, %r)' % (
721            self.__class__.__name__,
722            self.registry,
723            '[' + ", ".join([r.__name__ for r in self.required]) + ']',
724            self.name,
725            getattr(self.factory, '__name__', repr(self.factory)), self.info,
726            )
727