Tabpyを使用したTableau Prepフローをタスクスケジューラで自動化してみる

# はじめに
ローカルで作成したTableau Prepを毎回手作業で実行されている方がいるかと思います。
Tableau Prepフロー（以下、Prepフロー）の自動化は公式によると以下２つの方法があります。
1. Tableau Prep Conductor
    ローカルで作成したPrepフローをTableau Serverにパブリッシュし、それをTableau Server側で実行
    [(公式)Tableau Prep Conductor](https://help.tableau.com/current/server/ja-jp/prep_publishserver_overview.htm)
1. Prepフローを実行するバッチを作成し、タスクスケジューラに登録
   作成したPrepフローがコマンドラインで実行が可能なことを利用する方法
    [(公式)コマンド ラインからフロー出力ファイルを更新](https://help.tableau.com/current/prep/ja-jp/prep_run_commandline.htm)

今回はちょっとしたPrepフローを気軽に自動化したい人向けなので、Tableau Prep Conductorの話はしません。
また、コマンドラインでの実行も説明されてる方が多くいらっしゃるので、Tabpyを使用した場合の例でご紹介したいと思います。

# 環境
|ソフトウェア|バージョン|
|:--|:--|
|OS|Windoes10 Enterprise 64 ﾋﾞｯﾄ|
|Tableau Prep Builder|2021.3.3|
|Anaconda3|22.9.0|
|Python|3.9.13|

# （サンプル）Tableau Prepフロー
- **sample.tfl**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919346/7b195b41-24f9-a558-54bd-86ce8bf6621f.png)
##### 1. Input_dummy
ダミーのファイルを読み込む（Python内の処理で使用しない）
※今回の話ではインプットデータをそのままPythonで使用してもよいのですが、
　インプット無しでどこかのAPIからデータを取得し、それを出力する場合にはテキトーなファイルをインプットにします。

 ```text:dummy.txt
 header
 dummy
 ```

##### 2. Tabpyを実行
今回は以下のサンプルコードを実行します。
詳細は公式（[フローでの Python スクリプトの使用](https://help.tableau.com/current/prep/ja-jp/prep_scripts_TabPy.htm)）をご確認ください。

```python:get_test.py
# -*- coding: utf-8 -*-
import pandas as pd

# テスト用データフレームを生成
# インプットデータはダミーなので使用しない
def get_test(df: pd.DataFrame) -> pd.DataFrame:
    # ↓↓ インプットのdfは触らずにただ欲しいデータをAPIで取得すればよい
    # df_sample = get_data_from_api() # 今回は関数の実装はなし 
    df_sample = pd.DataFrame([['apple', 5], ['lemon', 2], ['peach', 3]], columns=['fruits', 'quantity'])
    df_sample = df_sample.append({'fruits': 'fig', 'quantity': 1}, ignore_index=True)
    return df_sample

# 【必須】get_output_schemaを定義：Outputのテーブル定義
def get_output_schema():
    return pd.DataFrame({
        'fruits': prep_string(),
        'quantity': prep_int()
    })

# Tabpyは関数定義だけでよい
# Tableau Prep Builderで実行する関数を設定する
```


Tableau Prep Builder側の設定はこんな感じになります。
<img width="600" alt="tabpy" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2919346/a627ee95-2c8c-5a52-9a6e-4ef506968a34.png">


##### 3. クリーニング
この辺りの処理は任意で設定してください。
他のデータと結合したり、計算フィールドを追加したり、etc

##### 4. 出力
任意の場所に出力してください。
今回はTableau Server（[各自環境] http\://my.server/）に出力します。

# 認証資格情報.jsonの作成
公式（[認証資格情報 .json ファイルの要件](https://help.tableau.com/current/prep/ja-jp/prep_run_commandline.htm#%E8%AA%8D%E8%A8%BC%E8%B3%87%E6%A0%BC%E6%83%85%E5%A0%B1-json-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E8%A6%81%E4%BB%B6)）がよくまとまっていますが、悩みポイントだけ記載します。
今回の例では「①DB接続なし、②Tableau Server接続あり、③Tabpy使用」となりますので、jsonファイルは下記のような感じになります。
※Talbeau Serverの接続先および認証情報は各自の情報に置き換えてください。
```json:認証資格情報.json
{
 "extensions": [
   {
	"extensionName": "pythonSupport",
	"regular": {
	  "host": "localhost",
	  "port": "9004"
	},
	"sensitive": {
	}
   }
 ],
 "tableauServerConnections":[
	{
	 "serverUrl":"http://my.server/",
	 "contentUrl": "",
	 "port":443,
	 "username": "abcd-user",
	 "password": "abcd-password"
	}
  ]
}
```

私の場合、悩みポイントは "contentUrl" でした。
公式を参照するとこう書いてあります。
>contentUrl (サイト ID。これは Tableau Server または Tableau Cloud の URL の /site/ の後ろに表示されます。たとえば、"https\://my.server/#/site/mysite" に接続するには、"contentUrl": "mysite" を設定します。))

ただ私の環境だと、"mysite" 部分がないんです。「http\://my.server/#/」のような感じです。
公式をよくよく読むと、ちゃんと書いてありました。
>注意: ContentUrl.は、.json ファイルのサーバー接続で必ず必要になります。既定のサイト (たとえば "https\://my.server/#/site/") に接続する場合は、ContentUrl を空白に設定します。例: "contentUrl": ""

このファイルを管理し易いフォルダに格納します。

# タスクスケジューラに登録するバッチファイルの作成
※Windowsのコマンドプロンプトでの実装を例にします。
##### 1. Prepフローの実行
こちらも公式（[フローの実行](https://help.tableau.com/current/prep/ja-jp/prep_run_commandline.htm#%E3%83%95%E3%83%AD%E3%83%BC%E3%81%AE%E5%AE%9F%E8%A1%8C)）がよくまとまっています。

ざっくり書きますと、Tableau Prep Builderインストール時に展開される以下のバッチファイル（インストール環境により異なります）にパラメータを付けて実行するコマンドになります。
ディレクトリ例）C:\Program Files\Tableau\Tableau Prep Builder 2021.3\scripts\\**tableau-prep-cli.bat**

上記ディレクトリ例だとすると下記コマンドで作成したsample.tflが実行できます。
※__[絶対パス]__ の部分は各自保存したディレクトリに置き換えてください。
```
cd "C:\Program Files\Tableau\Tableau Prep Builder 2021.3\scripts"
.\tableau-prep-cli.bat -c "[絶対パス]\認証資格情報.json" -t "[絶対パス]\sample.tfl"
```

##### 2. Tabpy Serverの起動
※前提としてTabpyをインストールする必要がありますが、ここでは言及しません。
　解説している方がいらっしゃいますのでこちらをご参照ください。
　[Tabpy 1.0 （2020-01版）インストール方法](https://qiita.com/bashiiiwa/items/3c3246d37de93dd3a34e)

コマンドは下記のような感じになります。
※ **"C:\Users\user\Anaconda3\Scripts"** の部分はインストール環境により異なります。
```
cd C:\Users\user\Anaconda3\Scripts
call activate.bat
call tabpy
```

##### 3. 1.コマンドと2.コマンドをまとめる
Tabpyを起動したウィンドウはそのまま残るため、①「Tabpyを起動する」ファイルと②「prepフローを起動する」ファイルに分けるのがポイントです。
雑ですが下記コマンドとなります。②から①を呼ぶ感じになります。

①「Tabpyを起動する」バッチファイル
```bat:Tabpy起動.bat
@echo off
rem Tabpyのプロセスを確認し、無かったらTabpyを起動
rem プロセスチェック
:CHECK
tasklist | find "tabpy.exe" > NUL
if %ERRORLEVEL% == 0 (
  echo Tabpyの起動が確認されました
  goto TABLEAU
) else (
  echo Tabpyを起動します
  cd C:\Users\user\Anaconda3\Scripts
  call activate.bat
  call tabpy
  rem ここで起動すればGOTOにはいかない
  goto CHECK
)
:TABLEAU
```

②「prepフローを起動する」バッチファイル
```bat:Prepフロー実行.bat
@echo off
rem Tabpyのプロセス確認と起動
START "" "[絶対パス]\Tabpy起動.bat"

rem Tableau Prepを実行
echo Tableau Prepを実行します
cd "C:\Program Files\Tableau\Tableau Prep Builder 2021.3\scripts"
.\tableau-prep-cli.bat -c "[絶対パス]\認証資格情報.json" -t "[絶対パス]\sample.tfl"

rem 終わったらTabpyをKILL
taskkill /IM "tabpy.exe"
```

# タスクスケジューラに登録
ポイントは以下２点です。他条件は任意で設定ください。
1. 登録するファイルは「Tabpy起動.bat」
1. 「管理者ユーザー」として上記バッチファイル実行

# おわりに
Tabpyの使用に関わらず、"Prepフロー" を実行するコマンドだけでもバッチファイル化しておくと、わざわざTableau Prep Builderを起動せずにすむので楽かと思います。
あと、コードは適宜置き換えてくださいませ。