2008-01-11

Траверсеры.txt

  2008-01-11 19:00

...

Траверсеры:

Как рассказывалось в Сценарий обслуживания запроса.txt, траверсер - это вынесенный в отдельный класс алгоритм поиска и активации объекта в базе данных. Траверсер может делать массу неожиданных вещей:

  • Изменять среду выполнения запроса или сам запрос, см. например SiteManager.txt;
  • Вернуть вместо одного объекта другой, созданный на его основе;
  • Применить особенный алгоритм для поиска объекта по идентификатору;
  • Запросить объект из внешнего хранилища.

В качестве примера, сделаем нечеткий траверсер. Суть его в том, чтобы по указанному в URL идентификатору объекта находить наиболее похожий на него из числа идентификаторов, имеющихся в наличии. Рассмотрим пример fuzzytraverser.

traverser

traverser

Любой траверсер - это вид (адаптер) преобразующий некоторый интерфейс к следующему интерфейсу:

        zope.publisher.interfaces.browser.IBrowserPublisher.

Для того, чтобы траверсер вызывался при прохождении некоторого объекта, этот объект должен предоставлять указанный интерфейс. Cоздадим интерфейс IFuzzyFolder для такой цели (см. файл interfaces.py):

        from zope.app.container.interfaces import ISimpleReadContainer
        class IFuzzyFolder(ISimpleReadContainer):
            """Marker for folders whose contained items keys are fuzzy."""

теперь зарегистрируем его::

теперь зарегистрируем траверсер:

        <view
            for=".interfaces.IFuzzyFolder"
            type="zope.publisher.interfaces.browser.IBrowserRequest"
            factory=".fuzzytraverser.FuzzyTraverser"
            provides="zope.publisher.interfaces.browser.IBrowserPublisher"
            permission="zope.Public" 
        /> 

А теперь посмотрим, как он устроен:

        from zope.publisher.interfaces import NotFound 
        from zope.app import zapi 
        from zope.app.container.traversal import ContainerTraverser 
        import difflib
        from interfaces import IFuzzyFolder

        class FuzzyTraverser(ContainerTraverser) :

            __used_for__ = IFuzzyFolder 

            def publishTraverse(self, request, name): 
                """See zope.publisher.interfaces.browser.IBrowserPublisher""" 
                subob = self.get(name) 

                if subob is not None: 
                    return subob 

                return super(FuzzyTraverser, self).publishTraverse(request, name)

            def get(self, name) :
                res = self.context.get(name, None)

                if res is not None :
                    return res

                imax = 0.5
                nmax = None
                for key in self.context.keys() :
                    i = difflib.SequenceMatcher(None,name,key).ratio()
                    if i >= imax :
                        imax = i
                        nmax = key

                return self.context.get(nmax,None)

Библиотека difflib используется, чтобы найти расстояния (в терминах "длины патча" или дистанции Левенштейна) между предъявленным и имеющимся идентификатором. Если похожего (с дистанцией меньше 0.5) не было найдено - то, значит такого идентификатора нет. Как показывает практика, такой простой алгоритм легко решает проблему легких опечаток в написании URL-а.

Посмотрим, как включается использование такого траверсера для обычной папки (Folder). В fuzzytraverser.demo.configure указываем:

        <configure
            xmlns:browser="http://namespaces.zope.org/browser"
            xmlns="http://namespaces.zope.org/zope">

          <class
              class="zope.app.folder.folder.Folder">

            <implements interface="..interfaces.IFuzzyFolder"/>

          </class>

        </configure>

Теперь любая папка (Folder) будет обрабатываться нашим траверсером и будет не чувствительна к ошибкам в наборе URL.

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

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

Чтобы оценить количество работы, требуемое для превращение учебного примера в рабочий продукт, можно взглянуть на продукт ng.fuzzytraverser, доступный на PyPI: и строк стало больше, и необходимыми дополнениями обросло.

Ссылки на эту статью:

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