2008-04-06

Аннотации и аннотирование

Andrey Orlov  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, в каких случаях это будет работать и почему?

Официальный сайт Zope3 Московская группа изучения реактивного движения The Dream Bot Site noooxml