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の設定
![ModulesPaths.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668569/08b223f1-5643-d824-6c3a-d9d60ca26b81.png)
スクリプトを配置しているパスを設定します。
[/home/redash/scripts]を記入します。

### 5-2.Modules to import prior to running the scriptの設定
![ImportScript.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1668569/bc9786ee-8bdb-264a-d571-7824a4b4f30d.png)
インポートするファイル名を設定します。
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

```python
        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

```python
        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内で実行する。
```python
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

```python
    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

```python
    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