Pythonで複数プロセスたちあげて結果を非同期で処理する

ていうのを見かけたので殺伐とした心を癒すためにやってみる心。

やりたい事

  • Pythonスクリプトから複数外部コマンドを実行
  • 実行結果を非同期に処理する
  • 割とお手軽にやりたい。

試してみる

それぞれ 1秒, 2秒, 3秒まってprintするようなスクリプトを3つ用意

# こんな感じ
from time import sleep

sleep(1)
print "echo1"

これを参考のまんま下記のようなコードで動かす。

from subprocess import Popen, PIPE
import time

running_procs = [
    Popen(['python', 'echo3.py'], stdout=PIPE, stderr=PIPE),
    Popen(['python', 'echo2.py'], stdout=PIPE, stderr=PIPE),
    Popen(['python', 'echo1.py'], stdout=PIPE, stderr=PIPE),
    ]

while running_procs:
    for proc in running_procs:
        retcode = proc.poll()
        if retcode is not None: # Process finished.
            running_procs.remove(proc)
            break
    else: # No process is done, wait a bit and check again.
        time.sleep(.1)
        continue

    # Here, `proc` has finished with return code `retcode`
    if retcode != 0:
        """Error handling."""
        print "error"

    print proc.stdout.read()

結果はこうなる

$ python hoge.py
echo1

echo2

echo3

やってること

1. 3つのプロセスをリストにしてfor inでまわす
2. proc.pollでreturn codeをチェック
3. return codeが帰ってきたら自らをプロセスリストから消してfor終了
4. 逆に3つのプロセスどれもがreturn codeかえさなかったら、ほんのちょっぴりまって1にもどる
5. return codeが帰ってきたらerror handling or stdoutから結果を受け取る

お手軽にやるにはいいかもしんない. proc.poll こういう挙動になるんだてのを知った。

備考

  • コマンドが終わるのpollingして、結果が帰ってきた順番に処理してるだけなので正確には非同期で処理してるとは言えない。
  • 例えば最後のstdoutからreadしたものをあれやらこれや処理するとしたら、その間ブロックされるので、その後の結果は処理されるのが待たされる。

はー癒された

Djangoの抽象モデル(Abstract Model)の単体テスト

DjangoでAbstractなモデルの単体テストってどう書くんだろうなって思って調べたときのメモ。

継承した先のモデルをテストすればいいとかあるかもしれないけど、抽象モデルなので実装を持つ事ができる。その実装を独立してテストしたいなぁと思った。

簡単な例を書く。こんな論理削除用の抽象モデルがあったとする。

class BaseModel(models.Model):

    del_flg = models.BooleanField(u'削除フラグ', default=False)

    def remove(self):
        self.del_flg = True
        self.save()

    def unremove(self):
        self.del_flg = False
        self.save()

    class Meta(models.Model):
        abstract = True

この場合、remove, unremove が意図した通りに動くかテストしたい。
それをやる時は以下のようなコードを書く。

from django.test import TestCase
from django.db import connection
from django.core.management.color import no_style

class BaseModelTest(TestCase):

    def _getTargetClass(self):
        from core.models import BaseModel # <= 上のヤツ
        from django.db import models

        class BaseDummy(BaseModel):
            pass

        return BaseDummy

    def _makeOne(self, *args, **kw):
        return self._getTargetClass().objects.create(*args, **kw)

    def setUp(self):
        self._style = no_style()
        sql, _ = connection.creation.sql_create_model(self._getTargetClass(), self._style)
        self._cursor = connection.cursor()
        for statement in sql:
            self._cursor.execute(statement)

    def tearDown(self):
        sql = connection.creation.sql_destroy_model(self._getTargetClass(), (), self._style)
        for statement in sql:
            self._cursor.execute(statement)

    def test_remove_unremove(self):
        base_dummy = self._makeOne()

        self.assertTrue(hasattr(base_dummy, 'del_flg'))
        self.assertEqual(base_dummy.del_flg, 0)

        base_dummy.remove()
        self.assertEqual(base_dummy.del_flg, 1)

        base_dummy.unremove()
        self.assertEqual(base_dummy.del_flg, 0)

簡単に説明すると

1. setUpで抽象モデル(BaseModel)を継承したBaseDummyモデル作成
2. BaseDummyのモデル定義から吐き出されるモデル生成用のSQLを取得(sql_create_model)
3. そのSQLを実行してDBにBaseDummyモデルのテーブルを作成
4. テスト実行
5. tearDownでモデル定義から吐き出される削除用のSQLを取得(sql_destroy_model)
6. そのSQLを実行してDBからBaseDummyモデルのテーブルを削除

て事をやってる。なんか面倒だけど、アドホックにこんな感じの事もできるんだなと思った。

こんな欲求に直面したとき、他の人たちはどんな風にしてるだろうかとふと気になった。

おわりん

参考

ほんとにあったかもしれない怖いコード (呪いのビデオ風)

納涼!ほんとにあった怖いコード(by CodeIQ×はてな)

http://partner.hatena.ne.jp/codeiq_matsuri2013_2

まずはこちらの画像をご覧いただきたい。

f:id:tell-k:20130826184950p:plain

お分かりいただけだろうか?

f:id:tell-k:20130826185014p:plain:w700

これは、とあるプログラマが、深夜23時頃に、終電で帰るのを諦めて、とある既存ライブラリを徹夜で書き直しをすることを決意した時に見つけてしまったコードだ。

f:id:tell-k:20130826185042p:plain

彼はこのコードを見て以来、心身ともにおかしくなってしまった。一部では彼は呪われてしまったという噂もある。

f:id:tell-k:20130826185101p:plain

当時の彼を知る同僚は、その頃から彼の言動がおかしくなったと証言している。

「BoldがSetされてるはずなのにSetされてないどころかそのままreturnされてる、ていうかCamelなのかunderscoreなのかどっち?。。。何をいってるか分からねぇと思うが、俺も何をいってるかわからねぇry)」

「いやこいつを理解できない俺がおかしい!!きっとこれは名のあるファンタジスタが書いたファンタスティックコードなんだ!ははは。。ひひ。。」

彼はその日から、取り憑かれたように、社内のファンタスティックなコードを探しては社内掲示版にアップし続けていった。彼はもうこの時、目の焦点があってなかったと言う。

ほどなくして彼は「探さないでください」という置き手紙とともに行方が分からなくなった。彼は行方をくらます直前にこんな妄言を吐いていたそうだ。

「俺にはこの記事のファイル名は"いーしーきゃんびー"って読めないし、"いーきゃんびー"、
もしくは、"いーしーあんゔぃー"だろうが!俺じゃない!俺がわるいんじゃない!全てはあいつが悪いだ!みんな呪われろ!ノロワレロ!ノロ。。」

彼はいま現在も行方が分かっていない。

ご注意

  • この記事の大半はフィクションであり、登場人物、画像、リンク先には関しては一切関連がございません。予めご了承ください。
  • また掲載されているコードは本来のコードの一部分だけを、記憶を頼りに再現したものとなっております。そのため呪われたりしませんのでご安心ください。

なんだよこれ。。。

お口直しに僕のお気に入りの動画を貼付けておきますね (´・ω・`)


疾走 - YouTube

task.delayからへんじがない。ただのしかばねのようだ(Celery+RabbitMQ)

とある開発環境でCelery + RabbitMQを動かしてたら、いつのまにかtask.delay()が息をしてないというかハングアップしてる感じになった時の調査と対処のメモ

環境

  • Debian squeeze
  • RabbitMQ 3.1.3 (apt-getでinstall)

現象

  • DjangoアプリからCelelyのtask.delayでジョブを実行してるけど処理された気配がない
  • 特にアプリ、Celery、RabbitMQで目立ったエラーは出ていない
  • Celery起動時にRabbitMQの接続は出来てるというログは出てる
[2013-07-25 16:08:55,270: WARNING/MainProcess] celery@hogehoge ready.
[2013-07-25 16:08:55,284: INFO/MainProcess] consumer: Connected to amqp://guest@127.0.0.1:5672//
  • task.delayを実行するとRabbitMQのログには受け付けた旨のログは残る。
=WARNING REPORT==== 25-Jul-2013::17:10:59 ===
closing AMQP connection <0.304.0> (127.0.1.1:45367 -> 127.0.1.1:5672):
  • ところがどっこい結果を見に行こうとするとハングアップする (hangs up)
>> from tasks import add
>> result = add.delay(1, 2)
>> result.get()
(ここでハングアップ。なんのへんじもない。ただのしかばねのようだ)
  • なんでハングアップになったのか全くわからない。なんでや。しばらく頭ひねる。

原因

原因はこのissueの最後の方の話しと同じ感じだった

https://github.com/celery/celery/issues/1312

  • RabbitMQは disk_free_limit という設定がある
  • disk_free_limit は disk_free(ディスクの空き容量) が disk_free_limitを下回ったら、RabbitMQへのPublishをブロックするという設定値
  • disk_free_limitはデフォルトで1GBに設定されている

今回の仮想マシンはディスク容量が8GBバイトと比較的少なめのディスク容量だった。
そしたらログやら、DBやらでいつのまにか1GBを切っていたため、突然ジョブキューが音も無くブロックされて死んだ様に見えた。

よくよく調べるとRabbitMQを再起動したりするとログに以下のようなメッセージが書き込まれてた...orz

=WARNING REPORT==== 25-Jul-2013::16:03:15 ===
disk resource limit alarm set on node rabbit@hogehoge.

Publishers will be blocked until this alarm clears

まじかー。再起動のコマンド叩いた時に教えて欲しかったわー。欲しかったわー

対処

といわけで対処方法。まぁ単純にdisk_free_limit以上の空き容量を作ればいいんですが、
そもそも8GBしかないし、開発用だし、disk_free_limitを小さく設定すればいいよねて感じで設定します。

$ sudo vi /etc/rabbitmq/rabbitmq.config

# write this.
[
 {rabbit, [{disk_free_limit, {mem_relative, 0.1}}]}
].
  • 設定ファイル(rabbitmq.config)を所定の位置に作成
  • disk_free_limitを{mem_relative, 0.1}(メモリ容量の10%)に設定
  • メモリが500MBくらいだったので、大体50MBに設定された。
  • 単純に0にする事もできるけど、無限にディスク容量を喰わないためのセーフティ機能なのであまりお勧めしない。

あとはrabbitmq-serverを再起動してstatusを確認すればOK

$ sudo service rabbitmq-server restart
$ sudo service rabbitmq-server status
Status of node rabbit@hogehoge ...
.
.
 {disk_free_limit,52212122}, <- コレね

おわり。

余談

  • いやー本当に1日ってあっという間におわっちゃいますね。ハハハッハハハッ(白目)

追記(2013/07/26)

無知をさらけ出すとイケメンにチヤホヤされる職場です。

[2013/07/26 8:10:54] イケメンCTO: @tell-k かなり前にもしかしたら聞いたカモですが
http://tell-k.hatenablog.com/entry/2013/07/25/211848
[2013/07/26 8:10:54] イケメンCTO: CELERY_IGNORE_RESULT = True とかって設定しています?
[2013/07/26 8:12:26] tell-k: 設定してなかったです。
[2013/07/26 8:18:10] イケメンCTO: @tell-k 上 or CELERYのログのEXPIREを指定しないとログがどんどんBROKERにたまっていて、
[2013/07/26 8:18:28] イケメンCTO: Rabittmqだとまさに反応がなくなるってことが昔何度かありました。
[2013/07/26 8:19:15] tell-k: なるほど。
[2013/07/26 8:19:21] tell-k: ありがとうございます。
[2013/07/26 8:19:31] tell-k: 設定しないと!
[2013/07/26 8:20:14] イケメンCTO: これ設定例 -> 設定例のコピペをくれる
[2013/07/26 8:20:09] tell-k: おぉぉぉ
[2013/07/26 8:20:26] tell-k: ありがとうございます。
[2013/07/26 8:20:29] tell-k: cto++
[2013/07/26 8:20:48] イケメンCTO: >CELERY_SEND_TASK_ERROR_EMAILS = True
これやると、エラーメールが飛んでくるので解析が便利になりやす、これはいれたほうがいいっすね、CELERY_IGNORE_RESULTと合わせて
[2013/07/26 8:20:58] イケメンCTO: まぁ、設定したのはイケメンエンジニア(ike4)っす
[2013/07/26 8:20:59] イケメンCTO: イケメンエンジニア(ike4)++

とあるオジサンのサンデー図工(iPad mini SmartCover)

夜更かしてインターネッツばかりみてる不健康なオジサン達。こんばんわ。今日はそんな良い子のオジサン達のために、オジサンが久々に図工してみた話を書くよ。

きっかけ

オジサンは 最近 iPad mini 用のケースを買いました。 monCarbone Mini Smart Mate てヤツです。

これで iPad miniタンの背面が洒落乙になってよかったなぁと思ってたんですが、肝心のフロントのカバーが Apple謹製のSmartCoverでしかも黄緑という残念な事態が発生。

f:id:tell-k:20130707235533j:plain

謹製かどうかは置いておいて黄緑が微妙すぐるので思い切ってフロントも新しいのにしようと思い立ちました。

SmartCover 探し

できればSmartCoverと同じ感じので、monCarboneにあう感じのがいいなぁというの探し始めたんですが、このSmartCoverってヤツはAppleが特許を取得してるので、Apple以外からは基本出されてないという状況。 一応 それっぽいのがありますが、特許侵害してるかも?てのと、ちゃちいというのを見かけたので、謹製で売ってるブラックにすればいいかなと思い、AppleStoreまで足を運んでみる事に。

AppleStoreについて「風呂フタの黒。風呂フタの黒」とつぶやきながら、アクセサリコーナーにいって、SmartCoverのブラックを手に取ってみると「あれ?これ黒じゃない?濃い灰色や。僕の求めてるのと違う」という事で早々に退散

自分で作ろう

というわけで今ある黄緑のださい風呂フタをケースに合わせるために自分で作ろうとと決心しました。今考えてみると、僕は探してる途中から、最適なものを探すという目的から逸脱して、「自分でつくる」という方向に無意識的に自分を誘導していたように思います。

「手段の為ならば目的を選ばないという様などうしようもないオジサンも確実に存在するのだ。つまりはとどのつまりは我々のような」

準備

という訳で、東急ハンズっていうお洒落スポットで、以下を購入

  • ラッカースプレー(つや消し黒)
  • 3M ダイノックフィルム カーボン(CA-421)

最近はこんなお洒落シートが気軽に手に入るですね。

黄緑の風呂フタを黒く塗る

ベランダにビニールを敷いて、風呂フタを寝かせて、ラッカースプレーで黒くする。割と均一に塗るのが難しくて、変なムラが残ってしまったのが残念。

スプレー使う時は、マスクと軍手とか、汚れてもいいTシャツとかでやるんだょぉ。

カーボンシートを貼る

ラッカースプレーで乾いたのを確認したら、風呂フタの三つに出っ張ってる部分に、いい感じで切り取ったシートを貼る。

風呂フタの上にA4の紙を置いて、指でなぞって型紙ぽいのを作って、それに併せてシートをカッターで切ると旨く切れた。とかいいつつ雑な出来になった事は内緒だ。

完成

f:id:tell-k:20130708011758p:plain

f:id:tell-k:20130708011720p:plain

f:id:tell-k:20130708011835p:plain


や。。やったよ。ワクワクさんと天国のノッポさん(死んでない)! 僕にも出来たよ!!!

と胸張って言えれば良かったが、近くで見るとすごいちゃちい、すごい安っぽい、しかも風呂フタをスタンドにしようと折り畳むと随所に黄緑の亀裂ガガガ。。。もうやだ普通のSmartCoverの黒買おう。。。orz

結論

ラッカースプレーを綺麗に塗るのむずいから、もっと別の塗料的なものを吟味した方が良かったなぁと思った。

まぁインターネッツしかしないオジサンもたまに図工とか思い出してやってみると、案外楽しいもんですね。enchantMoonとか発売されたら当初はアクセサリ用品とか少ないだろうから、こういうオジサンが増えるかもしれない。

おわり。

sphinxjp.themes.basicstrap 0.3.0 にしますた

やほー。こんなにいい天気なのにインターネッツの世界にどっぷりな不健康の皆たちー。

この前作った、sphinxテーマをアップグレードしたよー。

ぶっちゃけ殆ど使われてないだろうけど、なんとなく使ってるBootstrapとかがバージョンアップしてたので、もろもろそれに併せてバージョンアップした感じです。

sphinxjp.themes.basicstrap 0.3.0 : Python Package Index

あたらしくinner_themeとしてgeo-bootstrapとbootswatch-flatlyというのが利用できるようになりました。

booswatch-flatlyは割と綺麗なのでお気に入り。

あとは日本語でドキュメントかいてると、なんかテーマによっては「見出しの大きさ」が気になる事が多々あるので、見出しににそれぞれ font-size を設定できるようにしてみました。

何かご意見、ご要望があったら、適当にお知らせくださいー

そんじゃーね。

Mockのpatchの勘違い

良くわからないけど唐突に意識高くなったので俺ブログ書くわ!!!ブログ書くわ!!! けど後2分くらいしかきっと持たない!!!早く書くんだ俺!!!2分後にはまた死んだ魚ような目になってしまうぞ!

という訳でPythonのMockで小一時間ハマったネタを書く。

前提

まずこんな感じで fuga.pyをimportして利用したhoge.pyをテストするhogetests.pyがあるとする。

# fuga.py
# -*- coding: utf-8 -*-

def piyo():
    return 'piyopiyo'
# hoge.py
# -*- coding: utf-8 -*-

from fuga import piyo

def hogecall():
    return piyo()

if __name__ == "__main__":
    print hogecall() # => piyopiyo
# hogetests.py
# -*- coding: utf-8 -*-

import random
import unittest
from mock import patch

class HogeTests(unittest.TestCase):

    def _getTargetFunc(self):
        from hoge import hogecall
        return hogecall

    def test_hogecall(self):
        hogecall = self._getTargetFunc()
        self.assertEqual(hogecall(), "piyopiyo")

if __name__ == '__main__':
    unittest.main()

やりたい事

ここで hogecallの中で読んでるpiyo()て関数をモック化したい。

def hogecall():
    return piyo() # <= コイツをモック化したい

間違った

最初はmock.patchを利用してこのように書いた

@patch('fuga.piyo', return_value="hogehoge")
def test_hogecall2(self, piyo):
     hogecall = self._getTargetFunc()
     self.assertEqual(hogecall(), "hogehoge")

だがこれはうまく行かず hogehogeが欲しいのにpiyopiyoばっかり帰ってきやがる。無茶しやがって。

正解

正解は patchに噛ます対象が、fuga.piyoではなく、hoge.piyoだった。patchで指定する時は、実際の処理で使われてる関数を置き換えるというものらしい。

@patch('hoge.piyo', return_value="hogehoge") #<=  × fuga.piyo   ○ hoge.piyo
def test_hogecall2(self, piyo):
     hogecall = self._getTargetFunc()
     self.assertEqual(hogecall(), "hogehoge")

余談

  • hogeとかpiyoとかhogehogeとかpiyopiyoとかもう書いてる本人も良くわからない。多分大体あってる