herokuからShotgunへ定期的になにかする

#これはなに？
こちらの記事の続きです
>【Python＋heroku】Python入れてない状態からherokuで何か表示するまで
（前編）http://qiita.com/it_ks/items/afd1bdb792d41d0e1145
（後編）http://qiita.com/it_ks/items/ca6c7f6e8fc89e49e46d

このところherokuをさわっていたのは **Shotgun API** でshotgunへの的処理を行うためでした。

##補足1：使用経緯
[株式会社GUNCY'S](http://www.guncys.com/)さんの [Road to Stingray](http://area.autodesk.jp/column/tutorial/road_to_stingray/) というプロジェクトに
Shotgunセットアップ要員として参加しました。
ここではShotugnについて得られた知識等を（今後も進行系で）書いていけたらいいんじゃないかなと思っています。
あまり深いことはできていないので、範囲としては公式等で公開済みのもの（の日本語版）程度にとどまる気もしますが……

比較的小規模～中規模程度の案件かつ多拠点（遠隔作業）での事例ということになるかと思います。
（Shotgunは百人〜千人とかのプロダクションでも使われていますが、そうしたケースとはまた違う話になってくるのかなと）


##補足2：タグ
qiitaでの **「Shotgun」タグ** は
同名のrubyのライブラリに関する話題ですでに使われていました。
ので、こちらは「ShotgunSoftrware」というタグにして棲み分けた方が、各方面しあわせなのかなと思います。
もし当記事を読まれた方でShotgunに関する記事を投稿されようという方は、
意図に賛同いただけましたら同様のタグ付けをしていただけると幸いです。

#Shotgun
![sg_01.png](https://qiita-image-store.s3.amazonaws.com/0/102581/e465391e-98f2-5178-120d-11534c8584a0.png)
>https://shotgunsoftware.com/
（国内代理店）https://www.borndigital.co.jp/software/4070.html

さまざまな状況に対応できるようになっている反面、
セットアップなしの素のままではかなりジャジャ馬な一面もある逸品です（※個人の感想です）

##API
githubで公開されています。
>https://github.com/shotgunsoftware/python-api

**wiki**
>https://github.com/shotgunsoftware/python-api/wiki

##Pipeline Toolkit
上記APIをラップしつつ多様な外部ツールとの連携、Shotgun(web)の拡張を実現するために用意されたキット群の総称です。
https://support.shotgunsoftware.com/entries/94042238-Toolkit-Home-Page
今回は扱いません。

API同様githubで公開されていて、主に頭に「**tk-**」がついているものがそれです。tkはツールキットの略ですね。
https://github.com/shotgunsoftware

こちらの記事(↓)で取り上げたftrackでいうところの
http://qiita.com/it_ks/items/374a320bc8282c7f65a8
「ftrack connect」に相当するものと言えます。


#herokuスケジュール処理
公式ドキュメント：
>Scheduled Jobs with Custom Clock Processes in Python with APScheduler
https://devcenter.heroku.com/articles/clock-processes-python

ちょうどqiitaにherokuとPythonでの記事が
>Herokuで定期的に為替レートを取得し、ログをAmazon S3にアップロード
http://qiita.com/gumob@github/items/ca5d76186f94e592a5f0

定期処理自体は「**APScheduler**」モジュールを使います。
APSchedulerで検索すると、上位に出てくるのはコチラ。
>APSchedulerで少し進んだジョブスケジューリング
http://qiita.com/yushin/items/a026626dbb291dd43dd8


***

#定期処理の準備
まずは、herokuでの定期処理について設定していきます。
こちらに準じる形で進めます。
>https://devcenter.heroku.com/articles/clock-processes-python

##APScheduler導入
appのところまで降りて行ってvertual env をアクティベートしてから（←これは前回までの内容）、

`pip install apscheduler`

```py:
(sgenv) >pip install apscheduler
Collecting apscheduler
  Downloading APScheduler-3.0.5-py2.py3-none-any.whl (49kB)
    100% |################################| 53kB 2.4MB/s
Requirement already satisfied (use --upgrade to upgrade): six in c:\users\path\to\app\sgenv\lib\site-packages (from apscheduler)
Collecting futures (from apscheduler)
  Downloading futures-3.0.4-py2-none-any.whl
Collecting pytz (from apscheduler)
  Downloading pytz-2015.7-py2.py3-none-any.whl (476kB)
    100% |################################| 479kB 853kB/s
Collecting tzlocal (from apscheduler)
  Downloading tzlocal-1.2.tar.gz
Building wheels for collected packages: tzlocal
  Running setup.py bdist_wheel for tzlocal ... done
  Stored in directory: C:\Users\{user}\AppData\Local\pip\Cache\wheels\39\8d\3b\21db6b23bc7483a2e1cf391865427e8fbd09022
3d9db2cfab3
Successfully built tzlocal
Installing collected packages: futures, pytz, tzlocal, apscheduler
Successfully installed apscheduler-3.0.5 futures-3.0.4 pytz-2015.7 tzlocal-1.2
```

また、
requirements.txtに
`APScheduler==3.0.0`
と追記します。


##clock.py
定期処理したいPythonスクリプトを用意します。
先ほどのドキュメントに準じて「clock.py」という名前にしています。

```py:clock.py
from apscheduler.schedulers.blocking import BlockingScheduler

sched = BlockingScheduler()

@sched.scheduled_job('interval', minutes=3)
def timed_job():
    print('This job is run every three minutes.')

@sched.scheduled_job('cron', day_of_week='mon-fri', hour=17)
def scheduled_job():
    print('This job is run every weekday at 5pm.')

sched.start()
```

スクリプトの内容も、まずはドキュメントそのままで。

- 3分に一回ログを出力
- 毎日17時にログを出力

という二つのジョブがあります。

##Procfileへ追記
**Procfile**に下記の内容を追記します。

`clock: python clock.py`

`clock:`が設定名で、`python〜〜`が実行して欲しいコマンドです。
Procfileに記述しただけweb上のherokuダッシュボードに現れ、それぞれON/OFFできます。
無料のherokuでは一つしか稼動状態にできませんので、ひとつOFFにしたらひとつONにできるという感じです。
（ON/OFFするあたりは後述）

##デプロイ
`git add .`して`git commit -m "hogehoge"`して`git push heroku master`します。
addとcommitでは、さきほど追加したAPSchedulerの分いろいろずらずら流れます。

##スケール
ドキュメントだと
`heroku ps:scale clock=1`
と書いてありますが、これで、さきほどProcfileに追記した「clock」を「1（＝有効。ON）」にできます。
ただしすでに「web」というプロセスが動いているので、この状態で実行しても無効です。

```bat:
>heroku ps:scale clock=1
Scaling dynos... failed
 !    Please verify your account in order to change resources (please enter a credit card) For more information, see htt
ps://devcenter.heroku.com/categories/billing Verify now at https://heroku.com/verify
```

ブラウザでherokuにログインして、すでにONになってるのをOFFにしてから、「clock」をONにします。
<img width="754" alt="freedynos.png" src="https://qiita-image-store.s3.amazonaws.com/0/102581/66ea4b23-3077-65e6-ee05-32b9cf3e4118.png">
ブラウザじゃなくてコマンドからやってもいいんですけど。ダッシュボード使いたかったんです。

##確認
これで「clock」という処理が動き出してclock.pyの中身が実行されるようになりました。
ログを見てみたいと思います。herokuのログを見るときは↓

`heroku logs`


```bat:
>heroku logs
2016-02-02T13:36:08.456761+00:00 app[web.1]:     res = instance.__dict__[self.name] = self.func(instance)
2016-02-02T13:36:08.456761+00:00 app[web.1]:   File "/app/.heroku/python/lib/python2.7/site-packages/django/utils/functional.py", line 33, in __get__
2016-02-02T13:36:08.456762+00:00 app[web.1]:   File "/app/.heroku/python/lib/python2.7/site-packages/django/core/urlresolvers.py", line 417, in url_patterns
2016-02-02T13:36:08.456763+00:00 app[web.1]:     patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
2016-02-02T13:36:08.456765+00:00 app[web.1]:   File "/app/.heroku/python/lib/python2.7/site-packages/django/core/urlresolvers.py", line 410, in urlconf_module
（中略）
2016-02-08T12:23:23.304081+00:00 heroku[api]: Scale to web=0 by 〜〜@gmail.com
2016-02-08T12:23:24.758985+00:00 heroku[api]: Scale to clock=1 by 〜〜@gmail.com
2016-02-08T12:23:29.007235+00:00 heroku[clock.1]: Starting process with command `python clock.py`
2016-02-08T12:23:29.571304+00:00 heroku[clock.1]: State changed from starting to up
2016-02-08T12:26:30.559161+00:00 app[clock.1]: This job is run every three minutes.
```

無事動き出しました

#Shotgun APIを使う
無事動き出したら、clock.pyの中にShotgun APIを使った内容を書き足していきます。

##APIの準備
githubのInstallingに従ってpipします。
https://github.com/shotgunsoftware/python-api#installing

`pip install git+git://github.com/shotgunsoftware/python-api.git`

バージョンを固定したい場合はこちらとのことなので、
`pip install git+git://github.com/shotgunsoftware/python-api.git@v3.0.9`
requirements.txtにはこちらを書きました。ちなみに当時v3.0.25

##Shotgunにてスクリプトユーザを登録
Shotgunでの「ユーザ」は「**HumanUser**」と「**ScriptUser**」に分けられます。
前者がいわゆるユーザ、後者はPipeline Toolkitなどのスクリプトです。
shotgunから見れば、人でもスクリプトでもアクセスしてくる奴はユーザーということですね。
herokuからアクセスするためのScriptUserを登録しましょう。

Shotgunにログインしたら、右上のメニューから「Scripts」を開き、「+スクリプト」。
![sg_scriptuser.png](https://qiita-image-store.s3.amazonaws.com/0/102581/2e5de32f-a6ff-becf-af03-4fccbec2014d.png)

ここで登録する「**スクリプト名**」と、
登録後に得られる「**アプリケーションキー**」、
そしてライセンスされている**ShotgunサーバのURL**（「https://{studio-name}.shotgunstudio.com」）
が次に必要になるので控えておきます。

あと、気分が盛り上がるのでScriptUserに適当なアイコンをつけてあげましょう。

##herokuに環境変数登録
コマンドからでも(ryですがダッシュボードから。
一番右の「Settings」の「Config Variables」で環境変数を設定できます。

<img width="1204" alt="config_var_01.png" src="https://qiita-image-store.s3.amazonaws.com/0/102581/052d9931-8017-7d52-f6b0-8125b885433a.png">
「Reveal Config Vars」を押して、隠れてるのを表示します。
すると↓こんなふうになります。
![config_var_02.png](https://qiita-image-store.s3.amazonaws.com/0/102581/9279b953-428a-8da9-ba14-19b5d2019edc.png)
さきほど控えておいた「スクリプト名」「アプリキー」「URL」をそれぞれ登録しました。

##環境変数を読む
先ほど記入した環境変数は、Pythonスクリプト中では
`os.environ.get('{変数名}')`
のように書くことで利用できます。
clock.pyへはこういう感じで追記しました↓

```py:
import os

SG_KEY = os.environ.get('SG_KEY')
SG_SCRIPT = os.environ.get('SG_SCRIPT')
SG_SERVER = os.environ.get('SG_SERVER')
```

慣れてくると逆に import os を書き忘れたりするから注意です　←

##shotgunオブジェクト取得
>Shotgun() - Usage Example @Reference: Methods
https://github.com/shotgunsoftware/python-api/wiki/Reference%3A-Methods#usage-example

↑こちらの要領で、インポートしてインスタンスを受け取ります

```py:
import os
from shotgun_api3 import Shotgun

### get sg object ###
SG_KEY = os.environ.get('SG_KEY')
SG_SCRIPT = os.environ.get('SG_SCRIPT')
SG_SERVER = os.environ.get('SG_SERVER')

sg = Shotgun(SG_SERVER, SG_SCRIPT, SG_KEY)
```

##Shotgunを検索して、結果を3分おきにプリント
今回は、**「TEST」という名前のプロジェクトを検索**して、それをログに出力することにします。

今回は返ってくる結果はひとつしかいらないので、
使うメソッドは find_one です。

`sg.find_one("Project",[['name','is','TEST']],[])`

ヒットしたら↓このようにShotgun上でのデータの種類（Entity Type）とIDとの**辞書型**で返ってきます
`{'type': 'Project', 'id': 69}`
※今回「TEST」のIDは69のようでした。

これを組み込んで出来上がったスクリプトがこんな感じ↓

```py:clock.py
import os
from shotgun_api3 import Shotgun
from apscheduler.schedulers.blocking import BlockingScheduler

### get sg object ###
SG_SERVER = os.environ.get('SG_SERVER')
SG_SCRIPT = os.environ.get('SG_SCRIPT')
SG_KEY = os.environ.get('SG_KEY')
sg = Shotgun(SG_SERVER, SG_SCRIPT, SG_KEY)

sched = BlockingScheduler()

@sched.scheduled_job('interval', minutes=3)
def timed_job():
    testPrj = sg.find_one("Project",[['name','is','TEST']],[])
    print('every 3min search...',testPrj)

sched.start()
```

##確認
pushしてheroku logsします。

```bat:
2016-02-08T12:42:19.165168+00:00 heroku[clock.1]: State changed from crashed to starting
2016-02-08T12:42:23.381828+00:00 heroku[clock.1]: Starting process with command `python clock.py`
2016-02-08T12:42:24.077203+00:00 heroku[clock.1]: State changed from starting to up
2016-02-08T12:45:27.542367+00:00 app[clock.1]: ('every 3min search...', {'type': 'Project', 'id': 69})
2016-02-08T12:48:27.586542+00:00 app[clock.1]: ('every 3min search...', {'type': 'Project', 'id': 69})
2016-02-08T12:51:27.559255+00:00 app[clock.1]: ('every 3min search...', {'type': 'Project', 'id': 69})
（後略）
```

無事、検索してヒットした返り値が3分おきにプリントされてます。
定期的になにかするという目標を達しました。

***

#まとめ
##やったこと

- Procfileにプロセス追加、ダッシュボード上で有効化
- ShotgunAPIを使ったスクリプトを用意

ただし、heroku無料版は1日のうち6時間以上は必ずスリープする必要があるので、ずっと回すことはできません。

##今後の展開

- Shotgun APIの、find以外の処理（create、updateなど）も使う
- 定期処理じゃないShotgun API利用
- Shotgun APIと、また別のサービスのAPIとの連携

等が考えられます。

### 20200207追記

そういえばweb hookが付くみたいですね。楽しみです。

https://developer.shotgunsoftware.com/ja/3d448f5e/

##備考

find_one
https://github.com/shotgunsoftware/python-api/wiki/Reference%3A-Methods#find_one

検索に使うフィルタの書き方
Reference: Filter Syntax
https://github.com/shotgunsoftware/python-api/wiki/Reference%3A-Filter-Syntax