Фреймворк фільтра TSV (CSV)

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

TSV (значення розділені табуляцією) - це формат таблиці, яка зберігається в звичайному текстовому файлі і в якій кожен рядок – це рядок тексту, а стовпці розділяються символом табуляції. Його вміють відкривати всілякі програмки електронних таблиць, але перевага ще й в тому що його можна прочитати за допомогою звичайнісінького cat. Для прикладу табличка з групами крові (дані не перевірені, якщо ви сюди за ними, то краще спитайте лікаря):

bunyk@xubuntyk:~/tsv_filter_test$ cat blood.tsv 
Тип	Можливий донор для
A+	A+, AB+
O+	O+, A+, B+, AB+
B+	B+, AB+
AB+	AB+
A-	A+, A-, AB+, AB-
O-	A+, O+, B+, AB+, A-, O-, B-, AB-
B-	B+, B-, AB+, AB-
AB-	AB+, AB-


Давайте напишемо програму, яка візьме цю табличку, і перетворить її на табличку з парами дозволених переливань:

from csv_wrapper import transform_tsv # про це трохи далі 

def main():
    transform_tsv('blood.tsv', 'blood_pairs.tsv', transformation)

def transformation(row, num):
    if num == 0: # перший рядок - заголовок
        yield row # не чіпаємо
        return
    for recipient in row[1].split(', '): # пробігаємо по списку реципієнтів в другій колонці
        yield (row[0], recipient) # і віддаємо для кожної пари донор-реципієнт новий рядок

if __name__ == '__main__':
    main()

Отримуємо файл що починається так:

bunyk@xubuntyk:~/tsv_filter_test$ head blood_pairs.tsv 
Тип	Можливий донор для
A+	A+
A+	AB+
O+	O+
O+	A+
O+	B+
O+	AB+
B+	B+
B+	AB+
AB+	AB+

Тепер про те як працює transform_tsw. Вона використовує стандартну бібліотеку, але проблема зі стандартним модулем csv в тому що в python2 він дуже погано дружить з кодуваннями, і вимагає передавати йому байти, бо, бачте, з закінченнями рядків він сам хоче розібратись… Є ще модуль unicodecsv, який вирішує проблему з Python2, але в Python3 csv вже нормально спілкується Юнікодом, а open приймає кодування, тому там він не такий корисний.

Як варіант, ми використаємо адаптер описаний в книжці Porting to Python3. Тут його наводити не будемо, бо довгий (я його додав в бібліотеку butils), а просто розглянемо функцію transform_tsv, бо цікава:

def transform_tsv(src, dst, transformation, encoding='utf-8'):
    with UnicodeReader(src, encoding=encoding, delimiter='\t') as reader, \
        UnicodeWriter(dst, encoding=encoding, delimiter='\t') as writer:
        for i, src_row in enumerate(reader):
            for dst_row in transformation(src_row, i):
                writer.writerow(dst_row)

Тут за допомогою with ми відкриваємо одночасно два файли. А далі все очевидно. Проходимось по рядках першого, запускаємо фукцію трансформації і всі результати які вона видає записуємо у рядки другого.


Filed under: Кодерство Tagged: Python

Звідки в коді йде цей вивід? (вдосконалюємо логи)

Найважливіше питання для розробника якому треба пофіксити помилку, і він не знає де, але знає що в логах написано щось не те – хто саме й звідки ті логи пише.

Для вирішення такої проблеми я написав функцію watch_for_output, яка патчить sys.stdout та sys.stderr так що ті починають з кожним рядком що їм передають, виводити всі контактні дані того хто передає. Я помістив її в свою бібліотеку з всякими утилітами, тому використати її можна так:

import butils
butils.watch_for_output()

print 2 + 2

# OUT: test.py:4 print 2 + 2
# OUT: 4
# OUT: test.py:4 print 2 + 2 # print друкує ще перехід на новий рядок
# OUT: 

Біда тільки що коли вивід відбувається за допомогою не print, а наприклад якогось log.info, вийде таке:

2014-04-10 20:56:19,593 INFO zen.zenpython: Connecting to localhost:8789
/opt/zenoss/lib/python2.7/logging/__init__.py:863 stream.write(fs % msg)

Не дуже корисно… Але, ми бачимо в рядку лога таке: zen.zenpython. Це, скоріш за все, назва логера, а ми знаємо що логгер – сінглтон відносно назви (Note that Loggers are never instantiated directly, but always through the module-level function logging.getLogger(name). Multiple calls to getLogger() with the same name will always return a reference to the same Logger object. Logger objects). Тепер, ми можемо пропатчити його формат так, аби він друкував окрім всього іншого рядок і файл з якого викликаний:

import logging

FORMAT = '%(pathname)s:%(lineno)s %(message)s'
fmtr = logging.Formatter(FORMAT)
han = logging.StreamHandler()
han.setFormatter(fmtr)

log = logging.getLogger('zen.zenpython')
log.addHandler(han)

І ми отримуємо щось на зразок:

/opt/zenoss/Products/ZenHub/PBDaemon.py:602 Connecting to localhost:8789
2014-04-10 21:05:27,853 INFO zen.zenpython: Connecting to localhost:8789

Хей, тепер ми знаємо звідки можна починати копатись в коді!

Інші корисні поля, наприклад func (назва функції з якої відбувається логування) чи name (назва логера) описані в класі LogRecord.


Filed under: Кодерство Tagged: Python

Список акселераторів типів в PowerShell

$acceleratorsType = [type]::gettype("System.Management.Automation.TypeAccelerators")
# хитрий спосіб обійти той факт що цей тип чомусь недоступний.
$acceleratorsType::Add("accelerators", $acceleratorsType)
[accelerators]::Get | Format-Table -AutoSize
Key                   Value
---                   -----
int                   System.Int32
long                  System.Int64
string                System.String
char                  System.Char
bool                  System.Boolean
byte                  System.Byte
double                System.Double
decimal               System.Decimal
float                 System.Single
single                System.Single
regex                 System.Text.RegularExpressions.Regex
array                 System.Array
xml                   System.Xml.XmlDocument
scriptblock           System.Management.Automation.ScriptBlock
switch                System.Management.Automation.SwitchParameter
hashtable             System.Collections.Hashtable
type                  System.Type
ref                   System.Management.Automation.PSReference
psobject              System.Management.Automation.PSObject
pscustomobject        System.Management.Automation.PSObject
psmoduleinfo          System.Management.Automation.PSModuleInfo
powershell            System.Management.Automation.PowerShell
runspacefactory       System.Management.Automation.Runspaces.RunspaceFactory
runspace              System.Management.Automation.Runspaces.Runspace
ipaddress             System.Net.IPAddress
wmi                   System.Management.ManagementObject
wmisearcher           System.Management.ManagementObjectSearcher
wmiclass              System.Management.ManagementClass
adsi                  System.DirectoryServices.DirectoryEntry
adsisearcher          System.DirectoryServices.DirectorySearcher
psprimitivedictionary System.Management.Automation.PSPrimitiveDictionary
accelerators          System.Management.Automation.TypeAccelerators

Про деталі реалізації в C#, які змушують писати такий дивний код дивіться джерело.


Filed under: Кодерство Tagged: powershell

Робота з чергами повідомлень MSMQ в PowerShell

Інсталяція сервісу MSMQ

(1) Зайти в панель керування, (2) Programs and features, (3) натиснути Turn windows features on and off, (4) розкрити дерево до Microsoft Queue Message Server Core і увімкнути пункти “MSMQ HTTP Support” та “MSMQ Active Directory Domain Services Integration”, якщо ви в домені. (5) – Натиснути OK.

MSMQInstall

Операції

Перелічити черги:

Get-MsmqQueue | Select QueueName, MessageCount # дає деякі черги і кількість повідомлень
Get-WmiObject Win32_PerfFormattedData_msmq_MSMQQueue | Select Name, MessagesinQueue # дає трохи більше черг

Створити чергу:

$queue = New-MsmqQueue queue_name

Отримати існуючу чергу за іменем:

$queue = Get-MsmqQueue queue_name

Послати повідомлення в чергу $queue:

$msg = New-MsmqMessage "Hello world!"
Send-MsmqQueue -Name $queue.Path -MessageObject $msg

Послати тисячу повідомлень в чергу за 10000 секунд, при цьому відображаючи їх кількість в реальному часі:

function send_message($msg_text, $queue_name)
{
	$msg = New-MsmqMessage $msg_text
	$queue = Get-MsmqQueue $queue_name
	Send-MsmqQueue -Name $queue.Path -MessageObject $msg | Out-Null
	
	Clear-Host
	Get-MsmqQueue -Name $queue_name | Format-Table QueueName, MessageCount
	Start-Sleep -s 10
}
for($i=0; $i -le 1000; $i++) { send_message $i $queue_name }

Очистити чергу $queue від повідомлень:

Clear-MSMQQueue -InputObject $queue

Видалити чергу повідомлень $queue:

Remove-MSMQQueue -InputObject $queue

Посилання


Filed under: Кодерство Tagged: windows

Стиль – це результат гіркого досвіду

Imports should usually be on separate lines. (PEP8)

Колись давно, десь три роки тому, всередині фрейморку Zope якийсь добрий чоловік зробив отаку зміну.

Якщо конкретно, то мені в ній найбільше сподобалось оце:

І зараз я з прикладом поясню чому менше рядків краще лише в тому випадку коли рядки при цьому не стають довшими. Нехай ми працюємо з версією Zope зробленою ще до цього коміта і маємо трейс:

Переконуємось що App.special_dhtml містить клас DTMLFile. Значить причина помилки при імпорті в тому що код модуля не встигає прогнатись аж до того місця де йому кажуть створити цей клас. Методом дихотомії визначаємо що проблема в рядку 14:

import DocumentTemplate, Common, Persistence, MethodObject, Globals, os, sys

Десь тут виникає той самий ImportError, але рядок надто довгий аби точно знати де. Доведеться переписати аби рядків було більше. Далі import pdb; pdb.set_trace(); n n n n n ..., бачимо що винен модуль Globals, який імпортує App, а щоб імпортувати App, треба імпортувати special_dhtml, імпортом якого ми зараз займаємось.

Бачимо що й без нього можна обійтись, видаляємо імпорт, отримуємо наступну помилку.

The show must go on
I’ll face it with a grin
I’m never giving in
On – with the show
I’ll top the bill, I’ll overkill

Ну ви зрозуміли ;)


Filed under: Кодерство Tagged: Python

Як динамічно зібрати клас з функцій?

Це може дозволити повторне використання коду ще краще за звичайне OOP з наслідуванням.

Що найважливіше знати про Python? Те що код в блоці class – це такий самий код як і в модулі, просто виконується в просторі імен модуля. І функції там звичайнісінькі.

А щоб перенести something з простору імен класу в простір імен модуля, досить написати в класі просто something = something. Ось так:

def say(self, what):
    print self.name, 'says', what

class Human(object):
    def __init__(self, name):
        self.name = name
    print __init__ # <function __init__ at 0x7fdc3ba25758>
    say = say # here we move say into class namespace
    print say # <function say at 0x7fdc3bb1e1b8>

me = Human('Bunyk')
print me.say # <bound method Human.say of <__main__.Human object at 0x7fdc3ba2a310>>
me.say('hello') # Bunyk says hello

Ще ми бачимо що метод стає прив’язаним до об’єкта, власне коли цей об’єкт створюється класом. А в класі це не метод, це просто функція собі.

Правда я подумав що вираз something = something може спантеличити математиків і вони надовго задумаються над його значенням, і написав міксін, бо паттерни всі знають і люблять. :)


Filed under: Кодерство, Конспекти Tagged: Python

Скопіювати дерево проекту на інший сервер по трубі

Власне я робив так:

scp -r path/to/source/tree login@host:/path/to/destination/folder

Water valves with spigots

Але потім виявив що scp ходить за символьними посиланнями і копіює все також і там. Від чого іноді зациклюється…

Тому на просторах інтернету мені підказали ідею послати файли через трубу.

Найперша команда – бере вміст директорії, стискає, і посилає стиснені дані в стандартний вивід:

tar czf - path/to/source/tree

І навпаки – прийняти стиснуті дані з stdout і розпакувати їх в поточну директорію.

tar xvzf -

(При цьому директорія tree створиться, якщо дані пакувались з неї.)

Ну і:

ssh login@host "some; commands"

Виконує інші команди на віддаленому сервері. При цьому може приймати щось з stdout, через трубу. Таким чином ця труба веде з сервера на сервер…

І якщо зібрати все до купи:

tar czf - path/to/source/tree | ssh login@host "cd /path/to/destination/folder; tar xvzf -"

Єдина з цим всім проблема – колись таки доведеться вивчити ті всі ключі до tar. :)


Filed under: Інструменти, Кодерство Tagged: linux

Пітер Норвіг подарував мені Схему :)

Для тих хто не знає, Пітер Норвіг це не Миколай, а director of research (керівник дослідженнями) в Google. А також автор грубезної книжки зі штучного інтелекту, і класичної статті Як вивчити програмування за 10 років?.

Але якось випадково блукаючи інтернетом я набрів на статтю: (How to Write a (Lisp) Interpreter (in Python))

Я прочитав, мене поперло, взяв код, перевірив, додав ще функцій, сподобалось ще більше, відкрив SICP, а там:

(define (square x) (* x x))

А в Норвіга define обчислюється так:

elif x[0] == 'define':         # (define var exp)
        (_, var, exp) = x
        env[var] = eval(exp, env)

І якщо var – список, то воно як ключ в env не лізе. Вирішив замінювати

(define (square x) (* x x))

на

(define square (lambda (x) (* x x)))

І переписав так:

        elif x[0] == 'define':         # (define var exp)
            (_, var, exp) = x
            if isa(var, str):
                env[var] = eval(exp, env)
            elif isa(var, list):
                env[var[0]] = eval(['lambda', var[1:], exp])
            else:
                RuntimeError('Cannot assign to constant!')

А потім ще додав до стандартних функцій print, input, а потім getattr, та import, за допомогою яких можна доступитись до всієї стандартної бібліотеки Python.

Потім я за допомогою функцій (ага!) і рекурсії описав цикли while та for і намалював дві пентаграми черепахою:

Але писати цикли функціями коли це повинні бути спеціальні форми (як і cond, та напевне define) – це трохи збоченння. Я почав гуглити про то як реалізовують макроси, і о яке щастя, я знайшов ще другу частину: (An ((Even Better) Lisp) Interpreter (in Python))

А потім може я на цій реалізації пройду SICP і напишу компілятор в байт-код Python. І зроблю про це презентацію до наступного performance appraisal…


Filed under: Кодерство Tagged: книжки, lisp, Python

Я (не)? пишу код в стилі бароко.

Італійською barocco — “чудернацький”, “дивний”, “схильний до надлишків”.

Написав таке:

        x1 = int(request.form['x1'])

Потім подумав, і написав таке (назвемо це варіант A):

        x1, y1, x2, y2 = (
            int(request.form[field])
            for field in ('x1', 'y1', 'x2', 'y2')
        )

Потім ще раз подумав, повернувся до попереднього варіанту, і продовжив так (назвемо це варіант B):

        x1 = int(request.form['x1'])
        y1 = int(request.form['y1'])
        x2 = int(request.form['x2'])
        y2 = int(request.form['y2'])

Тому що кількість рядків в обох варіантах однакова, і генераторні вирази тут, пробачте мою французьку, – то таки барокко. Але схильність до нього в мене чомусь є. Може б то зайнятись мазохізмом і покодити на Java для профілактики?

UPD: Варіант DRY (Dmytrish Recommens You :) )

        f = lambda n: int(request.form[n])
        x1 = f('x1')
        y1 = f('y1')
        x2 = f('x2')
        y2 = f('y2')

Filed under: Кодерство Tagged: Python

Автоматизація браузера з Selenium

Виявляється, Selenium це набагато простіше ніж я думав. Щоб його встановити досить, як зазвичай написати

sudo pip install selenium

З читання раджу:

А зараз я спробую написати скрипт, який робить дві речі: залишає під цією публікацією коментар і перевіряє що на сторінці з’явився вміст цього коментаря… Такий собі тест-кейс:

from time import sleep
from getpass import getpass, getuser

from selenium import webdriver

def main():
    user = raw_input('Wordpress.com username: ')
    password = getpass('Password: ')
    
    print 'Starting browser ...'
    browser = webdriver.Firefox()
    # browser.maximize_window()

    login(browser, user, password)

    print 'Opening page'
    browser.get('http://bunyk.wordpress.com/2013/10/04/selenium-browser-automation/')

    comment = 'Hello from selenium on {platform} with {name} {version}'.format(
        platform=browser.capabilities['platform'],
        name=browser.capabilities['browserName'],
        version=browser.capabilities['version'],
    )

    add_comment(browser, comment)

    assert comment in browser.page_source

    print 'Closing browser'
    browser.close()

def add_comment(browser, text):
    print 'Filling comment field ...'
    textarea = browser.find_element_by_xpath('//*[@id="comment"]')
    textarea.clear()
    textarea.click()
    textarea.send_keys(text)

    print 'Waiting till submit button slides down ...'
    sleep(1)

    print 'Submiting comment ...'
    browser.find_element_by_xpath('//*[@id="comment-submit"]').click()


def login(browser, user, password):
    print 'Logging in ...'
    browser.get('http://%s.wordpress.com/wp-login.php' % user)

    login = browser.find_element_by_xpath('//*[@id="user_login"]')
    login.clear()
    login.send_keys(user)
    pwd = browser.find_element_by_xpath('//*[@id="user_pass"]')
    pwd.clear()
    pwd.send_keys(password)
    browser.find_element_by_xpath('//*[@id="wp-submit"]').click()
    print 'Login form submitted'


if __name__ == '__main__':
    main()

Також мушу зауважити ще наступне. Firebug дуже допомагає при розробці, даючи нам xpath селектори потрібних елементів сторінки. Але, якщо ми наприклад працюємо з Ext.js, вона дає елементам дивні id на зразок DialogButton-1306-btnEl, де число змінюється випадковим чином. Тому треба писати не простий xpath, а хитро*зроблений:

    # ok = browser.xpath('//*[@id="DialogButton-1306-btnEl"]')
    ok = browser.xpath('//button[contains(., "OK")]')

P.S. Автоматизація це круто. Особливо автоматизація клацань мишкою… Хоча звісно буває не легко.


Filed under: Кодерство, Павутина Tagged: тестування, Python