ラベル Python の投稿を表示しています。 すべての投稿を表示
ラベル Python の投稿を表示しています。 すべての投稿を表示

2013年4月22日

Tumblr API v2 を利用する

概要

TumblrのAPIを突然利用したくなったので、利用方法をメモする。

OAuth Key の取得

API を利用するには Twitter 等と同様 アプリケーションを登録して OAuth Keyを取得する必要がある。
プリの登録は、「http://www.tumblr.com/oauth/apps」で行なう。

Tumblr Register application

「Register application」ボタンで登録。

Tumblr reg app name

「Application name」、「Administrative contact email」、「Default callback URL」が必須なので入力する。
登録内容は後でも変更できる。callback URL はとりあえず自分のサイト等にしておけば良い。

登録が完了すると、「OAuth Consumer Key」、「Secret Key」が取れる。

クライアントアプリケーション作成

クライアントアプリケーショを作成する。
Python で rauth を利用して認証と表示までしたサンプルが以下。実際に利用する場合は「OAuth Consumer Key」、「Secret Key」を自分の物に変更する。
rauth は OAuth を簡単に処理する外部ライブラリでかなり便利。「pip install rauth」などであらかじめインストールしておくこと。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest

import os
import re
import webbrowser
from ConfigParser import SafeConfigParser

from rauth import OAuth1Service


class TumblrClientTest(unittest.TestCase):
    """
    Tumblr クライアントサンプルテスト
    """

    def test_sample(self):
        """
        テスト
        """
        client = TumblrClient()

        # sessin の取得
        session = client.get_session()

        # user/info 取得
        response = session.get('user/info').json()
        self.assertEqual(response['meta']['status'], 200)
        self.assertEqual(response['meta']['msg'], 'OK')

        # blog/info 取得
        path = 'blog/scipsy.tumblr.com/info?api_key={api_key}'
        response = session.get(path.format(
            api_key=client.get_api_key())).json()
        self.assertEqual(response['meta']['status'], 200)
        self.assertEqual(response['meta']['msg'], 'OK')
        self.assertEqual(response['response']['blog']['name'], 'scipsy')


class TumblrClient(object):
    """
    Tumblr クライアントサンプル
    """
    def get_api_key(self):
        """
        API key = OAuth Consumer Key
        """
        return 'OAuth Consumer Key'

    def get_consumer_secret(self):
        """
        Secret Key
        """
        return 'Secret Key'

    def get_session(self):
        """
        session の取得
        """
        # 設定ファイルのパス
        config_file = os.path.abspath(os.path.join(
            os.path.dirname(__file__), 'tumblr.cfg'))

        tumblr = OAuth1Service(
            name='tumblr',
            consumer_key=self.get_api_key(),
            consumer_secret=self.get_consumer_secret(),
            request_token_url='https://www.tumblr.com/oauth/request_token',
            access_token_url='https://www.tumblr.com/oauth/access_token',
            authorize_url='https://www.tumblr.com/oauth/authorize',
            base_url='https://api.tumblr.com/v2/')

        if os.path.exists(config_file):
            # 設定ファイルが存在する場合、設定ファイル読み込み
            config = SafeConfigParser()
            config.read(config_file)

            access_token = config.get('account', 'access_token')
            access_token_secret = config.get('account', 'access_token_secret')

            session = tumblr.get_session((access_token, access_token_secret))
        else:
            # 設定ファイルが存在しない場合、初回認証
            session = self.auth(tumblr)

            # 設定ファイル出力
            config = SafeConfigParser()
            sec_name = 'account'
            config.add_section(sec_name)
            config.set(sec_name, 'access_token', session.access_token)
            config.set(sec_name, 'access_token_secret',
                       session.access_token_secret)
            with open(config_file, 'wb') as configfile:
                config.write(configfile)

        return session

    def auth(self, tumblr):
        """
        ブラウザでの初回認証
        """
        request_token, request_token_secret = tumblr.get_request_token()
        authorize_url = tumblr.get_authorize_url(request_token)
        print '表示されたURLをブラウザで開きます: ' + authorize_url
        webbrowser.open(authorize_url)

        authed_url = raw_input('ブラウザアドレスバーのURLをペースとしてください: ')
        verifier = re.search(r'\oauth_verifier=([^#]*)', authed_url).group(1)

        # session 取得
        session = tumblr.get_auth_session(
            request_token,
            request_token_secret,
            method='POST',
            data={'oauth_verifier': verifier})

        return session

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

正しく認証されていると、設定画面の「Apps」に登録したアプリが表示される。

Tumblr apps

API のドキュメント

公式 APIドキュメントが「http://www.tumblr.com/docs/en/api/v2」に存在するので、あとは必要な物を利用するだけ。

2013年4月2日

matplotlib 1.2.1 コンパイル時に Tk 8.5 must be compiled with tcl.h from Tcl 8.5 のエラー

現象

「Tk 8.5 must be compiled with tcl.h from Tcl 8.5」のエラーは Tcl 8.4 と 8.5 が同居している場合に発生する。
Mac OS X の場合、MacPorts や Homebrew、ActiveTck なんかをインストールしている環境で発生しやすい。
Linux 系の場合、パスの設定が変になっている可能性がある。

対処

Mac OS X の場合は、tkagg を利用しないのが一番簡単だと思う。
setup.cfg に以下のように設定してコンパイルすれば良い。

[gui_support]
tkagg = False

本来は根源的に解決すべきだが、とりあえずはこれでも大体の場合は困らない。
ただ tkagg を必要とする場合は、別途考える必要があるかも。

2012年11月13日

Emacs で Python の補完を強化する jedi を設定してみた

概要

Emacs の Python 補完はいろいろあるが、どれもいまいち遅い。「emacs-jedi」は結構高速に補完してくれるみたいなので、設定してみた。

設定

Python のライブラリをインストール

pip install epc
pip install jedi

Emacs のライブラリを取得。

git clone git://github.com/kiwanami/emacs-deferred.git
git clone git://github.com/kiwanami/emacs-ctable.git
git clone git://github.com/kiwanami/emacs-epc.git
git clone git://github.com/tkf/emacs-jedi.git

init.el の設定

(require 'jedi)
(add-hook 'python-mode-hook 'jedi:ac-setup)

これで auto-complete を利用して補完が可能。eldoc が有効になっていると、関数の引数説明も表示される。
結構高速に動作してくれるのでしばらく利用してみる。

2012年11月7日

Python の pip コマンドでライブラリの更新状況を確認する

概要

Python の pip コマンド最新バージョン 1.2.1 では、ライブラリの更新状況が確認できない。
確認する方法をメモする。

コマンドの設定

pip コマンドは「site-packages/pip/commands/」以下にコマンドが存在し、このディレクトリにコマンドプログラムを配置することで、様々な拡張が可能。

listコマンドを追加する「list.py」が公開されているので、ダウンロードして、配置する。
利用可能か確認したり、利用方法を確認するには、以下のようにする。

pip list --help

ライブラリの更新状況を確認する場合は以下。

pip list -o

インストールしているライブラリの量にもよるが、結構時間がかかるので注意。

2012年11月5日

pyobjc 2.4 のインストール

概要

Python の Objective-C ブリッジライブラリ「pyobjc」の 2.4 が 2012/11/01 にリリースされたのでインストールメモ。

インストール

以下は pip でのインストール手順。core を先にインストールしないと依存性が解消されないので注意。

pip install -U pyobjc-core
pip install -U pyobjc

ドキュメント

各種ドキュメントは以下にある。

サンプルソース

pip でインストールするとサンプルがインストールされないので、以下のURLから取得する。

サンプルの内容は Mac での Objective-C の知識が無いと意図が理解はできないと思われる。
Python から Objective-C の API が簡単に利用できるというのが便利。

2012年11月1日

Python モジュールバージョン番号フォマットに関してのメモ

概要

Python モジュールを開発する際のバージョン番号のフォーマットに関してのメモ。

ドキュメント

PEPが存在する。

形式

形式は以下。

N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
  • major
  • minor
  • micro
  • releaselevel
    • dev
    • alpha
    • beta
    • candidate
    • final
  • serial

バージョン番号表示

setup.py でバージョン番号が表示できるように作成する。

python setup.py --version

関連プログラム

2012年10月30日

Twitter 関連のライブラリを API 1.1 に対応させるのは結構大変

Python から Twitter API を扱うライブラリに「tweepy」があるが、これは API 1.1 に対応していない。
そこで、API 1.1 の調査を兼ねていろいろ変更してみている「sakito / tweepy — Bitbucket」。

単純に endpoint を 1.1 にするだけでも大丈夫そうな感じがするが、細かい所まで対応しようとするとかなり大変。
使う機能以外を気にすると時間がかかりすぎる気がするので主要な機能だけ、徐々に対応してみようと思う。

2012年10月29日

Python の in 演算子

概要

Python の in 演算子に関して詳細に記述してある「python `in` operator use cases」という記事を見つけた。
日本語を使うとどうなるかとりあえず試してみた。

ソース

いつも通り unittest で記述した。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest


class TestIn(unittest.TestCase):

    def test_list(self):
        """
        list
        """
        lst = ['あ', 'い', 'う', 'え', 'お']
        self.assertTrue('あ' in lst)
        self.assertFalse('か' in lst)

    def test_nested_list(self):
        """
        nested list
        """
        lst = [
            ['あ', 'い', 'う', 'え', 'お'],
            ['さ', 'し', 'す', 'せ', 'そ'],
            [],
        ]
        self.assertFalse('あ' in lst)
        self.assertTrue(['あ', 'い', 'う', 'え', 'お'] in lst)
        self.assertTrue([] in lst)

    def test_dict(self):
        """
        dict
        """
        dct = {'名前': 'サンプル', '国': '日本', 'OS': 'Mac',
               '言語': {'web': 'python'}}
        self.assertTrue('名前' in dct)
        self.assertFalse('web' in dct)

    def test_dict_class(self):
        """
        dict class
        """
        class Person(dict):
            pass

        p = Person()
        self.assertEqual({}, p)

        p['name'] = '名前'
        self.assertTrue('name' in p)

    def test_set(self):
        """
        set
        """
        stlst = {'foo', 'bar', 'foo'}
        self.assertEqual(set(['foo', 'bar']), stlst)
        self.assertTrue('foo' in stlst)

    def test_generator(self):
        """
        generator
        """
        self.assertTrue(2, xrange(4))

    def test_string(self):
        """
        string
        """
        msg = 'Python はシンプルで強力な言語です'
        self.assertTrue('Python' in msg)
        self.assertTrue('強力' in msg)
        self.assertEqual(0, msg.find('Python'))
        self.assertEqual(25, msg.find('強力'))

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

まとめ

当然のように日本語でも特に問題はない。in はそれほど複雑ではないが、いろいろと応用が効いて便利。

2012年10月28日

多言語対応テンプレート言語 mustache を試してみた

概要

{{ mustache }}」は多言語対応のテンプレート言語。各種エディタの plugin も提供されていて便利そうなので、試してみた。

ドキュメント

文法は「mustache」を参照。

Python で試してみる

とりあえずPython で試してみる。「Pystache」を使う。

pip install Pystache

「pystache」コマンドがインストールされるので、簡単なサンプルを試してみる。

pystache 'Hello {{name}}' '{"name": "World"}'

普通はテンプレートファイルを作成する。以下のような内容で「sample.txt」として作成。

<html>
<head>
<title>こんにちは {{name}}</title>
</head>
<body>
サンプル
{{#lists}}
  <b>{{name}}</b>
{{/lists}}
</body>
</html>

これを利用するソースは以下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest

from pystache.loader import Loader
from pystache import Renderer


class TestPystache(unittest.TestCase):

    def test_file(self):
        DATA_DIR = './data'
        loader = Loader(search_dirs=[DATA_DIR, ],
                        file_encoding='utf-8',
                        extension='txt')
        template = loader.load_name('sample')

        context = {'name': 'World',
                   'lists': [
                       {'name': 'a'},
                       {'name': 'b'},
                       {'name': 'c'},
                   ],
                   }

        renderer = Renderer()
        actual = renderer.render(template, context)

        self.assertEqual('<html>\n'
                         '<head>\n'
                         '<title>こんにちは World</title>\n'
                         '</head>\n'
                         '<body>\n'
                         'サンプル\n'
                         '  <b>a</b>\n'
                         '  <b>b</b>\n'
                         '  <b>c</b>\n'
                         '</body>\n'
                         '</html>\n'.decode('utf-8'),
                         actual)

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

まとめ

多言語対応なのは利点。
Python だと Jinja2 という強力なテンプレートエンジンがあるので、それほど利点がないかもしれない。
テンプレートエンジンの選択肢が少ないような言語だとかなり便利かもしれない。

2012年10月27日

Python 標準ロギングモジュール logging の使い方メモ

概要

logging を利用してみる。

ドキュメント

モジュールの使い方は、公式ドキュメント「logging — Logging facility for Python」に概要が書いてある。 また「Logging HOWTO」が具体的な使い方に関して書いてある。

使い方

以下が一番簡単な例。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.warning('わーにんぐ')
    logging.info('いんふぉ')

if __name__ == '__main__':
    main()

実行すると以下のようになる。これはデフォルトレベルが「WARNING」以上だから。

WARNING:root:わーにんぐ

ログのレベルは以下のようになる。

レベル 関数 数値 概要
CRITICAL logging.critical() 50 停止してしまうような致命的な問題用
ERROR logging.error() 40 重大な問題用
WARNING logging.warning() 30 実行機能で問題が発生した場合用
INFO logging.info() 20 動作情報表示用
DEBUG logging.debug() 10 詳細な情報表示用
NOTSET 0 全てを出力。基本的に設定用の値

ファイルへの出力

エラーをファイルに出したい場合があるが以下のようにする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.basicConfig(filename='example.log', level=logging.DEBUG)
    logging.debug('でばっぐ')
    logging.info('いんふぉ')
    logging.warning('わーにんぐ')

if __name__ == '__main__':
    main()

「sample.log」ファイルに以下のように出力される。

DEBUG:root:でばっぐ
INFO:root:いんふぉ
WARNING:root:わーにんぐ

ログ出力形式の指定

ログの出力形式はいろいろ変更できる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s',
                        level=logging.DEBUG)
    logging.debug('でばっぐ')
    logging.info('いんふぉ')
    logging.warning('わーにんぐ')

if __name__ == '__main__':
    main()

以下のように出力される。

2012-10-01 11:21:46,999:DEBUG:でばっぐ
2012-10-01 11:21:46,999:INFO:いんふぉ
2012-10-01 11:21:47,000:WARNING:わーにんぐ

使えるフォーマットは「LogRecord attributes」に一覧がある。主な物は以下。

フォーマット 概要
%(asctime)s 実行時刻
%(filename)s ファイル名
%(funcName)s 関数名
%(levelname)s DEBUG、INFO等のレベル名
%(lineno)d 行番号
%(name)s 呼びだしたログの定義名
%(module)s モジュール名
%(message)s ログメッセージ
%(process)d プロセスID
%(thread)d スレッドID

設定ファイルの利用

フォーマットやログレベルの設定は、通常は設定ファイルを書いて利用する。設定ファイルのファイル名な何でも良い。ここでは以下の内容で「logging.conf」として作成する。
以下の例だと、コンソールとファイルに「DEBUG」レベルでログを出力する。

[loggers]
keys=root, logExample

[handlers]
keys=consoleHandler, fileHandler

[formatters]
keys=logFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_logExample]
level=DEBUG
handlers=consoleHandler, fileHandler
qualname=logExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=logFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=logFormatter
args=('example.log',)

[formatter_logFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

利用するソースは以下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import logging.config


def main():
    logging.config.fileConfig('logging.conf')
    logger = logging.getLogger('logExample')

    logger.debug('でばっぐ')
    logger.info('いんふぉ')
    logger.warning('わーにんぐ')

if __name__ == '__main__':
    main()

Handlerの利用

上記のサンプルだと「RotatingFileHandler」を利用している。これはファイルが一定量になると回転するハンドラ。
標準のハンドラでも十分な場合もあるが、PyPIで探すと他にもいろいろなハンドラが存在している。
以下とか便利かもしれない。

存在しない Handler は上記を参考に自作してみると良いかもしれない。

まとめ

logging は障害対処時に非常に有効。多用することになるので、使い方に慣れておくと良い。

2012年10月26日

Python で標準添付の Debuggerのpdb を利用してデバッグするメモ

概要

pdb を利用してみる。

ドキュメント

公式ドキュメント「pdb — The Python Debugger」に概要が書いてある。

使い方

スクリプトを直接起動してデバッグする方法と、インタラクティブshell で起動する方法があるが、ここでは、スクリプトを起動してデバッグする方法で書く。
以下がデバッグするスクリプトのサンプル「even.py」。
自分の場合、スクリプトを直接デバッグすることがあまりなく、デバッグするのはライブラリとかをunittest経由でデバッグすることがが多いのでサンプルは unittest で書いている。
以下の例はクラス内の関数が偶数だけ返す所でバグがあり、奇数を返すようになっている。そんなに良い例ではないかも。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest


class Sample(object):
    """
    テスト対象のクラス
    """
    def to_even(self, lst):
        """
        リストを渡すと、偶数だけにして返す
        """

        # 意図的にバグらせていて、奇数しか返さない
        evenlst = [v for v in lst if v % 2 == 1]
        return evenlst


class TestSample(unittest.TestCase):
    """
    テストクラス
    """

    def test_main(self):
        clazz = Sample()

        #  0 から 10 のリストを生成 [0, 1, 2, .....]
        lst = xrange(10)
        evenlst = clazz.to_even(lst)

        # 偶数かテストする
        for item in evenlst:
            num = item % 2
            self.assertEqual(0, num)


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

とりあえず実行してみると以下のようにエラーになる。

> python even.py 
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "even.py", line 33, in test_main
    self.assertEqual(0, num)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

pdbを起動してみる。起動は以下のようにする。

python -m pdb even.py

起動すると pdb プロンプトが起動する。

> /path/to/even.py(3)<module>()
-> import unittest
(Pdb) 

上記の出力から「3行目」の「import」で実行が停止しているのがわかる。
「list」で停止位置の周辺ソースが表示される。

(Pdb) list
  1     #!/usr/bin/env python
  2     # -*- coding: utf-8 -*-
  3  -> import unittest
  4  
  5  
  6     class Sample(object):
  7         """
  8         テスト対象のクラス
  9         """
 10         def to_even(self, lst):
 11             """
(Pdb) 

行数とともに、現在の停止場所に「->」がある。

「list」のようにデバッグに利用できるコマンドの主な物は以下になる。コマンドには省略形が存在する物もある。

コマンド 省略形 概要
help h コマンドのヘルプを表示。
「help」でコマンド一覧、
「help コマンド名」でコマンドのヘルプ
が表示される。
where w スタックトレースを表示
down d スタックトレース中に1レベル下げる
up u スタックトレース中に1レベル上げる
step s 現在行を実行し、次の行に進む
次の行が関数の場合、関数内で停止
next n 現在行を実行し、次の行に進む
次の行が関数でも、関数内では停止しない
return r 現在の関数から抜ける
list l 現在行周辺のソースを表示
デフォルトは11行表示する
args a 現在関数の引数一覧を表示
p 式 式内容を表示
pp 式 式内容を pprint で表示
quit q デバッガ終了

現在行が「import」なので「n(ext)」で進める。
間違えて「s(tep)」すると「import」を実行する Python のライブラリ内に入る。その場合は「r(eturn)」とかすると戻ってこれる。

(Pdb) n
> /path/to/even.py(6)<module>()
-> class Sample(object):
(Pdb) 

class の行になるが、実行されるわけではない。
再度「n」するが、この「n」は一度実行すると次回は「リターンキー」で同じ事を実行する。

(Pdb) 
> /path/to/even.py(18)<module>()
-> class TestSample(unittest.TestCase):
(Pdb) 

「c(ontinue)」すると、停止するまで実行する。

(Pdb) c
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "even.py", line 33, in test_main
    self.assertEqual(0, num)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
The program exited via sys.exit(). Exit status:  True
> /path/to/even.py(3)<module>()
-> import unittest

ブレークポイント

通常 step 実行とかしないで、ブレークポイントを利用する。ブレークポイント関連のコマンドは以下。

コマンド 省略形 概要
break 行数 or 関数名 b 指定行数にブレークポイントを付ける
tbreak 行数 or 関数名 一時的ブレークポイントを付ける
一回通過すると消える
clear 行数 or bp番号 cl ブレークポイントを削除
disable bp番号 ブレークポイントを停止
enable bp番号 ブレークポイントを有効化
ignore bp番号 回数 ブレークポイントを回数分無視する
condition bp番号 ブレークポイントの状態を確認
commands bp番号 ブレークポイントを表示
continue c ブレークポイントで停止してる場合、実行を継続

「33行目」にブレークポイントを設定し「c」して「l」で確認してみる。

(Pdb) b 33
Breakpoint 1 at /path/to/even.py:33
(Pdb) c
> /path/to/even.py(33)test_main()
-> for item in evenlst:
(Pdb) l
 28             #  0 から 10 のリストを生成 [0, 1, 2, .....]
 29             lst = xrange(10)
 30             evenlst = clazz.to_even(lst)
 31  
 32             # 偶数かテストする
 33 B->         for item in evenlst:
 34                 num = item % 2
 35                 self.assertEqual(0, num)
 36  
 37  
 38     if __name__ == '__main__':
(Pdb) 

ここで「evenlst」の内容を表示してみる。

(Pdb) p evenlst
[1, 3, 5, 7, 9]
(Pdb) 

バグっている事がわかる。

「n」を2回して「l」して「p」してみる。「p」で複数表示の場合はカンマで区切る。

(Pdb) p item,num
(1, 1)
(Pdb) 

バグ原因が判明したら「q」などで終了すれば良い。

まとめ

他者の作成したライブラリ等はソース修正するとまずいので、 print を入れることができない場合が多く、そういう場合は pdb は便利。
本当はライブラリ内で Exception が発生したりするので、結構デバッグは大変だったりする。
自作のスクリプトの場合は、普通は pdb を使うよりも、いかに良くテストを書くかの方が重要な気がしている。pdbを使わなければいけないのはテストが下手だと思った方が良い。

2012年10月24日

pyregexp を利用して Emacs の検索置換に Python の正規表現を利用してみる

概要

Emacs の正規表現は結構使いずらくて、覚えられないので、「pyregexp」を利用して、Python の正規表現を利用してみる。

pyregexpに関して

Python を利用して Emacs 内の検索・置換の処理で Python の正規表現を利用できるようにする Emacs Lisp。動作には Python が必要。

設定

ソースを取得して、pathの通った所に配置。

git clone git://github.com/benma/pyregexp.git

init.el 等で以下のように設定する。

(require 'pyregexp)
(define-key global-map (kbd "C-c r") 'pyregexp-replace)
(define-key global-map (kbd "C-c q") 'pyregexp-query-replace)
(define-key esc-map (kbd "C-r") 'pyregexp-isearch-backward)
(define-key esc-map (kbd "C-s") 'pyregexp-isearch-forward)

使い方

公式サイト「pyregexp」の一部をそのまま引用する。

以下のようなテキストを用意。これの重複した文字を検索して、重複を削除してみる。

We we can delete double double words
by by using backreferences.

「C-c r」して入力を開始すると、「Regexp」になるので、「(?i)(\w+) \1」を入力。一致箇所がハイライトされるる。3箇所一致しているはず。一番最後の文字は「いち」なので間違わないように。
置換する場合は「enter」をすると「Replace」になるので、「\1」を入力。置換結果が「We we => We」のように表示される。問題なければ、enter することで置換される。

Python の式を利用することも可能。

以下のようなファイルを用意。

hoge
foo
bar

「C-c r」して「^」を入力すると、文字列の先頭が一致する。enter して Replace にして「C-c C-c」すると「Replace (using expression)」になるので「str(i+1) + ": "」を入力すると、先頭に「1: 2: 3:」のような文字が入力される。

str だけでなく、int等も利用できるのでかなり便利。

「wdired-change-to-wdired-mode」でも利用可能。かなり高度な置換ができる。

まとめ

まだ使いはじめたばかりだが、自分は Python に慣れているので、かなり便利に利用できている。

2012年10月23日

Python の lambda メモ

概要

仕事の関係で Python の lambda に関して最近質問された。lambda は結構読みずらいようで質問の回数が多い気がする。毎回似たような質問に答えるのが面倒なので、回答用に概要をメモしておく。

Python の lambda

lambda は無名関数を作成するための機能。

def a(b):
    return c

と同じ物をが、lambda だと以下のように記述できる。

a = lambda b: c

簡単なサンプル

とりあえず簡単なサンプルが以下。時々 lambda は改行ができないと思っている人がいるみたいだが、括弧でかこめば普通に改行できる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest

class TestLambda(unittest.TestCase):

    def test_basic(self):

        # def を利用
        def a(b):
            return b * 10

        self.assertEqual(20, a(2))

        # lambda を利用
        a = lambda b: b * 2

        self.assertEqual(4, a(2))

        # 括弧を利用すると途中改行が可能
        a = (lambda b:
             b * 4)

        self.assertEqual(8, a(2))

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

まとめ

確かに lambda を使うべき場面もあるが、普通はlambda はそんなに多用しない方が良いと思う。利用しても、意味ない場合も多い。
変な所で、変なテクニック使ってもおもしろくともなんとも無いので、普通に読みやすく書くのが一番良い。
あと、書いたは良いが、質問される内容はもっと高度になる場合が多いので、このエントリは自分の手抜き用には役に立たないかもしれない。

2012年10月20日

munin の plugin 作成メモ

概要

Munin」の plugin 作成の手順とサンプルをメモしておく。

ドキュメント

公式ドキュメントは結構ちらばっていてやや読みずらいが、plugin を作成したいならちゃんと読んだ方が良い。

plugin サンプル

とりあえず、サンプルを見て作成するのが速い。

参考に nginx の稼動を記録する plugin を作成してみる。nginx の status を利用して監視をするので、 status は有効にしておく。
nginx の稼動確認 plugin は公式の munin plugin としてすでに存在しているが、存在している plugin を作成することで、自作の plugin が既存の plugin と同じ動作をすることが確認できるので、最初に作成する plugin はすでに存在している plugin を作成してみるのが良い。

  • /usr/share/munin/plugins/nginx_request
  • /usr/share/munin/plugins/nginx_status

munin 標準の nginx plugin は Perl で作成されており、「LWP::UserAgent」が必要な plugin になっている。動作させる場合は、「/etc/munin/plugins」に「ln -s」する。動作させて nginx を監視したグラフを生成しておく。

munin の plugin はプログラム言語を問わないので、とりあえず自分は Python で実装してみたのが以下。「nginx_request.py」の名前で作成した。
以下の plugin は正規表現を「https://github.com/samuel/python-munin/blob/master/munin/nginx.py」の物を利用して、実質30から40分程度で作成したので、あまり最適化してないし、エラー処理もいいかげん。またバグがあるかもしれないので、ちゃんと検証してから利用すること。一応 Python 2.6 と 2.7 で動作するはず。
自作の plugin も動作させる場合は、「/etc/munin/plugins」に「ln -s」すれば良い。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import re
import sys
import urllib


def get_env():
    """
    get env
    """
    url = os.environ.get('url')
    if url is None:
        url = 'http://localhost/nginx_status'
    return url


def get_fields():
    """
    fields
    """
    return ('request',)


def get_server_status():
    """
    server status
    """
    # https://github.com/samuel/python-munin/blob/master/munin/nginx.py
    status_re = re.compile(
        r'Active connections:¥s+(?P<active>¥d+)¥s+'
        r'server accepts handled requests¥s+'
        r'(?P<accepted>¥d+)¥s+(?P<handled>¥d+)¥s+(?P<requests>¥d+)¥s+'
        r'Reading: (?P<reading>¥d+) Writing: (?P<writing>¥d+) Waiting: (?P<waiting>¥d+)')
    try:
        status = status_re.search(urllib.urlopen(get_env()).read()).groupdict()
    except Exception, e:
        status = None
    return status


def do_data():
    """
    data
    """
    request = get_server_status()['requests']
    fields = get_fields()
    for field in fields:
        print '{0}.value {1}'.format(field, request)


def do_autoconfig():
    """
    autoconfig
    """
    if get_server_status() is None:
        print 'no (no nginx status on {0})'.format(get_env())
    else:
        print 'yes'


def do_config():
    """
    config
    """
    print 'graph_title nginx Requests'
    print 'graph_args --base 1000 -l 0'
    print 'graph_vlabel Requests per second'
    print 'graph_category nginx'

    fields = get_fields()
    for field in fields:
        print '{0}.label requests'.format(field)
        print '{0}.min 0'.format(field)
        print '{0}.type DERIVE'.format(field)
        print '{0}.draw LINE2'.format(field)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        if sys.argv[1] == 'config':
            do_config()
            sys.exit(0)
        if sys.argv[1] == 'autoconfig':
            do_autoconfig()
            sys.exit(0)
    else:
        do_data()

上記の plugin は 外部設定に対応している。「/etc/munin/plugin-conf.d」に「nginx」みたいな名前で以下のようなファイルを作成すると、監視 URL を変更できる。

[nginx_*]
env.url http://domain/nginx_status

plugin のデバッグ

基本的には「munin-run」を利用するとデバッグがしやすい。
上記の plugin をデバッグする場合は、以下のようにする。

sudo munin-run nginx_request autoconfig
sudo munin-run nginx_request config
sudo munin-run nginx_request

今後の課題

munin の plugin 作成の設定値を全部認識しているわけではないので、徐々に確認していく。

2012年10月17日

プロセス管理に daemontools ではなく Supervisor を利用してみませんか

概要

サーバでは様々なプロセスを実行する必要があるが、これをどう管理するかは悩み所になる。
ここでは強力で柔軟なプロセス管理ツール「Supervisor」に関して記述する。

Supervisorに関して

Supervisor」は Python で記述された、プロセス管理ツール。
プロセス管理ツールと言うと「upstart」や「daemontools」が利用されていると思われる。
「upstart」は強力ではあるが、柔軟性がまだそれほど無いため、高度なプロセス管理はまだ難しい状況だと思われる。
「daemontools」は ノウハウが蓄積されており、強力で柔軟性が高いツールだが、2001年以降公式では更新されておらず、動作が不安定にってしまう環境も存在している。パッチを当てれば良いが、環境によって動作が変化してしまう場合もあり、安心して利用できない状況になってしまった。
Supervisor」は積極的に開発され、非常に安定したツールとなっている。
機能的には「daemontools」とほぼ同様の事が可能。
Python で記述されることで、Unix互換システムにおいて、快適に動作する。当然 Mac でも動作する。
現在の Linux サーバは Python はほぼ必須でインストールされている、という状況なので、ツールが Python で記述されていることは利点の一つになりえる。

Supervisorの利点は以下となる。全機能はマニュアル「Supervisor」を参照。

  • 積極的に開発されている
  • 設定ファイルが比較的簡単
  • プロセスのグループ化が容易
  • プロセスの状態により処理を実行することが可能
  • サブプロセスの管理も可能
  • ログを高度に処理できる
  • Webインターフェースが存在する
  • XMP-RPCインターフェースが存在する

インストール

Supervisorをインストールする場合、通常virtualenvにより環境を構築する。
参考に、以下インストール手順を示した fabfile を配置した。以下のスクリプトはインストールディレクトリは「/opt/python」で、作業ディレクトリは「/opt/supervisor」として記述してある、また、この fabfile はあまり、汎用的には記述していない、あくまで参考である。

実行は「fab install」で実行可能

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fabric.decorators import task
from fabric.api import (
    sudo,
    run,
    cd,
    put,
    env,
)
from fabric.contrib.files import exists


@task
def install():
    """
    Supervisor
    """
    setup_venv()
    install_supervisor()
    setup_bin()
    setup_conf()
    setup_upstart()


@task
def setup_venv():
    """
    virtualenv による Python 環境構築
    """
    with cd(env.path):
        run('wget https://raw.github.com/pypa/virtualenv/master/virtualenv.py')
        sudo('python virtualenv.py --distribute /opt/python')


@task
def install_supervisor():
    """
    Supervisor インストール
    """
    run('source /opt/python/bin/activate')
    sudo('/opt/python/bin/pip install Supervisor')


@task
def setup_bin():
    """
    実行ファイル設定
    """
    work_dir = '/opt/supervisor/bin'
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}/*'.format(work_dir),
        '{0}/'.format(work_dir), use_sudo=True)
    sudo('chown root:root {0}/*'.format(work_dir))
    sudo('chmod 755 {0}/*'.format(work_dir))


@task
def setup_conf():
    """
    設定ファイル
    """
    work_dir = '/opt/supervisor'
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}/etc'.format(work_dir))
        sudo('mkdir -p {0}/var/log'.format(work_dir))
        sudo('mkdir -p {0}/var/run'.format(work_dir))

    work_dir = '/opt/supervisor/etc'
    conf_file = '{0}/supervisord.conf'.format(work_dir)
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)

    work_dir = '/opt/supervisor/etc/supervisor.d'
    conf_file = '{0}/*.conf'.format(work_dir)
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)


@task
def setup_upstart():
    """
    upstart 設定
    """
    conf_file = '/etc/init/supervisor.conf'
    put('./fabfile{}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)
    sudo('chown root:root {0}'.format(conf_file))

管理用スクリプトの配置

Supervisor をインストールすると、インストール場所の「bin」以下に「supervisord」と「supervisorctl」の2ファイルが生成される。
これを直接利用しても良いが、自分は、一つのサーバで複数のSupervisorを起動する要件が発生する場合があるため、以下のようにスクリプトを作成している。
このスクリプトは実行する Python の指定と、設定ファイルの指定をしているだけだが、用途によっては便利だろう。

以下が「supervisord」と同じ機能のファイル

#!/opt/python/bin/python
# -*- coding: utf-8 -*-
import os
import sys

SRC_DIR=os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.argv.extend(['-c', '{0}/etc/supervisord.conf'.format(SRC_DIR)])

import supervisor.supervisord

if __name__ == '__main__':
    supervisor.supervisord.main()

以下が「supervisorctl」と同じ機能のファイル。

#!/opt/python/bin/python
# -*- coding: utf-8 -*-
import os
import sys

SRC_DIR=os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.argv[1:1] = ['-c', '{0}/etc/supervisord.conf'.format(SRC_DIR)]

import supervisor.supervisorctl

if __name__ == '__main__':
    supervisor.supervisorctl.main(sys.argv[1:])

設定ファイル

設定ファイルの雛形は「echo_supervisord_conf」プログラムによって生成可能。
とりあえず以下のように記述すると最低限の動作はする。「%(here)s」は設定ファイルのあるディレクトリを示す。
「include」の「file」は現在のバージョンではまだ「%(here)s」が使えない。

[supervisord]
childlogdir = %(here)s/../var/log
logfile = %(here)s/../var/log/supervisor.log
logfile_maxbytes = 50MB
logfile_backups = 10
loglevel = info
pidfile = %(here)s/../var/run/supervisor.pid
umask = 022
nodaemon = false
nocleanup = false

[inet_http_server]
port = 127.0.0.1:9001
username =
password =

[supervisorctl]
serverurl = http://127.0.0.1:9001
username =
password =

[rpcinterface:supervisor]
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface

[include]
files = /opt/supervisor/etc/supervisor.d/*.conf

upstartの設定

Supervisor自身をSupervisorで管理することはできないので、Supervisorは upstartで管理する。
以下のようなファイルを「/etc/init」以下に「supervisor.conf」として配置する。

description     "supervisor"

start on runlevel [2345]
stop on runlevel [!2345]

respawn

chdir /opt/supervisor
exec sudo -i /opt/python/bin/python /opt/supervisor/bin/supervisord -n -d /opt/supervisor

pre-stop exec sudo -i /opt/python/bin/python /opt/supervisor/bin/supervisorctl stop all

プロセス管理設定

これで Supervisor は起動している。「supervisorctl status」を実行するとまだ管理対象が存在しないことがわかる。
とりあえず、nginx の管理を設定してみる。

管理対象のプロセスはフォアグラウンドで起動させる必要がある。nginx の設定を変更する。

daemon off;

Supervisor の設定ファイルで「include」の「files」で指定したディレクトリに以下のようなファイルを「nginx.conf」のような名前で配置する。

[program:nginx]
command=/opt/nginx/sbin/nginx -c /opt/nginx/conf/nginx.conf
redirect_stderr=true
stdout_logfile=%(here)s/../var/log/%(program_name)s_stdout.log
stderr_logfile=%(here)s/../var/log/%(program_name)s_stderr.log
autostart=true
autorestart=true

ここで注意すべきなのは「%(here)s」は、元設定ファイルのカレントディレクトリになるということ。パスには注意する。
以下のようにすることで、有効になる。

# 設定読み込み
supervisorctl reread
# 設定反映
supervisorctl update
# 確認
supervisorctl status

設定を反映するまでは、有効にならない。

以下のように、起動、終了、再起動が可能。

supervisorctl stop nginx
supervisorctl start nginx
supervisorctl restart nginx

「autostart=true」を設定しているので、Supervisor が起動すると自動起動する。
「autorestart=true」を設定しているため、異状終了しても管理対象プロセスは復旧してくる。kill などして、確認してみると良い。

Webインターフェース、XML-RPC

SupervisorはHTTPのWebインターフェエースをもっている。また XML-RPC による管理も可能。
HTTPSを利用したい場合は、nginx などの HTTPサーバを経由させれば良い。
上の設定ファイルを利用している場合はポート9001になる。

その他機能

他の機能はマニュアルを読むのが良い。機能は高機能なので、いろいろできる。

2012年10月15日

システム構築、デプロイツール Fabric について

概要

サーバ構築等のシステム構築は多数のコマンドを打鍵することになる。そうしたコマンドをメモの形で残しておいても、コピペで実行していればミスは発生するし、安全性が低い。
プログラマーなら、手順全てをプログラムしておくのが良い。
それを可能にするツールが「Fabric」。

インストール

Fabric」は Python で作成されている。インストールは easy_install か pip 等で実施する。

pip install fabric

インストールで「fab」というコマンドがインストールされるので、PATHを通しておく。

事前準備

サーバに対して実行する場合は、サーバにログインできる状態にしておく必要がある。

簡単な使い方

以下のようなファイルを「fabfile.py」の名前で作成。

from fabric.api import run

def host_uname():
    run('uname -s')

「fabfile.py」の存在するディレクトリで、ホストを指定して、「fab」コマンドを実行する。

fab -H 192.0.2.178 host_uname

この例だと「192.0.2.178」のサーバにログインして「uname -s」を実行する。ログインする時に通常のログイン処理が実行される。

もっと自動化したい場合は、「evn」を設定することで、自動的にログインすることも可能。

from fabric.api import env

# SSH のキーのパスを設定
env.key_filename = [os.path.join(os.path.dirname(__file__), 'keys/id_rsa')]
# ログイン先の IP
env.hosts = [
    '192.0.2.178',
]
# ログインユーザ名
env.user = 'username'
# sudo する際のパスワード
env.password = 'password'

マニュアル

マニュアルは「Fabric」に存在する。 Operationsの章とかは理解しやすいかもしれない。ほとんどの事が実行できる。 あとは「fabfile.py」で検索するとサンプルが沢山あるのでそれを見るのが速い。

fabfileの分割

通常は「fabfile.py」を作成して実行するが、システム構築等を実行するとすぐに「fabfile.py」が肥大化する。
「fabfile.py」を分割したい場合は「fabfile」というディレクトリを作成し、その中に「__init__.py」ファイルを作成。
同じディレクトリに Python ファイルを用意して、「__init__.py」で import すれば良い。

たとえば以下のようなディレクトリ構成にする。

.
├── fabfile
    ├── __init__.py
    ├── commons.py
    ├── etc
    │   └── ufw
    │       ├── before.rules
    │       └── before6.rules
    ├── ufw.py

「__init__.py」の中身は以下のような感じ。

import commons
import ufw

実行は「ufw.py」の中身が以下のようだとすると。「fab ufw.status」で実行できる。

from fabric.decorators import task
from fabric.api import sudo

@task
def status():
    """
    Firewall の状態確認
    """
    sudo('ufw status')

あとは API を使いこなせば、ほとんど何でも自動実行可能。事前に構築環境と同じOSなどを用意して、十分に動作を確認してから実行できるので、非常に便利。

まとめ

Fabric」に関しては利用してみないと便利さが良くわからないかもしれないが、かなり便利なツール。環境構築を汎用的に実施したい人には特にお勧め。

2012年10月12日

RRDtool を Python から使う

概要

RRDtool」に関して調査しているが、Shell スクリプトだと個人的には面倒なので、Python から利用する方法を調査してみる。

モジュール

ちゃんとメンテナンスされているのは「python-rrdtool」と「PyRRD」がある。
python-rrdtool」は本家で解説(rrdpython)されているモジュールで、Cモジュールから RRDtool を利用する。ほとんど RRDtool そのまま利用するモジュール。
PyRRD」は、RRDtoolを結構ちゃんとラップしようとしている。Python 的に利用しようとすると便利だが、RRDtoolの一部機能が上手く使えない模様。

どちらを利用するか悩んだが、PyRRD の方が書き易そうだが、結局 RRDtool のマニュアル読むことになって、どのオブジェクト使うか調べるのが面倒。
素直に書ける方がRRDtoolのマニュアルとの対応が取りやすいので、「python-rrdtool」の方を使うことにした。

インストール

pip でインストールする。

pip install python-rrdtool

ただし、Macでインストールすると、Mac の ls が GNU ls でないため「error: ls -t appears to fail. Make sure there is not a broken」のエラーが出るかもしれない。
エラーになる場合は、GNU ls(coreutils)を別途インストールするなりして対処するか、「python-rrdtool」をダウンロードしてきて、「configure」ファイルで「ls」の出力チェックをしている場所をコメントアウトするとコンパイルが通る。

RRDtool を MacPorts でインストールしている場合はソースをダウンロードしてきて、以下でインストールする。

env CPPFLAGS=-I/opt/local/include LDFLAGS=-L/opt/local/lib python setup.py install

使い方

rrdtoolコマンドがわかっていれば、それほど難しくはない。
例えば Shell で以下のように書く場合。

rrdtool create load.rrd \
    --step 300 \
    DS:load1:GAUGE:600:0:U \
    DS:load5:GAUGE:600:0:U \
    DS:load15:GAUGE:600:0:U \
    RRA:AVERAGE:0.5:1:600 \
    RRA:AVERAGE:0.5:6:700 \
    RRA:AVERAGE:0.5:24:775 \
    RRA:AVERAGE:0.5:288:797 \
    RRA:MAX:0.5:1:600 \
    RRA:MAX:0.5:6:700 \
    RRA:MAX:0.5:24:775 \
    RRA:MAX:0.5:288:797 \
    RRA:MIN:0.5:1:600 \
    RRA:MIN:0.5:6:700 \
    RRA:MIN:0.5:24:775 \
    RRA:MIN:0.5:288:797

python-rrdtoolでは以下のように書ける。このサンプルはほとんど変数とか利用してないが、もうすこし Python らしく書くことも可能。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rrdtool


def create():
    data_sources = [
        'DS:load1:GAUGE:600:0:U',
        'DS:load5:GAUGE:600:0:U',
        'DS:load15:GAUGE:600:0:U',
    ]
    rras = [
        'RRA:AVERAGE:0.5:1:600',
        'RRA:AVERAGE:0.5:6:700',
        'RRA:AVERAGE:0.5:24:775',
        'RRA:AVERAGE:0.5:288:797',
        'RRA:MAX:0.5:1:600',
        'RRA:MAX:0.5:6:700',
        'RRA:MAX:0.5:24:775',
        'RRA:MAX:0.5:288:797',
        'RRA:MIN:0.5:1:600',
        'RRA:MIN:0.5:6:700',
        'RRA:MIN:0.5:24:775',
        'RRA:MIN:0.5:288:797',
    ]

    rrdtool.create('pyload.rrd',
                   '--step', '300',
                   data_sources,
                   rras)


def main():
    create()

if __name__ == '__main__':
    main()

まとめ

個人的には Shell で書くよりはいろいろと応用が効くので楽。