Аннотации и аннотирование
2008-04-06 21:01Аннотирование - один из способов ассоциации раздельно разработанных компонент и объединения их в едином продукте. Наиболее известный пример аннотаций - это DublinCore, используемый для хранения всей метаинформации о компоненте совершенно независимым от него способом. В данной статье дан обзор этого подхода, уводящий значительно дальше, чем DublinCore, вплоть до построения ассоциаций компонент в период работы приложения.
Аннотации:
Аннотирование - способ ассоциации двух компонентов, использующий интерфейс IAnnotations, который предоставляет доступ к абстрактному хранилищу аннотаций (в качестве которого, чаще всего, выступает выделенный атрибут самого объекта). Аннотирование позволяет ассоциировать с компонентом дополнительные данные так, как будто они являются другим интерфейсом этого же компонента (о развитии этой идеи можно прочитать в Адаптация как способ ассоциации компонент.txt). Такое ассоциирование не требует изменений ни в схеме, ни в определении классов объектов, и является важным способом обеспечения независимости компонентов.
Самое заметное применение аннотаций в Zope3 - хранение метаданных DublinCore - достаточно хорошее и повсеместно используемое решение, хотя и сделанное с некоторым перегревом.
Изложенная ниже формализация работы механизма аннотаций немного отличается от общепринятой, но ничуть не хуже ее.
Рассказ об аннотировании должен включать в себя три составляющих: интерфейс IAnnotations, адаптер к IAnnotations и проектирование адаптера для доступа к аннотации через интерфейс IAnnotations, но основная цель этой статьи - рассказ об использовании IAnnotations и проектирование кода для доступа к аннотациям.
Немного теории:
Дадим формальное описание компонентов, используемых при программировании аннотаций и описывающих их терминов.
- Интерфейс IAnnotations
- это словарь, позволяющий получить
компонент, ассоциированный с данным, по уникальному ключу. В
качестве такого ключа принято использовать специальную нотацию,
гарантирующую уникальность: путь к объекту, реализующему
интерфейс ассоциированного компонента, разделенный точками (т.е.
так же, как в операторе import).
Принцип работы интерфейса аннотаций
- Адаптер IAnnotations
- аннотируемый компонент никогда не предоставляет интерфейс IAnnotations непосредственно, но может быть адаптирован к нему. Благодаря этому подходу, разработчик аннотируемого компонента может выбрать используемое хранилище аннотаций указанием специального интерфейса. На практике, наиболее распространенным является хранение аннотаций в специальном атрибуте объекта, которое включается декларацией у аннотируемого компонента интерфейса IAttributeAnnotatable. Такой компонент можно привести к интерфейсу IAnnotations и аннотировать.
- Хранилище аннотаций
- специальный компонент, доступ к которому
реализуется адаптером IAnnotations. Обратите внимание:
доступ к хранилищу осуществляется через интерфейс
IAnnotations, но сам компонент никогда не предоставляет этот
интерфейс непосредственно: он должен быть получен при
помощи адаптера, который и обеспечивает связь с хранилищем
аннотаций. Выбор адаптера осуществляется декларацией у
аннотируемого компонента специального интерфейса (например,
IAttributeAnnotatable), что эквивалентно выбору хранилища
аннотаций.
Адаптер к интерфейсу аннотации
- Адаптер к интерфейсу аннотации компонента
- специальный компонент, инкапсулирующий код доступа к компоненту, предоставляющий интерфейс аннотации. Пример такого адаптера приведен в практической части статьи.
В данной статье не будет рассматриваться проектирование хранилищ аннотаций. Способ реализации хранилища аннотаций вполне понятен из приведенного формального определения, а специальная статья об этом, возможно, будет написана когда-либо позднее. Цель же данной статьи - введение в практическое использование самих аннотаций.
Программирование адаптера к интерфейсу компонента-аннотации:
Если точно следовать приведенному формальному определению, то код, использующий IAnnotations выглядит примерно так:
annotations = IAnnotations(context) try : na = annotations[noteannotationkey] except KeyError : na = annotations[noteannotationkey] = NoteAnnotation()
Здесь
- context
- контекст, приводимый к интерфейсу IAnnotations;
- na
- компонент, используемый в качестве аннотации;
- noteannotationkey
- уникальный ключ, используемый при сохранении аннотаций данного типа.
Такой код реализован в noteannotationsimple (cм. noteannotationsimple/browser/edit.py), где он вынесен непосредственно в MixIn класс директивы form (единственный способ редактировать данные, получаемые "непонятно откуда"; ниже будет дан более вменяемый подход)).
Простейший способ получить доступ к аннотации через веб - задействовать пространство имен ++annotations++ (которое включено только в отладочном режиме, так как считается небезопасным). Это пространство имен позволяет получить доступ через веб к любой из аннотаций. Чтобы им воспользоваться, нужно выбрать в браузере вид "Интроспекция" (собственно пространство имен ++annotations++ введено в рамках продукта zope.app.apidoc.codemodule.browser.introspector), в конце которого указано ноль или более ссылок на доступные аннотации. Непосредственно вызвать объект-аннотацию можно добавив в URL строчку:
"++annotations++" <УНИКАЛЬНЫЙ КЛЮЧ>
Если компонент-аннотация предоставляет какие-то интерфейсы и для этих интерфейсов зарегистрированы виды - вы сможете увидеть их (как правило, вид "Интроспекция" есть всегда).
Но, так как обычно компонент-аннотация предоставляет один-единственный интерфейс, то возможен более удобный путь, позволяющий работать с данными аннотаций также, как с данными других интерфейсов аннотируемого компонента (даже вкладки видов редактирования появятся вместе с другими вкладками компонента без особых усилий). Чтобы обеспечить это, нужно инкапсулировать вышеприведенный код в специальный адаптер компонента к интерфейсу, предоставляемому компонентом-аннотацией. Пример такого адаптера можно посмотреть в noteannotation (noteannotation/noteannotableadapter.py), вот его текст:
from noteannotation import NoteAnnotation from zope.app.annotation.interfaces import IAnnotations from interfaces import noteannotationkey, INoteAnnotation def NoteAnnotableAdapter(context) : annotations = IAnnotations(context) try : na = annotations[uniqkey] except KeyError : na = annotations[uniqkey] = NoteAnnotation() return INoteAnnotation(na)
И фрагмент конфигурации :
<adapter factory=".noteannotableadapter.NoteAnnotableAdapter" provides=".interfaces.INoteAnnotation" for=".interfaces.INoteAnnotable" trusted="true" />
Тогда работа с аннотациями будет выглядеть просто и элегантно:
na = INoteAnnotation(obj)
что обеспечивает доступ к данным аннотации внутри исходного кода, причем, чисто синтаксически этот доступ неотличим от доступа к данным других интерфейсов, предоставляемых компонентом.
Очевидно, что в редком случае предоставления компонентом-аннотацией нескольких интерфейсов, потребуется несколько таких адаптеров.
Настройка аннотирования компонента:
В декларации адаптера указано, что он адаптирует интерфейс INoteAnnotable. Удобно, чтобы этот интерфейс существовал отдельно от аннотируемого компонента. В этом случае связывание аннотаций окажется возможным на этапе конфигурации сервера приложений Zope3, включением в конфигурацию специальной директивы ZCML :
<class class="note.note.Note"> <implements interface=".interfaces.INoteAnnotable"/> <implements interface="zope.annotation.interfaces.IAttributeAnnotatable"/> </class>
(еще раз обратите внимание на декларацию интерфейса хранилища аннотаций IAttributeAnnotable).
Более того, используя продукты, аналогичные ng.schema, предоставляющему специальные поля InterfaceChoice и InterfaceSet , позволяющие динамически установить интерфейсы компоненту, можно динамически настраивать экземпляр контент-объекта во время работы сервера, компонуя его из разных компонентов. Хороший пример такого подхода - это продукты ng.content.article, ng.content.annotation, в первом из которых экземпляр условной "статьи" по воле пользователя превращается в описание "продукта", "события" или "словарной статьи", каждый из которых обладает специальными полями и информерами для отображения этих полей.
Использование стандартных директив видов для редактирования аннотаций:
Практически все стандартные директивы создания видов и форм, описанные в "Директивы для создания видов и форм.txt", в своем коде явно приводят компонент к тому интерфейсу, для которого рисуется форма, поэтому они без малейших затруднений могут быть использованы для работы с аннотациями. Следует обратить внимание только на одну особенность их конфигурации: они должны вызываться для того интерфейса, который прописан в качестве входного для адаптера к интерфейсу аннотации, а вот форма рисоваться должна по схеме интерфейса аннотации.
В нашем примере, входной интерфейс адаптера - "interfaces.INoteAnnotable", интерфейс аннотации - ".interfaces.INoteAnnotation" (указаны, соответственно, в полях for и provides директивы adapter). В этом случае форма редактирования будет описана следующим образом :
<editform schema="..interfaces.INoteAnnotation" for="..interfaces.INoteAnnotable" label="Annotation" name="annotation.html" permission="zope.ManageContent" menu="zmi_views" title="Annotation" />
Аналогично и со всеми другими директивами управления формами (этот факт до сих пор использовался в качестве контрольного вопроса на понимание материала, и служил своеобразным "ослиным мостиком" поэтому стоит обратить особое внимание на него, особенно в сравнении с указаниями из "Директивы для создания видов и форм.txt").
Особенности реализации пользовательских интерфейсов для аннотаций:
В этом разделе дана только общая идея и немного рекламы продуктов ng.* ;), а подробные указания будут даны отдельной статьей.
Пользовательский интерфейс - это одна, максимум две страницы для одного компонента. А предоставляемых интерфейсов у компонента, построенного ассоциацией аннотаций, не только много, но они еще и меняются от экземпляра к экземпляру, да еще и на этапе выполнения. Поэтому обычный подход, связанный с адаптером вида (который адаптирует интерфейс контент-типа компонента) в этом случае проваливается: страница должна набираться из кусков, причем каждый кусок зависит от одного их интерфейсов-маркеров, а отображать на странице интерфейс, полученный адаптаций (аналогично директиве управления объектом). Примеры таких конструкций давно известны - например меню, собираемое из элементов в зависимости от объекта - но самой важной конструкцией являются так называемые viewlet'ы и viewlet-менеджеры. Документация об этом будет еще написана, а пока стоит посмотреть пример реализации ng.skin.base, в котором вся страница по умолчанию набрана из вот таких вьюлетов.
Несколько слов надо сказать и про меню форм редактирования в стандартном интерфейсе: если писать интерфейс редактирования "в лоб", то получится, как минимум, 2+N пунктов меню, где N-количество аннотаций, которых даже с маленьким CMS DreamBot запланировано около десятка, причем каждый пункт меню - это формочка из двух-трех полей. Решение дает продукт ng.zcmlmultiform, идея которого такая же, как и у вьюлетов: единая форма, составленная из элементов, по одному на каждый интерфейс. Пример использования продукта также можно посмотреть в ng.skin.base.
Заключение:
За рамками этой статьи осталось проектирование хранилищ аннотаций, которое заслуживает отдельного обсуждения. Но хочется подчеркнуть некоторый общий момент этой статьи: адаптация может использоваться как способ ассоциирования компонент, причем вплоть до ассоциирования в период исполнения, и аннотации - только один из способов использования такого подхода. При этом все, сказанное выше о декларации форм и программировании пользовательских интерфейсов остается в силе. С дальнейшим развитием этой темы можно ознакомится в Адаптация как способ ассоциации компонент.txt
И еще пара слов о контрольных вопросах. Попробуйте декларировать у компонента интерфейс IAnnotations, в каких случаях это будет работать и почему?