21 мая 2010 г.

Впечатления от DevConf::Python()

Перед конференцией Александр Кошелев, организатор питонячьей секции, взял интервью у некоторых докладчиков:

Впечатления о докладах

Андрей Попп рассказал про gevent. Мотивационная часть абсолютно правильная, а вот решение выглядит как хак. Я бы не рискнул использовать gevent, не покопавшись основательно внутри: для подобных хаков важно понимать, как они устроены, чтобы представлять, где они подведут.
Андрей Светлов разложил по полочкам импорт в питоне. Доклад ориентирован в основном на новичков, но и для продвинутых пользователей было немало интересного.
Андрей Власовских рассказал про свою библиотеку funcparserlib. Андрей великолепный докладчик и библиотека интригует простотой использования не в ущерб возможностям. Надеюсь в скором времени её опробовать на реальных задачах.
Мастер-класс Ивана Сагалаева о потоковой генерации XML не впечатлил. Слишком уж сильно он был ориентирован на новичков, а у меня, вероятно, были завышенные ожидания. Однако начинающие программисты могли на практике увидеть правильный процесс создания продукта — довольно редкая возможность.
Ещё один доклад Андрей Попп посвятил созданию web-приложений на repoze.bfg. Доклад интересный, а вот сам repoze.bfg не впечатлил: не нравится мне enterprise стиль.
Виктор Коцеруба при поддержке Артема Семенова долго критиковал django. В основном по делу, но как-то очень сдержанно. Я ожидал от Виктора более провокационного изложения. По ходу доклада не все присутствующие соглашались со всеми доводами, много дискутировали.
В догонку Александр Соловьев мастерски представил альтернативу django — Svarga. Думаю, этот доклад заслуживает отдельного поста.
Во второй день Тимофей Первезенцев очень подробно и аргументированно изложил, что требуется от шаблонизаторов, и какие есть проблемы у имеющихся библиотек. Доклад вызвал много обсуждений и сильно затянулся. В результате Александру Соловьеву осталось довольно мало времени на его мастер-класс про API mercurial. Однако Александр не только уложился по времени, но и очень подробно и доходчиво показал, как с ним можно работать.

Общие впечатления

К организации конференции можно сделать много замечаний, но это всё не существенно. Рядом с залом стоял флип-чарт, которым мы активно пользовались для дополнительных обсуждений во время перерывов. Отличная возможность, но я бы всё-таки предпочёл видеть маркерную доску, с которой можно стирать написанное: это и более удобно для исправлений, и меньше психологический барьер начать использовать (в других секциях флипы, по-моему, так и не начали использовать). В кофебрейк, обед и за вечерним спонсорским пивом сдвигались столы и большая часть питонеров продолжала обсуждение. В общем, много знакомств, море общения, куча новый идей!
И напоследок фотографии с коференции, выложенные Тимофеем. Многие участники на них подписаны.

6 мая 2010 г.

DevConf::Python() 2010 17 мая в Москве

В Москве 17-18 мая пройдёт DevConf 2010 — конференция веб-разработчиков с отдельным потоком, посвящённым Python. По-моему это первое мероприятие по питону в Москве такого масштаба.
Предварительный список докладов 17 мая:
  • Потоковая генерация XML, Иван Сагалаев
  • Разработка cетевых приложений с gevent, Андрей Попп
  • Расширение механизма импорта в Питоне, Андрей Светлов
  • Внешние языки DSL на funcparserlib, Андрей Власовских
  • PyCharm: новая IDE для Python от JetBrains, Дмитрий Жемеров
  • Python и Cython, Александр Шигин
  • Разработка web-приложений с repoze.bfg, Андрей Попп
  • Django ±, Артем Семенов и Виктор Коцеруба
  • Разумная альтернатива Django, Александр Соловьев
Кроме того, 18 мая пройдёт весьма интересный мастер-класс Александра Соловьева "Свой gist.github.com на Mercurial".
Актуальная информация о программе секции доступна сайте DevConf 2010.
Ещё одна очень интересная фишка конференции — флип-чарты, обсуждения за маркерной доской в перерывах между докладами. Любой из слушателей может перехватить инициативу и рассказать что-то интересное по заявленной теме. Это потрясающая возможность не просто сделать доклад, а провести полноценное обсуждение интересной темы! Доклад в такой форме не требует тщательной подготовки. Ну и ещё один плюс — для ведущих флип-чартов, попавших в программу, вход на конференцию бесплатный.

4 мая 2010 г.

Блокировка объектов при редактировании в админке

Одна из недавних встреч питонеров (Moscow Python meetup) была посвещена теме NoSQL. Я отношу скептически к повсеместному переходу на NoSQL, но всё же нахожу ему применение в отдельных задачах. Так, на встрече я рассказал про блокирование редактируемых объектов на базе memcache. Проблема вполне типичная для всех редакторских интерфейсов в CMS. Один и тот же объект могут одновременно начать редактировать несколько пользователей, в этом случае правки одного из пользователей перетираются другим. Более того, иногда возникают ситуации, когда у одного пользователя оказываются открыты несколько окон редактирования одного объекта и он перетирает собственные изменения.
В моём варианте решения при открытии страницы редактирования берётся блокировка (если объект ещё не заблокирован), а затем со страницы периодически шлётся AJAX запрос на её обновление. Ответ может быть как успешный, так и нет, если другой редактор насильно перехватил блокировку. При завершении редактирования или уходе со страницы блокировка снимается. Кроме того, если блокировку не обновлять, то она через некоторое время протухает автоматически — это решает проблему снятия блокировки, хоть и с запозданием, при закрытии окна (падении браузера, выключении питания у компьютера и т.д.). Переменная edit_session необходима для решения проблемы нескольких открытых окон редактирования одного объекта, фактически она содержит идентификатор одного такого окна. Для обновления блокировки используются команды memcache gets и cas, чтобы обеспечить атомарность операций (исключить условие гонки). Ниже приведена серверная часть, слегка переписанная, чтобы оторвать от контекста нашего движка (функции и переменные сделаны глобальными).
import os, logging
from time import time

logger = logging.getLogger(__name__)

class LockError(Exception):
    def __str__(self):
        return 'Problems with object lock'

class LockedByOther(LockError):
    def __init__(self, user):
        LockError.__init__(self, user)
        self.user = user
    def __str__(self):
        return 'Object is already locked by user: %s (%s)' % \
                                (self.user.name, self.user.login)

class LockIsLost(LockError):
    def __str__(self):
        return 'The object lock is lost'

def create_lock(obj_key, user, force=False):
    '''Marks model object as editted. Returns edit session on success or
    raises exception. obj_key is global identifier of object. When force is
    True the current lock is ignored.'''
    CACHE.clear_cas()
    edit_session = os.urandom(5).encode('hex')
    value = dict(edit_session=edit_session,
                 user_id=user.id,
                 time=time())
    if force:
        if CACHE.set(obj_key, value, time=MODEL_LOCK_TIMEOUT):
            return edit_session
        else:
            raise LockError()
    for i in range(3):
        if CACHE.add(obj_key, value, time=MODEL_LOCK_TIMEOUT):
            return edit_session
        old_value = CACHE.gets(obj_key)
        if old_value is None:
            # Should try add() again
            continue
        if not old_value or time()-old_value['time'] > MODEL_LOCK_TIMEOUT:
            # Somebody's lock is already expired
            if CACHE.cas(obj_key, value, time=MODEL_LOCK_TIMEOUT):
                return edit_session
            else:
                continue
        # Somebody holds active lock, no farther attempts
        break
    else:
        logger.error('Failed to lock model object. Problem with memcached?')
        raise LockError()
    lock_user = get_user(id=old_value['user_id'])
    assert lock_user is not None
    raise LockedByOther(lock_user)

def update_lock(obj_key, user, edit_session):
    '''Updates model object lock as being active. Raises exception on
    error. obj_key is global identifier of object.'''
    CACHE.clear_cas()
    for i in range(3):
        old_value = CACHE.gets(obj_key)
        if not old_value:
            raise LockIsLost()
        elif old_value['edit_session']!=edit_session:
            lock_user = get_user(id=old_value['user_id'])
            assert lock_user is not None
            raise LockedByOther(lock_user)
        new_value = dict(edit_session=edit_session,
                         user_id=user.id,
                         time=time())
        if CACHE.cas(obj_key, new_value, time=MODEL_LOCK_TIMEOUT):
            return
    else:
        # No runtime error here since we want to give user a chance to
        # restore lock.
        raise LockIsLost()

def remove_lock(obj_key, edit_session):
    '''Removes lock for model object. obj_key is global identifier of
    object.'''
    CACHE.clear_cas()
    # We can't garuantee memcache's delete method will remove only our
    # lock, so we update the record with empty value and minimal (1 sec)
    # timeout.
    old_value = CACHE.gets(obj_key)
    # It's too late to do something in case of error, so we just ignore
    # returned value.
    if old_value and old_value['edit_session']==edit_session:
        CACHE.cas(obj_key, '', time=1)
Надеюсь, назначение и работа методов понятна из названий и комментариев.
А какие способы используете вы для организации одновременного редактирования объектов?