Write your blackbird plugin #003

blackbird pluginを書こう第三弾ってことで、第二弾で細かく書けなかったところ、主に

* JobBaseの各メンバプロパティについて
    + queueとか
    + loggingとか
* 各基底クラス側で実装されている便利関数について
    + こちらは落穂拾い的な内容になると思います

ではどどーんと逝きましょう。


About each member properties of `JobBase` class
--------------------------------------------------

### self.options

こちらは第二弾でも説明させていただきましたが、コンフィグファイルに記載した内容をkeyとvalueとして解釈して、
dictionary形式にしたものです。具体的には

```ini
[SECTION_NAME_IS_ANYTHING_OK]
hostname = hogehoge.com
hogehoge = fugafuga
```

みたいなコンフィグファイルがあったら、

```python
self.options = {
    'hostname': 'hogehoge.com',
    'hogehoge': 'fugafuga'
}
```

みたいな感じのdictionaryが出来上がります。ここについては第二弾でも結構詳しく書いたつもりなので、
こんな感じです。もっとkwsk！ってのがあればコメントか僕宛にコンタクトを取ってもらえれば書かせていただこうかなと思います。


### self.queue

MainのプロセスがThread間でメッセージをやりとりする用のQueueです。特に難しいことはしてなくって、
Python謹製の[`Queue.Queue`](http://docs.python.jp/2/library/queue.html#Queue.Queue)をそのまま使ってます。
なので、`self.queue.put`メソッドはそのまま`Queue.Queue.put`を読んでいるものと同じだと考えてくださいまし。

notes:

putメソッドについてですが(プラグインを書く人は主にputメソッドを使うことになると思ってます)、
`block`という引数があって自分がput操作をしているときは他のConsumer及びProducerからの操作を
blockするというものです。
しかし、こちらはひとつのItemに対してkey, clock, hostがかぶることはないという条件のもと、
**`block=False`を強く推奨しています**。

### self.logger

ロギング用のインスタンスです。このインスタンスはPython謹製の[loggingモジュール](http://docs.python.jp/2/library/logging.html)を出力フォーマットだけ変更しただけのものになるので、
loggingモジュールのメソッドがそのまま使用可能です。

```python
self.logger.debut('This is Debug log!! HOGEHOGE!!!!!!!!!!')
```

そのため上記のように書くことも可能です。ログの出力レベルですが、defaultでは`WARN`です(こちらはglobalセクションで変更が可能です)。

### self.invalid_key_list

```javascript
//今更ですがちょっとプロパティ名、ミスってるかなって気もしてますｗｗｗ
//invalidじゃないなーって感じw ignoreとか??
```

こちらは、Itemのkeyがこのlistの中に存在している場合、Queueに入れないっていうブラックリストです。
たとえば、

```python
class ConcreteJob(blackbird.plugins.base.JobBase):

    def __init__(self, options, queue=None, logger=None):
        super(ConcreteJob, self).__init__(options, queue, logger)
        self.invalid_key_list = [
            'host.IGNORE_KEY_NAME001',
            'host.IGNORE_KEY_NAME002'
        ]
```

ってかけば、某memcachedの`stats`コマンドのようなまるっと取ってくるってところに振り分け処理を書かずにロジックを別のメソッドに切り出すことが可能になります。
この場合、listに勝手に入れるだけではもちろんダメでございまして、

```python
#..snip..

def filter_item(item):
    if not item.data['key'] in self.invalid_key_list:
        return item

def filter_items(items):
    filtered_items = list()
    for entry in items:
        if not items.data.['key'] in self.invalid_key_list:
            filtered_items.append(entry)

    return filtered_items
```

的にいい感じで実装してもらえればと思います。



各基底クラス側で実装されている便利関数について
--------------------------------------

続きまして、各基底クラス側で実装されている便利関数についてです。
まぁ、正直多くはありませんが、それなりにプラグインづくりを手助けしてくれるとは思います。


### `blackbird.plugins.base.ValidatorBase.detect_hostname`

名前の通りhostnameを取ってくるようなメソッドです。
低レイヤーな関数を使っているのですが(Pythonはこういうのがさらっと使えるのがいいですね)、その実すごいシンプルで、

```python
return socket.getfqdn() or socket.gethostname() or 'localhost'
```

ってやってるだけです。

1. socket.getfqdn()の結果があれば、それをそのままreturn
2. なければsocket.gethostname() (short hostname、`hostname -s`の結果ですね)をreturn
3. それもダメなら`localhost`を文字列として返すってかんじですね。

使いドコロとしては、

```python
class Validator(base.ValidatorBase):
    def __init__(self):
        self.__spec = None

    @property
    def spec(self):
        self.__spec = (
            "[{0}]".format(__name__),
            "hostname = string(default={0})".format(self.detect_hostname()),
        )
        return self.__spec
```

こんなところですかね。ちなみにここは大先輩の提案により実装されたものでありまして、この場を借りましてありがとうございますですm(\_ \_)m

### Timer context

Timer contextはwithでくくったコンテキスト内の処理時間を測定するcontextです。
僕たちはredisやmemcachedへのkeyの書き込みや読み込みに使ってratencyを測定してMetricにしてます。
使い方は普通のContextとなんにもかわんなくって、

```python
import blackbird.plugins.base.Timer

with Timer() as timer:
    for entry in xrange(0, 9):
        print entry

    return [timer.sec, timer.msec]
```

とかってやれば、timerクラスのsecプロパティが処理時間を秒(seconds)で返して、
msecプロパティが処理時間をミリ秒(mill seconds)で返してくれます。


About Blabckbird Exception
--------------------------

Exception(ここでいう本当の意味でのってのは、あなたのプラグインに予期せぬエラーが発生したときのExceptionって意味ですね)が
発生した時に予期せぬものなのか、それとも意図的に`raise ValueError`のように発生させられたものなのかを区別するために、
独自のExceptionを実装してあります。
といっても、エラー文字列を指定してraiseするだけなので、ValueErrorとかとまったく同じ方法でraiseします。たとえば、

```python
import blackbird.plugins.base.BlackbirdPluginError

try:
    value = self.options['key']
except KeyError as exception:
    raise blackbird.plugins.base.BlackbirdPluginError(
        exception.__str__
    )
```

とすることで、Exceptionを吐いた時の挙動をdebuggingしやすくします。


----------

`blackbird.plugins.base`モジュール側でやってることってこんなもんなんですが、
ここがわかんねーよとか、ここは違うんじゃねとか、こういうメソッドがbase側にも欲しいみたいなのが
ありましたらどんどん教えてもらえればなーと考えておりますm(\_ \_)m