May 30, 2016

Примечание

Это статья из лекций http://lectureswww.readthedocs.io/6.www.sync/3.framework/pyramid/5.urldispatch.html

Traversal роутинг в Pyramid

Диспетчеризация URL

Каждый поступающий на сервер приложений Pyramid запрос (request) должен найти вид (view), который и будет его обрабатывать.

В Pyramid имеется два базовых подхода к поиску нужного вида для обрабатываемого запроса: на основе сопоставления (matching), как в большинстве подобных фреймворков, и обхода (traversal), как в Zope. Кроме того, в одном приложении можно с успехом сочетать оба подхода.

Pattern Matching

Простейший пример с заданием маршрута (заимствован из документации):

# Здесь config - экземпляр pyramid.config.Configurator
config.add_route('idea', 'site/{id}')
config.add_view('mypackage.views.site_view', route_name='idea')

Traversal

Использование обхода лучше проиллюстрировать на небольшом примере:

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

# Класс некоторого ресурса
class Resource(dict):
    pass

# Дерево ресурсов (жёстко закодированное) в фабрике корня
def get_root(request):
    return Resource({'a': Resource({'b': Resource({'c': Resource()})})})

# Вид-для-вызова, который умеет показывать ресурс Resource (в context)
def hello_world_of_resources(context, request):
    output = "Ресурс и его дети: %s" % context
    return Response(output)

if __name__ == '__main__':
    config = Configurator(root_factory=get_root)
    config.add_view(hello_world_of_resources, context=Resource)
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

В этом примере иерархия для обхода жестко задана в методе get_root с помощью вложенных словарей, тогда как реальные приложения должны сами определять необходимый доступ по ключам (метод __getitem__ помогает организовать такой доступ). В коде также присутствует корневая фабрика, с которой собственно и начинается обход узлов (node) дерева ресурсов. Вид-для-вызова (view callable) представлен функцией hello_world_of_resources. Говоря несколько упрощённо, на основе URL запроса в результате обхода иерархии Pyramid находит ресурс и применяет к нему «наилучший» вид-для-вызова (в нашем примере — он единственный).

Обход словаря

Предупреждение

В примерах используется Python3

Метод обхода дерева (traversal), позволяет находить ресурсы во вложенных структурах. Такой механизм хорошо применим для некоторых практических задач, например список всех URL маршрутов сайта является деревом, в котором каждый конечный URL это отдельная ветка дерева. Поэтому всю структуру сайта можно поместить в словарь.

К примеру, известно, что смерь сказочного персонажа “кащея” находится в яйце, которое в свою очередь в утке, которая в зайце и т.д. В сумме получается вложенная структура, которую можно описать так:

остров -> дуб -> сундук -> заяц -> утка -> яйцо -> игла -> СмертьКощея

Мы можем такую, плоскую, вложенную структуру легко представить в виде URL:

http://localhost:8080/mytraversal/остров/дуб/сундук/заяц/утка/яйцо/игла

А так-как любой URL является веткой дерева, то несложно описать это в Python:

{
     'остров': {
         'дуб': {
             'сундук': {
                 'заяц': {
                     'утка': {
                         'яйцо': {
                             'игла': СмертьКощея()
                         }
                     }
                 }
             }
         }
     }
 }

СмертьКощея() - это объект класса СмертьКощея, который может выглядеть к примеру так:

class СмертьКощея(object):

    def __json__(self, request):

        return {
            'имя': 'кощей',
            'статус': request.context == self and 'мертв' or 'жив ещё',
        }

В принципе, этого достаточно что бы наш сайт-убийца “кощея” заработал. Осталось лишь прописать пути и добавить представление (view).

View будет просто возвращать объект, например, если мы ввели:

URL: 'остров'
объект: {'дуб': {
            'сундук': {
                'заяц': {
                    'утка': {
                        'яйцо': {
                            'игла': СмертьКощея()
                        }
                    }
                }
            }
        }}
URL: 'остров/дуб'
объект: {'сундук': {
            'заяц': {
                'утка': {
                    'яйцо': {
                        'игла': СмертьКощея()
                    }
                }
            }
        }}
URL: 'остров/дуб/сундук'
объект: {'заяц': {
            'утка': {
                'яйцо': {
                    'игла': СмертьКощея()
                }
            }
        }}
URL: 'остров/дуб/сундук/заяц'
объект: {'утка': {
            'яйцо': {
                'игла': СмертьКощея()
            }
        }}
URL: 'остров/дуб/сундук/заяц/утка'
объект: {'яйцо': {
            'игла': СмертьКощея()
        }}
URL: 'остров/дуб/сундук/заяц/утка/яйцо'
объект: {'игла': СмертьКощея()}
URL: 'остров/дуб/сундук/заяц/утка/яйцо/игла'
объект: СмертьКощея()

Такие функции-представления (View) должны принимать 2 параметра, где первый параметр будет являться объектом, обычно именуемым context, а второй параметр request:

def traverse_koshey(context, request):
    return context  # Наш объект

Роуты создаются почти так же как в pattern matching, за исключением того, что структура путей передается в виде “фабрики”, которая возвращает словарь или ему подобный (dict-like) объект. Путь указывается в виде статической и динамической части, например /fixedpath/*traverse:

config.add_route('koshey', '/mytraversal/*traverse', factory=my_factory)

Фабрика, которая возвращает структуру сайта:

def my_factory(request):
    return {
        'остров': {
            'дуб': {
                'сундук': {
                    'заяц': {
                        'утка': {
                            'яйцо': {
                                'игла': СмертьКощея()
                            }
                        }
                    }
                }
            }
        }
    }

View добавляется стандартно:

config.add_view(traverse_koshey, route_name='koshey', renderer='json')

Все готово, можно перемещаться по объектам:

Полный пример:

Есть один нюанс, json renderer, по умолчанию, все не латинские символы отображает как UTF коды \uxxxx, поэтому мы увидим следующий вывод:

{"\u043e\u0441\u0442\u0440\u043e\u0432": {"\u0434\u0443\u0431": {"\u0441\u0443\u043d\u0434\u0443\u043a": {"\u0437\u0430\u044f\u0446": {"\u0443\u0442\u043a\u0430": {"\u044f\u0439\u0446\u043e": {"\u0438\u0433\u043b\u0430": {"\u0441\u0442\u0430\u0442\u0443\u0441": "\u0436\u0438\u0432 \u0435\u0449\u0451", "\u0438\u043c\u044f": "\u043a\u043e\u0449\u0435\u0439"}}}}}}}}

Но можно изменить его поведение следующим образом:

 from pyramid.renderers import JSON
 ...
 config.add_renderer('myjson', JSON(indent=4, ensure_ascii=False))
 config.add_view(traverse_koshey, route_name='koshey', renderer='myjson')

Результат:

http://localhost:8080/mytraversal/

{
    "остров": {
        "дуб": {
            "сундук": {
                "заяц": {
                    "утка": {
                        "яйцо": {
                            "игла": {
                                "имя": "кощей",
                                "статус": "жив ещё"
                            }
                        }
                    }
                }
            }
        }
    }
}

http://localhost:8080/mytraversal/остров/дуб/сундук/заяц/утка/яйцо/игла

{
    "имя": "кощей",
    "статус": "мертв"
}

Полный код:

Привязка View к ресурсам

В Пирамиде объект (context) который передается во вью, именуют еще как “ресурс”. Есть возможность жестко привязать View к типу ресурса. Например, наше представление traverse_koshey должно вызываться, только когда пришел объект класса СмертьКощея:

config.add_view(traverse_koshey, route_name='koshey_context',
                renderer='myjson',
                context=СмертьКощея)

Параметр context указывает на то, что это View принадлежит ТОЛЬКО объектам класса СмертьКащея.

Все пути, кроме полного (который возвращает нужный объект), вернут 404 код ответа. Полный путь http://localhost:8080/mytraversal/остров/дуб/сундук/заяц/утка/яйцо/игла.

Добавим в нашу структуру еще ресурсов:

def my_factory(request):
    return {
        'превед': Человек(),
        'остров': {
            'ясень': {
                'что то здесь': 'не так!'
            },
            'дуб': {
                'сундук': {
                    'заяц': {
                        'утка': {
                            'яйцо': {
                                'игла': СмертьКощея()
                            }
                        }
                    }
                }
            }
        }
    }

Здесь Человек() это новый тип ресурса, который имеет метод __getitem__ как у словаря и при обращении по ключу возвращает другой ресурс:

class Человек(object):

    name = 'Человек'

    def __getitem__(self, name):
        return Имя(name)


class Имя(object):

    __parent__ = Человек()

    def __init__(self, name):
        self.name = name

Например мы обращаемся по URL http://localhost:8080/mytraversal/превед/Пирамид. превед вернет ресурс Человек, а Пирамид вызовет метод __getitem__, который вернет ресурс Имя('Пирамид'). Таким образом мы можем строить дерево динамически при помощи dict-like объектов.

Для ресурса Имя мы можем создать отдельное представление и жестко привязать его к этому типу.

def traverse_hello(context, request):
    """
    http://localhost:8080/mytraversal/первед/Пирамид
    """
    return Response('Превед ' + context.__parent__.name + ' ' + context.name)

...

config.add_view(traverse_hello, route_name='koshey_context',
                renderer='text',
                context=Имя)

Результат вывода по адресу http://localhost:8080/mytraversal/превед/Пирамид, будет обычный текст (Content-Type: plain/text):

Превед Человек Пирамид

Полный пример:

Комбинация обоих методов

Фреймворк Pyramid позволяет использовать оба способа URL маршрутизации одновременно.

Добавим к примеру с “кащеем” hello world с использованием pattern matching:

def hello_world(request):
    return Response('Hello %(name)s!' % request.matchdict)

...

# Pattern matching routes
config.add_route('hello', '/hello/{name}')
config.add_view(hello_world, route_name='hello')

Полный пример:


Comments

comments powered by Disqus