RedashのPythonで自作ロジックを動作させる方法


1.はじめに

・複数のDataSourceからexecute_queryを利用してデータを取得してDataFrameに加工。
・add_result_rowとadd_result_columnを利用してRedash用のテーブル化。
上記2点を行うのが面倒だった為、ラッパークラスの作成する事にした。

2.環境把握

Redashは、docker上で動かしているので、
スクリプトはdocker内部に設置する必要がある。

3.ディレクトリ構成を決める

Redash側にスクリプトのパスを設定するので、予め決めておく必要があります。
今回は以下のディレクトリ構成で設定します。

home
  └ redash
      └ scripts

※scriptsディレクトリの中にスプリプトを配置します。

4.ロジックの作成

ラッパークラスを作成します。
最初は簡単なクラスで大丈夫ですが、Redash側に設定するのでファイル名だけは確定させておいてください。

wrapper.py
class Test():
    def __init__(self):
        pass
    ...

5.Redashの設定

ここからRedash側の設定を変更していきます。

5-1.AdditionalModulesPathsの設定


スクリプトを配置しているパスを設定します。
[/home/redash/scripts]を記入します。

5-2.Modules to import prior to running the scriptの設定


インポートするファイル名を設定します。
wrapperを入力します。
※複数ある場合はカンマ区切りで入力。(カンマの後はスペースを空けない事)

5-3.設定を保存

Saveを押して保存します。
設定は保存されていますが、まだ動作しないので注意してください。

6.Dockerを再起動する。

コンテナ自体を一旦破棄して、再度作り直します。

docker-compose down
docker-compose up -d

※コンテナ自体を作る直す必要はないかもしれないですが、一応作り直しています。

6-1 Redash側の動作

ここは飛ばしてもらっても設定に影響はないです。

https://github.com/getredash/redash/blob/master/redash/query_runner/python.py#L103-L105

        if self.configuration.get("allowedImportModules", None):
            for item in self.configuration["allowedImportModules"].split(","):
                self._allowed_modules[item] = None

Redashの起動時に _allowed_modules配列にwrapperキーの値がNoneとして追加されます。

https://github.com/getredash/redash/blob/master/redash/query_runner/python.py#L107-L110

        if self.configuration.get("additionalModulesPaths", None):
            for p in self.configuration["additionalModulesPaths"].split(","):
                if p not in sys.path:
                    sys.path.append(p)

Redashの起動時に 環境変数にスクリプトのパス「/home/redash/scripts」が登録されます。

7.スクリプトファイルをDocker内に転送する。

まだ実体がDocker内には存在していないので、今の状態でスクリプトを実行するとファイルがないエラーが発生します。
なので、実体をDocker内へ転送します。

docker cp ./scripts/. `docker-compose ps -q server`:/home/redash/scripts/
docker cp ./scripts/. `docker-compose ps -q worker`:/home/redash/scripts/

スクリプトファイルのオーナーがrootであっても
スクリプトは実行できるのを確認しているので、この手順では権限変更を行いません。

8.実際にRedash内で実行する。

from wrapper import test

hoge = test()
hoge.fuga()

8-1.Redash側の動作

https://github.com/getredash/redash/blob/master/redash/query_runner/python.py#L248-L251

    def run_query(self, query, user):
        self._current_user = user

        try:
        ....

実行した際に、run_query関数が走ります。
RestrictedPythonで設定して動かしますが、この記事では触れませんのでご了承ください。

https://github.com/getredash/redash/blob/master/redash/query_runner/python.py#L117-L126

    def custom_import(self, name, globals=None, locals=None, fromlist=(), level=0):
        if name in self._allowed_modules:
            m = None
            if self._allowed_modules[name] is None:
                m = importlib.import_module(name)
                self._allowed_modules[name] = m
            else:
                m = self._allowed_modules[name]

            return m

Redashのロジック内でimportしたモジュールは、custom_import関数内で、importlibを使用してインポートされます。

10.参考サイト

https://qiita.com/tsuboyataiki/items/90dbe94553d3dea39b19
https://qiita.com/reflet/items/ad734edc87c73cbaa459
https://docs.docker.jp/engine/reference/commandline/cp.html