Версия 32 от 2010-07-12 09:51:35

Убрать это сообщение

Руководство по BlueBream (часть 2)

Содержание

  1. Интерфейсы (Interface)
    1. Введение
    2. Определение интерфейсов
    3. Пример
    4. Объявление интерфейсов
    5. Реализация интерфейсов
    6. Интерфейсы-маркеры
  2. Контент компоненты (Content component)
    1. Введение
  3. Схемы и виджеты
    1. Введение
    2. Схема против интерфейса
    3. Основные поля схемы
      1. Свойства, которые поддерживают все поля
      2. Bytes, BytesLine
      3. Text, TextLine
      4. SourceText
      5. Password
      6. Bool
      7. Int
      8. Float
      9. Datetime
      10. Tuple, List
      11. Dict
      12. Choice
      13. Object
      14. DottedName
      15. URI
      16. Id
      17. InterfaceField
    4. Автоматическая генерация форм с помощью пакета forms
      1. Текстовые виджеты
      2. Логические виджеты
      3. Виджеты единичного выбора
      4. Виджеты множественного выбора
      5. Другие виджеты
  4. Формы
    1. Введение
    2. Концепции
      1. Виджеты
      2. Действие (Action)
    3. Создание формы
    4. Создание формы редактирования (EditForm)
    5. Выводы
  5. Юнит тестирование (Unit testing)
    1. Введение
    2. Запуск тестов
  6. Компонентная архитектура Zope
    1. Введение
    2. Адаптеры
      1. Реализация
      2. Регистрация
      3. Получение адаптера
      4. Получения адаптера по интерфейсу
    3. Утилита
      1. Простая утилита
      2. Именованная утилита

Интерфейсы (Interface)

Введение

Интерфейсы - это объекты, которые определяют (документируют) внешнее поведение объектов, которые их предоставляют. Интерфейс определяет поведение через:

Вот несколько мотиваций для использования интерфейсов:

Классическая книга по программной инженерии Design Patterns, авторами которой являются Банда четырех рекомендует программировать на интерфейс, а не на реализацию. Определение формального интерфейса помогает в понимании системы. Более того, интерфейсы приносят все преимущества компонентной архитектуры Zope.

В некоторых современных языках программирования: Java, C#, VB.NET и т.д., интерфейсы - явный аспект языка. Поскольку в Python интерфейсов нет, Zope реализует их через механизм мета-классов, от которых необходимо наследоваться.

Я могу сделать X

У меня есть X

Вы можете сделать X используя меня

Понимание различий этих трех подходов очень важна, так как другие языки программирования не обязательно их реализуют. Обычно используется только первый.

Определение интерфейсов

В Java, например, интерфейсы - специальные типы объектов, которые могут служить в качестве интерфейсов в их ограниченной области видимости.

С другой стороны интерфейс из пакета zope.interface определяет интерфейс путем реализации мета-класса, одной из основополагающих концепций языка Python. Тем не менее, интерфейс просто используют существующий шаблон Python.

Пример

Вот классический пример в стиле hello world:

   1 >>> class Host(object):
   2 ...
   3 ...     def goodmorning(self, name):
   4 ...         """Say good morning to guests"""
   5 ...
   6 ...         return "Good morning, %s!" % name

В этом классе вы определили метод goodmorning. Если вы вызываете этот метод из экземпляра этого класса, он вернет Good morning, ...!.

   1 >>> host = Host()
   2 >>> host.goodmorning('Jack')
   3 'Good morning, Jack!'

Здесь host - экземпляр класса (объект), который использует код приведенный выше. Если вам нужны детали реализации, вам следует обратится к классу Host, либо через исходный код, либо через API документации.

Теперь будем использовать интерфейсы. Для данного класса определим интерфейс следующим образом:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class IHost(Interface):
   4 ...
   5 ...     def goodmorning(guest):
   6 ...         """Say good morning to guest"""

Как видите, интерфейс является потомком класса zope.interface.Interface. Префикс I - очень полезная конвенция для именования интерфейсов.

Объявление интерфейсов

В предыдущей главе Вы уже увидели как объявлять интерфейс с использованием пакета zope.interface. Эта глава объясняет технологию интерфейсов более подробно.

Возьмем в качестве примера следующее:

   1 >>> from zope.interface import Interface
   2 >>> from zope.interface import Attribute
   3 
   4 >>> class IHost(Interface):
   5 ...     """A host object"""
   6 ...
   7 ...     name = Attribute("""Name of host""")
   8 ...
   9 ...     def goodmorning(guest):
  10 ...         """Say good morning to guest"""

Интерфейс IHost обладает двумя атрибутами: name и goodmorning. Вспомните что, по крайней мере в Python, методы также являются атрибутами классов. Атрибут name определен с использованием класса zope.interface.Attribute. Когда вы добавляете атрибуту name к интерфейсу IHost, то не устанавливаете начальное значение. Здесь цель определения этого атрибута - просто показать, что любая реализация этого интерфейса будет содержать атрибут name. В этом случае, мы даже не указали тип атрибута! Вы можете передать строку документации в качестве первого параметра конструктору Attribute.

ругой атрибут, goodmorning - это метод, который определен с использованием синтаксиса функций. Заметьте, что self в этом случае не обязателен, потому что детали реализации - это уже класс. Например, модуль может реализовать интерфейс. Если модуль реализует этот интерфейс, он будет обладать атрибутом name и содержать определенную функцию goodmorning. Функция goodmorning будет принимать один аргумент.

Сейчас мы осветим связку интерфейс-класс-объект. Объект - это "реальная живая" вещь, и объекты являются экземплярами классом. Интерфейс - действительное формальное описание объекта, соответственно классы - просто реализация интерфейса. Вот почему следует программировать интерфейсы, а не реализации.

Теперь вам следует ознакомится еще с двумя понятиями объединяющими эти концепции. Первое - предоставлять (provide), а второе - реализовать (implement). Объекты предоставляют интерфейсы, а классы реализуют интерфейсы. Другими словами, объекты предоставляют интерфейсы, которые реализуют их классы. В предыдущем примере host (объект) предоставляет IHost (интерфейс), а Host (класс) реализует IHost (интерфейс). Один объект может предоставлять более одного интерфейса; также один класс может реализовать более одного интерфейса. Объекты также могут предоставлять интерфейсы прямо, вдобавок к тому, что реализуют их классы.

Классы - реализация объектов. В Python, классы - вызываемые объекты, так почему бы другим вызываемым объектам не реализовать интерфейс? Да, это возможно. Вы можете указать любому вызываемому объекту, что он создает объекты, предоставляющие некие интерфейсы, говоря что этот вызываемый объект реализует интерфейсы. Вызываемые объекты в общем случае называются фабриками (factories). Так как функции - вызываемые объекты, они могут реализовать интерфейсы.

Реализация интерфейсов

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

Представьте себе пример, в котором Host реализует IHost:

   1 >>> from zope.interface import implements
   2 
   3 >>> class Host(object):
   4 ...
   5 ...     implements(IHost)
   6 ...
   7 ...     name = u''
   8 ...
   9 ...     def goodmorning(self, guest):
  10 ...         """Say good morning to guest"""
  11 ...
  12 ...         return "Good morning, %s!" % guest

Если вам интересно, как работает функция implements, обратитесь к блогу James Henstridge (http://blogs.gnome.org/jamesh/2005/09/08/python-class-advisors/). В разделе адаптеры, вы найдете функцию adapts, которая работает таким же образом.

Так как Host реализует IHost, экземпляры класса Host предоставляют IHost. Существуют также вспомогательные методы для просмотра деклараций. Декларация также может быть описана вне класса. Если вы не сделали interface.implements(IHost), как показано в примере выше, то после определения класса, вы можете сделать следующее:

   1 >>> from zope.interface import classImplements
   2 >>> classImplements(Host, IHost)

Интерфейсы-маркеры

Интерфейсы могут использоваться для того, чтобы объявить о том, что объект принадлежит к некоему специальному типу. Интерфейс без каких-либо атрибутов и методов называют маркером.

Вот пример интерфейса-маркера:

   1 >>> from zope.interface import Interface
   2 
   3 >>> class ISpecialGuest(Interface):
   4 ...     """A special guest"""

Этот интерфейс может использоваться для того, чтобы объявить о том, что объект является специальным гостем.

Контент компоненты (Content component)

Введение

Взгляните на следующий пример:

   1 >>> from zope import interface
   2 
   3 >>> class IPerson(interface.Interface):
   4 ...     name = interface.Attribute("Name")
   5 >>> class Person(object):
   6 ...     interface.implements(IPerson)
   7 ...     name = None
   8 >>> jack = Person()
   9 >>> jack.name = "Jack"

В этом примере jack - это контент-компонент. Следовательно, контент-компонент - не что иное, как объект, который предоставляет определенный интерфейс. Как было сказано в предыдущей главе, можно использовать zope.schema для определения полей интерфейса. В предыдущем интерфейсе, их можно определить примерно так:

   1 >>> from zope import interface
   2 >>> from zope import schema
   3 
   4 >>> class IPerson(interface.Interface):
   5 ...     name = schema.TextLine(
   6 ...         title=u"Name",
   7 ...         description=u"Name of person",
   8 ...         default=u"",
   9 ...         required=True)

Если вы разрабатываете корпоративное приложение, содержимое (контент) - это одна из наиболее важных вещей, которую следует организовать в первую очередь. Для того, чтобы обучить вас писать приложения с контент-компонентами, эта глава содержит пример простого приложения сборщика заявок.

Сначала рассмотрим желание клиента, которое будет реализовано в следующих главах.

  1. Один контейнер заявок для каждого проекта. Много контейнеров могут быть добавлены в проект.
  2. В контейнер может быть добавлено любое количество заявок.
  3. Каждая заявка будет добавлена с описанием и одним начальным комментарием.
  4. Следующие комментарии могут быть добавлены к заявкам.
  5. Эта глава начинает простую реализацию сборщика заявок.

Как говорилось выше, наша цель - разработать полностью рабочий, хотя необязательно красиво выглядящий, веб ориентированный сборщик заявок. Корневым объектов будет Collector, который может содержать объекты заявок (Ticket) от различных пользователей. Так как вы хотите разрешить пользователям отвечать на заявки, вам следует предоставить заявкам возможность хранить в себе ответы, которые и есть объектами Comment.

Это значить, что у вас есть два компонента контейнера: Collector содержит только заявки, и может быть добавлен в любую папку Folder или другой контейнер, который способен содержать его. Для того, чтобы сделать сборщик заявок поинтереснее, он также должен иметь описание, которое вкратце представляет предмет/тему дискуссии. Заявки, с другой стороны могут хранится только в объекте Collector. Каждая из них будет иметь краткое и развернутое описание. И напоследок, Comment должен хранится только в заявках.

Такая настройка должна содержать все необходимые вещи, которые сделают проект полезным. Позже вы добавите еще много мета данных к этим компонентам для реализации более плотной интеграции с BlueBream и добавления дополнительного функционала.

Наиболее удобное место для расположения пакета - $HOME/myzope/lib/python. Для создания пакета, добавьте папку:

   1 $ cd $HOME/myzope/lib/python/
   2 $ mkdir collector

(на GNU/Linux).

Для того, чтобы сделать эту папку пакетом, добавьте туда пустой файл init.py. В GNU/Linux вы можете сделать примерно следующее:

   1 $ echo "# Make it a Python package" >> collector/__init__.py

но вы также можете использовать текстовый редактор и сохранить файл под этим именем. Просто удостоверьтесь, что в этом файле содержится правильный Python код. Файл должен содержать по крайней мере пробелы, так как пустые файлы плохо обрабатываются архиваторами.

Теперь вы будете работать только внутри пакета collector, который размещен в $HOME/myzope/lib/python/collector.

Схемы и виджеты

Введение

На ранних этапах разработки, разработчики BlueBream (раньше Zope 3) решили, что писать HTML формы вручную и вручную проверять правильность введенных данных довольно громоздко. Команда BlueBream поняла, что если расширить интерфейсы, можно будет автоматически генерировать формы и выполнять проверку введенных данных. Эта глава предоставляет информацию и формально представляет пакеты zope.schema и zope.formlib.

Schema - это интерфейс, который определен с помощью специальных атрибутов, называемых "полями".

Схема разрабатывалась для достижения трех основных целей:

Схема против интерфейса

Как упоминалось выше, схемы - это просто расширения интерфейсов и поэтому они зависят от пакета zope.interface. Поля схем равноценны методам в интерфейсах. Они совместимы друг с другом, так как описывают разные аспекты объекта. Методы интерфейса описывают функциональность компонента, тогда как поля схемы представляют состояние.

Поэтому разработка нового синтаксиса для написания схем необязательно, схемы просто используют декларацию интерфейса.

   1 from zope.interface import Interface
   2 from zope.schema import Text
   3 
   4 class IExample(Interface):
   5 
   6    text = Text(
   7        title=u"Text",
   8        description=u"The text of the example.",
   9        required=True)

Основные поля схемы

После того, как мы увидели простой пример схемы, давайте рассмотрим все основные поля и их свойства.

Свойства, которые поддерживают все поля

Bytes, BytesLine

Bytes и BytesLine отличаются только тем, что BytesLine не может содержать символ новой строки. Bytes ведут себя так же, как и строка в Python.

Поля Bytes и BytesLine доступны для выполнения итераций.

Text, TextLine

Два этих поля отличаются только тем, что TextLine не может содержать символ новой строки. Текстовые поля содержат юникод, а это значит, что они предназначены для хранения текстов, которые будут читать пользователи приложение.

Поля Text и TextLine доступны для выполнения итераций.

SourceText

SourceText - специальное поле, наследуемое от Text, которое содержит исходный код любого типа. Это поле более менее похоже на маркер для того, чтобы указать механизму создания форм, что нужны специальные поля ввода для исходников.

Password

Password - специальный дочерний класс поля TextLine, создан только в целях отделения механизмов отображения. Также для паролей обычно используется специальная проверка вводимых данных.

Bool

Поле Bool не имеет атрибутов. Оно отображает значение прямо в тип данных bool языка Pytho.

Int

Поля Int прямо отображаются в целочисленный тип языка Python.

Оба атрибута могут использоватся вместе, чтобы позволить разработчику устанавливать принимаемые значения.

Float

Поле Float прямо отображается в тип данных float языка Python.

Оба атрибута могут использоватся вместе, чтобы позволить разработчику устанавливать принимаемые значения.

Datetime

Похожее на Int и Float, Datetime, имеет поля min и max, определяет границы возможных значений. Принимаемые значения этих полей должны быть экземплярами встроенного типа данных datetime.

Tuple, List

Причина, по которой эти поля существуют такова, что они просто отображаются в родные типы данных Python - tuple и list, соответственно.

Поля Tuple и List доступны для итераций.

Dict

Dict - поле, что отображает множество значений на другое множество значений.

Это поле доступна для выполнения итераций.

Choice

Поле Choice позволяет выбрать конкретное значение из предостваленного списка. Вы можете предоставлять значения как простую последовательность (список или кортеж), либо указать словарь (vocabulary) (по ссылке или имени), который предоставляет эти значения. Словари предоставляют гибкий список значений, другими словами - множество допустимых значений меняется с изменениями в системе. Так как они более сложные, они описаны отдельно в "Словари и Поля".

Конструктор также принимает аргумент values, который определяет статическое множество значений. Эти значения сразу же конвертируются в статический словарь (vocabulary).

Object

Это поле определяет объект, который должен реализовать конкретную схему. Допускаются только объекты, которые реализуют схему.

DottedName

Наследовано от поля BytesLine, поле DottedName представляет правильные имена в стиле Python (ссылки на объекты). Это поле может быть использовано, когда нужно предоставить правильное имя в стиле Python.

Поле не имеет дополнительных атрибутов.

URI

Наследовано от поля BytesLine, поле URI уверяет, что значение - всегда правильный URI. Это особенно полезно, когда нужно хранить ссылки на ресурсы (такие как RSS ленты или изображения) на удаленных компьютерах.

Это поле не имеет дополнительных атрибутов.

Id

Вместе поля DottedName и !URI, создают поле Id. Любое имя или URI представляют правильный идентификатор в Zope. Идентификаторы используются для определения многих типов объектов, таких как права доступа и пользователи, но также используются для предоставления ключей аннотаций.

Это поле не имеет дополнительных атрибутов.

InterfaceField

Поле Interface не имеет дополнительных атрибутов. Это значение - это объект, который предоставляет zope.interface.Interface, другими словами - это должен быть интерфейс.

Для получения полного списка API схем/полей посмотрите инструмент документации по API по ссылке http://localhost:8080/++apidoc++ или модуль zope.schema.interfaces.

Автоматическая генерация форм с помощью пакета forms

Формы - более Zope-специфические, чем схемы, и находятся в пакете zope.formlib. Виды для полей схемы называются виджетами. Виджеты отвечают за отображение данных и их конвертацию в специальный тип данных для представления. На данный момент существуют виджеты в основном для HTML (веб обозревателя).

Виджеты разделены на две группы: отображения и ввода. Виджеты отображения зачастую очень простые и показывают только текстовое представление Python объекта. Виджеты ввода более сложные и имеют более широкий выбор. Следующий список показывает все доступные в обозревателе виджеты ввода (находятся в zope.formlib.widget):

Текстовые виджеты

Текстовые виджеты всегда требуют ввода с клавиатуры. Строковое представление поля потом конвертируется в нужный Python объект, например integer или date.

Логические виджеты

Ответственность логических виджетов заключается в том, чтобы сконвертировать некий двоичный ввод в значения True или False.

Виджеты единичного выбора

Виджеты, которые позволяют выбрать одно значение из списка, обычно являются видами для поля, словаря (vocabulary) и запроса (request), вместо пары "поле" и "запрос", поэтому используются прокси-виджеты, которые отображают "поле-запрос" в "поле-словарь-запрос". Например, ChoiceInputWidget, который принимает поле Choice и объект запроса, - это просто функция, которая ищет другой виджет, который зарегистрировать на поле Choice, его словарь и запрос. Ниже приведен список всех доступных виджетов выбора одного значения.

Виджеты множественного выбора

Эта группа виджетов используется для отображения форм ввода полей, основанных на коллекциях, таких как List или Set. Подобно виджетам единичного выбора, здесь используются прокси-виджеты с целью поиска правильного виджета. Первый шаг - отображение "поле-запрос" в "поле- тип значения-запрос", используя виджет CollectionInputWidget. Этот шаг позволяет использовать разные виджеты, когда, например, тип значения - поле "Int" или "Choice". Другой (не обязательный) прокси-виджет используется для отображения "поле-тип значения-запрос" в "поле-словарь-запрос", в том случае, если тип значения - поле выбора.

Другие виджеты

Вот простой интерактивный пример, который показывает отрисовку и конвертацию данных виджета:

   1 >>> from zope.publisher.browser import TestRequest
   2 >>> from zope.schema import Int
   3 >>> from zope.formlib.widget import IntWidget
   4 >>> field = Int(__name__='number', title=u'Number', min=0, max=10)
   5 >>> request = TestRequest(form={'field.number': u'9'})
   6 >>> widget = IntWidget(field, request)
   7 >>> widget.hasInput()
   8 True
   9 >>> widget.getInputValue()
  10 >>> print widget().replace(' ', '\n  ')
  11 <input
  12    class="textType"
  13    id="field.number"
  14    name="field.number"
  15    size="10"
  16    type="text"
  17    value="9"
  18 
  19 />

Заметьте, что вы, обычно, не будете иметь дело с этими методами напрямую вообще, так как генератор форм и конвертер данных делают всю работу за вас. Единственный метод, который обычно переопределяется - _validate(), который используется для проверки ввода пользовательских значений. Это ведет нас к следующей теме - пользовательские виджеты.

Есть два способа создавать пользовательские виджеты. Для небольших настроек параметров (свойств виджета), можно использовать поддирективу browser:widget директив browser:addform и browser:editform. Например, чтобы поменять виджет для поля name, можно использовать следующий ZCML код.

   1 <browser:addform
   2   ... >
   3 
   4   <browser:widget
   5       field="name"
   6       class="zope.formlib.widget.TextWidget"
   7       displayWidth="45"
   8       style="width: 100%"/>
   9 
  10 </browser:addform>

В этом случае мы заставляет систему использовать TextWidget для "name", устанавливаем ширину в 45 символов и добавляем атрибут "style", который пытается установить ширину поля ввода на максимально доступную.

Вторая возможность поменять виджет для поля - создать свой класс вида. Здесь, пользовательские виджеты легко реализуемые с помощью класса обертки CustomWidget. Вот вводный пример:

   1 from zope.formlib.widget import CustomWidget
   2 from zope.formlib.widget import TextWidget
   3 
   4 class CustomTextWidget(TextWidget):
   5     ...
   6 
   7 class SomeView:
   8     name_widget = CustomWidget(CustomTextWidget)

Более подробную информацию о схемах можно получить в файле README.txt пакета zope.schema. Сайт разработчиков Zope 3 также содержит немного дополнительного материала.

Это завершение ввода в схемы и формы. Для получения примеров по схемам и формам на практике, просмотрите первые главы раздела "Контент-компоненты - Основы".

Формы

Введение

BlueBream обладает библиотекой HTML форм - zope.formlib, предназначенной для генерации форм и виджетов. Вместо использования библиотеки, вы также можете создать форму вручную. Но formlib избегает большого количества рутинной и повторяющейся работы. formlib генерирует формы для получения пользовательского ввода. Вы также можете создавать проверки ввода и ответы.

Формы - это веб компоненты, которые используют виджеты для отображения и ввода данных. Обычно шаблон отображает виджеты, получая доступ к атрибуту или методу низлежащего класса. Библиотека form поддерживает проверку пользовательского ввода. Библиотека автоматически ковертирует отправленные пользователем данные форм в объекты языка Python.

Библиотека formlib предоставляет базовые классы для создания классов видов. Наиболее используемыми базовыми классами являются DisplayForm, AddForm и EditForm. DisplayForm, на самом деле, не является веб формой, которую можно заполнить и отправить, а просто удобным способом отображения значений, которые основаны на конкретном контексте/интерфейсе.

Есть еще другое сообщество, поддерживающее библиотеку z3c.form. Многие проекты используют эту библиотеку, также она хорошо задокументирована.

Концепции

Перед продвижением дальше давайте рассмотрим основополагающие концепции форм.

Виджеты

Formlib определяет виджет следующим образом: “виды привязанные к полям схемы”

=== Поля ==

Поля строятся на полях схемы.

=== Форма ==

Класс формы может определить упорядоченную коллекцию "полей формы" используя конструктор Fields. Поля формы отличаются и строятся на полях схемы. Поле схемы определяет значения атрибутов. Поля формы определяют то, как поле схемы используется в форме. Самый простой путь определить коллекцию полей формы - передать схему в конструктор Fields.

Действие (Action)

Создание формы

AddForm может использоваться как базовый класс для видов. Проимпортировать можно следующим образом:

from zope.formlib.form import AddForm A typical registration of view can be done like this:

   1 <browser:page
   2    for="zope.site.interfaces.IRootFolder"
   3    name="add_sample_app"
   4    permission="zope.ManageContent"
   5    class=".views.AddSampleApplication"
   6    />

Вам нужно определение схемы, как объяснено в предыдущей главе:

   1 class AddSampleApplication(form.AddForm):
   2 
   3     form_fields = form.Fields(ISampleApplication)
   4 
   5     def createAndAdd(self, data):
   6         name = data['name']
   7         description = data.get('description')
   8         namechooser = INameChooser(self.context)
   9         app = SampleApplication()
  10         name = namechooser.chooseName(name, app)
  11         app.name = name
  12         app.description = description
  13         self.context[name] = app
  14         self.request.response.redirect(name)

Создание формы редактирования (EditForm)

Использование EditForm очень похоже на использование AddForm.

Выводы

Этот раздел продемонстрировал библиотеку zope.formlib, которая используется для генерации HTML форм и виджетов.

Юнит тестирование (Unit testing)

Введение

Этот раздел обсуждает юнит тестирование и интегральное тестирование. Doctest-основанное тестирование очень тяжело использовать в Zope 3, а в Zope 3 преимущественно используется разработка на тестах (Test-driven Development). Для того, чтобы объяснить идею, давайте представим себе пример использования. Модулю необходима функция, которая возвращает Good morning, name!. name будет предоставлено в качестве аргумента. Перед написанием рабочего кода, напишите для него юнит тест. На самом деле написание кода и тестов осуществляется практически параллельно. Так что создадим файл с именем example1.py и с определением функции:

   1 def goodmorning(name):
   2     "This returns a good morning message"

Заметьте, что вы еще не написали никакой логики. Но очень необходимо запустить тесты успешно с провалами!. Так, теперь создадим файл с именем example1.txt, содержащий примеры использования, используя формат reStructuredText:

Вот тесты для модуля example1.

Сначала импорт модуля:

   1   >>> import example1

Теперь вызов функции goodmorning без аргументов:

   1 >>> example1.goodmorning()
   2 Traceback (most recent call last):
   3 ...
   4 TypeError: goodmorning() takes exactly 1 argument (0 given)

Теперь вызов функции goodmorning с одним аргументом:

   1 >>> example1.goodmorning('Jack')
   2 'Good morning, Jack!'

Заметьте, что примеры написаны так же, как будто выполнены из командной строки. Вы можете использовать командную строку python и вставлять код оттуда. Теперь создадите другой файл test_example1.py со следующим содержимым:

   1 import unittest
   2 import doctest
   3 
   4 def test_suite():
   5     return unittest.TestSuite((
   6         doctest.DocFileSuite('example1.txt'),
   7         ))
   8 
   9 if __name__ == '__main__':
  10     unittest.main(defaultTest='test_suite')

Вот начальный код для запуска теста. Теперь запустим тест командой python2.4 test_example1.py. Вы получите вывод со следующим текстом:

File "example1.txt", line 16, in example1.txt
Failed example:
    example1.goodmorning('Jack')
Expected:
    'Good morning, Jack!'
Got nothing

Как видим, один тест провалился, так что пришло время реализовать функцию:

   1 def goodmorning(name):
   2     "This returns a good morning message"
   3     return "Good morning, %s!" % name

Теперь запустите тест заново, он запустится без провалов.

Теперь подумайте о других функциях, необходимых этому модулю. Перед началом написания кода опишите его в текстовом файле. Опишите API, напишите тест, код, который продолжает этот цикл, пока все требования не будут удовлетворены.

Запуск тестов

Принято класть модули тестирования в модуль tests каждого пакета. Но файлы doctest-ов могут быть отправлены в свой пакет. Например, для пакета ticketcollector. В таком случае основной файл doctest-а может быть размещен по пути ticketcollector/README.txt, и создан пакет zopetic.tests, внутри этого пакета создайте модули тестирования, например test_main.py, test_extra.py и т.д.. Для запуска тестов, перейдите в домашний каталог экземпляра приложения:

   1 $ cd ticketcollector
   2 $ ./bin/test

Компонентная архитектура Zope

Введение

Zope Component Architecture (ZCA) is a framework for supporting component based design and programming. It is very well suited to developing large Python software systems. The ZCA is not specific to the BlueBream: it can be used for developing any Python application.

The ZCA is all about using Python objects effectively. Components are reusable objects with introspectable interfaces. A component provides an interface implemented in a class, or any other callable object. It doesn’t matter how the component is implemented, the important part is that it comply with its interface contracts. Using ZCA, you can spread the complexity of systems over multiple cooperating components. It helps you to create two basic kinds of components: adapter and utility.

There are two core packages related to the ZCA:

zope.interface is used to define the interface of a component. zope.component deals with registration and retrieval of components. Remember, the ZCA is not about the components themselves, rather it is about creating, registering, and retrieving components. Remember also, an adapter is a normal Python class (or a factory in general) and utility is a normal Python callable object.

The ZCA framework was developed as part of the BlueBream project. As noted earlier, it is a pure Python framework, so it can be used in any kind of Python application. There are many projects including non-web applications using it.

Адаптеры

Реализация

This section will describe adapters in detail. Zope component architecture, as you noted, helps to effectively use Python objects. Adapter components are one of the basic components used by Zope component architecture for effectively using Python objects. Adapter components are Python objects, but with well defined interface.

To declare a class is an adapter use adapts function defined in zope.component package. Here is a new FrontDeskNG adapter with explicit interface declaration:

>>> from zope.interface import implements >>> from zope.component import adapts

>>> class FrontDeskNG(object): ... ... implements(IDesk) ... adapts(IGuest) ... ... def init(self, guest): ... self.guest = guest ... ... def register(self): ... guest = self.guest ... next_id = get_next_id() ... bookings_db[next_id] = { ... 'name': guest.name, ... 'place': guest.place, ... 'phone': guest.phone ... } What you defined here is an adapter for IDesk, which adapts IGuest object. The IDesk interface is implemented by FrontDeskNG class. So, an instance of this class will provide IDesk interface.

>>> class Guest(object): ... ... implements(IGuest) ... ... def init(self, name, place): ... self.name = name ... self.place = place

>>> jack = Guest("Jack", "Bangalore") >>> jack_frontdesk = FrontDeskNG(jack)

>>> IDesk.providedBy(jack_frontdesk) True The FrontDeskNG is just one adapter you created, you can also create other adapters which handles guest registration differently.

Регистрация

To use this adapter component, you have to register this in a component registry also known as site manager. A site manager normally resides in a site. A site and site manager will be more important when developing a Zope 3 application. For now you only required to bother about global site and global site manager ( or component registry). A global site manager will be in memory, but a local site manager is persistent.

To register your component, first get the global site manager:

>>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager() >>> gsm.registerAdapter(FrontDeskNG, ... (IGuest,), IDesk, 'ng') To get the global site manager, you have to call getGlobalSiteManager function available in zope.component package. In fact, the global site manager is available as an attribute (globalSiteManager) of zope.component package. So, you can directly use zope.component.globalSiteManager attribute. To register the adapter in component, as you can see above, use registerAdapter method of component registry. The first argument should be your adapter class/factory. The second argument is a tuple of adaptee objects, i.e, the object which you are adapting. In this example, you are adapting only IGuest object. The third argument is the interface implemented by the adapter component. The fourth argument is optional, that is the name of the particular adapter. Since you gave a name for this adapter, this is a named adapter. If name is not given, it will default to an empty string (‘’).

In the above registration, you have given the adaptee interface and interface to be provided by the adapter. Since you have already given these details in adapter implementation, it is not required to specify again. In fact, you could have done the registration like this:

>>> gsm.registerAdapter(FrontDeskNG, name='ng') There are some old API to do the registration, which you should avoid. The old API functions starts with provide, eg: provideAdapter, provideUtility etc. While developing a Zope 3 application you can use Zope configuration markup language (ZCML) for registration of components. In Zope 3, local components (persistent components) can be registered from Zope Management Interface (ZMI) or you can do it programmatically also.

You registered FrontDeskNG with a name ng. Similarly you can register other adapters with different names. If a component is registered without name, it will default to an empty string.

Получение адаптера

Retrieving registered components from component registry is achieved through two functions available in zope.component package. One of them is getAdapter and the other is queryAdapter. Both functions accepts same arguments. The getAdapter will raise ComponentLookupError if component lookup fails on the other hand queryAdapter will return None.

You can import the methods like this:

>>> from zope.component import getAdapter >>> from zope.component import queryAdapter In the previous section you have registered a component for guest object (adaptee) which provides IDesk interface with name as ng. In the first section of this chapter, you have created a guest object named jack.

This is how you can retrieve a component which adapts the interface of jack object (IGuest) and provides IDesk interface also with name as ng. Here both getAdapter and queryAdapter works similarly:

>>> getAdapter(jack, IDesk, 'ng') #doctest: +ELLIPSIS <FrontDeskNG object at ...> >>> queryAdapter(jack, IDesk, 'ng') #doctest: +ELLIPSIS <FrontDeskNG object at ...> As you can see, the first argument should be adaptee then, the interface which should be provided by component and last the name of adapter component.

If you try to lookup the component with an name not used for registration but for same adaptee and interface, the lookup will fail. Here is how the two methods works in such a case:

>>> getAdapter(jack, IDesk, 'not-exists') #doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: ... >>> reg = queryAdapter(jack, ... IDesk, 'not-exists') #doctest: +ELLIPSIS >>> reg is None True As you can see above, getAdapter raised a ComponentLookupError exception, but queryAdapter returned None when lookup failed.

The third argument, the name of registration, is optional. If the third argument is not given it will default to empty string (‘’). Since there is no component registered with an empty string, getAdapter will raise ComponentLookupError. Similarly queryAdapter will return None, see yourself how it works:

>>> getAdapter(jack, IDesk) #doctest: +ELLIPSIS Traceback (most recent call last): ... ComponentLookupError: ... >>> reg = queryAdapter(jack, IDesk) #doctest: +ELLIPSIS >>> reg is None True In this section you have learned how to register a simple adapter and how to retrieve it from component registry. These kind of adapters is called single adapter, because it adapts only one adaptee. If an adapter adapts more that one adaptee, then it is called multi adapter.

Получения адаптера по интерфейсу

Adapters can be directly retrieved using interfaces, but it will only work for non-named single adapters. The first argument is the adaptee and the second argument is a keyword argument. If adapter lookup fails, second argument will be returned.

>>> IDesk(jack, alternate='default-output') 'default-output'

Keyword name can be omitted:

>>> IDesk(jack, 'default-output') 'default-output'

If second argument is not given, it will raise TypeError:

>>> IDesk(jack) #doctest: +NORMALIZE_WHITESPACE +ELLIPSIS Traceback (most recent call last): ... TypeError: ('Could not adapt',

Here FrontDeskNG is registered without name:

>>> gsm.registerAdapter(FrontDeskNG)

Now the adapter lookup should succeed:

>>> IDesk(jack, 'default-output') #doctest: +ELLIPSIS <FrontDeskNG object at ...> For simple cases, you may use interface to get adapter components.

Утилита

Now you know the concept of interface, adapter and component registry. Sometimes it would be useful to register an object which is not adapting anything. Database connection, XML parser, object returning unique Ids etc. are examples of these kinds of objects. These kind of components provided by the ZCA are called utility components.

Utilities are just objects that provide an interface and that are looked up by an interface and a name. This approach creates a global registry by which instances can be registered and accessed by different parts of your application, with no need to pass the instances around as parameters.

You need not to register all component instances like this. Only register components which you want to make replaceable.

Простая утилита

A utility can be registered with a name or without a name. A utility registered with a name is called named utility, which you will see in the next section. Before implementing the utility, as usual, define its interface. Here is a greeter interface:

>>> from zope.interface import Interface >>> from zope.interface import implements

>>> class IGreeter(Interface): ... ... def greet(name): ... """Say hello""" Like an adapter a utility may have more than one implementation. Here is a possible implementation of the above interface:

>>> class Greeter(object): ... ... implements(IGreeter) ... ... def greet(self, name): ... return "Hello " + name The actual utility will be an instance of this class. To use this utility, you have to register it, later you can query it using the ZCA API. You can register an instance of this class (utility) using registerUtility:

>>> from zope.component import getGlobalSiteManager >>> gsm = getGlobalSiteManager()

>>> greet = Greeter() >>> gsm.registerUtility(greet, IGreeter) In this example you registered the utility as providing the IGreeter interface. You can look the interface up with either queryUtility or getUtility:

>>> from zope.component import queryUtility >>> from zope.component import getUtility

>>> queryUtility(IGreeter).greet('Jack') 'Hello Jack'

>>> getUtility(IGreeter).greet('Jack') 'Hello Jack' As you can see, adapters are normally classes, but utilities are normally instances of classes. Only once you are creating the instance of a utility class, but adapter instances are dynamically created whenever you query for it.

Именованная утилита

When registering a utility component, like adapter, you can use a name. As mentioned in the previous section, a utility registered with a particular name is called named utility.

This is how you can register the greeter utility with a name:

>>> greet = Greeter() >>> gsm.registerUtility(greet, IGreeter, 'new') In this example you registered the utility with a name as providing the IGreeter interface. You can look up the interface with either queryUtility or getUtility:

>>> from zope.component import queryUtility >>> from zope.component import getUtility

>>> queryUtility(IGreeter, 'new').greet('Jill') 'Hello Jill'

>>> getUtility(IGreeter, 'new').greet('Jill') 'Hello Jill' As you can see here, while querying you have to use the name as second argument.

Calling getUtility function without a name (second argument) is equivalent to calling with an empty string as the name. Because, the default value for second (keyword) argument is an empty string. Then, component lookup mechanism will try to find the component with name as empty string, and it will fail. When component lookup fails it will raise ComponentLookupError exception. Remember, it will not return some random component registered with some other name. The adapter look up functions, getAdapter and queryAdapter also works similarly.

Перевод: Ростислав Дзинько