diff options
Diffstat (limited to 'docs/adapter.rst')
| -rw-r--r-- | docs/adapter.rst | 543 |
1 files changed, 543 insertions, 0 deletions
diff --git a/docs/adapter.rst b/docs/adapter.rst new file mode 100644 index 0000000..298a862 --- /dev/null +++ b/docs/adapter.rst @@ -0,0 +1,543 @@ +================ +Adapter Registry +================ + +Adapter registries provide a way to register objects that depend on +one or more interface specifications and provide (perhaps indirectly) +some interface. In addition, the registrations have names. (You can +think of the names as qualifiers of the provided interfaces.) + +The term "interface specification" refers both to interfaces and to +interface declarations, such as declarations of interfaces implemented +by 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 provide +interfaces. The registry doesn't care about what gets registered, so +we'll use integers and strings to keep the examples simple. There is +one exception. Registering a value of None unregisters any +previously-registered value. + +If an object depends on a specification, it can be looked up with a +specification 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) + + >>> registry.lookup([zope.interface.implementedBy(C2)], IP2, '') + 12 + + +and it can be looked up for interfaces that its provided interface +extends:: + + >>> registry.lookup([IR1], IP1, '') + 12 + >>> registry.lookup([IR2], IP1, '') + 12 + +But if you require a specification that doesn't extend the specification the +object 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 also +won'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 prefered +when using IR2:: + + >>> registry.register([IR2], IP1, '', 21) + >>> registry.lookup([IR2], IP1, '') + 21 + +Finding out what, if anything, is registered +-------------------------------------------- + +We can ask if there is an adapter registered for a collection of +interfaces. This is different than lookup, because it looks for an +exact 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 registered +exactly for the given interfaces. + +lookup1 +------- + +Lookup of single adapters is common enough that there is a specialized +version 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 one +object that implements an interface is adapted to another object that +supports a different interface. The adapter registry supports the +computation of adapters. In this case, we have to register adapter +factories:: + + >>> 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() + >>> y = registry.queryAdapter(x, IP1) + >>> y.__class__.__name__ + 'Y' + >>> y.context is x + True + +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 x + True + +When the adapter factory produces `None`, then this is treated as if no +adapter has been found. This allows us to prevent adaptation (when desired) +and let the adapter factory determine whether adaptation is possible based on +the 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 None + True + >>> 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 x + True + >>> y = registry.adapter_hook(IP1, x, 'bob') + >>> y.__class__.__name__ + 'Y2' + >>> y.context is x + True + +The `adapter_hook()` simply switches the order of the object and +interface arguments. It is used to hook into the interface call +mechanism. + + +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 specific +adapters 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 the +same 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 a +bug. Let's make sure this works now: + + >>> adapter = {} + >>> registry.register((), IQ, '', adapter) + >>> registry.lookup((), IQ, '') is adapter + True + +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 an +exception to the statement, made earlier, that the registry doesn't +care what gets registered. + +Multi-adapters +============== + +You can adapt multiple specifications:: + + >>> 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 q + True + +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 q + True + +Default Adapters +---------------- + +As with single adapters, you can define default adapters by specifying +None 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 a +specification. Sometimes, we want to get all of the objects that +match some specification. We use subscriptions for this. We +subscribe objects against specifications and then later find all of +the 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:: + + >>> registry.subscribe([IR1], IP2, 'sub12 2') + >>> registry.subscriptions([IR1], IP2) + ['sub12 1', 'sub12 2'] + +If subscribers are registered for the same required interfaces, they +are 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 defined +for less general required interfaces are returned before subscribers +for 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 can +specify 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'] + +Unregistering subscribers +------------------------- + +We can unregister subscribers. When unregistering a subscriber, we +can 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 given +interfaces 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 compute +adapters, but with subscriptions, we get multiple adapters. Here's an +example 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 normal +output. It returns None. A handler is unlike adapters in that it does +all 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 + + >>> registry.subscribe([IR1], None, handler) + >>> registry.subscriptions([IR1], None) == [handler] + True |
