Entries tagged “Python”

wafのgaim daemon

written by shn, on Feb 27, 2010 10:53:00 PM.

ビルドツールをwafに代えたのだが、そこにあるテキトーなautomatic buildコードが、FreeBSDで使えるFAM系?のライブラリのgaimに対応していなかったので書き散らかした。 せっかくなのでアップします。

http://gist.github.com/316701

./waf daemonでイケルと思う。_famはうまく動かなかったのでイロイロやってみたけど諦めた。

eventlet kqueue hub

written by shn, on Jan 29, 2010 2:14:00 PM.

先日のAppleのイベント用にこんなのを作って、動かしたりしてたいました。

中身はテキトーに書いたeventletベースCometサーバをFreeBSDで動かしてるのですが、eventletはkqueueのhubが無いのでちょっと負荷が心配になった。ので、kqueueのhubをでっち上げた。例によってテストは無いけど、一晩動いてたのでこの方向でいいのかしら。

http://gist.github.com/287764

kqueueバージンだったので、誰か優しく手ほどきしてください。

eventletでCometサーバ

written by shn, on Jan 17, 2010 6:05:00 PM.

eventletづいてるので、Cometサーバを書いてみたよ。

tinycomet.py at master from shnjp's tinycomet - GitHub

eventlet.wsgiを使って、200行ぐらいで簡単なのが書けました。楽しいですね。

使い方

python tinycomet.pyで起動して、 /update/<UUID>にPOSTするとデータを作成

% curl -v 'http://127.0.0.1:8090/update/hogehoge' -d mogemoge
...
< HTTP/1.1 201 Created
< X-Tc-Timestamp: 2
< Content-Length: 0
...

受け取るには、/wait/<UUID>

% curl -v 'http://127.0.0.1:8090/wait/hogehoge'
...
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
< X-Tc-Timestamp: 4
< Content-Length: 7
...
mogemoge

waitにsince=<timestamp>をつけると、その時間になるまで待ちます。 各レスポンスのヘッダにあるX-TC-Timestampが、そのデータの最終更新時刻になります。 timestampは論理時間なので、実世界の時刻とは関係ありません

% curl 'http://127.0.0.1:8090/wait/hogehoge?since=4' &
[1] 97027
# waitに入る
% curl -v 'http://127.0.0.1:8090/update/hogeoge' -d fugafuga
fugafuga[1] + done ....

ほかに、waitにtimeout=[timeout:float]を与えると、timeout秒待って408 Request Timeoutを返します。

また、updateにfinished=1を与えると、次のwaitが終わった後にデータを消します。またcontent_type=[mimetype]でContent-Typeを指定できます。デフォルトはapplication/octet-stream

いろいろ引っかかったところ

event(pyevent)はだめくさい

eventletのdefault hubからthread safeじゃないという理由でpyeventが外されていたのだけど、今回はsingle threadなので試してみた。 しかしうまくimport出来ないので諦め。trunkも、配布バージョン(2007年!!)もだめじゃった。

python2.6からkqueueが使えるそうなので、自分でHubを書くのがよいのかな。eventlet自体もtrunkは結構変わっているので見ないといけなさげ

WaitForMultipleObjectsがやりたい

waitのところは、グローバルなConditionを作って

# wait側
with _update_lock:
    while data.last_update <= since:
        _update_lock.wait(timeout)

# update側
with _update_lock:
    _update_lock.notify_all()

とやっているので、捌くdataの数が増えると破綻すると思う。dataごとにイベントを持たせて、WaitForMultipleObjects()的なことがしたいのだけど、この枠組みではどうやるんだろう。オシエテエライヒト

/wait_multiはmultipart/*を吐くコードを書くのが面倒なので実装していない。

eventletでgearman worker

written by shn, on Jan 16, 2010 8:45:00 PM.

eventletっていうライブラリが僕好みの匂いを発していたので遊んでいる。 gearmanを使って、ブログのURLを与えたらそこのFeedのリストを返すってWorkerを書いていたので、それをeventlet化してみよう。

まず、gearmanのeventlet化、gearmanのライブラリにはpure-pythonのやつを使っていたのでさくっと行けた。

green_gearman.py

from eventlet import patcher
from eventlet.green import socket, select

for mod in ['worker', 'connection']:
    patcher.inject('gearman.%s' % mod, globals(),
        ('socket', socket),
        ('select', select)
    )
del patcher

eventletにはpatcherというざっくりしたモジュールがあって、これを使うと指定したモジュールのglobalsを置き換えてくれる。これでいいんかなっていうぐらい簡単。

試しにecho serverを書いてみる。

from eventlet import coros, api
from green_gearman import GearmanWorker

def echo_func(job):
    print api.getcurrent(), job.arg
    time.sleep(5.)

def main():
    pool = coros.CoroutinePool(max_size=200)
    waiters = []

    for idx in range(10):    
        worker = GearmanWorker(['0.0.0.0:4730'])
        worker.register_function('echo', echo_func)

        waiters.append(pool.execute(worker.work))

    for w in waiters:
        w.wait()
main()

gearmanのprotocolは同時に1ジョブしか処理できないっぽいので、とりあえず10個ぐらい突っ込んでみる。 gearman -h localhost -p 4730 -s -f echo hogehogeを20個ぐらい飛ばしてみた所、

<greenlet.greenlet object at 0x804137f30> hogehoge
<greenlet.greenlet object at 0x804127c90> hogehoge
<greenlet.greenlet object at 0x804137db0> hogehoge
<greenlet.greenlet object at 0x804137e10> hogehoge

ってなってるので、なんかまぁそれっぽくなってる気がする。こんなコードじゃよくわからんな… これで良いのでしょうか。

さて肝心のWorkerもこれにのせたいのだが、urllib2を使っていたので、代わりにfrom eventlet.green import urllib2に代えるだけっぽい(まだやってない…)。

んでつらつらコード見てたんだが、今のところ(eventlet 0.9.2)はlibeventサポートがdisabledされているようで、ちょっとがっかり。

eventlet化する場合、全てのIOをこれにしないとブロックが発生してみんな止まることになっちゃうのだけど、MySQL周りとかをeventlet化するのはめんどくさそうなのでアレでアレしてアレですね。

CSSツールをうpした。 - スプライト作成、ネスト、変数

written by shn, on Nov 14, 2009 4:35:00 PM.

Python Hack-a-thonに来たので、昔作ったツールのメンテをしたりしてました。 んで、githubにうpした。

githubのプロジェクトはこちら: github cssc.git

  • YAMLで段組を定義して、CSSスプライトを生成する make_sprite.py
  • IE6のためにPNGをなるたけ綺麗にGIFにする to_gif.py
  • CSSの記述に、ネストや、変数などを使えるようにするコンバータ cssc.py

で構成されてます。

make_sprite.py

YAMLの定義ファイルからCSSスプライトを生成します。 CSSスプライトっていうのは、GoogleのCSSスプライトのような奴で、複数の画像を一枚にまとめて、リクエスト回数、ファイルサイズを節約します。

CSSスプライトを使用すると、タグの代わりに<div>などの要素にbackgroundを指定することのなります。 CSSスプライトの中で画像の座標を調べるのは面倒なので、そこらへんの生成はcssc.pyを通して自動化しています。

使い方

make_sprite.py -o sprite.png -c sprite.json -b ./images/ sprite.yaml
  • -o sprite.png 出力CSSスプライト名
  • -c sprite.json 座標定義ファイル。あとでcssc.pyで使います。
  • -b ./images/ 画像のベースディレクトリ
  • sprite.yaml 画像の段組を記載したyaml

yamlの記法

段組はyamlで記述します。

冗長に書くと、以下のような感じです。

direction: vertical
images:
  - direction: horizontal
    images:
      - left-arrow.png
      - google.png
      - right-arrow.png
  - direction: horizontal
    images:
      - google-large.png
      - direction: vertical
        images:
          - down.png
          - up.png
          - close.png

基本的に directionとimagesのマップのネストで段組を表現します。directionは horizontal もしくは vertical 、imagesは画像ファイル名もしくは、マップのリストになります。

directionを省略して、直接子イメージのリストを書くことも出来ます。その場合の方向は、親の方向と逆になります。 この省略記法を利用すると、上のyamlは以下のようになります。

direction: vertical
images:
    - [left-arrow.png, google.png, right-arrow.png]
    - [google-large.png, [down.png, up.png, close.png]]

to_gif.py

pngをgifにします。

使い方は to_gif.py in.png out.gif 

cssc.py

CSS手書き派は日々、ネストができないことや、変数が使えないことに憤慨しています。

つまりこういう書き方がしたい!

{% set header_height = '24px' %}
{% set main_color = '#001122' %}

#site-header {
    height: {{ header_height }};

    h1#logo {
        font-size: 24px;
        color: {{ main_color }};
    }

    a {
        color: #aabbcc;

        _:hover {
            border: 1px solid #ffeedd;
        }
    }
}

OK! それなら、そうすればよいわ

./cssc.py -o out.css in.cssc

in.csscにみょうちきりんなcssもどきを書くと、in.cssにちゃんとしたものをはいてくれます。

上のmake_sprite.pyで、座標ファイルを出力した場合は以下のような感じで使います。

./cssc.py --coords main,http://static.com/main.png -o out.css in.css

--coordsオプションに
スプライト名:スプライトのURLを指定します。 座標はスプライト名.jsonから読み込みます。

csscファイルの方は、以下のように。冗長

h1#logo {
    {{ sprite_background('main', 'logo.png') }}
}

sprite_background関数の第1引数にスプライト名(コマンドラインで指定する奴)、第2引数に元画像ファイル名を指定します。

cssc詳細

{% set width = '100px' %} /* 変数などはJinja2そのまんまなので[Jinja2のドキュメント](http://jinja.pocoo.org/2/documentation/)を見るのが良いと思います。 */
// C++スタイルのコメントも使えます
{# jinja2スタイルも使えるぜ... #} 

div {
    /* 普通にcssのスタイル定義を書きます */
    border: 1px solid black;
    margin: 4px 8px;

    width: {{ width }}; // これはjinja2

    span {
        /* ネストすると "div span" になります。 */
        font-weight: bold;
    }

    a {
        text-decoration: none;

        _:hover {
            // 親要素とスペース無しで続け対場合は '_' を使います。
            // この場合、このブロックは "div a:hover" になります。
            text-decoration: underline;
        }
    }
}

あとがき

  • いまどきCSSを手書きしている奴はいるのだろうか
  • でもYUI compressorとか使うならMakeで処理できるようにしておきたいよね。
  • みんなここらへんの地味な所どうしてんだろう。
  • PyParsingはじめてトライしてみたんだけど遅い… たぶん文法定義が悪い

FAPWS3でSet-Cookieが出来ないっていう

written by shn, on Aug 10, 2009 2:58:00 AM.

今日は残ってる仕事をしに会社に行ったはずが、Zineのpluginとか作って遊んでたら一日が終わった。 そんななかで子一時間はまったのが、FAPWS3がSet-Cookieヘッダを食っちゃうって話。

FAPWS3駆動にしたZineでadmin panelにログインできないなー、おかしいなーと奮闘してたらコレが原因だった。

Unable to set cookie with Fapws3-0.2 not Set-Cookie headers send :(

fapws.baseのstart_responseが、Set-Cookieを食っちゃってるのが原因なので、cookie関連のコードを全部消しちまえばちゃんとzineが動くようになる。

なんでstart_response.set_cookie()なんてメソッドが用意されてんのかよくわからないけど、作者の勘違いかな。

以下パッチ:

--- base.py 2009-08-09 19:37:49.587425000 +0900
+++ fapws_base.py   2009-08-09 18:38:08.873789000 +0900
@@ -1,17 +1,13 @@
 # -*- coding: utf-8 -*-

 import datetime
-from Cookie import SimpleCookie, CookieError
 try:
     import cStringIO as StringIO
 except ImportError:
     import StringIO
 import traceback, sys, string

 status_reasons = {
     100: 'Continue',
@@ -90,7 +86,6 @@
         self.status_reasons = "OK"
         self.response_headers = {}
         self.exc_info = None
-        self.cookies = SimpleCookie()
         # NEW -- sent records whether or not the headers have been send to the
         # client
         self.sent= False
@@ -111,36 +106,11 @@
         key=str(key)
         val=str(val)
         self.response_headers[key]=val
-    def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=None):
-        self.cookies[key] = value
-        self.response_headers['Set-Cookie'] = self.cookies
-        if max_age:
-            self.cookies[key]['max-age'] = max_age
-        if expires:
-            if isinstance(expires, str):
-                self.cookies[key]['expires'] = expires
-            elif isinstance(expires, datetime.datetime):
-                expires = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
-            else:
-                raise CookieError, 'expires must be a datetime object or a string'
-            self.cookies[key]['expires'] = expires
-        if path:
-            self.cookies[key]['path'] = path
-        if domain:
-            self.cookies[key]['domain'] = domain
-        if secure:
-            self.cookies[key]['secure'] = secure
-    def delete_cookie(self, key):
-        if self.cookies:
-            self.cookies[key] = ''
-        self.cookies[key]['max-age'] = "0"
+
     def __str__(self):
         res = "HTTP/1.1 %s %s\r\n" % (self.status_code, self.status_reasons)
         for key, val in self.response_headers.items():
-            if key.upper() != "SET-COOKIE":
-                res += '%s: %s\r\n' % (key,val)
-        if self.cookies:
-            res+=str(self.cookies)+"\r\n"
+            res += '%s: %s\r\n' % (key,val)
         res += "\r\n"
         return str(res)

SQLAlchemyメモ

written by shn, on Aug 5, 2009 1:49:00 PM.

毎回ググるので、メモっておく。 このブログがSEOされて、このエントリが上位に来れば、次ググる時楽になる寸法だ!

INSERT IGNORE INTO ...

insert(..., prefixes=['IGNORE'])

SELECT ... FOR UPDATE

select(..., for_update=True)

随時更新したい。

WebShot 2009

written by shn, on May 21, 2009 10:09:00 PM.

昨日の夜中に突然ウェブページのSnapShotを撮るサーバが欲しくなった。SBMのサイトによくある奴。FreeBSD / Linuxでやりたいのよね。

先人の知恵を探すのだが、良い検索語がわからない。「Webnailと呼ばれているらしい」ということでGoogleさんに聞いてみたら「もしかして: Webmail」とか言われて死にたくなった。結局 "Webpage screenshot"とかで良かったよ。

んで探すと良く出てくるのがXvfb + firefoxの組み合わせ。2006年のSimpleAPIの時にみなこぞって作ってたらしく、その頃の話が出てくる。 この方法は、Firefoxを使っているので、いろいろな通知系を殺さないといけないのと、そもそもonLoadのイベントが取れないからsleepを適当にやってからScreenshot撮るってのがぜんぜんだめ。

んでもうちょっと探すと、Xvfb + QtWebKitを使った話がでてきた。これだとonLoadのイベントが取れる。

試してみたら、かなり簡単に撮れて拍子抜け。PortsでXorg + Qt一式のビルドに3、4時間かかったけどな!

元コードをちょっと弄って、scrollbarを消してみた

frame = self.page.mainFrame()
# hide menu bar
frame.setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff)
frame.setScrollBarPolicy(Qt.Vertical, Qt.ScrollBarAlwaysOff)

縦長のScreenShotを撮るには baseSize = QSize(800, 600) # まず基準となるサイズにする self.page.setViewportSize(baseSize) # contentSizeの方が多きかったら(スクロールバーが出てたら) contentSize = self.page.mainFrame().contentsSize() if contentSize.width() > baseSize.width(): self.page.setViewportSize(contentSize)

あと僕の環境だと、Qtの画像縮小は汚ないのでPILとかを使ったほうが良い。

これの前段にSquidとか挟むべきなんだろうな。まぁなにはともあれ、やりたい事はできた

自己参照のrelation @ sqlalchemy 0.5

written by shn, on Apr 13, 2009 12:31:00 PM.

自己参照型のrelationを作ろうとしていて、子供を追加しても「循環参照だ!」って怒られてプチ困った。 マニュアル読んだら答かいてあったよメモ

以下、Adjacency List Relationships - Mapper Configuration からコピペ

親 > 子は普通で良い

mapper(Node, nodes, properties={
    'children': relation(Node)
})

子 > 親はremote_sideという引数にカラムを指定する

mapper(Node, nodes, properties={
    'children': relation(Node, backref=backref('parent', remote_side=[nodes.c.id]))
})

remote_side

used for self-referential relationships, indicates the column or list of columns that form the "remote side" of the relationship.

とのこと。これ以上はコードを追ってないので、なんでこうしないと普通の1:Nとして関係が作れないかはわからず…

Werkzeugで遊ぼう! - Routing編

written by shn, on Mar 7, 2009 2:34:00 AM.

リクエストから、各URLへと対応するハンドラを導くのに、Werkzeugではwerkzeug.routingを使う。

wz_routing.py

#! -*- coding:utf-8 -*-
from werkzeug import run_simple
from werkzeug.routing import Map, Rule, Submount, Subdomain

class WSGIApplication(object):
    def __init__(self):
        self.url_map = Map([
            Rule('/hello', endpoint='hello'),
            Rule('/hello/<string:lang>', endpoint='hello'),
        ])
        self.url_adapter = self.url_map.bind('localhost')

    def __call__(self, environ, start_response):
        endpoint, arguments = self.url_adapter.match(environ['PATH_INFO'])

        handler = getattr(self, 'handle_%s' % endpoint)
        return handler(environ, start_response, **arguments)

    def handle_hello(self, environ, start_response, lang='en'):
        writer = start_response('200 OK', [('Content-Type', 'text/plain; charset=utf-8')])
        if lang == 'en':
            return ['Hello']
        elif lang == 'ja':
            return ['こんにちわ']
        elif lang == 'he':
            return ['הלו']
        return ['no hello yet']

if __name__ == '__main__':
    run_simple('localhost', 4000, WSGIApplication())

$ python wz_routing.pyして、$ curl http://localhost:4000/hello とか、 $ curl http://localhost:4000/hello/ja とかしてみるといろいろなhelloが楽しめると思う。

Ruleには、<int>やら、<float>やらも使えるので、/blog/archive/2009/03/とか、twitter.com/shn/repliseみたいなナウいURLが設計しやすくて良いですね~

__call__の中身は、以下のようにも書き換えられる。

return self.url_adapter.dispatch(
    lambda e, a: getattr(self, 'handle_%s' % e)(environ, start_response, **a),
    path_info=environ['PATH_INFO']
)

goodbyeも楽しみたいなーと思って、/goodbyeにアクセスするとNotFound例外が飛ぶ。こいつもwsgi的な奴なので、ちょちょっとするとエラーを表示してくれる。

from werkzeug.exceptions import HTTPException
try:
    endpoint, arguments = self.url_adapter.match(environ['PATH_INFO'])
except HTTPException, e:
    return e(environ, start_response)
else:
    ...

Submountを使うと、Ruleの塊を指定したディレクトリ以下にマッチしてくれるようにしてくれる。似たようなのにSubdomainてのもあるけど、まだ使ったこと無い。subdomain毎にbind()しないといけないのかな?