1.1.3 adapt() and the Adaptation Protocol

Component adaptation is the central focus of the protocols package.
All of the package's protocol declaration API depends on component adaptation
in order to function, and the rest of the package is just there to make it
easier for developers to use component adaptation in their frameworks and
programs.

Component adaptation is performed by calling the adapt() function,
whose design is based largely on the specification presented in PEP 246:

adapt(

component, protocol, [, default])

Return an implementation of protocol (a protocol object) for
component (any object). The implementation returned may be
component, or a wrapper that implements the protocol on its
behalf. If no implementation is available, return default. If no
default is provided, raise protocols.AdaptationFailure.

The component adaptation process performed by adapt() proceeds
in four steps:

If protocol is a class or type, and component is an instance
of that class or type, the component is returned unchanged. (This quickly
disposes of the most trivial cases).

If component has a __conform__ method, it is called,
passing in the protocol. If the method returns a value other than
None, it is returned as the result of adapt().

If protocol has an __adapt__ method, it is called,
passing in component. If the method returns a value other than
None, it is returned as the result of adapt().

Perform default processing as described above, returning default
or raising protocols.AdaptationFailure as
appropriate.

This four-step process is called the adaptation protocol. Note
that it can be useful even in the case where neither the component nor the
protocol object are aware that the adaptation protocol exists, and it
gracefully degrades to a kind of isinstance() check in that
case. However, if either the component or the protocol object has been
constructed (or altered) so that it has the appropriate __conform__
or __adapt__ method, then much more meaningful results can be
achieved.

Throughout the rest of this document, we will say that a component
supports a protocol, if calling adapt(component,protocol) does
not raise an error. That is, a component supports a protocol if its
__conform__ method or the protocol's __adapt__ method
return a non-None value.

This is different from saying that an object provides a protocol. An
object provides a protocol if adapt(ob,protocol) is ob. Thus,
if an object provides a protocol, it supports the protocol, but
an object can also support a protocol by having an adapter that provides the
protocol on its behalf.

Now that you know how adapt() works, you can actually make use of it
without any of the other tools in the protocols package. Just define
your own __conform__ and __adapt__ methods, and off
you go!

In practice, however, this is like creating a new kind of Python ``number''
type. That is, it's certainly possible, but can be rather tedious and is
perhaps best left to a specialist. For that reason, the protocols
package supplies some useful basic protocol types, and a ``declaration API''
that lets you declare how protocols, types, and objects should be adapted to
one another. The rest of this document deals with how to use those types and
APIs.

You don't need to know about those types and APIs to create your own kinds of
protocols or components, just as you don't need to have studied Python's
numeric types or math libraries to create a numeric type of your own. But,
if you'd like your new types to interoperate well with existing types, and
conform to users' expectations of how such a type behaves, it would be a good
idea to be familiar with existing implementations, such as the ones described
here.