human.rst 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. ==========================
  2. Using the Adapter Registry
  3. ==========================
  4. This is a small demonstration of the :mod:`zope.interface` package including its
  5. adapter registry. It is intended to provide a concrete but narrow example on
  6. how to use interfaces and adapters outside of Zope 3.
  7. First we have to import the interface package:
  8. .. doctest::
  9. >>> import zope.interface
  10. We now develop an interface for our object, which is a simple file in this
  11. case. For now we simply support one attribute, the body, which contains the
  12. actual file contents:
  13. .. doctest::
  14. >>> class IFile(zope.interface.Interface):
  15. ...
  16. ... body = zope.interface.Attribute('Contents of the file.')
  17. ...
  18. For statistical reasons we often want to know the size of a file. However, it
  19. would be clumsy to implement the size directly in the file object, since the
  20. size really represents meta-data. Thus we create another interface that
  21. provides the size of something:
  22. .. doctest::
  23. >>> class ISize(zope.interface.Interface):
  24. ...
  25. ... def getSize():
  26. ... 'Return the size of an object.'
  27. ...
  28. Now we need to implement the file interface. It is essential that the object states
  29. that it implements the ``IFile`` interface. We also provide a default body
  30. value (just to make things simpler for this example):
  31. .. doctest::
  32. >>> class File(object):
  33. ...
  34. ... zope.interface.implements(IFile)
  35. ... body = 'foo bar'
  36. ...
  37. Next we implement an adapter that can provide the ``ISize`` interface given any
  38. object providing ``IFile``. By convention we use ``__used_for__`` to specify the
  39. interface that we expect the adapted object to provide, in our case
  40. ``IFile``. However, this attribute is not used for anything. If you have
  41. multiple interfaces for which an adapter is used, just specify the interfaces
  42. via a tuple.
  43. Again by convention, the constructor of an adapter takes one argument, the
  44. context. The context in this case is an instance of ``File`` (providing ``IFile``)
  45. that is used to extract the size from. Also by convention the context is
  46. stored in an attribute named ``context`` on the adapter. The `twisted`_ community
  47. refers to the context as the ``original`` object. However, you may feel free to
  48. use a specific argument name, such as ``file``:
  49. .. doctest::
  50. >>> class FileSize(object):
  51. ...
  52. ... zope.interface.implements(ISize)
  53. ... __used_for__ = IFile
  54. ...
  55. ... def __init__(self, context):
  56. ... self.context = context
  57. ...
  58. ... def getSize(self):
  59. ... return len(self.context.body)
  60. ...
  61. Now that we have written our adapter, we have to register it with an adapter
  62. registry, so that it can be looked up when needed. There is no such thing as a
  63. global registry; thus we have to instantiate one for our example manually:
  64. .. doctest::
  65. >>> from zope.interface.adapter import AdapterRegistry
  66. >>> registry = AdapterRegistry()
  67. The registry keeps a map of what adapters implement based on another
  68. interface the object already provides. Therefore, we next have to register an
  69. adapter that adapts from ``IFile`` to ``ISize``. The first argument to
  70. the registry's ``register()`` method is a list of original interfaces.In our
  71. cause we have only one original interface, ``IFile``. A list makes sense, since
  72. the interface package has the concept of multi-adapters, which are adapters
  73. that require multiple objects to adapt to a new interface. In these
  74. situations, your adapter constructor will require an argument for each
  75. specified interface.
  76. The second argument is the interface the adapter provides, in our case
  77. ``ISize``. The third argument is the name of the adapter. Since we do not care
  78. about names, we simply leave it as an empty string. Names are commonly useful,
  79. if you have adapters for the same set of interfaces, but they are useful in
  80. different situations. The last argument is simply the adapter class:
  81. .. doctest::
  82. >>> registry.register([IFile], ISize, '', FileSize)
  83. You can now use the the registry to lookup the adapter:
  84. .. doctest::
  85. >>> registry.lookup1(IFile, ISize, '')
  86. <class 'FileSize'>
  87. Let's get a little bit more practical. Let's create a ``File`` instance and
  88. create the adapter using a registry lookup. Then we see whether the adapter
  89. returns the correct size by calling ``getSize()``:
  90. .. doctest::
  91. >>> file = File()
  92. >>> size = registry.lookup1(IFile, ISize, '')(file)
  93. >>> size.getSize()
  94. 7
  95. However, this is not very practical, since I have to manually pass in the
  96. arguments to the lookup method. There is some syntactic candy that will allow
  97. us to get an adapter instance by simply calling ``ISize(file)``. To make use of
  98. this functionality, we need to add our registry to the ``adapter_hooks`` list,
  99. which is a member of the adapters module. This list stores a collection of
  100. callables that are automatically invoked when ``IFoo(obj)`` is called; their
  101. purpose is to locate adapters that implement an interface for a certain
  102. context instance.
  103. You are required to implement your own adapter hook; this example covers one
  104. of the simplest hooks that use the registry, but you could implement one that
  105. used an adapter cache or persistent adapters, for instance. The helper hook is
  106. required to expect as first argument the desired output interface (for us
  107. ``ISize``) and as the second argument the context of the adapter (here
  108. ``file``). The function returns an adapter, i.e. a ``FileSize`` instance:
  109. .. doctest::
  110. >>> def hook(provided, object):
  111. ... adapter = registry.lookup1(zope.interface.providedBy(object),
  112. ... provided, '')
  113. ... return adapter(object)
  114. ...
  115. We now just add the hook to an ``adapter_hooks`` list:
  116. .. doctest::
  117. >>> from zope.interface.interface import adapter_hooks
  118. >>> adapter_hooks.append(hook)
  119. Once the hook is registered, you can use the desired syntax:
  120. .. doctest::
  121. >>> size = ISize(file)
  122. >>> size.getSize()
  123. 7
  124. Now we have to clean up after ourselves, so that others after us have a clean
  125. ``adapter_hooks`` list:
  126. .. doctest::
  127. >>> adapter_hooks.remove(hook)
  128. That's it. I have intentionally left out a discussion of named adapters and
  129. multi-adapters, since this text is intended as a practical and simple
  130. introduction to Zope 3 interfaces and adapters. You might want to read the
  131. ``adapter.txt`` in the ``zope.interface`` package for a more formal, referential
  132. and complete treatment of the package. Warning: People have reported that
  133. ``adapter.txt`` makes their brain feel soft!
  134. .. _twisted: https://twistedmatrix.com/