18 авг. 2011 г.

Еще один велосипед для Django

Вчера выложил в открытый доступ под лицензией BSD свой новый велосипедик для Django.

Называется творение "django-supergeneric". Исходный код доступен тут: https://github.com/lig/django-supergeneric

Причиной написания этого стало желание перестать постоянно писать одно и то же. Почти всегда для каждой модели приходится писать похожие друг на друга, как близнецы-братья, view для просмотра списка объектов, одного объекта, создания объекта, редактирования объекта и удаления объекта. Понятно, что есть generic views, но их тоже надо конфигурировать, причем многие параметры повторяются. Да, и каждый раз для всех пяти view надо прописать urlpatterns, которые тоже для всех объектов очень похожи.

Вот поэтому и появился django-supergeneric.

Пример использования можно посмотреть в демо-проекте: https://github.com/lig/django-supergeneric/tree/master/project

Upd: страничка на ohloh: https://www.ohloh.net/p/django-supergeneric, плюсуйте, кто будет пользоваться, если не лень.

22 дек. 2010 г.

Пам-парам, по "монгам"

Пару месяце назад я начал активно использовать MongoDB как для своих открытых проектов, так и по работе. Надо сказать, что этот относительно кратковременный опыт весьма и весьма положительный.

Я не хочу здесь пересказывать документацию. Однако, хочу описать некоторые вкусности, неочевидности и, конечно, подводные камни, с которыми я столкнулся в самом начале пути. Возможно, кому-то это сэкономит нервы в начале освоения этой документ-ориентированной базы данных.

В основном я использую MongoDB из Python, для чего использую PyMongo напрямую или MongoEngine (подобие ORM для MongoDB с несколькими фишками для использования его с Django). И вот как раз с взаимодействием с MongoDB из Python-а и связаны пара подводных камушков. Точнее с параллельным использованием JavaScript встроенного в MongoDB и PyMongo.

Вообще говоря, "питоновский" тип DateTime прекрасно обрабатывается MongoDB. Внимательный читатель документации сразу обратит внимание (я этого не сделал), что PyMongo нужно передавать дату/время в UTC, т.е. нужно использовать datetime.utcnow(), а не привычное большинству datetime.now(). Это первый подводный камень, который, впрочем, вполне очевиден и в явном виде отражен в документации.

Есть еще один подводный камень связанный с хранением времени. Предположим, что, по каким-то причинам, мы хотим хранить время в формате unixtimestamp, т.е. вещественное количество секунд прошедших с 1.01.1970 с тремя знаками после запятой или даже просто целым числом. Это может быть нужно чтобы упростить и ускорить запись журнала каких-либо событий в MongoDB. Пока мы работаем с этими числами через PyMongo, все хорошо. Но потом встает необходимость оперировать с этим значением времени из функции JavaScript, например, при выполнении операции Map/Reduce или просто в консоли MongoDB. Пусть в базе лежит такой документ (timestamp получен сейчас, т.е., как минимум, в 2010 году):
{ "_id" : ObjectId("4d125e16f52cb12e01d5d041"), "timestamp" : 1293038527, "name" : "jump" }
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/1Ot. Код раскрашен с помощью Paste.NoPHP.ru.
Пусть нам нужно получить год, месяц и число, в который произошло событие. Вот с чего начал я:
db.events.find().forEach(function (event) {
var date = new Date(event.timestamp);
print(date.getFullYear(), date.getMonth(), date.getDate());
})
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/1Ov. Код раскрашен с помощью Paste.NoPHP.ru.
Какого же было мое удивление, когда я увидел в ответ "1970 0 16", т.е. нулевой (!) месяц и всего 16-й день от начала эпохи. Чтение документации по JavaScript Date Object открыло мне глаза прежде всего на то, что в JavaScript timestamp - это целое число миллисекунд прошедших от начала эпохи. И код превратился в такой:
db.events.find().forEach(function (event) {
var date = new Date(event.timestamp * 1000);
print(date.getFullYear(), date.getMonth(), date.getDate());
})
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/1Ow. Код раскрашен с помощью Paste.NoPHP.ru.
Этот код вернул уже "2010 11 22", но сегодня 22.12.2010. Да, да, да, вспоминаем нулевой месяц. Из той же документации по JavaScript Date Object я узнаю, что, оказывается, именно месяц считается в JavaScript начиная с нуля (зачем???), тогда код приобретает окончательный вид:
db.events.find().forEach(function (event) {
var date = new Date(event.timestamp * 1000);
print(date.getFullYear(), date.getMonth() + 1, date.getDate());
})
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/1Ox. Код раскрашен с помощью Paste.NoPHP.ru.
Теперь ответ правильный: "2010 12 22".

На сегодня, думаю, этого достаточно. В следующий раз поделюсь опытом использования MapReduce в MongoDB. Покажу как возвращать из Reduce списки значений и какого подхода к расчету значений в Reduce следует избегать.

20 дек. 2010 г.

Git mass push. Как заслать во все "ремоуты".

Думаю, все, кто работал с git-ом, сталкивались с ситуацией, когда удаленных репозиториев несколько и хотелось бы сделать "git push" во все. Например, у меня проекты лежат одновременно на sf.net, github.com и в собственном приватном хранилище.

Я для себя написал маленький скриптик на bash:
#~/bin/bash
git remote show | while read repo_name
do
git push $repo_name $*
done
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/1Nu. Код раскрашен с помощью Paste.NoPHP.ru.
Если положить скрипт в файл "~/bin/git-mass-push", то его можно будет запускать командой "git mass-push", для которой будет работать стандартное автодополнение git-а. При этом, этой команде можно передать любые параметры, которые принимает "git push" после имени репозитория, что видно из кода.

Пользуемся, радуемся;)

30 марта 2010 г.

Yammy? Dummy? ... Botty!:) Простой бот на xmpppy

Сейчас появляется всё больше сервисов, использующих протокол XMPP (он же Jabber).

Например: juick.com, facebook.com, identi.ca.

Часто хочется написать своего бота, чтобы общаться с такими сервисами, может быть написать свой сервис или просто автоматически отвечать своим друзьям на глупые сообщения;)

В этом может помочь библиотека xmpppy.

Я использую вот такой класс, в качестве основы для моих ботов:


class BotBase(object, xmpp.Client):
    
    bot_name = None
    
    class InvalidCommandError(Exception):
        pass
    
    def __init__(self, *args, **kwargs):
        self.bot_settings = BOT[self.bot_name]
        self.__class__.__name__ = 'Client'
        xmpp.Client.__init__(self, server=self.bot_settings['HOST'],
            *args, **kwargs)
    
    def connect(self):
        xmpp.Client.connect(self, use_srv=self.bot_settings['SERVER'])
        return self.auth(self.bot_settings['USERNAME'],
            self.bot_settings['PASSWWORD'], self.bot_settings['XMPP_RESOURCE'])
    
    def run(self):
        self.UnregisterDisconnectHandler(self.DisconnectHandler)
        self.RegisterDisconnectHandler(self.reconnectAndReauth)
        self.RegisterHandler('message', self.messageHandler)
        self.RegisterHandler('presence', self.presenceHandler)
        self.sendInitPresence(0)
        while self.Process(1): pass
        self.disconnect()
    
    def messageHandler(self, connection, message):
        raise NotImplementedError
    
    def presenceHandler(self, connection, presence):
        raise NotImplementedError

Как видно, в коде используются настройки бота. Они выглядят вот так:


BOT['dummy'] = {
    'HOST': 'serge.matveenko.ru',
    'USERNAME': 'dummy',
    'PASSWWORD': 'dummy5',
    'XMPP_RESOURCE': 'mybot-0.1',
    'USER_AGENT': 'MyBot/',
    'SERVER': 'serge.matveenko.ru',
    'JID': 'lig@serge.matveenko.ru',
}

Для любого нового бота теперь нужно создать класс-наследник BaseBot, в котором установить атрибут bot_name и реализовать методы messageHandler и presenceHandler. В словаре BOT должен существовать ключ соответствующий этому bot_name с настройками для этого бота.

Запуск бота осуществляется так:


bot = MyBot.initBot(debug=False)
if not bot.connect():
    raise IOError('Can not auth with server.')
bot.run()

, где MyBot — класс вашего бота, а параметр debug говорит сам за себя.

That's all folks!

26 февр. 2010 г.

26 нояб. 2009 г.

Bash в bash-е, в bash-е, ..., в bash-е.

Думаю, всем знакома ситуация, когда, во время компиляции какого-либо ПО, выполнение `./configure` останавливается с сообщением о неудовлетворенной зависимости. И приходится установить что-нибудь еще и не одно, и не только из репозиториев, а еще и скомпилить что-нибудь другое, а там тоже зависимости.

Если всё это делать в одном терминале, то неизбежны: путаница в зависимостях, безуспешное копание в истории ввода и прочие пляски. Можно, конечно, для каждой задачи открывать новую вкладку в терминале или новый терминал, но тут может вмешаться параллельная задача и порядок задач будет нарушен, и открутить его в обратную сторону будет уже очень сложно - опять бардак.

Достаточно давно я выработал для таких случаев очень удобный, на мой взгляд, способ - рекурсивный запуск `bash`. Если для установки или сборки какого-либо приложения требуется установить или собрать что-нибудь другое, то в момент возникновения такой необходимости я запускаю `bash` прямо в этом же терминале. Это можно делать несколько раз, потом выходить из последнего запущенного экземпляра и получать действие, которое потребовало удовлетворить его зависимости предпоследним в истории. Примерно вот так:
lig@host:~/soft/application$ ./configure
Failed
"dependency" required
lig@host:~/soft/application$ bash
lig@host:~/soft/application$ sudo aptitude install dependency
lig@host:~/soft/application$ exit
lig@host:~/soft/application$ ./configure
Success
lig@host:~/soft/application$ 
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/e. Код раскрашен с помощью Paste.NoPHP.ru.

Однако, мне всегда не давало покоя, что я не вижу уровень вложенности `bash`. И вот недавно с небольшой посторонней помощью дошел до решения.

Небольшая добавка в `~/.bashrc`, которая добавляет в приглашение командной строки отображение уровня рекурсии начиная с ноля:
if [ -z $recursion ]; then
    recursion="0";
    export recursion;
else
    recursion=$(($recursion+1));
fi;

PS1='$recursion:'$PS1
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/c. Код раскрашен с помощью Paste.NoPHP.ru.

После добавления этого кода в `~/.bashrc` первый листинг будет выглядеть вот так:
0:lig@host:~/soft/application$ ./configure
Failed
"dependency" required
0:lig@host:~/soft/application$ bash
1:lig@host:~/soft/application$ sudo aptitude install dependency
1:lig@host:~/soft/application$ exit
0:lig@host:~/soft/application$ ./configure
Success
0:lig@host:~/soft/application$ 
Постоянная ссылка на этот кусок кода: http://paste.nophp.ru/f. Код раскрашен с помощью Paste.NoPHP.ru.

Вот так-то лучше:)

24 нояб. 2009 г.

«Вредная» монополия Microsoft

По мотивам новости «Microsoft откроет свои магазины рядом с магазинами Apple» (news.techlabs.by) и обсуждения в рассылке SPb LUG.
Монопо́лия (от греч. μονο (mono) — один и πωλέω (poleo) — продаю) — фирма (ситуация на рынке, на котором действует такая фирма), действующая в условиях отсутствия значимых конкурентов (выпускающая товар(ы) и/или оказывающая услуги, не имеющие близких заменителей). Первые в истории монополии создавались сверху санкциями государства, когда одной фирме давалось привилегированное право торговли тем или иным товаром.
(wikipedia)
Тут надо понять что есть рынок в нашем случае. Рынок операционных систем? Вряд ли. Скорее рынок коммерческого ПО. А Linux - это ПО с открытым исходным кодом, т.е. MS не конкурент Linux и Open Source в целом. Ух ты!

А теперь вспомним, что конкуренция - это хорошо. А почему? А потому что конкуренция - это двигатель прогресса. Конкуренция заставляет соперничать и разрабатывать новое, полезное пользователю.

Но, если Microsoft - монополист, то им не надо будет развивать свои продукты? Это же хорошо. Это значит, что Open Source победит тогда и только тогда, когда перестанет лезть в коммерческие и области. Тогда, когда мы найдем для него некоммерческий рынок сбыта, такой же емкий, как комерческий.

Если главный путь распространения коммерческого ПО - это магазины, значит ПО с открытым исходным кодом надо раздавать с другой стороны двери, т.е. на улице. Да, так и только так.