All articles, tagged with “python”

Хочу на работу

Хочу на fulltime удалённую работу python/django разработчиком со стандартной занятостью в пять рабочих дней в неделю. Последний год пробовал работать во freelance режиме на odesk.com — слишком гибко для меня.

Краткое резюме можно посмотреть по адресу lorien.name/cv.html Если вкратце, то занимаюсь django с 2007 года, питон в основном применял для веб-разработок и для различных скриптов сбора и обработки информации.

Мои контакты:

  • email/jabber: lorien@lorien.name
  • skype: lorien.name

Статус проекта pybb

Зимой 08-09 я активно работал над проектом pybb, затем весной я забил на проект и вот последний месяц опять работаю :) В течении года от нескольких человек поступили существенные контрибуции, за что им большое спасибо. Правда некоторые я вырезал уже, потому что понял, что проект начинает превращаться в очередной all-in-one кухонный комбайн. Из существенного — были оторваны личные сообщения, были вырезаны левые поля типа location, site, icq и т.д. из форумного профиля, была вырезана генерация специфических вещей из главных вьюшек типа index, category, topic и перенесена в template tags, было вырезано велосипедное поле для аватарки с ресайзом и заменено на поле из пакета sorl-thumbnail, всего и не припомню. Суть этих изменений в том, что pybb должен содержать лишь базовый функционал форумного движка, который каждый уже сам сможет расширять при желании для нужд конкретного проекта.

Я подумываю также вырезать шаблоны из стандартной поставки pybb и выложить их в виде отдельного пакета. Кстати, один уже такой пакет я создал — это pybb-templates-fluxbb. Шаблоны, имитирующие вид форума fluxbb (punbb в девичестве). Большая часть шаблонов уже сделана, остались правки по мелочи и i18n. Посмотреть на то как выглядят шаблоны можно тут http://pydev.ru

Отсутствие шаблонов в базовой поставке позволит более вдумчиво разносить JS-логику и template тэги. Часть будет специфичной для шаблона, а часть будет общая для всех шаблонов т.е. будет лежать в пакете pybb. Пример общей js логики — скрипт для прорисовывания дополнительных файловых полей в форме для создания сообщения. Ещё один пример: скрипты markitup редактора.

Я не думаю останавливаться на достигнутым и надеюсь вырезать что-нибудь ещё. Возможно, генерацию email извещений — был ведь какой-то стандартный пакет, возможно, им заменю :-)

Также, я думаю, имеет смысл создать проект с именем типа pybb-standalone, который будет представлять из себя готовый django-проекты, который можно будет поднять на сервере с минимальными усилиями. От текущего варианта установки волосы дыбом встают.

Приятно, что на базе pybb работают несколько живых форумов:

Разгоняем django сайт с помощью staticgenerator

Я неспешно переезжаю на VDS от gandi.net. Был дедик с объёмом памяти один гиг, а переезаю на 256 метров. Пока не знаю, удастся ли втиснуться. Конечно же, возникают вопросы, а можно ли убыстрить мои сайты, заставить их жрать меньше памяти. Сегодня ночью я сражался с web-brains, а точнее с byteflow, на котором они работают. Выяснилось, что более одного запроса в секунду apache/mod_wsgi демон не обрабатывает, хоть ты тресни. Я пока не понял, во что именно упирается byteflow, но это не важно в рамках данного сообщения. Решил я закешировать веб-мозги, да так шобы прям в усмерть закешировать. Как раз вчера один знакомый упоминал staticgenerator и я понял — самое время призвать на помощь его мощности статическогенерические.

Идея, лежащая в основе staticgenerator оень простая и дерзкая. Адрес веб-страницы похож на файловый путь. Когда мы запрашиваем страницу /2009/08/07/hello-world, то staticgenerator создаёт в заданном каталоге такую же структуру т.е. каталог 2009, в нём каталог 08, в нём каталог 07, в нём каталог hello-world, а в нём файл index.html, куда и записывается содержимое сгенерированной страницы. Всё это делает специальная middleware. Далее начинается самое интересное, мы настраиваем веб-сервер так, чтобы он смотрел в заданном каталоге закэшированный файл и если там он есть, то файл выдаётся пользователю и джанго-демон даже не дёргается. Вот такие пироги :)

Далее я опишу технические детали того, как я прикрутил staticgenerator конкретно к byteflow.

Для начала неплохо установить staticgenerator через easy_install

Далее идём в settings_local.py проекта и пишем там такое в конце:

# Здесь мы просим staticgenerator складывать кэш файл в каталог нужный
WEB_ROOT = '/web/cache/staticgenerator/web-brains.com'

# Это регулярные выражения. Будут кэшироваться только те страницы, которые
# удовлетворяют выражениям. Я поковырялся в urls файлах byteflow и выбрал те,
# которые относятся к непосредственному отображению данных
STATIC_GENERATOR_URLS = (
    r'^/$',
    r'^/feeds',
    r'^/\d',
    r'^/tag',
    r'^/tags',
    r'^/featured',
    r'^/author',
    r'^/sitemap\.xml',
    r'^/robots\.txt',
)

# Так можно в byteflow добавить новую middleware из setttings.py
ADDITIONAL_MIDDLEWARE= ('staticgenerator.middleware.StaticGeneratorMiddleware',)

Ещё я в settings.py добавил cache_manager в ADDITIONAL_APPS. А что такое cache_manager? А это такое приложение, которое сигналы ловит при создании нового поста или нового комментария и грохает весь кэш. Можно, конечно, хорошо подумать и написать очищение только изменившихся страниц, а можно просто грохнуть быстренько всё :) Я запихал код в cache_manager/__init__.py — таким образом я добился его выполнения при старте сайта т.е. установки нужных обработчиков сигналов.

from subprocess import Popen from staticgenerator import quick_delete

from django.db.models.signals import post_save
from django.conf import settings

from blog.models import Post
from discussion.models import CommentNode

def clear_cache(**kwargs):
    Popen('rm -rf %s/*' % settings.WEB_ROOT, shell=True)

post_save.connect(clear_cache, sender=Post)
post_save.connect(clear_cache, sender=CommentNode)

Код элементартный. Если создали Post или CommentNode — грохаем кэш!

Так, так. Вот мы сайт настроили. Осталось веб-сервер настроить. Веб-сервер у нас, конечно, nginx. У меня такой конфиг для веб-мозгов:

server { server_name .web-brains.com; access_log /var/log/www/web-brains.com-access.log; error_log /var/log/www/web-brains.com-error.log warn;

    include fastcgi_params;

    location /admin-media {
        alias /home/web/lib/django_src/django/contrib/admin/media;
    }
    location /static/ {
        root /web/web_brains;
    }

    # Нас интересует ниженарисованный кусок конфига
    # Делаем корнем для статики тот каталог, куда
    # staticgenerator кэш пишет.
    root /web/cache/staticgenerator/web-brains.com;
    location / {
        # Всё что не GET, отдаём бэкенду. У меня это apache/mod_wsgi
        if ($request_method != GET) {
            proxy_pass http://127.0.0.1:8080;
            break;
        }
        # Вот тут чуток меняем путь поиска т.к. staticgenerator
        #  пишет кэш в index.html файлы
        if (-f $request_filename/index.html) {
            rewrite (.*) $1/index.html break;
        }
        # Если файл таки не нашёлся, идём в бэкенд
        # иначе ничего не делаем и благополучно выкидываем
        # закэшированный файл пользователю
        if (!-f $request_filename) {
            proxy_pass http://127.0.0.1:8080;
            break;
        }
    }
}

Гм, ну вот и всё. Ща сохраню статью и заодно проверю, что кэш грохнулся. Я запускал ab тесты прямо на VPS. Раньше морда веб-мозгов раздавалась со скростью 1page/second, а щас 668pages/second. Клёво :)

Деплоймент python проекта: pip & virtualenv

При работе над 3task мы разработали удобный способ установки зависимостей для проекта. Америку я не открою, просто расскажу, что мы сделали. Надеюсь, кому-нибудь пригодится.

Зависимости обрабатываются с помощью двух инструментов: pip и virtualenv.

Virtualenv позволяет создать собственное окружение проекта, в которое можно установить любые библиотеки любых версий и они никак не будут влиять на другие проекты. Это достаточно удобно. К примеру, можно обновлять джангу и не бояться, что другие проекты попадают, как это могло бы случиться в системе, где несколько проектов используют глобально установленную django.

Pip — это инструмент для установки python-приложений. Грууубо говоря, это аналог easy_install, отличающийся рядом вкусных плюшек. Одна из убойных его фишек — возможность ставить софт прямо из репозитория. Я пробовал ставить из git, svn, hg — всё работает. Возможно, easy_install умеет что-то подобное, я не в курсе, честно говоря. Другие отличительные черты pip: поддержка virtualenv и возможность ставить софт по заранее составленному списку.

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

Файлик build/buildenv.sh руководит всем процессом:

#!/bin/sh
echo Creating environment
virtualenv .env

echo Install PIP inside virtual environment
./.env/bin/easy_install pip

echo Installing dependencies
./.env/bin/pip install -E .env -r ./build/pipreq.txt

То есть сначала создаём виртуальное окружение с помощью инструмента virtualenv. Далее устанавливаем pip через easy_install. Можно, конечно и средствами пакетного менеджера вашей ОС, но easy_install стоит почти везде, так что грех этим не воспользоваться. Третий шаг — установка необходимых библиотек в виртуальное окружение по списку.

Приведу пример файла зависимостей:

-e svn+http://code.djangoproject.com/svn/django/trunk@10748#egg=django
-e svn+http://django-filebrowser.googlecode.com/svn/trunk@358#egg=django-filebrowser
-e svn+http://sorl-thumbnail.googlecode.com/svn/trunk@449#egg=solr
-e svn+http://django-tagging.googlecode.com/svn/trunk@156#egg=django-tagging

-e hg+http://bitbucket.org/lorien/django-account#egg=django-account
-e hg+http://hg.barbuza.info/supercaptcha/#egg=supercaptcha
-e hg+http://bitbucket.org/lorien/pybb#egg=pybb

django-renderform
simplejson
markdown
beautifulsoup
http://django-tinymce.googlecode.com/files/django-tinymce-1.5.tar.gz

Как видите, источники можно указывать самые разные: адреса hg/git/svn репозиториев, адреса архивов, просто имена (поиск идёт через pypi.python.org). Единственное условие: пакет по указанному адресу должен обязательно содержать файл setup.py.

Если специально не запрещать, то пакет ищется сначала в виртуальном окружении, в случае неудачи — в глобальном окружении. Это мне помогло в случае PIL. Почему-то не хотел он компиляться при установке через pip — я поставил его через apt. Драйвера баз данных типа mysqdb мне тоже кажется удобным ставить отдельно — в глобальную область видимости.

Можно почитать по теме:

Работа

У меня есть два сообщения про работу.

1)Ищу удалённую работу. Умею делать сайты на django и писать скрипты на python. Вот здесь можно прочитать что-то типа резюме: lorien.name/resume.html

2) Сегодня с утра набросал сайтик pyjob.ru. Почти что русский клон djangogis.com, за исключением того, что я не стал делать каталог разработчиков т.е. pyjob.ru — это просто список вакансий. На данный момент список пустой. Буду прилагать усилия в направлении его наполняемости.

Если у вас есть какие-то предложения по первому или второму пункту, то мой email/jabber: lorien@lorien.name

UPD: Объявление о поиске работы уже неактуально.

Распараллеливание заданий

В питоне есть модуль threading, позволяющий одновременно выполнять несколько заданий. Часто бывает так, что количество допустимых потоков меньше, чем количество заданий. Например, при работе с сетью мы упрёмся в ширину канала, при вычислениях мы упрёмся в скорость процессора или в GIL.

Для такой задачи я написал удобный код, суть которого это выполнение X заданий в Y потоков. До изобретения велосипеда я пробовал threadpool библиотеку, но она выдавала странные эксепшны время от времени и я углядел в этом знак свыше — боги одобряют очередной велосипед.

Так появился этот код: http://dumpz.org/8724/. Модуль содержит единственную функцию

def make_work(callback, tasks, limit, results=None):

Для каждого задания запускается callback, одновременно запускается не более, чем limit потоков. Если нужно сохранять выхлоп callback’ов, то создаём объект Queue и передаём в аргументе results. Аргумент tasks может быть или объектом Queue или tuple или list. В последних двух случаях он автоматически преобразовывается в Queue.

UPD: Порефакторенная с учётом заменчаний версия make_work: http://dumpz.org/10066/

linkfeed.ru & python

Захотелось мне пощупать linkfeed.ru биржу ссылок. Поэтому пришлось писать python-адаптер. Как оказалось биржа вполне себе прогрессивная — возможно забирать базу данных в виде XML. Даже какой-то SOAP API есть для оптимизаторских контор.

Если кому нужно, адаптер можно взять тут: hg.pydev.ru/linkfeed. Код пока очень сырой — я его минут 10 назад написал :-) Как пользоваться написано в README

Приручаем socks прокси

Бывает нужно наладить работу библиотеки через socks прокси, но сама библиотека не предоставляет такой возможности. Если внутри неё используется модуль socket, то проблему можно решить с помощью socksipy

Рассмотрим конкретный пример. Библиотека xmpppy. Она позволяет работать только c HTTP/HTTPS проксями. Попробуем это изменить. Исследовав исходный код, увидим, что подключение к сети осуществляется в методе xmpp.transports.TCPsocket.connect, где создаётся сокет. Этот метод и нужно манкипатчить.

Я сделал так:

import xmpp
import xmpp_hack

xmpp_hack.PROXY = '12.34.56.789'
xmpp_hack.PORT = '7890'
... далее работаем с библиотекой xmpp как обычно!...

Вся суть в xmpp_hack модуле:

import xmpp
import logging
import socket
# Импорируем библиотеку socksify
import socks

from xmpp import transports

PROXY = ''
PORT = 0

# Это метод, которым мы проманкипатчим xmpp либу
# Он в точности похож на оригинал за исключением пары строчек
def custom_connect(self,server=None):
    """ Try to connect. Returns non-empty string on success. """
    try:
        if not server: server=self._server

        # Комментируем стандартное создание сокета
        #self._sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        logging.debug('Using %s:%s SOCKS5 proxy' % (PROXY, PORT))
        # И создаём socks-обёртку, которая имитирует обычный сокет
        self._sock = socks.socksocket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.setproxy(socks.PROXY_TYPE_SOCKS5, PROXY, PORT)

        # Далее продолжается код из оригинального метода
        self._sock.connect(server)
        self._send=self._sock.sendall
        self._recv=self._sock.recv
        self.DEBUG("Successfully connected to remote host %s"%`server`,'start')
        return 'ok'
    except socket.error, (errno, strerror): 
        self.DEBUG("Failed to connect to remote host %s: %s (%s)"%(`server`, strerror, errno),'error')
    except Exception, ex:
        raise
        #logging.error(ex)

# Собственно манкипатчинг!
transports.TCPsocket.connect = custom_connect

Удачного манкипатчинга :-)

Парсим yahoo красиво

На днях обнаружил неплохую альтернативу для поиска из скриптов. Это использование Yahoo поиска через BOSS API.

Основные плюсы:

  • Цитирую: BOSS offers developers unlimited daily queries, though Yahoo! reserves the right to limit unintended usage, such as automated querying by bots. In the near future, we will be introducing a fee structure for BOSS. For more infomation, please see the Fee Structure section below.Для работы с API можно использовать python-библиотеку
  • Yahoo сама сделала python библиотеку для работы с API

Как поставить библиотеку, а также сделать простые запросы хорошо описано тут. У меня возникли трудности с тем, что пакет util не копировался куда нужно. Я поступил просто — посмотрел, что util используется только в пакете yos.yql и скопировал его туда ручками.

Простенький пример:

>>> from yos.boss import ysearch
>>> from yos.yql import db
>>> data = ysearch.search("Django",count=10)
>>> table = db.create(data=data)
>>> table.rows
[
  { u'dispurl': u'www.<b>djangoproject.com</b>',
    u'title': u'<b>Django</b> | The Web framework for perfectionists with deadlines',
    u'url': u'http://www.djangoproject.com/', u'abstract': u'<b>Django</b> is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. <b>...</b> <b>Django</b> focuses on automating as much as possible <b>...</b>',
    u'clickurl': u'http://www.djangoproject.com/',
    u'date': u'2008/06/19',
    u'size': u'8524'
  }, 
  "would display 9 more results, but removed to save space"
]

Планета русского веба данных.

Агрегатор блогов по теме веб данных (семантический веб) можно считать официально работающим — сегодня были отлажены последние крупные баги. Лента будет работать по адресу http://lenta.webofdata.ru Можно читать сообщения online через сайт, а можно подписаться на фид и читать через любимый news reader.

Код агрегатора доступен по адресу http://hg.pydev.ru/feedzilla. Так что любой желающий может завести на нём свою планетку :-) Интересная фича агрегатора — он позволяет выводить только тематические сообщения. Тематичность определяется по наличию нужных тэгов или слов в тексте. Алгорим можно посмотреть тут