Python

индекс
250,37

Nagare — пример использования фреймворка

В прошлом посте я презентовал Nagare — революционный (хоть и имеющий аналоги на Smalltalk и CL) питоновский web-фреймворк. Тот пост был несколько сумбурен и отражал, скорее, степень моего восторга, чем реальные особенности технологии. Сегодня я попробую дать немного более практический пример.

Начнем сначала. Прежде всего, нам потребуется Stackless Python. Для инсталляции на FreeBSD (далее, я буду использовать эту систему в качестве базы, для Linux отличий быть не должно, для MacOS и Windows есть особенности, но они не критичны) необходимо скачать дистрибутив:

$ fetch \http://www.stackless.com/binaries/stackless-264-export.tar.bz2


распаковать его:
$ tar -xvjf stackless-264-export.tar.bz2

сконфигурировать (инсталлировать будем в /usr/local/stackless)
$ cd stackless-2.6.4
$ ./configure --prefix=/usr/local/stackless

скомпилировать и установить:
$ make
$ sudo make install


Вообще-то, наиболее правильным было бы инсталлировать stackless-python через систему ports, но он в ней почему-то отсутствует.

Теперь нам нужно скачать и распаковать virtualenv:
$ fetch \http://pypi.python.org/packages/source/v/virtualenv/virtualenv-1.4.5.tar.gz
$ tar -xvzf virtualenv-1.4.5.tar.gz

после чего создать виртуальную «песочницу», в которой будет работать Nagare (/usr/local/nagare):
$ cd virtualenv-1.4.5
$ sudo /usr/local/stackless/bin/python ./virtualenv.py /usr/local/nagare


Теперь, нужно установить, собственно, сам Nagare:
sudo /usr/local/nagare/bin/easy_install 'nagare[full]'


Все, Nagare установлен и готов к работе.
Создадим приложение:
$ /usr/local/nagare/bin/nagare-admin create-app test1
Application 'test1' created.

Note:
1. Edit the file 'test1/setup.py' to set the informations about your new application.
2. Register your application with:
- cd "test1"
- "/usr/local/nagare/bin/python" setup.py develop


Будет создан каталог test1, содержащий следующие файлы:

./test1/setup.py
./test1/test1/__init__.py
./test1/test1/app.py
./test1/test1/models.py
./test1/static/__init__.py
./test1/data/__init__.py
./test1/conf/__init__.py
./test1/conf/test1.cfg

Файл setup.py содержит всякую служебную информацию типа лицензии, имени автора, его email и т.п., для тестового приложения можно его не заполнять, работать будет и так.

В файле conf/test1.cfg включим режим отладки и подключим БД (по умлочанию — sqlite). После исправлений он будет выглядеть так:

[application]
path = app test1
name = test1
debug = on

[database]
activated = on
uri = sqlite:///$here/../data/test1.db
metadata = test1.models:__metadata__
debug = on


Теперь выполним команду:
$ sudo /usr/local/nagare/bin/python ./setup.py develop


Все, приложение зарегистрировано, можно его протестировать:
$ /usr/local/nagare/bin/nagare-admin serve test1
Application 'app test1' registered as '/test1'
03/17/10 18:00:35 - serving on \http://127.0.0.1:8080


По умолчанию приложение будет доступно по адресу \http://127.0.0.1:8080/test1.
Приступим к разработке.
Сначала зададим таблицы базы данных. Для этого отредактируем файл test1/models.py, добавив определения таблиц (интересующихся подробностями отошлю к документации Elixir). В итоге, файл будет выглядеть так:

from elixir import *
from sqlalchemy import MetaData

__metadata__ = MetaData()

class GuestBookRecord(Entity):
  text=Field(Unicode(1000))
  name=Field(Unicode(50))



Создадим саму базу данных:
$ /usr/local/nagare/bin/nagare-admin create-db test1
2010-03-17 18:08:22,895 INFO sqlalchemy.engine.base.Engine.0x...ac6c PRAGMA table_info("test1_models_guestbookrecord")
2010-03-17 18:08:22,900 INFO sqlalchemy.engine.base.Engine.0x...ac6c ()
2010-03-17 18:08:22,934 INFO sqlalchemy.engine.base.Engine.0x...ac6c
CREATE TABLE test1_models_guestbookrecord (
id INTEGER NOT NULL,
text VARCHAR(1000),
name VARCHAR(50),
PRIMARY KEY (id)
)

2010-03-17 18:08:22,971 INFO sqlalchemy.engine.base.Engine.0x...ac6c ()
2010-03-17 18:08:22,982 INFO sqlalchemy.engine.base.Engine.0x...ac6c COMMIT


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

from __future__ import with_statement

import os
from nagare import presentation, var

# Импортируем структуру таблиц
from models import *

class Test1(object):
  def __init__(self):
    self.name=var.Var("")    #  В эти переменные будут помещаться значения полей формы
    self.text=var.Var("")      #

#  Метод для добавления записи в БД
  def add_rec(self):
    nr=GuestBookRecord()
    nr.name=self.name()
    nr.text=self.text()
    session.save(nr)

@presentation.render_for(Test1)
def render(self, h, *args):
# Заголовок
  h<<h.h1("Guest book")
# Форма для имени и текста сообщения
  with h.form:
    with h.table:
      with h.tr:
        with h.td:
          h<<"Name:"
        with h.td:
          h<<h.input(type="text", value=self.name()).action(lambda v: self.name(v))
      with h.tr:
        with h.td(valign="top"):
          h<<"Text:"
        with h.td:
          with h.textarea().action(lambda v: self.text(v)):
            pass
      with h.tr:
        with h.td(colspan=2):
          h<<h.input(type="submit", value="Add").action(self.add_rec)
  h<<h.hr
# Вывод ранее введенных сообщений:
  with h.table:
    recs=GuestBookRecord.query.all()
    for r in recs:
      with h.tr:
        with h.td(valign="top", align="right"):
          h<<h.b(r.name)<<":"
        with h.td:
          h<<r.text
  return h.root

# ---------------------------------------------------------------

app = Test1




Запустив приложение командой:
$ /usr/local/nagare/bin/nagare-admin serve test1


По адресу \http://127.0.0.1:8080/test1 мы обнаружим премилую гостевую книгу, в стиле «привет из 90-х».

Приведенный выше пример не раскрывает и 10% возможностей Nagare и приведен только для примера. В следующий раз я покажу, как при помощи Nagare можно создавать более сложные приложения.
+14
17 марта 2010, 18:48
20

комментарии (42)

–9
dymdym #
стандартный для фреймворка функционал
Хуйня этот ваш нагар
0
Hemul #
я не ставил себе целью сразу писать целую книгу про Nagare. Все будет! Функционал там отнюдь не стандартный, просто это пока не очень заметно :)
0
rdolgov #
про «премилую гостевую книгу» — скрин бы не помешал.
+7
Hemul #
вот она, аж слезы наворачиваются:



:)
0
khizhaster #
хе-хе, там скорей всего два поля и кнопка, что ж там смотреть то? :) Это ж стартовый пример.
0
SADKO #
Эхъ молодёж, вам не понять…
+1
khizhaster #
Но-но, это мой второй в жизни скриптик на перле был на заре изучения в далёком 99-м!
+3
karguine #
Вы бы написали основные преимущества и отличия от остальных подобных фреймворков.

Такую гостевую книгу можно на чём угодно написать. Ещё неизвестно, где удобнее.
0
Hemul #
см. тут
+3
vostryakov #
Интересно почитать примеры, где Nagare использует возможности Stackless. Если я правильно понимаю, то на нем можно эффективно реализовывать системы с большим кол-вом коммуникаций между потоками. Например, хотя бы стандартный чат, где все общаются со всеми.

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

Жду продолжения :)
0
Hemul #
основная возможность stackless, используемая в Nagare — это сохранение continuations между запросами, так что приложение можно писать «линейно», аналогично десктопному. Но и взаимодействие между потоками можно устроить. Например, при помощи глобальных переменных :)
0
vostryakov #
Да, вот про это интересно почитать будет! :)
0
Hemul #
да, собственно, технология настолько проста, что почти невидима :)
В приведенном примере

@presentation.render_for(Test1)
def render(self, h, *args):

— так задается функция, аналогичная функции перерисовки в десктопных приложениях. Все глобальные переменные и методы объекта Test1 сохраняются при перезагрузках, можно хранить в них, например, статус авторизованности пользователя. Глобальные переменные еще и общие для всех потоков.
0
el777 #
> Но и взаимодействие между потоками можно устроить. Например, при помощи глобальных переменных
Это как? Расскажите, очень интресно.
0
Hemul #
да банально. На каждую сессию создается экземпляр класса (в данном случае Test1), «локальные переменные» — это поля объектов. Можно задать переменные прямо в модуле, они будут общими для всех объектов.
0
el777 #
Race condition? Если его будут менять в несколько потоков, то этот объект очень часто будет в неадекватном состоянии.
0
Hemul #
вроде в питоне это как-то решается, типа гигантского лока что-то есть. Но, конечно, это самый примитивный метод, ничто не мешает использовать всякие продвинутые способы типа IP с семафорами и т.п., правда, это не будет работать на кластере, наверное.
0
el777 #
В том-то и проблема, что гагантский лок убьет всю параллельность — т.к. каждая нить будет упираться в нее и ждать другую. Семафоры это тоже самое, только сделанное Вашими руками. В многопоточных системах глобальные данные это полный ахтунг. Особенно когда начнется неконтролируемое изменение и это повлечет фантомные баги.

Мне интересно есть ли в Нагаре какие-то средства обмена данными между потоками, или придется все в лоб решать?
0
Hemul #
все зависит от частоты транзакций и их сложности. Думаю, для абсолютного большинства приложений ситуация взаимной блокировки будет большой редкостью.

Вообще-то я пока не разбирался с этой частью функционала. На сайте разработчиков есть пример чата, реализованного по технологии Comet, там, кажется, должно быть именно то, что нужно — взаимодействие между различными сессиями.
0
Deepwalker #
Nagare довольно спорное творение — вам часто требовались продолжения? Конечно возможности stackless сериализовать тасклет вызывает некоторый зуд в руках, но нам бы пример не для хомячков — пример, где реально нужны эти самые продолжения.
У меня вот не рождается мысли, где бы это применить.
0
Hemul #
да практически в любом приложении с нетривиальной логикой или таком, где нужно работать с большим количеством вводимых пользователем данных. Например, вместо того, чтобы делать одну огромную форму, можно очень легко сделать «визард» с кнопками «вперед» и «назад», практически ничего не меняя, поскольку значения с предыдущих шагов будут спокойно лежать в переменных.
–1
Deepwalker #
Это легко делается без продолжений.
–1
Hemul #
но с продолжениями это делается естественно, а все остальные решения похожи на костыль :)
–1
Deepwalker #
Не вижу ничего костыльного в сессиях — это их прямое предназначение. А вот сериализация целого тасклета пахнет накладными расходами.
В общем неоднозначен Nagare.
0
yul #
сессии самый что ни на есть костыль для stateless HTTP.
0
Deepwalker #
У нас с вами разные понятия о костылях — хранение парочки переменных от запроса до запроса это явно меньший оверхед, чем хранение от запроса до запроса всего тасклета. И в чем принципиальная разница между эти двумя техниками, я не особо улавливаю.
Ну и главное — не вижу ничего ужасного в stateless природе HTTP, это имеет свои преимущества.
–1
yul #
Хорошо, можете сказать, зачем нужны сессии, кроме как для обхода этого ограничения? В чем может быть преимущество отсутствия возможности? Если какая-либо возможность есть, ей можно либо пользоваться, либо нет. Хочешь, чистишь память после выполнения запроса, не хочешь — не чистишь.
з.ы. еще и в карму плюнуть не забыл, ну-ну…
0
Deepwalker #
Вы правда не читали мой комментарий? Это распространенная проблема, к сожалению.

P.S. не кармадрочерствуйте — да плюнул, наверное для этого там и есть плюсик и минус, как вы считаете?
0
Deepwalker #
Nagare где хранит сериализованные тасклеты?
+1
Hemul #
там есть варианты. Наиболее продвинутый — хранить в memcache, что позволяет запускать приложение на кластере.
+6
kmike #
какая-то жуть — шаблоны через with) Это что, рекомендованный способ там?
–1
Hemul #
это не шаблоны :) Шаблоны там тоже есть, но смысл из примененять в простой программе? Конструкции с with — просто очень красивый способ автоматического закрытия тэгов. Кстати, при использовании шаблонов ничто не мешает вставлять в них куски кода, сгенерированного таким образом, что позволяет, иногда, сильно упростить сами шаблоны.
+5
kmike #
В том-то и дело, что это не шаблоны)

По-моему так и в этом простом примере html-код было бы писать быстрее, проще, короче, понятнее, а with — ужас!

> with h.td(valign=«top», align=«right»)

тут мало того что в код на python имеем вкрапления html, так этот html еще и используется не только как разметка, но еще и как представление — все эти align=right делаются в css. Напомнило вывод html в php через echo, только с микро-бонусом закрывающихся тегов.

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

Чтоб теги сами закрывались, лучше уж что-нибудь вроде haml использовать. Но вообще проблемы не вижу, кстати, с закрытием тегов — они в IDE и так сами закрываются при создании.

Логика в шаблонах — вопрос холиварный, понятное дело) Но мне лично никогда сложной логики в шаблонах не требовалось, а когда требовалось, то выходило, что это вовсе и не логика отображения информации, и ее в шаблон пихать неправильно.
0
Hemul #
а зачем в данном конкретном примере шаблон? И css? with-конструкции дают возможность писать простой и очень читабельный, чистый код. Никто не призывает верстать с их помощью мегапорталы, хоть это и возможно :)
+1
dkuznetsov #
Видел стенд ребят, что создали этот фреймворк на PyCon. Спасибо за пример… HTML в Python-коде можно писать много где, например, в Nevow. Но я никогда этого не понимал. В современном вебе страница в любом случае имеет дизайн, верстальщик делает шаблоны… Разве что быстро что-то «накидать» и никому никогда не показывать.
+1
Hemul #
html там не главное, я применил этот метод просто для упрощения примера. Это всего лишь одна из побочных, но красивых фич, как и трансляция питона в javascript, например, которая там также присутствует. Если ее оторвать, фреймворк потеряет немного, но эта вещь из тех, что лучше иметь, чем не иметь.
0
dkuznetsov #
Да, надо будет попробовать его как-нибудь, если подвернется подходящая задача.
0
Savvy #
В примере не увидел никаких континуаций. Функция Test1.render() рендерит одну страничку, на получение значения из формы вешается банальный колбэк. Когда форму пришлют — свистнем. Примерно также делается во многих фреймворках. Надо чтобы что-то типа

print 'Введите первое значение'
v1 = input()
print 'Введите второе значение'
v2 = input()
print 'Сумма', v1+v2

в вебе выполнялось. Т.е. я понимаю, что видимо это в фреймворке есть, но пример, по-моему, неудачный. Насчет красоты такого употребления with — спорно, в свое время (правда тогда оператора with не было) было подобное в Twisted.web под названием stan, но по-моему умерло — всем когда-нибудь приходит понимание, что нельзя дизайн в код писать.
0
Hemul #
Такой стиль характерен для CLI, функция render() — для GUI. Поскольку web-приложение это однозначно GUI, то там однозначно стоит использовать render().

Что касается континуаций. Я слегка сократил код, использовав lambda-функции для заполнения полей Test1.name и Test1.text. При наступлении события вызываются методы action() для всех полей формы, выполняются лямбды, которые сохраняют данные в переменных, после чего вызывается action() для submit-а и данные кладутся в базу. По сути, да, тут континуации не нужны. Но с тем же успехом я мог бы класть данные не в базу а дописывать их в массив. Правда, тогда сообщения были бы видны только добавившему их пользователю, что для гостевой книги несколько странно :)

Вот как следовало бы написать код в этом случае:


class Test1(object):
  def __init__(self):
    self.name=var.Var("")
    self.text=var.Var("")
    self.messages=[]

  def add_rec(self):
    self.messages.append([self.name,self.text])

@presentation.render_for(Test1)
def render(self, h, *args):
...............................................

# Вывод ранее введенных сообщений:
  with h.table:
    for r in self.messages:
      with h.tr:
        with h.td(valign="top", align="right"):
          h<<h.b(r[0])<<":"
        with h.td:
          h<<r[1]

.................................................

0
Hemul #
Упс, «self.messages.append([self.name,self.text])» надо заменить на «self.messages.append([self.name(),self.text()])»
0
Savvy #
В принципе, да, колбэки — это тоже континуации. Но тогда нет обещанной революционности. Интересным было бы такое (http://cocoon.apache.org/2.1/userdocs/flow/continuations.html у меня теги не работают). Ни одного колбэка. И это уже сейчас реализуется в рамках одного процесса в питоне (даже без stackless-а) с помощью yield.
+2
rule #
в которой будет наботать Nagare
поправьте пожалуйста, присоединяюсь к выше высказавшимся участникам обсуждения, хотелось бы что-то типа таблички возможностей, которые дают право жить этому фреймворку

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.