===============================
Использование реестра адаптеров
===============================

Данный документ содержит небольшую демонстрацию пакета ``zope.interface`` и его
реестра адаптеров. Документ рассчитывался как конкретный, но более узкий пример
того как использовать интерфейсы и адаптеры вне Zope 3.

Сначала нам необходимо импортировать пакет для работы с интерфейсами::

  >>> import zope.interface

Теперь мы разработаем интерфейс для нашего объекта - простого файла. Наш файл
будет содержать всего один атрибут - body, в котором фактически будет сохранено
содержимое файла::

  >>> class IFile(zope.interface.Interface):
  ...
  ...     body = zope.interface.Attribute(u'Содержимое файла.')
  ...

Для статистики нам часто необходимо знать размер файла. Но было бы несколько
топорно реализовывать определение размера прямо для объекта файла, т.к. размер
больше относится к мета-данным. Таким образом мы создаем еще один интерфейс для
представления размера какого-либо объекта::

  >>> class ISize(zope.interface.Interface):
  ...
  ...     def getSize():
  ...         'Return the size of an object.'
  ...

Теперь мы должны создать класс реализующий наш файл. Необходимо что бы наш
объект хранил информацию о том, что он реализует интерфейс `IFile`. Мы также
создаем атрибут с содержимым файла по умолчанию (для упрощения нашего
примера)::

  >>> class File(object):
  ...
  ...      zope.interface.implements(IFile)
  ...      body = 'foo bar'
  ...

Дальше мы создаем адаптер, который будет предоставлять интерфейс `ISize`
получая любой объект предоставляющий интерфейс `IFile`. По соглашению мы
используем атрибут `__used_for__` для указания интерфейса который как мы
ожидаем предоставляет адаптируемый объект, `IFile` в нашем случае. На самом
деле этот атрибут используется только для документирования. В случае если
адаптер используется для нескольких интерфейсов можно указать их все в виде
кортежа.

Опять же по соглашению конструктор адаптера получает один аргумент - context
(контекст). В нашем случае контекст - это экземпляр `IFile` (объект,
предоставляющий `IFile`) который используется для получения из него размера.
Так же по соглашению контекст сохраняется а адаптере в атрибуте с именем
`context`. Twisted комьюнити ссылается на контекст как на объект `original`.
Таким образом можно также дать аргументу любое подходящее имя, например
`file`::

  >>> class FileSize(object):
  ...
  ...      zope.interface.implements(ISize)
  ...      __used_for__ = IFile
  ...
  ...      def __init__(self, context):
  ...          self.context = context
  ...
  ...      def getSize(self):
  ...          return len(self.context.body)
  ...

Теперь когда мы написали наш адаптер мы должны зарегистрировать его в реестре
адаптеров, что бы его можно было запросить когда он понадобится. Здесь нет
какого-либо глобального реестра адаптеров, таким образом мы должны
самостоятельно создать для нашего примера реестр::

  >>> from zope.interface.adapter import AdapterRegistry
  >>> registry = AdapterRegistry()

Реестр содержит отображение того, что адаптер реализует на основе другого
интерфейса который предоставляет объект. Поэтому дальше мы регистрируем адаптер
который адаптирует интерфейс `IFile` к интерфейсу `ISize`. Первый аргумент к
методу `register()` реестра - это список адаптируемых интерфейсов. В нашем
случае мы имеем только один адаптируемый интерфейс - `IFile`. Список
интерфейсов имеет смысл для использования концепции мульти-адаптеров, которые
требуют нескольких оригинальных объектов для адаптации к новому интерфейсу. В
этой ситуации конструктор адаптера будет требовать новый аргумент для каждого
оригинального интерфейса.

Второй аргумент метода `register()` - это интерфейс который предоставляет
адаптер, в нашем случае `ISize`. Третий аргумент - имя адаптера. Сейчас нам не
важно имя адаптера и мы передаем его как пустую строку. Обычно имена полезны
если используются адаптеры для одинакового набора интерфейсов, но в различных
ситуациях. Последний аргумент - это класс адаптера::

  >>> registry.register([IFile], ISize, '', FileSize)

Теперь мы можем использовать реестр для запроса адаптера::

  >>> registry.lookup1(IFile, ISize, '')
  <class '__main__.FileSize'>

Попробуем более практичный пример. Создадим экземпляр `File` и создадим адаптер
использующий запрос реестра. Затем мы увидим возвращает ли адаптер корректный
размер при вызове `getSize()`::

  >>> file = File()
  >>> size = registry.lookup1(IFile, ISize, '')(file)
  >>> size.getSize()
  7

На самом деле это не очень практично, т.к. нам нужно самим передавать все
аргументы методу запроса. Существует некоторый синтаксический леденец который
позволяет нам получить экземпляр адаптера просто вызвав `ISize(file)`. Что бы
использовать эту функциональность нам понадобится добавить наш реестр к списку
adapter_hooks, который находится в модуле с адаптерами. Этот список хранит
коллекцию вызываемых объектов которые вызываются автоматически когда вызывается
IFoo(obj); их предназначение - найти адаптеры которые реализуют интерфейс для
определенного экземпляра контекста.

Необходимо реализовать свою собственную функцию для поиска адаптера; данный
пример описывает одну из простейших функций для использования с реестром, но
также можно реализовать поисковые функции которые, например, используют
кэширование, или адаптеры сохраняемые в базе. Функция поиска должна принимать
желаемый на выходе интерфейс (в нашем случае `ISize`) как первый аргумент и
контекст для адаптации (`file`) как второй. Функция должна вернуть адаптер,
т.е. экземпляр `FileSize`::

  >>> def hook(provided, object):
  ...     adapter = registry.lookup1(zope.interface.providedBy(object),
  ...                                provided, '')
  ...     return adapter(object)
  ...

Теперь мы просто добавляем нашу функцию к списку `adapter_hooks`::

  >>> from zope.interface.interface import adapter_hooks
  >>> adapter_hooks.append(hook)

Как только функция зарегистрирована мы можем использовать желаемый синтаксис::

  >>> size = ISize(file)
  >>> size.getSize()
  7

После нам нужно прибраться за собой, что бы другие получили чистый список
`adaper_hooks` после нас::

  >>> adapter_hooks.remove(hook)

Это все. Здесь намеренно отложена дискуссия об именованных и мульти-адаптерах,
т.к. данный текст рассчитан как практическое и простое введение в интерфейсы и
адаптеры Zope 3. Для более подробной информации имеет смысл прочитать
`adapter.txt` из пакета `zope.interface`, что бы получить более формальное,
справочное и полное трактование пакета. Внимание: многие жаловались, что
`adapter.txt` приводит их мозг к расплавленному состоянию!
