2012年2月12日日曜日

HTTPリクエストパラメータを抽出する

テストケースのないシステムをメンテンナンスをすることもあると思います。 HTTPで提供するAPIのテストケースを作成しなければならない場合は、 とにかくログを投げまくってテストするなどの方法が有効だったりするんですが、 (まあそもそもテストケースがないシステムについてのツッコミは受け付けません) ログから有効なテストケースを抽出するために、 リクエストパラメータの組み合わせによるURLの抽出なんかが一つの案です。 ということでログからリクエストパラメータの抽出を行うスクリプトをざっくり書いてみました。

import sys
from collections import defaultdict
from furl import furl

sampling_urls = defaultdict(dict)

for line in sys.stdin:
    url = line.strip()
    f = furl(url)
    param_keys = tuple(sorted(f.args.keys()))
    param_combination = sampling_urls[str(f.path)]
    if param_keys not in param_combination:
        param_combination[param_keys] = url

for path, param_combination in sampling_urls.items():
    for combination, sample_url in param_combination.items():
        print path, combination, sample_url

インプットログ
http://xxx/api?param1=a&param2=a
http://xxx/api?param1=b&param2=b
http://xxx/api?param1=c&param2=c&param3=c
http://xxx/api?param1=d&param2=d&param3=c
http://xxx/api?param1=e&param2=d&param4=d
http://xxx/api2?param1=e&param2=d&param4=d
http://xxx/api3?param1=e&param2=d&param4=d
実行結果
$ python sampling.py < input 
/api2 ('param1', 'param2', 'param4') http://xxx/api2?param1=e&param2=d&param4=d
/api ('param1', 'param2', 'param3') http://xxx/api?param1=c&param2=c&param3=c
/api ('param1', 'param2', 'param4') http://xxx/api?param1=e&param2=d&param4=d
/api ('param1', 'param2') http://xxx/api?param1=a&param2=a
/api3 ('param1', 'param2', 'param4') http://xxx/api3?param1=e&param2=d&param4=d
前に記事に書いたfurlというurlパーサーを利用しています。 itertools.combinationsとかを使わないとできないかと思いましたが、 tupleを辞書のキーにすることで簡単にできました。 valueが空の時の処理などは必要に応じて作成する必要がありますが、 それなりに有効そうです。

2012年1月30日月曜日

furlを使ってみた

ブログを久しく書いていないので、ライブラリの覚書を少しずつ書くことにする。

最近少し話題になったfurlについて使い方を簡単に書いておく。

インストールはいつものように
easy_install furl
pip install furl
でできる。

使ってみたログを書いてみる
>>> from furl import furl
>>> url = furl("http://pypi.python.org/pypi?%3Aaction=search&term=test&submit=search")
>>> url.host
'pypi.python.org'
>>> url.port
80
>>> url.query
Query('term=test&%3Aaction=search&submit=search')
>>> url.query.params
{'term': 'test', ':action': 'search', 'submit': 'search'}
>>> url.args
{'term': 'test', ':action': 'search', 'submit': 'search'}
cgi.parse_qsとかよりは結構使いやすい気がするが一点気になることが・・・
>>> url = furl("http://pypi.python.org/pypi?%3Aaction=search&term=test&submit=search&term=test2")
>>> url.args
{'term': 'test', ':action': 'search', 'submit': 'search'}
>>> url.query.params
{'term': 'test', ':action': 'search', 'submit': 'search'}
リクエストパラメータに同じキーのものが複数入っていた場合、
どちらか一方しかargsに入らない。

HTTPの仕様では同じキーに別の値を入れることができて、
サーバ側では配列で受け取れるようになっているのだとばっかり。

そのようなリクエストは、このままでは正しくparseできない。(やり方発見できず)
簡単に使えるし、良い感じなだけに惜しいなー。

2011年10月23日日曜日

第12回Python mini Hack-a-thon参加報告

Python mini Hack-a-thonに参加してきました。
昼食時にPyConJPの寺田さんや世界の小宮さんの話を聴けるなど、
非常に楽しい一日になりました。

で、重要なHack-a-thonで何をやっていたかというと、
まずPython ORMの調査
これはstormというORMを使ってみるというところで進めていました。
とあるRDBで使いたいのだけれどそのRDBでの利用についての記述が一切ない。
試したかったけど自分のマシンにそのRDBをインストールするのが嫌だったので、
SqlAlchemy、elixir、sqlobjectでサンプルコードを書いてsqliteにデータを入れたり消したりということを試して遊んでました。

昼食後眠くなり、ORMの調査は放棄して、
ProjectEulerの問題解いたり、自分のgae上のwebアプリ直したりしてた。
最後の3時間くらいで成果発表用の何かを作成しようと思って、
akb48のメンバーを検索するモジュールを書いてました。

pyfesでのmoriyoshiさんの発表に感銘を受けたので完全にパクリです。詳細はこちら
moriyoshiさんはperlのacmeの移植だそうなんで、移植のパクリです。お恥ずかしい。

せっかくなんでgithubに上げました。(ドキュメント空です。すんません。後追加します。)
https://github.com/shocking-noise/akb48

実行方法は下記の通り
1.必要なモジュールのインストール(BeautifulSoup、yaml)
2.scraperを実行してyaml生成
3.akb48モジュールをインポートして利用

こだわりポイントは誕生日から今日の年齢を算出するところです。
(ググッてコピペレベルですが)

発表後の指摘として、yamlは邪道だという話があったので、
クラスを生成するコードジェネレータ作るように修正したいです。
(本当のファンなら一字一字コードを打つべきという意見も・・・、別にファンでは無・・・(略))


他の方々の発表は興味深いものが多くで、とても面白かったです。
自分もPyPIにあげられるようなライブラリ作れるようになりたいなぁと思いました。
次回も時間が会えばぜひ参加したいです。

主催者の皆様ありがとうございました。

(追記)
調べたら、moriyoshiさんがpyakb48というモジュールをPyPIにあげていたようです。
中身みたのですが、かなりちゃんとつくられていました(doctestとかもある)。
私のは超劣化コピーですね。勉強して出なおしてきます。

2011年8月24日水曜日

Pythonで演算子オーバーロード

今更ながらPythonの演算子オーバーロード について調べたのでメモ。
クラスのメソッドを作成するだけなので 簡単にできますね。


class OverLoadTest(object):
    def __init__(self, a):
        self.a = a

    # +
    def __add__(self, b):
        self.a = self.a + b
    # -
    def __sub__(self, b):
        self.a = self.a - b
    # /
    def __div__(self, b):
        self.a = self.a / b
    # *
    def __mul__(self, b):
        self.a = self.a * b

    def __repr__(self):
        return str(self.a)

if __name__ == "__main__":
    o = OverLoadTest(5)
    print o
    o + 5
    print o
    o - 5
    print o
    o / 5
    print o


普段はJavaで書いているから演算子オーバーロードが頭にないなー。

使ってるライブラリでも演算子オーバーロード見たことない気がする。
(知らないだけか?)
効果的に使えるようになりたいですね。

2011年8月7日日曜日

FlaskでStreaming

久々にFlaskドキュメントページ見てたら、
Streamingのsnipetが掲載されてました。
(もしかして結構前から?)

Werkzeugには0.5から機能としてはあるみたいなんで、
きっと昔からできたんでしょう。

なかなか使い道ないけどTwitterのようなStreamingAPIとかかっこ良いですよね。

つーことでコード。

from flask import Flask
from flask import Response
import time

app = Flask(__file__)

@app.route("/streaming")
def streaming():
    def generate():
        while True:
            time.sleep(1)
            yield "test\n"
    return Response(generate(),direct_passthrough=True,mimetype='text/plain')

if __name__ == "__main__":
    app.run()


ResponseにStreamingするためのジェネレータを渡すだけ。
direct_passthroughはジェネレータを変更させないための引数らしい、wrapして返すため、後で変更されても影響を受けないようになると思われる(WSGIの仕様っぽい)。
mime/typeはお好きなものを。






それにしても簡単だなー。

2011年7月2日土曜日

Pythonで動的にサブクラスを生成

excelとかDBとかにテストケース用のデータを追加したら
勝手にテストクラスにテストケース(つまりテストメソッド)が追加される、
とかそういうことをやりたい。

で、unittest.TestCaseクラスのサブクラスを
テストデータから動的に生成して実行したいと思ったときに、
どうやれば良いのかちょっと悩んだので、メモ。

動的にサブクラスを生成するのは、
非常に単純でtype関数を使えばできる。
type関数は[クラス名]、[親クラスのタプル(多重継承も可)]、[付与したい属性の辞書]で
動的にクラス生成をできる。


import unittest

def create_testmethod():
    """
    テストメソッドを動的に生成
    """
    def testMethod(self):
        self.assertEquals(1,1)
    return testMethod

def create_testclass():
    """
    TestCaseのサブクラスを動的に生成
    """
    test_class = type('TestA', (unittest.TestCase,), {})
    setattr(test_class,'testMethod',create_testmethod())
    return test_class

def suite():
    """
    テストスイートを生成
    """
    suite = unittest.TestSuite()
    test_class = create_testclass()
    suite.addTest(unittest.makeSuite(test_class))
    return suite

if __name__ == "__main__":
    unittest.TextTestRunner().run(suite())


動的にテストクラス(unittest.TestCaseのサブクラス)を生成して、
動的にテストメソッドを追加することができる。
これを応用すればソースコード書かずにテストクラス、テストメソッドを動的に追加するようなプログラムができる。



Quick Lookup:

2011年5月27日金曜日

DebugToolbarをFlaskにも

Flaskいいよー。Flask。
extensionsもガンガン増えてかなり色々なことができるようになりました。

今日のネタはDebugToolbarです。
ちょっとハマったりしたのでそのメモがわりに。
DebugToolbarはDjangoのextensionでもあるものらしく、
レスポンスタイムとかログとかHttpHeaderとか見れるすぐれもの。

Flaskでも使えるとなれば(Djangoでは使ったこと無いけど)これは試してみるしか

いつものようにeasy_install なり pipなりでflask-debugtoolbarをインストールしたらええやないか。

実行する最小セットは下記の通り。

from flask import Flask
#DebugToolbarをインポート
from flaskext.debugtoolbar import DebugToolbarExtension

app = Flask(__name__)
#パネルを登録
app.config['DEBUG_TB_PANELS'] = (
    'flaskext.debugtoolbar.panels.headers.HeaderDebugPanel',
    'flaskext.debugtoolbar.panels.logger.LoggingPanel',
    'flaskext.debugtoolbar.panels.timer.TimerDebugPanel',
)
#Debug用のシークレットキーを追加、デバッグモードON
app.config['SECRET_KEY'] = 'asd'
app.config['DEBUG'] = True

toolbar = DebugToolbarExtension(app)

@app.route('/')
def test():
    app.logger.info("hello")
    return ""

if __name__ == "__main__":
    app.run()

これでローカルホストからは右にデバッグ用の怪しいボタンが出てくるので、
押すと色々なデバッグ情報が見れるようになる。
自分の環境ではflaskがurl_ruleっていう関数がねーみたいなエラーがでたので、
flaskを最新版(0.7)に換えたらOKでした。

こういうのが増えてくると開発しやすくなりますねー。