May 30, 2016
|
Примечание
Это статья из лекций http://lectureswww.readthedocs.io/6.www.sync/3.framework/pyramid/5.urldispatch.html
См.также
Каждый поступающий на сервер приложений Pyramid запрос (request) должен найти вид (view), который и будет его обрабатывать.
В Pyramid имеется два базовых подхода к поиску нужного вида для обрабатываемого запроса: на основе сопоставления (matching), как в большинстве подобных фреймворков, и обхода (traversal), как в Zope. Кроме того, в одном приложении можно с успехом сочетать оба подхода.
Простейший пример с заданием маршрута (заимствован из документации):
# Здесь config - экземпляр pyramid.config.Configurator
config.add_route('idea', 'site/{id}')
config.add_view('mypackage.views.site_view', route_name='idea')
Использование обхода лучше проиллюстрировать на небольшом примере:
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/остров/дуб/сундук/заяц/утка/яйцо/игла
{
"имя": "кощей",
"статус": "мертв"
}
Полный код:
В Пирамиде объект (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')
Полный пример: