Contents
Adapters allow extending the class behavior outside the class itself. This allows more modular, nice looking code in complex systems where there are hundreds of applicable methods per class.
The downside is that adapters cannot be found by “exploring” classes or source code. They must be well documented to be discoverable.
Read more about adapters in zope.component README.
Adapters are matched by
There are two kinds of adapters
Adapter provides functionality for an class and this functionality becomes available when interface is queried from the instance of class.
Below is an example how to make a custom “image provider”. Image provider provides a list of images for arbitary content.
This is the image provider interface:
from zope.interface import Interface
class IProductImageProvider(Interface):
def getImages(self):
""" Get Images associated with the product.
@return: iterable of Image objects
"""
This is our content class:
class MyShoppableItemType(folder.ATFolder):
""" Buyable physical good with variants of title and price and multiple images """
implements(IVariantProduct)
meta_type = "VariantProduct"
schema = VariantProductSchema
This is the adapter for the content class:
import zope.interface
from getpaid.variantsproduct.interfaces.multiimageproduct import IProductImageProvider
class FolderishProductImageProvider(object):
""" Mix-in class which provide product image management functions.
Assume the content itself is folderish archetype content type and
all contained image objects are product images.
"""
zope.interface.implements(IProductImageProvider)
def __init__(self, context):
# Each adapter takes the object itself as the contruction parameter
# and possibly provided other parameters for the interface adaption
self.context = context
def getImages(self):
""" Return sequence of images.
Perform folder listing and filter image content from it.
"""
images = self.context.listFolderContents(contentFilter={"portal_type" : "Image"})
return images
configure.zcml registers the adapter for my custom content type MyShoppableItemType:
<adapter for=".shop.MyShoppableItemType"
provides=".interfaces.IProductImageProvider"
factory=".images.FolderishProductImageProvider" />
Then we can query the adapter and use it. Unit testing example:
def test_get_images(self):
self.loginAsPortalOwner()
self.portal.invokeFactory("MyShoppableItemType", "product")
product = self.portal.product
image_provider = IProductImageProvider(product)
images = image_provider.getImages()
# Not yet any uploaded images
self.assertEqual(len(images), 0)
Following interfaces are useful when registering adapters
You can specify any number of interface in <adapter for=”” /> . Separate them with space or newline.
Below is a view like example which registers against
Emulate view registration (context, request):
<adapter for="zope.interface.Interface zope.publisher.interfaces.browser.IBrowserRequest"
provides="gomobile.mobile.interfaces.IMobileTracker"
factory=".bango.BangoTracker" />
There are two functions
- zope.component.getAdapter will raise exception if adapter is not found
- zope.component.queryAdapter will return None if adapter is not found
getAdapter/queryAdapter arguments
# Tuple consisting of (Object implementing interface in <adapter for=”“> first declaration, object implementing interface in <adapter for=”“> second declaration, ...)
# Adapter marker interface
Example registration:
<!-- Register header animation picking logic - override this for your custom logic -->
<adapter
provides="plone.app.headeranimation.interfaces.IHeaderAnimationPicker"
for="plone.app.headeranimation.behaviors.IHeaderBehavior
Products.CMFCore.interfaces.IContentish
zope.publisher.interfaces.browser.IBrowserRequest
"
factory=".picker.RandomHeaderAnimationPicker" />
Corresponding query code:
from zope.component import getUtility, getAdapter, getMultiAdapter
# header implements IHeaderBehavior
# doc implements Products.CMFCore.interfaces.IContentish
# request implements zope.publisher.interfaces.browser.IBrowserRequest
from Products.CMFCore.interfaces import IContentish
from zope.publisher.interfaces.browser import IBrowserRequest
self.assertTrue(IHeaderBehavior.providedBy(header))
self.assertTrue(IContentish.providedBy(doc))
self.assertTrue(IBrowserRequest.providedBy(self.portal.REQUEST))
# Throws exception if not found
picker = getMultiAdapter((header, doc, self.portal.REQUEST), IHeaderAnimationPicker)
The following code checks if IHeaderBehavior adapter is registered correctly:
from zope.component import getGlobalSiteManager
sm = getGlobalSiteManager()
registrations = [a for a in sm.registeredAdapters() if a.provided == IHeaderBehavior ]
self.assertEqual(len(registrations), 1)
Getting all multi-adapters (context, request)
Example:
from zope.component import getAdapters
adapters = getAdapters((context, request), provided=Interface)
Warning
This does not list locally registered adapters like Zope views.
Local adapters are effective only inside a certain container, like a folder. They use five.localsitemanager to register themselves.