вторник, 28 августа 2012 г.

Синхронизация git-репозитория на usb flash

Для некоторых проектов бывает нецелесообразно использовать центральные репозитории  наподобие github или bitbucket, а синхронизировать данные проекта тем не менее хочется.
В этом случае можно организовать перенос проекта между машинами на обыкновенной флешке.

Самое простое решение это архивация файлов проекта и копирование архива на флешку, но делать это каждый раз несколько утомительно. Гораздо удобнее воспользоваться возможностями, которые предоставляет git.

Создание локального репозитория в директории проекта:
git init 

Добавление файлов проекта в репозиторий:
git add . 

Допустим флешка смонтирована в директорию /media/usb, тогда следующая команда создаст копию репозитория на флешке:
git clone --local --bare --no-hardlinks . /media/usb/project/path

- обратите внимание на опцию --no-hardlinks, без этой опции команда выдает ошибку Invalid cross-deive link.

Чтобы не указывать каждый раз путь к копии репозитория добавим его с помощью команды:
git remote add usb file:///media/usb/project/path

Теперь после внесения изменений и коммита в локальный репозиторий можно переносить изменения на флешку командой:
git push usb

Для получения обновлений с флешки используется команда:
git pull usb master

Вместо master можно указать другую ветку.
При создании проекта на другой рабочей машине нужно скопировать репозиторий с флешки:
git clone file:///media/usb/project/path

Т.о. получаем удобную систему для синхронизации проекта между двумя и более рабочими машинами с помощью usb флешки или любого другого портативного носителя без использования центрального репозитория.

среда, 27 июня 2012 г.

Python-скрипт для получения курсов валют

Довольно часто встречается задача получения курсов валют и сохранение результатов в базу. Задача достаточно проста, чтобы привлекать для этого "тяжелую артиллерию" типа java, поэтому предлагается воспользоваться Python.
Почему Python?
Во-первых, это современный и популярный язык, во-вторых, python повсеместно используется для автоматизации задач в Linux, так что практически на любом хосте с Linux будет Python, ну и в-третьих интересно было попробовать Python на реальной задаче.
Итак, имеем базу данных на MSSQL 2000, в которую нужно сохранить список курсов валют на каждый день. В базу нужно сохранять не все доступные курсы, а только те, которые актуальны для приложения. Таблица со списком актуальных валют также находится в базе данных.
Еще имеется внешний ресурс, который выдает список курсов, он расположен по адресу http://www.cbr.ru/scripts/XML_daily.asp. Курсы выдаются в формате XML.
Алгоритм работы скрипта выглядит следующим образом:
  1.  Получить из базы список актуальных валют,
  2.  Получить и распарсить XML с курсами валют от cbr.ru,
  3.  Найти среди списка курсов курсы нужных валют и сохранить в базу,
  4.  Отправить сообщение на электронную почту с результатами работы скрипта.
Для работы скрипта понадобится следующее:
  1. Linux - ну это понятно, я использовал Ubuntu 12, но скорее всего подойдет любой современный дистрибутив,
  2. Python 2.6 и выше
  3. База данных, у меня используется MSSQL 2000
  4. Библиотека pymssql для создания запросов к БД
Про установку pymssql нужно рассказать чуть подробнее. Хотя для Ubuntu существует пакет deb, но он у меня не заработал, т.е. правильный запрос к базе не возвращал никаких результатов, при этом не выдавая какую-либо ошибку. Поэтому, по многочисленным советам я установил последнюю версию pymssql через pip.
Сначала установим pip:

sudo apt-get install python-pip 
Затем установим библиотеку Cython, которая нужна для сборки pymssql и не только его:
sudo pip install cython
И еще pyrex, если она не установлена
sudo pip install pyrex
После чего наконец можно установить pymssql:
sudo pip install pymssql

Примеры использования pymssql можно найти на странице проекта, а у меня получение списка валют выглядит следующим образом:
def load_currencies():
        currencies = []
        conn = pymssql.connect(host = 'dbhost', user='user',password='secret',database='my_database')
        try:
                cur = conn.cursor()
                cur.execute('SELECT ShortName, ID FROM [Currency] where not exists(select * from Currency_Exchange_Rate \
                        where ListID = %d and FromCurrency = Currency.ID and [Date] = dbo.GetDay(GETDATE()))', 1)
                row = cur.fetchone()
                while row:
                        currencies.append({'Name':row[0], 'ID':row[1]})
                        row = cur.fetchone()
        finally:
                conn.close()
        return currencies
Функция load_currencies возвращает список актуальных валют, для которых не установлен курс на текущую дату.

Затем, получение и парсинг списка курсов валют:
u = urllib2.urlopen("http://www.cbr.ru/scripts/XML_daily.asp", timeout=10)
parser = xml.sax.make_parser()
handler = CurrencyRateHandler()
parser.setContentHandler(handler)
parser.parse(u)
print handler.mapping

Для загрузки файла используется метод urlopen из библиотеки urllib2. Он возвращает поток, который можно сохранить в файл, а можно напрямую передать парсеру, как в данном случае. Для разбора полученного XML используется стандартный SAX-парсер. Чтобы парсер мог что-то сделать с полученным файлом ему необходим обработчик. Код обработчика представлен в классе CurrencyRateHandler, который наследуется от библиотечного класса xml.sax.handler.ContentHandler. Код обработчика:
class CurrencyRateHandler(xml.sax.handler.ContentHandler):
        def __init__(self):
                self.inTag = ""
                self.charCode = ""
                self.mapping = {}
        def startElement(self, name, attributes):
                if name in set(["CharCode","Nominal", "Value"]):
                        self.buffer = ""
                        self.inTag = name
                else:
                        self.inTag = ""
        def characters(self, data):
                if self.inTag:
                        self.buffer += data
        def endElement(self,name):
                if name == "CharCode":
                        self.charCode = self.buffer
                        self.mapping[self.charCode] = {}
                elif name == "Nominal":
                        self.mapping[self.charCode][self.inTag] = int(self.buffer)
                elif name == "Value":
                        self.mapping[self.charCode][self.inTag] = float(self.buffer.replace(',','.'))
                self.inTag = ""
SAX-обработчик получает события startElement и endElement при обработке открывающего и закрывающего тегов XML соответственно. Содержимое тега поступает в метод characters. Обработчик устроен достаточно просто он проверяет на соответствие имени тега с определенными шаблонами и аггрегирует данные в словарь mapping. В результате разбора получается словарь ключами, которого являются коды валют, а значениями словари, каждый из которых содержит информацию о курсе валюты и номинале обмена (1, 10 или 100 единиц). Затем нам нужно из полученного от парсера словаря отфильтровать только те валюты, которые нас интересуют и записать значения их курсов в базу. Фильтрация словаря выполняется с помощью функции filter:
update_rates(filter(lambda c: rates.has_key(c['Name']), currencies), rates)
Функция update_rates выполняет запись в базу следующим образом:
def update_rates(currencies, rates):
        conn = get_connection()
        try:
                cur = conn.cursor()
                cur.executemany('INSERT INTO Currency_Exchange_Rate(Date,ListID, FromCurrency, Factor,Amount)\
        VALUES(dbo.GetDay(GETDATE()),1,%d,%d,%s)', map(lambda c: (c['ID'], rates[c['Name']]['Nominal'], rates[c['Name']]['Value']), currencies))
                conn.commit()
        finally:
                conn.close()
самое интересное в этой функции находится в строке 6, там для формирования списка значений для команды INSERT используется функция map и лямбда-функция, возвращающая кортеж значений для вставки в базу из словаря курсов валют. Вот в общем-то и все, остаются всякие полезные мелочи вроде логгирования и отправки сообщения по электронной почте. Полученный скрипт можно поместить в /etc/cron.daily, чтобы он выполнялся ежедневно по расписанию. Для этого в заголовке нужно указать оболочке какой интерпретатор следует использовать (python конечно же):
#!/usr/bin/env python
import sys
import pymssql
import urllib2
import xml.sax.handler
и дать файлу разрешение на выполнение:
sudo chmod 744 updater.py

понедельник, 4 июня 2012 г.

Ubuntu и FireFox 12 по-русски

После очередного обновления у FireFox на 12ю версию интерфейс браузера неожиданно стал англоговорящим.
Система Linux Mint 12.
Пакет firefox-locale-ru установлен.
Удаление пакета firefox-locale-en не помогает.
По совету из форума  на сайте mozilla-russia нашел свою версию FireFox и установил LangPack (который в колонке Русификация).
Задача решена - интерфейс русский.