Adapter-system LInux

Embed Size (px)

Citation preview

  • 7/29/2019 Adapter-system LInux

    1/10

    ================Adapter Registry================

    Adapter registries provide a way to register objects that depend onone or more interface specifications and provide (perhaps indirectly)some interface. In addition, the registrations have names. (You canthink of the names as qualifiers of the provided interfaces.)

    The term "interface specification" refers both to interfaces and tointerface declarations, such as declarations of interfaces implementedby a class.

    Single Adapters===============

    Let's look at a simple example, using a single required specification::

    >>> from zope.interface.adapter import AdapterRegistry>>> import zope.interface

    >>> class IR1(zope.interface.Interface):... pass

    >>> class IP1(zope.interface.Interface):... pass>>> class IP2(IP1):... pass

    >>> registry = AdapterRegistry()

    We'll register an object that depends on IR1 and "provides" IP2::

    >>> registry.register([IR1], IP2, '', 12)

    Given the registration, we can look it up again::

    >>> registry.lookup([IR1], IP2, '')12

    Note that we used an integer in the example. In real applications,one would use some objects that actually depend on or provideinterfaces. The registry doesn't care about what gets registered, sowe'll use integers and strings to keep the examples simple. There isone exception. Registering a value of None unregisters anypreviously-registered value.

    If an object depends on a specification, it can be looked up with aspecification that extends the specification that it depends on::

    >>> class IR2(IR1):... pass>>> registry.lookup([IR2], IP2, '')12

    We can use a class implementation specification to look up the object::

    >>> class C2:... zope.interface.implements(IR2)

  • 7/29/2019 Adapter-system LInux

    2/10

    >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '')12

    and it can be looked up for interfaces that its provided interfaceextends::

    >>> registry.lookup([IR1], IP1, '')12>>> registry.lookup([IR2], IP1, '')12

    But if you require a specification that doesn't extend the specification theobject depends on, you won't get anything::

    >>> registry.lookup([zope.interface.Interface], IP1, '')

    By the way, you can pass a default value to lookup::

    >>> registry.lookup([zope.interface.Interface], IP1, '', 42)42

    If you try to get an interface the object doesn't provide, you alsowon't get anything::

    >>> class IP3(IP2):... pass>>> registry.lookup([IR1], IP3, '')

    You also won't get anything if you use the wrong name::

    >>> registry.lookup([IR1], IP1, 'bob')>>> registry.register([IR1], IP2, 'bob', "Bob's 12")>>> registry.lookup([IR1], IP1, 'bob')"Bob's 12"

    You can leave the name off when doing a lookup::

    >>> registry.lookup([IR1], IP1)12

    If we register an object that provides IP1::

    >>> registry.register([IR1], IP1, '', 11)

    then that object will be prefered over O(12)::

    >>> registry.lookup([IR1], IP1, '')11

    Also, if we register an object for IR2, then that will be preferedwhen using IR2::

    >>> registry.register([IR2], IP1, '', 21)>>> registry.lookup([IR2], IP1, '')21

    Finding out what, if anything, is registered--------------------------------------------

  • 7/29/2019 Adapter-system LInux

    3/10

    We can ask if there is an adapter registered for a collection ofinterfaces. This is different than lookup, because it looks for anexact match.

    >>> print registry.registered([IR1], IP1)11

    >>> print registry.registered([IR1], IP2)12

    >>> print registry.registered([IR1], IP2, 'bob')Bob's 12

    >>> print registry.registered([IR2], IP1)21

    >>> print registry.registered([IR2], IP2)None

    In the last example, None was returned because nothing was registeredexactly for the given interfaces.

    lookup1

    -------Lookup of single adapters is common enough that there is a specializedversion of lookup that takes a single required interface::

    >>> registry.lookup1(IR2, IP1, '')21>>> registry.lookup1(IR2, IP1)21

    Actual Adaptation-----------------

    The adapter registry is intended to support adaptation, where oneobject that implements an interface is adapted to another object thatsupports a different interface. The adapter registry supports thecomputation of adapters. In this case, we have to register adapterfactories::

    >>> class IR(zope.interface.Interface):... pass

    >>> class X:... zope.interface.implements(IR)

    >>> class Y:

    ... zope.interface.implements(IP1)... def __init__(self, context):

    ... self.context = context

    >>> registry.register([IR], IP1, '', Y)

    In this case, we registered a class as the factory. Now we can call`queryAdapter` to get the adapted object::

    >>> x = X()

  • 7/29/2019 Adapter-system LInux

    4/10

    >>> y = registry.queryAdapter(x, IP1)>>> y.__class__.__name__ 'Y'>>> y.context is xTrue

    We can register and lookup by name too::

    >>> class Y2(Y):... pass

    >>> registry.register([IR], IP1, 'bob', Y2)>>> y = registry.queryAdapter(x, IP1, 'bob')>>> y.__class__.__name__ 'Y2'>>> y.context is xTrue

    When the adapter factory produces `None`, then this is treated as if noadapter has been found. This allows us to prevent adaptation (when desired)and let the adapter factory determine whether adaptation is possible based onthe state of the object being adapted.

    >>> def factory(context):

    ... if context.name == 'object':... return 'adapter'

    ... return None

    >>> class Object(object):... zope.interface.implements(IR)... name = 'object'

    >>> registry.register([IR], IP1, 'conditional', factory)>>> obj = Object()>>> registry.queryAdapter(obj, IP1, 'conditional')'adapter'>>> obj.name = 'no object'

    >>> registry.queryAdapter(obj, IP1, 'conditional') is NoneTrue>>> registry.queryAdapter(obj, IP1, 'conditional', 'default')'default'

    An alternate method that provides the same function as `queryAdapter()` is`adapter_hook()`::

    >>> y = registry.adapter_hook(IP1, x)>>> y.__class__.__name__ 'Y'>>> y.context is xTrue

    >>> y = registry.adapter_hook(IP1, x, 'bob')>>> y.__class__.__name__ 'Y2'>>> y.context is xTrue

    The `adapter_hook()` simply switches the order of the object andinterface arguments. It is used to hook into the interface callmechanism.

  • 7/29/2019 Adapter-system LInux

    5/10

    Default Adapters---------------- Sometimes, you want to provide an adapter that will adapt anything.For that, provide None as the required interface::

    >>> registry.register([None], IP1, '', 1) then we can use that adapter for interfaces we don't have specificadapters for::

    >>> class IQ(zope.interface.Interface):... pass>>> registry.lookup([IQ], IP1, '')1

    Of course, specific adapters are still used when applicable::

    >>> registry.lookup([IR2], IP1, '')21

    Class adapters--------------

    You can register adapters for class declarations, which is almost thesame as registering them for a class::

    >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', 'C21')>>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '')'C21'

    Dict adapters-------------

    At some point it was impossible to register dictionary-based adapters due abug. Let's make sure this works now:

    >>> adapter = {}>>> registry.register((), IQ, '', adapter)>>> registry.lookup((), IQ, '') is adapterTrue

    Unregistering-------------

    You can unregister by registering None, rather than an object::

    >>> registry.register([zope.interface.implementedBy(C2)], IP1, '', None)>>> registry.lookup([zope.interface.implementedBy(C2)], IP1, '')

    21

    Of course, this means that None can't be registered. This is anexception to the statement, made earlier, that the registry doesn'tcare what gets registered.

    Multi-adapters==============

    You can adapt multiple specifications::

  • 7/29/2019 Adapter-system LInux

    6/10

    >>> registry.register([IR1, IQ], IP2, '', '1q2')>>> registry.lookup([IR1, IQ], IP2, '')'1q2'>>> registry.lookup([IR2, IQ], IP1, '')'1q2'

    >>> class IS(zope.interface.Interface):... pass>>> registry.lookup([IR2, IS], IP1, '')

    >>> class IQ2(IQ):... pass

    >>> registry.lookup([IR2, IQ2], IP1, '')'1q2'

    >>> registry.register([IR1, IQ2], IP2, '', '1q22')>>> registry.lookup([IR2, IQ2], IP1, '')'1q22'

    Multi-adaptation----------------

    You can adapt multiple objects::>>> class Q:... zope.interface.implements(IQ)

    As with single adapters, we register a factory, which is often a class::

    >>> class IM(zope.interface.Interface):... pass>>> class M:... zope.interface.implements(IM)... def __init__(self, x, q):... self.x, self.q = x, q

    >>> registry.register([IR, IQ], IM, '', M)

    And then we can call `queryMultiAdapter` to compute an adapter::

    >>> q = Q()>>> m = registry.queryMultiAdapter((x, q), IM)>>> m.__class__.__name__ 'M'>>> m.x is x and m.q is qTrue

    and, of course, we can use names::

    >>> class M2(M):... pass>>> registry.register([IR, IQ], IM, 'bob', M2)>>> m = registry.queryMultiAdapter((x, q), IM, 'bob')>>> m.__class__.__name__ 'M2'>>> m.x is x and m.q is qTrue

    Default Adapters

  • 7/29/2019 Adapter-system LInux

    7/10

    ----------------

    As with single adapters, you can define default adapters by specifyingNone for the *first* specification::

    >>> registry.register([None, IQ], IP2, '', 'q2')>>> registry.lookup([IS, IQ], IP2, '')'q2'

    Null Adapters=============

    You can also adapt no specification::

    >>> registry.register([], IP2, '', 2)>>> registry.lookup([], IP2, '')2>>> registry.lookup([], IP1, '')2

    Listing named adapters----------------------

    Adapters are named. Sometimes, it's useful to get all of the named

    adapters for given interfaces::>>> adapters = list(registry.lookupAll([IR1], IP1))>>> adapters.sort()>>> assert adapters == [(u'', 11), (u'bob', "Bob's 12")]

    This works for multi-adapters too::

    >>> registry.register([IR1, IQ2], IP2, 'bob', '1q2 for bob')>>> adapters = list(registry.lookupAll([IR2, IQ2], IP1))>>> adapters.sort()>>> assert adapters == [(u'', '1q22'), (u'bob', '1q2 for bob')]

    And even null adapters::

    >>> registry.register([], IP2, 'bob', 3)>>> adapters = list(registry.lookupAll([], IP1))>>> adapters.sort()>>> assert adapters == [(u'', 2), (u'bob', 3)]

    Subscriptions=============

    Normally, we want to look up an object that most-closely matches aspecification. Sometimes, we want to get all of the objects thatmatch some specification. We use subscriptions for this. We

    subscribe objects against specifications and then later find all ofthe subscribed objects::

    >>> registry.subscribe([IR1], IP2, 'sub12 1')>>> registry.subscriptions([IR1], IP2)['sub12 1']

    Note that, unlike regular adapters, subscriptions are unnamed.

    You can have multiple subscribers for the same specification::

  • 7/29/2019 Adapter-system LInux

    8/10

    >>> registry.subscribe([IR1], IP2, 'sub12 2')>>> registry.subscriptions([IR1], IP2)['sub12 1', 'sub12 2']

    If subscribers are registered for the same required interfaces, theyare returned in the order of definition.

    You can register subscribers for all specifications using None::

    >>> registry.subscribe([None], IP1, 'sub_1')>>> registry.subscriptions([IR2], IP1)['sub_1', 'sub12 1', 'sub12 2']

    Note that the new subscriber is returned first. Subscribers definedfor less general required interfaces are returned before subscribersfor more general interfaces.

    Subscriptions may be combined over multiple compatible specifications::

    >>> registry.subscriptions([IR2], IP1)['sub_1', 'sub12 1', 'sub12 2']>>> registry.subscribe([IR1], IP1, 'sub11')>>> registry.subscriptions([IR2], IP1)

    ['sub_1', 'sub12 1', 'sub12 2', 'sub11']>>> registry.subscribe([IR2], IP2, 'sub22')>>> registry.subscriptions([IR2], IP1)['sub_1', 'sub12 1', 'sub12 2', 'sub11', 'sub22']>>> registry.subscriptions([IR2], IP2)['sub12 1', 'sub12 2', 'sub22']

    Subscriptions can be on multiple specifications::

    >>> registry.subscribe([IR1, IQ], IP2, 'sub1q2')>>> registry.subscriptions([IR1, IQ], IP2)['sub1q2']

    As with single subscriptions and non-subscription adapters, you canspecify None for the first required interface, to specify a default::

    >>> registry.subscribe([None, IQ], IP2, 'sub_q2')>>> registry.subscriptions([IS, IQ], IP2)['sub_q2']>>> registry.subscriptions([IR1, IQ], IP2)['sub_q2', 'sub1q2']

    You can have subscriptions that are indepenent of any specifications::

    >>> list(registry.subscriptions([], IP1))[]

    >>> registry.subscribe([], IP2, 'sub2')>>> registry.subscriptions([], IP1)['sub2']>>> registry.subscribe([], IP1, 'sub1')>>> registry.subscriptions([], IP1)['sub2', 'sub1']>>> registry.subscriptions([], IP2)['sub2']

  • 7/29/2019 Adapter-system LInux

    9/10

    Unregistering subscribers-------------------------

    We can unregister subscribers. When unregistering a subscriber, wecan unregister a specific subscriber::

    >>> registry.unsubscribe([IR1], IP1, 'sub11')>>> registry.subscriptions([IR1], IP1)['sub_1', 'sub12 1', 'sub12 2']

    If we don't specify a value, then all subscribers matching the giveninterfaces will be unsubscribed:

    >>> registry.unsubscribe([IR1], IP2)>>> registry.subscriptions([IR1], IP1)['sub_1']

    Subscription adapters---------------------

    We normally register adapter factories, which then allow us to computeadapters, but with subscriptions, we get multiple adapters. Here's anexample of multiple-object subscribers::

    >>> registry.subscribe([IR, IQ], IM, M)>>> registry.subscribe([IR, IQ], IM, M2)

    >>> subscribers = registry.subscribers((x, q), IM)>>> len(subscribers)2>>> class_names = [s.__class__.__name__ for s in subscribers]>>> class_names.sort()>>> class_names['M', 'M2']>>> [(s.x is x and s.q is q) for s in subscribers][True, True]

    adapter factory subcribers can't return None values::

    >>> def M3(x, y):... return None

    >>> registry.subscribe([IR, IQ], IM, M3)>>> subscribers = registry.subscribers((x, q), IM)>>> len(subscribers)2

    Handlers--------

    A handler is a subscriber factory that doesn't produce any normaloutput. It returns None. A handler is unlike adapters in that it doesall of its work when the factory is called.

    To register a handler, simply provide None as the provided interface::

    >>> def handler(event):... print 'handler', event

  • 7/29/2019 Adapter-system LInux

    10/10

    >>> registry.subscribe([IR1], None, handler)>>> registry.subscriptions([IR1], None) == [handler]True