Python
errbot

Errbotプラグインにおける設定周りの挙動

Errbotは本体設定とは別に、botプラグインごとの設定をストレージに保持することができます。

これは、APIトークンなどの設定を置けるようになり、プラグイン開発側にもパブリックなプラグインとして公開しやすくなるメリットがあります。
Gitter眺めてたら、Config周りのやり取りがあったので、軽く自分の理解をまとめてみることにしました。

Botプラグインの設定についての概要

外からの見え方

Errbotはインストールされている各Botプラグインにに対して、

  • !plugin config example で、プラグインの設定取得
  • !plugin config example (dictの文字列表現) で、プラグインの設定反映

ができます。(いずれも、bot管理者ユーザーのみ)

内部での取扱い

プラグイン内部では、self.config でこれらのアクセスが可能になってます。

test.py
class ExamplePlugin(BotPlugin):

    @botcmd
    def hello(self, msg):
        """!helloに対して、設定として保存しているEXAMPLE_VALUEと答えるコマンド
        """
        return self.config['EXAMPLE_VALUE']

設定用コマンドの挙動

前述のように、「dictの文字列表現」と書きましたが、何も全部の形式を受け付けるわけではありません。

!plugin config plugin config example
[@CHANGE_ME ➡ @errbot] [␍] 
Default configuration for this plugin (you can copy and paste this directly as a command):
 !plugin config example
{'EXAMPLE_KEY_1': 'Example value', 'EXAMPLE_KEY_2': ['Example', 'Value']}

Botプラグインが有効になった瞬間には、基本的に設定値には何も入っていません。
この状態で!plugin config exampleと呼びかけると、上のように返ってきます。
これは、Botプラグインclassが持っている、get_config_templateが返しているのですが、
デフォルトではこの形式と過不足なく同じになっていないと、受け付けてくれません。

example.py
class ExamplePlugin(BotPlugin):

    def get_configuration_template(self):
        """
        Defines the configuration structure this plugin supports

        You should delete it if your plugin doesn't use any configuration like this
        """
        return {'EXAMPLE_KEY_1': "Example value",
                'EXAMPLE_KEY_2': ["Example", "Value"]
               }
[@CHANGE_ME ➡ @errbot] >>> !plugin config example {'EXAMPLE_KEY_1': 1}
[@CHANGE_ME ➡ @errbot] [␍] 
Incorrect plugin configuration: {'EXAMPLE_KEY_1': 1} doesn't contain the key EXAMPLE_KEY_2
# 「キーが足りない」と怒られる

[@CHANGE_ME ➡ @errbot] >>> !plugin config example {'EXAMPLE_KEY_1': 1, 'EXAMPLE_KEY_2' : 'test2'}
[@CHANGE_ME ➡ @errbot] [␍] 
Incorrect plugin configuration: Example value [<class 'str'>] is not the same type as 1 [<class 'int'>]
# 「型が一致してない」と怒られる

[@CHANGE_ME ➡ @errbot] >>> !plugin config example {'EXAMPLE_KEY_1': 'test1', 'EXAMPLE_KEY_2' : 'test2'}
[@CHANGE_ME ➡ @errbot] [␍] 
Incorrect plugin configuration: ['Example', 'Value'] [<class 'list'>] is not the same type as test2 [<class 'str'>]
# 「型が一致してない」と怒られる(2)

[@CHANGE_ME ➡ @errbot] >>> !plugin config example {'EXAMPLE_KEY_1': 'test1', 'EXAMPLE_KEY_2' : [2]}
[@CHANGE_ME ➡ @errbot] [␍] 
Incorrect plugin configuration: Example [<class 'str'>] is not the same type as 2 [<class 'int'>]
# 表面的な型だけでなく、listなら要素の型が違っても許されない

[@CHANGE_ME ➡ @errbot] >>> !plugin config example {'EXAMPLE_KEY_1': 'test1', 'EXAMPLE_KEY_2' : ['test2', ]}
[@CHANGE_ME ➡ @errbot] [␍] 
Plugin configuration done.
# 許された

実態としては、check_configurationメソッドとが呼ばれており、このメソッドが、errbot.utils.ValidationExceptionを返さなければバリデーションOKとなっています。12

もし、設定が必要なプラグインを外部に公開する場合は、get_configuration_templateを設定して、どういう設定が必要なのかを明示してあげると良いでしょう。

設定値はどこに置かれるか?

Errbotにおいて、Botプラグインの永続化データはStorageプラグインを通して、プラグインごとの領域に保存されます。
が、この設定値はプラグインごとの領域ではなくErrbot全体情報を持つcore領域に置かれます。

見てみる

そのままだとShelf形式でちょっと分かりづらいので、昨年のACで作ったYAML形式にするストレージプラグインを使ってみます。

起動時

起動直後のdataフォルダです。core.ymlだけが存在しています3

$ ls -l data/       
total 4
-rw-r--r-- 1 attakei users 12 Jun 10 22:46 core.yml
drwxr-xr-x 2 attakei users  6 Jun 10 22:41 plugins

このあと、!plugin reload exampleなど、一度状態を保持しないといけないアクションが発生すると、
その時初めてストレージが生成されます。今回は、速攻でリロードしたので永続化対象はなく、
空のdictが保存されています。

$ ls -l data/
total 8
-rw-r--r-- 1 attakei users 12 Jun 10 22:46 core.yml
-rw-r--r-- 1 attakei users  3 Jun 10 22:46 example.yml
drwxr-xr-x 2 attakei users  6 Jun 10 22:41 plugins
$ data/example.yml 
{}

設定をセーブする

前段に書いたとおり、ルールに従って設定を保存してみます。

!plugin config example {"EXAMPLE_KEY_1": "test1", "EXAMPLE_KEY_2": ["t1", "t2"]}

$ ls -l data/         
total 8
-rw-r--r-- 1 attakei users 82 Jun 10 22:50 core.yml
-rw-r--r-- 1 attakei users  3 Jun 10 22:50 example.yml
drwxr-xr-x 2 attakei users  6 Jun 10 22:41 plugins

example.ymlは全く増えてないですね。その代わりに、core.ymlのファイルサイズが大きく増加しています。
中身を覗いてみましょう。

$ cat data/core.yml 
configs:
  example:
    EXAMPLE_KEY_1: test1
    EXAMPLE_KEY_2:
    - t1
    - t2

coreの中にconfigsというキーがあり、ここに各プラグインごとに保存されるようです。

「なんで各プラグイン側の領域に管理しないんだ?」と思ったのですが、
よく考えればプラグイン用ストレージはまるっと永続化データの置き場となっていることを考えると、
こっちに置いたほうが良いんだろうなと考え直しました。

リンク


  1. 仮に何もせずにreturnするメソッドにすると、すべてを許されます 

  2. ast.literal_evalを使ってるので、関数とかは一切使えません 

  3. pluginsフォルダは互換性のために自動で作られる場所で、現行では何も入りません