Использование схем интерфейсов.txt
2008-01-11 20:17Использование схем интерфейсов: В Zope понятия схемы интерфейса и самого интерфейса немного смешены: схема является продолжением определения интерфейса и, глядя на код, это выглядит как единое целое. Использование схем: Ограничение обращений к атрибутам описаниями схемы: Это та опция, которая в Zope фактически отсуствтует. Автоматическая генерация кляуз безопасности: Едва ли не самое существенное преимущество Zope перед другими аналогичными системами - это развитая система безопасности. ...
Использование схем интерфейсов:
В Zope понятия схемы интерфейса и самого интерфейса немного смешены: схема является продолжением определения интерфейса и, глядя на код, это выглядит как единое целое. Однако в наших определяниях они отличаются.
Краткие определения терминов схемы интерфейса:
- Схема (Схема Интерфейса)
- Декларация атрибутов и методов, наличие которых ожидается от объекта, для которого декларировано наличие интерфейса. Схема не является обязательной частью интерфейса, но является его удобным расширением;
- Поле (Поле схемы)
- Декларация конкретного атрибута схемы. Поле обладает интерфейсом, рядом настроечных аргументов, и позволяет описать тип атрибута схемы: что в нем может содержаться и в каком качестве. Поле содержит как описательные (название, описание), так и декларативные (обязательность, тип, умолчание) поля;
- Widget
- Адаптер поля к виду, взаимодействующему с клиентом. Обычно вид отвечает за то, чтобы значение, передаваемое в поле и получаемое из него, преобразовывалось из внутреннего формата во внешний и обратно.
- Ограничения
- Ограничения, накладываемые интерфейсом на предоставляющий его класс. Имеют специальный синтаксис, но есть много важных частных случаев, когда запрограммированные обертки специального назначения имеют другой синтаксис.
Определение схем:
Определение схемы интерфейса осуществляется прямо в теле определения самого интерфеса: атрибутам интерфейса назначаются поля, функции интерфейса декларируются и к ним привязываются ограничения.
Рассмотрим интерфейс объекта IPersona для гипотетической адресной книги:
from zope.interface import Interface,Invalid,invariant
from zope.schema import Text, TextLine, Int
def check_age(ob) :
if not (0 < ob.age < 120) :
raise Invalid
class IPersona(Interface):
"""A Persona object."""
title = TextLine(
title=u"FIO",
description=u"First, Last name and so on.",
default=u"",
required=True)
about = Text(
title=u"About this perosna",
description=u"Short text about this persona.",
default=u"",
required=False)
profession = TextLine(
title=u"Profession",
description=u"Profession and expirience.",
default=u"",
required=True)
age = Int(
title=u"Age",
description=u"Age.",
default=0,
required=True)
def HappyBirthDay(miss=Int(title=u"Count of missed years",default=1)) :
pass
invariant(check_age)
Расшифруем некоторые части кода для большей понятности:
- IPersona
- собственно интерфейс;
- title, about и т.п.
- атрибуты схемы интерфейса, которым назначены поля;
- Text,TextLine,Int
- поля, назначенные атрибутам.
- HappyBirthDay
- функция, определенная в интерфейсе. Как уже упоминалось, она не содержит класса;
- invariant(check_age)
- ограничение, не позволяющие классу выйти за пределы, допустимые условием check_age (в данном случае проверяется диапазон возраста).
Дальнейшие примеры будут оснаваны на этой схеме.
На самом деле это очень простая схема, потому что все поля используются для ввода простых значений. Есть более сложные случаи: ввод кортежей, множеств и даже объектов. Пример будет рассмотрен в статье Использование сложных полей ввода.txt.
Использование схем:
Ограничение обращений к атрибутам описаниями схемы:
Это та опция, которая в Zope фактически отсуствтует. Ее можно было бы ввести, переопределив базовый класс Interface (что сделало бы работу более сложной, зато код - более устойчивым), но этого никто не сделал. В то же время, следует помнить, что реально из-за использования схем для автоматической генерации адаптеров, деклараций безопасности и многих других дел, такое ограничение регулярно всплывает в какой-либо форме (например, надписи "Forbidden"), поэтому стоит следовать рекомендациям введения в компоненты:
- Обращайтесь к атрибутам и функциям компонента только через интерфейс, к которому его привели;
- Никогда не передавайте интерфейс компонента вместо самого компонента.
Автоматическая генерация кода:
Интерфейс позволяет провести некоторую интроспекцию объекта, которую лучше назвать "экстроспекцией", потому что не происходит погружение внутрь объекта, чтобы узнать, что в нем есть на самом деле. Наоборот, исследуется внешнее описание, чтобы узнать что в нем должно быть.
Приведем два примера такого кода, более-менее полезные практически. Первый - это отбор и установка значений атрибутов, что часто используется в классе ZCML-директивы form. Класс, о котором идёт речь, задаётся атрибутом class директивы form (реальный пример подобного кода можно найти внутри продукта zope.app.file):
def update(self,**kw) :
for name in IA.names :
setattr(IA(self.context),kw[name])
Второй пример - это установка в классе специальных "полей свойств". Фактически, это некоторое нарушение базовой модели, связанное с дублированием интерфейса в самом классе. Класс Persona может выглядеть так:
class Persona(object) :
implements(IPersona)
title = ""
about = ""
profession = ""
age = 0
def HappyBirthDay(self,miss=1) :
pass
И остается только надеяться (совершенно оправданно, кстати), что вся среда взаимодействия с компонентом IPersona установит ей правильные значения.
Существует способ, позволяющий гарантировать правильную установку значений: использование FieldProperty :
from zope.schema.fieldproperty import FieldProperty
class Persona(object) :
implements(IPersona)
title = FieldProperty(IPersona['title'])
about = FieldProperty(IPersona['about'])
profession = FieldProperty(IPersona['profession'])
age = FieldProperty(IPersona['age'])
def HappyBirthDay(self,miss=1) :
pass
Это даст гарантию того, что атрибутам будут установлены правильные значения по умолчанию и что некоторые, входящие в поле, проверки, будут выполнены при присваивании значения атрибуту.
То же самое можно сделать более удобным способом:
from zope.schema.fieldproperty import FieldProperty
class Persona(object) :
implements(IPersona)
for name in IPersona.names() :
setattr(Persona,name,FieldProperty(IPersona[name]))
<a title="Документ: не тестировалось" href="%40%40searchpage.html?name.any_of:record:tuple=%D0%BD%D0%B5%20%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BB%D0%BE%D1%81%D1%8C">не тестировалось</a>
Я на досуге написал готовое решение такого рода: доработанный implements, который можно найти в ng.lib, хотя, как я неоднократно подчеркивал - я против таких методов. Проверки должны делаться на компонентном уровне взаимодействия, т.е. неверное значение просто не должно попасть на уровень объектного взаимодействия. А раз так, то проверка в FieldProperty будет повторной и, следовательно, излишней. К сожалению нестрогость и некоторая недостаточность компонентной модели Zope оставляет это делом вкуса.
Автоматическая генерация кода класса:
Из вышепрведенных примеров очевидна возможность написания программы, генерирующей текст класса по его интерфейсу. Такая программа действительно есть, называется pyskel и позволяет генерировать код скелета объекта по его схеме. Ниже следует пример такого кода для класса Persona:
from zope.interface import implements
from ipersona import IPersona
class Persona:
__doc__ = IPersona.__doc__
implements(IPersona)
def HappyBirthDay(self, miss=<zope.schema._bootstrapfields.Int object at 0xb7b923ac>):
"See IPersona"
# See book.messageboard.ipersona.IPersona
about = None
# See book.messageboard.ipersona.IPersona
age = None
# See book.messageboard.ipersona.IPersona
profession = None
# See book.messageboard.ipersona.IPersona
title = None
Легко заметить, что утилита не использует FieldPriperty, а значит либо она была написана раньше, либо авторы разделяют вышеприведенное мнение.
Автоматическая генерация форм:
Намного важнее возможность сгенерировать код формы, используемой для работы с объектом по готовой схеме. Как показано в примере, описание схемы объекта состоит в присваивании каждому атрибуту объекта некоторого значения, называемого Поле. Каждое Поле имеет интерфейс, благодаря которому к нему привязывается Widget. Виджет позволяет ввести значение неким стандартным способом и проверить его допустимость (иногда - изменить до разрешенного значения).
В случае веб-форм (а это не единственный случай, когда можно использовать концепцию поле-виджет, пример - ng.ftp), виджет отвечает за отрисовку фрагмента формы, соотвествующей полю, а также за проверку и преобразование поля из формы удобной для восприятия человеком в машинное представление и обратно.
Приведем код декларации формы редактирования IPersona:
<browser:editform
name="persona.html"
for=".interfaces.IPersona"
permission="zope.ManageContent"
/>
Его достаточно, чтобы Zope сгенерировал форму редактирования объекта с интерфейсом IPersona, форму, в которой для каждого элемента будет отображен подходящий widget для ввода, а все вводимые значения будут проверятся на ограничения, описанные в полях интерфейса.
Таким образом, для создания веб-интерфейса по управлению компонентами, достаточно декларировать желаемые виды для этих компонентов, а вся остальная работа будет проделана автоматически.
Автоматическая генерация кляуз безопасности:
Едва ли не самое существенное преимущество Zope перед другими аналогичными системами - это развитая система безопасности. Основным уровнем грануляции безопасности является интерфейс. Иными словами, запрещается или разрешается не обращение к определенным атрибутам, а приведение к определенным интерфейсам. Разумеется, это можно ограничить и до уровня атрибутов. Но в принципе, такая возможность представляется вспомогательной и ее использование не рекомендуется.
ПРИМЕР :
Рассмотрим кусок кода ZCML, регистрирующий объект персона:
<content class="persona.Persona">
<implements
interface=".interface.IEmployment"
/>
<factory
id="persona.Persona"
description="Some perosna description"
/>
<require
permission="persona.view"
interface="ipersona.IPersona"
/>
<require
permission="persona.taskforse"
set_schema=".interfaces.IEmployment"
/>
</content>
Видно, что к имеющемуся интерфейсу добавлен еще один (ранее не декларированный), основной интерфейс (просмотр личных данных) разрешен для использования всем, обладающим привилегией persona.view, а новый интерфейс IEmployment - только тем, кто обладает привилегией persona.taskforce (участие в рабочей группе, например). Если интерфейсы, участвующие в раздаче прав какого-то компонента, пересекутся хотя бы по одному атрибуту - возникнет конфликт, который придется разрешить, например, введением дополнительного интерфейса.
Тестирование :
Объектов в системе много. Интерфейсов значительно меньше. Писать полный набор тестов для каждого объекта - тяжело, и, собственно, неразумно. Прохождение объектом теста означает только то, что данный объект может каким-либо образом использоваться, что, в общем, примерно совпадает с тем, что говориться про интерфейс. Поэтому тесты пишутся для интерфейсов объекта. Мало того, случаи, когда необходимо полностью, с нуля написать весь тест, относительно редки, так необходимы только при введении уникальных интерфейсов (что происходит редко, даже при построении больших систем). А для уже известных интерфейсов проще взять готовый тест: Тесты некоторых интерфейсов, делающие полное покрытие интерфейса, написать очень сложно. Как пример можно привести тестирование {} тестами для интерфейсов IContainer: эти тесты охватывают много неочевидных случаев. .



