Вот с последним всё оказалось очень и очень фигово. Класс системы ивентов, который принимает контекст, пользователя и соединение с бд, должен был импортироваться в куче классов, при этом сам должен иметь возможность импортировать другие классы для хендлеров ивентов. В общем, циклическая зависимость. Тучи их. Пришлось отказаться и сделать более прозрачно. С другой стороны, достаточно логично.
С написанием больших приложений на пайтоне, проблема циклических зависимостей встаёт всё острее и острее. Стоит помнить одно — нужно проектировать систему на БУМАГЕ перед имплементацией и сразу выделять где есть кольца. Не всегда это "плохой дизайн", но решать лучше проблемы до написания сотен строк кода.
Кстати говоря, для решения проблемы циклических зависимостей можно использовать Слабые ссылки (Weak References). Как я понял их суть(хотя я пока о них не читал почти ничего) в создании ссылок на первые объекты в циклической зависимости. Т.е. тогда она станет линейной. Потому почитаю о них и попробую что и как.
Главный недостаток всего этого — документация. Её хоть и много, но примеров не достаточно, не везде и не всё изложено как надо, слишком много информации разбросано, слишком мало информации по некоторым частям. Трудно всё понять сразу.
Пока всё идёт к разделению разработки проекта на такие части:
1) Реализация видов(бывшие экшны в контроллерах). К слову, виды можно реализовывать в группах — классами-хендлерами(http://docs.pylonshq.com/pyramid/dev/narr/handlers.html), что позволяет реализовывать большие проекты без дискомфорта, который бы возник, будь только одна возможность добавления видов — через добавление каждого вида по отдельности.
2) Конфигурация видов. Достаточно просто. Добавил вид, указал и забыл.
3) Реализация фабрики, методов __getitem__. Это и есть новый, мало знакомый элемент, который ещё только предстоит изучить.
4) Подписывание на ивенты(http://docs.pylonshq.com/pyramid/dev/narr/events.html), через которые, к слову, можно всё реализовать, в т.ч. подключить миддлвари, препроцессинг, постпроцессинг etc. Именно этот элемент Pyramid позволяет сделать всю настройку приложения более централизованной, без потери гибкости. Т.е. теперь не нужно лезть в middleware.py и добавлять всё своё чудо в функцию пайлонсов, не нужно редактировать базовый класс контроллера. Всё делается очень и очень просто!
5) Реализация моделей графов(те что в models.py), которые, судя по всему, должны являться только адаптерами между графом объектов и БД(хотя может быть и не бд, например).
etc.
Все пункты хороши, вот только 3 и 5 пока только появились и их особо никто не успел распробовать, нет юз-кейсов, нет никаких указаний как это лучше использовать. Трудно сказать как оно всё отразится на программировании, но разграничение на такие части должно хорошо сказаться на результате.
docs.pylonshq.com .
"Что это за PersistentApplicationFinder? Судя по всему, он сам генерирует фабрику!"
С виду, можно подумать и так, но на самом деле это просто прослойка между фабрикой и приложением. Цель прослойки — открыть коннект к бд и передать его в фабрику.
Иначе говоря, appmaker это и есть get_root, просто он обёрнут в класс, который поднимает коннект к ZODB. Никакой магии, просто пример того, как можно это использовать.
Вообще, я не понимаю зачем создан ZODB, какие задачи выполняет и нужен ли он для чего-то бОльшего чем просто демонстрация.
У многих могут возникнуть вопросы на счёт этого примера "Что это за PersistentApplicationFinder? Судя по всему, он сам генерирует фабрику!"
С виду, можно подумать и так, но на самом деле это просто прослойка между фабрикой и приложением. Цель прослойки — открыть коннект к бд и передать его в фабрику.
Иначе говоря, appmaker это и есть get_root, просто он обёрнут в класс, который поднимает коннект к ZODB. Никакой магии, просто пример того, как можно это использовать.
Вообще, я не понимаю зачем создан ZODB, какие задачи выполняет и нужен ли он для чего-то бОльшего чем просто демонстрация.
Сделав функцию-обработчик запроса(вид), мы должны его настроить так, чтобы траверсал его находил когда нам нужно, а мы акцентировали своё внимание только на нужном нам коде, который мы пишем.
docs.pylonshq.com
docs.pylonshq.com
Я не зря хайлайчу слово view_config. В Pyramid есть два варианта настройки вида:
1) Через __init__.py(где производится настройка приложения).
2) Через декоратор view_config.
Первый вариант позволяет спрятать конфигурацию вида так, что ты будешь писать код и не думать о том, что и как у тебя с текущим видом, грубо говоря.
Второй вариант более привычный и лучше подойдёт для больших приложений.
Собственно, что есть настройка вида: указание какой рендерер использовать(вообще, это сразу строка с путём до темплейта), имя функции(а вот это — самое интересное, ибо по нему ищутся методы и это значение не обязано быть уникальным для проекта), контекст(об этом чуть позже) и ещё тучу всяких плюшек, о которых потом может быть напишу.
Стоит отметить, что разрешается использование нескольких декораторов для одной функции:http://docs.pylonshq.com/pyramid/dev/narr/views.html?highlight=view_config#view-config-placement
More than one pyramid.view.view_config decorator can be stacked on top of any number of others. Each decorator creates a separate view registration.
Нас в первую очередь интересуют 2 параметра настройки вида: name, context. Если name и так понятно, по нему будет искаться 'delete' метод из предыдущей заметки, то с контекстом не всё так просто.
Контекстом может быть:
1) Адрес до класса, инстансом которого должен быть контекст
2) Родитель класса, инстансом которого должен быть контекст
3) Веб-эксепшн
Возможно, что-то не совсем так, возможно, есть ещё возможные варианты контекстов, но суть должна быть понятна: в параметре вида context мы указываем тип контекста, который поддерживается видом, который обрабатывается видом.
К слову, здесь есть полная поддержка zope.interfaces. Можно сделать 3 класса от одного родителя(или 3 имплементации одного интерфейса) и отдавать из через фабрику, а в настройке нашего вида указывать родителя. Таким образом вид будет матчиться под все три класса если они будут контекстами.
Если мы не указали контекст в параметрах, то матчиться будет по имени.
И на последок, 2 настройки вида для матчинга первого примера и второго:
#('delete', User, '/lol')
@view_config(context="proj.models.User", name="delete") #хотя фиг знает, лучше фигурировать инстансами, а не классами, предположим что мы в графе отдаём инстанс
def delete_all(context, request):
....context.delete_all()
#('delete', SqlUser(id=10), '')
@view_config(context="proj.models.SqlUser", name="delete")
def delete_by_id(context, request):
....context.delete()
Главной и самой интересной частью пирамиды является траверсал, а точнее модели и траверсал. Очень трудно понять с ходу что же такое "модель" в данной системе, что такое корневая фабрика, что такое граф моделей, как вообще работает траверсал, зачем и как это правильно использовать.
Что же такое "фабрика моделей" и вообще траверсал?
Траверсал — модуль пайтона из комплекта repoze, который позволяет определять контекст, контроллер и дополнительные параметры запроса по url path БЕЗ необходимости в "обычном" маппинге, как мы привыкли в пайлонс или где-либо ещё.
Вся логика его работы здесь:
docs.pylonshq.com
Как он работает:
After the path info is converted, a lookup is performed against the object graph for each path segment. Each lookup uses the __getitem__ method of an object in the graph.
Traversal pops the first element (a) from the path segment sequence and attempts to call the root object’s __getitem__ method using that value (a) as an argument; we’ll presume it succeeds.
When the root object’s __getitem__ succeeds it will return an object, which we’ll call “A”. The context temporarily becomes the “A” object.
...
This process continues until the path segment sequence is exhausted or a lookup for a path element fails. In either case, a context is found.
В результате, последний найденный объект в графе становится контекстом, следующий(текущий в поиске, который провалился) — именем контроллера, а остальное — дополнительной частью пути, которая добавляется в запрос как параметр.
Пример:
/user/delete/lol
Вызываем фабрику — получаем саму фабрику как контекст.
Запрашиваем у фабрики объект по имени user. Нам возвращают модель User(чтобы быть ближе к тому что делали с разными ORM, предположим что это не инстанс, а ссылка на класс).
У полученного объекта запрашивается следующий объект — 'delete'. Этот объект не был найден, ибо контекстом стал объект, у которого даже нет метода __getitem__, т.е. это даже не фабрика.
Контекст не меняется(последним контекстом была ссылка на класс User). Названием контроллера для поиска стало delete, а остаток от пути — '/lol'.
Ещё пример:
/user/10/delete
context = get_root() #fabric
context = context.__getitem__('user') #User class
context = context.__getitem__('10') #Вот здесь интересно! Допустим, что мы сделали статичную функцию __getitem__ у класса User, которая принимает аргумент name(искомое имя). В этой функции мы вызываем метод get_by_id этого же класса User(однако, можем вообще сделать прямой запрос через pymongo или SA, или вообще вызвать метод другого класса). Как результат, мы возвращаем инстанс модели SqlUser(например, хотя можно инстанс этого же класса или вообще что угодно, как хочется), поле id которого равно 10.
#User instance with id 10
context = context.__getitem__('delete') #Exception
return ('delete', context, subpath)
Напомню результаты обоих примеров:
('delete', User, '/lol')
('delete', SqlUser(id=10), '')
Как видно из этих примеров, огромную часть логики можно переложить в отдельное место, а разработка теперь ещё больше разделена на части, что упрощает всё, позволяет акцентироваться на нужном в нужное время. Разработка состоит и из построение этого графа.
Следует отметить тот факт, что если корневой фабрикой ничего не указано или None, то она становится пустым классом, т.е. контекстом становится сам пустой класс, а именем для поиска контроллера — первый же элемент в url path. docs.pylonshq.com
Нужно добавить, что я употреблял слово "контроллер" только для обратной совместимости с пайлонс и вообще для людей, которые привыкли называть хендлеры путей в вебе контроллерами.
Сейчас посмотрел ещё раз и, в общем то, оно мне нравится. Не всё, конечно, но общие концепции хороши.
Из непонятных мне вещей:
1) Причина отказа от создания директорий для контроллеров и моделей.
Зачем? Они что, решили ориентироваться на столь малые приложения, где всё может уместиться в один файл? Бред и ужас. Если "рыба" Pylons выглядит как нормальный проект, в котором можно сделать свой фейс-бук, то после создания проекта через темплейты Pyramid, я могу предположить что мне предлагают написать веб-морду для пиджина, трансмишшн или просто написать хомяк/гостевушку. Я бы мог понять если бы они решили сделать как в Django, т.е. чтобы была необходимость создавать вложенные приложения, т.е. создавая дерево приложений, которые имплементируют свою, определённую функциональность, цель, задачу. НО, как мы видим, ничего такого нет. Хотите что-то бОльшее — сами создавайте и контроллер(простите, вид), и новый конфиг, и модели.
2) Идеология контекста, моделей.
Сказано что это не те модели, к которым мы привыкли. Однако, толком не рассказано что это теперь это такое. Сказано что теперь контекстом становится МОДЕЛЬ. При этом, контекст формируется АВТОМАТИЧЕСКИ, из ПУТИ. При этом само выбирает какую модель использовать.
Новый маппер(роутер) — Траверсал. Это круто, в нём заложена и великая цель — определять какую модель нужно достать и какой вид дёрнуть.
Как это делает Траверсал:
путь: site/bar/foo
bar — название модели
foo — вид
путь: site/cor/snor/bar/buz/foo
buz — название модели
foo — вид
И что это за бред? Куда это? Как это вообще правильно использовать чтобы если кто-то ввёл /user/buy вместо item/buy, то всё не упало к чертям?
cd34.com Пара заметок о переходе с пайлонс на пирамиду
docs.pylonshq.com Until the end of 2010, Pyramid was known as repoze.bfg; it was merged into the Pylons project as Pyramid in November of that year.
А люди то и не знали!
А люди то и не знали!
github.com ! Конечно, немного быдлокод, но надо будет заюзать, своё писать всё-равно лень, кекеке.
А завтра нужно будет подключать гугл... Привет Конечно, я пока не доволен своей имплементацией универсальных плагинов для repoze, но оно меня радует, очень радует.
@odin в #1032105 : repoze.bfg + Pylons = Pyramid.
Переписывая ядро Pylons для 2.0 они поняли, что изобретают тоже самое, чем является repoze.bfg и решили вместо дублирования функционала смержиться с repoze.bfg. Таким образом, repoze.bfg с версии 1.4 становится ядром Pyramid(repoze.bfg-1.3 будет получать только багфиксы), Pylons 1.x останется на поддержке, а все новые фичи и улучшения Pylons будут уже реализованы в рамках Pyramid, тем самым воплощая идеи версии 2.0...
...и вместо yaml конфигов нам достается zcml, чудесно!
интересные новости принес Переписывая ядро Pylons для 2.0 они поняли, что изобретают тоже самое, чем является repoze.bfg и решили вместо дублирования функционала смержиться с repoze.bfg. Таким образом, repoze.bfg с версии 1.4 становится ядром Pyramid(repoze.bfg-1.3 будет получать только багфиксы), Pylons 1.x останется на поддержке, а все новые фичи и улучшения Pylons будут уже реализованы в рамках Pyramid, тем самым воплощая идеи версии 2.0...
...и вместо yaml конфигов нам достается zcml, чудесно!
cascade="all, delete, delete-orphan"
в relationship неудачная идея для меня :}
pylonshq.com
точно напьюсь :)
1.0 Release точно напьюсь :)
wiki.pylonshq.com
Тут те же самые canvas.tostring_rgb() и PIL.Image.fromstring(), но чуть иная система работы с канвасом бекэнда Agg. Почему никому (судя по выдаче гугла) не приходила идея не делать тустринг, фромстринг, а сразу матплотлибом сохранить картинку? Это работает (и результат мне нравится больше), но почему возникает потеря производительности?
Еще одна вариация того способа, которым я в Джанго делаю картинки на лету: Тут те же самые canvas.tostring_rgb() и PIL.Image.fromstring(), но чуть иная система работы с канвасом бекэнда Agg. Почему никому (судя по выдаче гугла) не приходила идея не делать тустринг, фромстринг, а сразу матплотлибом сохранить картинку? Это работает (и результат мне нравится больше), но почему возникает потеря производительности?
request.params
This is a dictionary-like object with a combination of everything in request.GET and request.POST. You will generally use request.params rather than request.GET or request.POST, although they all share the same API. I’ll cover request.params more closely in a minute because it is the main object you will use to deal with form submissions.
pylonsbook.com
я, видимо, что-то не так понимаю. зачем при обработке форм юзать сразу и GET, и POST? Привет грабли, на которые не так давно наступал жуйк?