Shotgun API でのフィルタの書き方を考える - Versionsへの問い合わせ

#これはなに？
Shotgun APIを使って、「Versions」エンティティの情報を取得してみたいと思います。

#前提
これまでの流れを踏まえて
http://qiita.com/it_ks/items/e026f61fb8c3683ac035

- Shotgun APIをPython環境に導入済み
- Shotgun上にスクリプトユーザ作成済み

…というところから始めたいと思います。

##Versionsエンティティ
Shotgun的には、レビューを受けたりクライアントへの進捗報告時等に出す成果物（映像・画像。途中も含む）を「バージョン」と呼びます。
（ちなみに作業者が適当な作業段階でつける区切りは「スナップショット」という呼び方で保持します）
前者はクラウドへ上げたりして管理/共有しますが、後者は作業者の範疇というか裁量で好きに扱う感じです。

- チェック対象となるバージョン＝「バージョン」＝クラウドへ
- 作業者内のバージョン＝「スナップショット」＝お手元限定

エンティティというのは、Shotgun上のデータテーブルのことです。
Shot，Asset，Taskなど様々な種類が用意されており、Versionもその一つです。

エンティティを調べるときには`schema_entity_read()`メソッドを使います。

```py
>>> sg.schema_entity_read().keys()
['Candidate', 'MocapTake', 'ActionMenuItem', 'Asset', 'Department', 'RevisionTicketConnection', 'Revision', 'CutVersionC
onnection', 'PublishedFileDependency', 'Step', 'MocapPass', 'Icon', 'Task', 'ApiUser', 'Launch', 'Ticket_sg_test_ticket_
to_task_Connection', 'Project', 'CutItem', 'LocalStorage', 'Release', 'LaunchSceneConnection', 'ReleaseTicketConnection'
, 'LaunchShotConnection', 'CustomEntity01', 'Shot', 'BannerUserConnection', 'AssetAssetConnection', 'EventLogEntry', 'As
setShootDayConnection', 'DeliveryTarget', 'PageSetting', 'Playlist', 'ShootDaySceneConnection', 'Camera', 'TankAction',
'Routine', 'Reply', 'AppWelcomeUserConnection', 'Attachment', 'Tool', 'TicketTicketConnection', 'PhysicalAssetMocapTakeC
onnection', 'Cut', 'Group', 'MocapSetup', 'Note', 'TaskDependency', 'PageHit', 'TemerityNode', 'Status', 'AssetElementCo
nnection', 'Slate', 'MocapTakeRangeShotConnection', 'TimeLog', 'ShootDay', 'AssetBlendshapeConnection', 'MocapTakeRange'
, 'PublishedFile', 'ProjectUserConnection', 'ShotShotConnection', 'PublishedFileType', 'TankType', 'TaskTemplate', 'Play
listShare', 'PerformerShootDayConnection', 'PipelineConfiguration', 'HumanUser', 'FilesystemLocation', 'Performer', 'Ver
sion', 'PhysicalAsset', 'ElementShotConnection', 'AssetSceneConnection', 'AssetMocapTakeConnection', 'PlaylistVersionCon
nection', 'Booking', 'PublishEvent', 'CameraMocapTakeConnection', 'Delivery', 'AssetLibrary', 'Phase', 'Ticket', 'AssetS
hotConnection', 'ClientUser', 'TankContainer', 'RvLicense', 'PerformerRoutineConnection', 'Element', 'PermissionRuleSet'
, 'Blendshape', 'GroupUserConnection', 'AssetSequenceConnection', 'Page', 'TankPublishedFile', 'Sequence', 'TankDependen
cy', 'Scene', 'PerformerMocapTakeConnection', 'RevisionRevisionConnection']

>>> len(sg.schema_entity_read().keys())
99
```
カウントすると「99」と返ってきましたが、この内エンティティ名が「Connection」で終わっているものに関しては、エンティティ同士をリンクするためのものなので、無視してかまいません。
Connection系エンティティや「CustomEntity」の数は使い方で変わってくるため、ここでの99というエンティティ数は固定ではありません。

Versionsエンティティに画像や動画を上げると、Media Appで閲覧できるようになります。


#find
問い合わせに用いる最もベーシックなメソッドは find です。
フィルタ等考えず、とりあえずVersionsを調べたいときは、このように書くことが出来ます

```py
sg.find('Version',[],[])
```

しかしながら、この問い合わせはやめた方がいいと思います。
なにもフィルタを掛けない場合、**サンプルプロジェクトも含めて全て**のVersionが対象になるため、取得するだけでもそこそこの時間がかかってしまうのです。
実際の使用時にはプロジェクトを限定するフィルタは最低でも用いた方がいいでしょう。この記事ではその辺りも含め、どういう風にフィルタリングしていくか考えていきます。

#find_one
フィルタ無しでは大量の結果が返ってきてしまうので、結果が一つだけ返ってくる**find_one**メソッドに一旦切り替えます。
これを使いながら、どんな風にフィルタリングしてくれようか考えてみます。

```py
>>> sg.find_one('Version',[],[])
{'type': 'Version', 'id': 2}
```

findもfind_oneも、3つめの引数が「返り値に含めてほしい**フィールド**」です。
なにも指定しなかったら、上の例文のように、typeとidだけが返ってきます。

#schema_field_read()
そのエンティティにどんなフィールドがあるか確認するときには、schema_field_read()メソッドを使います。
ここでは「Version」エンティティが持っているフィールドを調べたいので、
このような感じ
`sg.schema_field_read('Version').keys()`
※このメソッドも、様々な情報が返ってきちゃいますので、**keys**でとりあえずフィールド名だけ取得します。

```py
>>> sg.schema_field_read('Version').keys()
['sg_version_type', 'open_notes_count', 'code', 'playlists', 'sg_task', 'image', 'updated_at', 'sg_uploaded_movie_frame_
rate', 'sg_path_to_frames', 'tasks', 'sg_department', 'frame_range', 'client_approved', 'id', 'updated_by', 'sg_path_to_
movie', 'sg_uploaded_movie_webm', 'open_notes', 'client_approved_at', 'cached_display_name', 'task_template', 'created_b
y', 'sg_status_list', 'notes', 'sg_frames_aspect_ratio', 'sg_first_frame', 'sg_frames_have_slate', 'sg_uploaded_movie_mp
4', 'description', 'sg_uploaded_movie_image', 'media_center_import_time', 'viewed_by_current_user_at', 'client_code', 's
g_movie_has_slate', 'sg_uploaded_movie', 'user', 'entity', 'published_files', 'step_0', 'sg_last_frame', 'viewed_by_curr
ent_user', 'sg_uploaded_movie_transcoding_status', 'sg_movie_aspect_ratio', 'created_at', 'client_approved_by', 'project
', 'filmstrip_image', 'tag_list', 'frame_count', 'flagged']
```

ちなみに、VersionエンティティはShotエンティティを同じくらい、フィールド多いです。
![field.png](https://qiita-image-store.s3.amazonaws.com/0/102581/ae53130f-fe19-7cbe-92d3-366fa72d3652.png)
（バージョンが「50」、ショットが「63」。shotgun自体のバージョンアップで増減するものと思われます）

#「プロジェクト」でフィルタリングしてみる

「プロジェクトでフィルタリングしてみよう！」と方針を立てたとします。

##調べる
まず、プロジェクトフィールドにはどのようにプロジェクトの情報が書き込まれているかを確認しましょう。
さきほどのfind_oneを使って、このように問い合わせてみます。

`sg.find_one('Version',[],['project'])`

すると

```py
>>> sg.find_one('Version',[],['project'])
{'project': {'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}, 'type': 'Version', 'id': 2}
```

プロジェクトフィールドには、「{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}」という形でプロジェクトのデータが書き込まれていることがわかりました。

※[Big Buck Bunny](https://peach.blender.org/about/)はblenderで制作されクリエイティブ・コモンズとして公開されているプロジェクト。Shotgunにはサンプルプロジェクトとしていろいろ登録されている。

これをfindメソッドの第二引数に渡してあげれば、
**「『○○プロジェクトに属する』バージョン」**という問い合わせ方ができるはずです。

##やってみる 1　（よくある失敗）
そろそろ該当する結果全てがほしいので、find_oneではなく普通のfindに戻します。
さっき得られたプロジェクトの情報をフィルタとして使って、このように書いてみます。

`sg.find('Version',[{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}],[])`

```py
>>> sg.find('Version',[{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}],[])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\～～\site-packages\shotgun_api3\shotgun.py", line 606, in find
    filters = _translate_filters(filters, filter_operator)
  File "C:\Users\～～\site-packages\shotgun_api3\shotgun.py", line 2646, in _translate_filters
    return _translate_filters_dict(wrapped_filters)
  File "C:\Users\～～\site-packages\shotgun_api3\shotgun.py", line 2662, in _translate_filters_dict
    new_filters["conditions"] = _translate_filters_list(sg_filter["filters"])
  File "C:\Users\～～\site-packages\shotgun_api3\shotgun.py", line 2673, in _translate_filters_list
    conditions.append(_translate_filters_dict(sg_filter))
  File "C:\Users\～～\site-packages\shotgun_api3\shotgun.py", line 2657, in _translate_filters_dict
    raise ShotgunError("Invalid filter_operator %s" % filter_operator)
shotgun_api3.shotgun.ShotgunError: Invalid filter_operator None
```

フィルターのところにそのまま突っ込んでも、このようにエラーになります。
フィルタには特有の書き方があるのです。

##フィルタの書き方
フィルタの書き方は、公式ではコチラにまとめられています

>Reference: Filter Syntax
>https://github.com/shotgunsoftware/python-api/wiki/Reference:-Filter-Syntax

フィルタは一度に複数指定可能です。
フィルタひとつ分は、下記のような構成のリストになります

`['{フィールド}', 'リレーション', '値']`

**フィールド**は、さきほどschema_field_readで得られた、仕分けに使いたいフィールドです。ここでは 'project' です。
（schema_field_readじゃなくても、Shotgun(web)上でカラムを右クリックして「Configure field...」で調べてもいいです）

**リレーション**は、「～～に該当したものを返す」「該当しないものを返す」などフィルタの振る舞いを制御します。
'is'、'is_not'、'contains'、'between' ... など、いろいろあります。
ここでは'is'を使います。

**値**は、検索に使う値です。ここはここで、フィールドの **型** ごとにいろいろあります。find_oneで事例を得てから書きます。

この、要素数3のリストが一つのフィルタになり、これを複数組み合わせた**二重リスト**がフィルタの記法になります。

**一重**リストが許されるのはフィルタがない場合のみで、
たとえ「フィルタ一つしか指定しないし」と思っても、二重リストにする必要があります。
（もっと複雑な指定の仕方もあります）

今回のフィルタはこのようになります。

`[['project','is',{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}]]`

##やってみる 2 （返ってくる量にlimitをかける）

```py
sg.find('Version',[['project','is',{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}]],[])
```

…実は、サンプルプロジェクト”Big Buck Bunny”には相当数のバージョンが登録されています。
limit引数で、返り値の数量を指定できます。

```py
sg.find('Version',[['project','is',{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}]],[],limit=10)
```

```py
>>> sg.find('Version',[['project','is',{'type': 'Project', 'id': 65, 'name': 'Big Buck Bunny'}]],[],limit=10)
[{'type': 'Version', 'id': 2}, {'type': 'Version', 'id': 3}, {'type': 'Version', 'id': 4}, {'type': 'Version', 'id': 5},
 {'type': 'Version', 'id': 6}, {'type': 'Version', 'id': 7}, {'type': 'Version', 'id': 8}, {'type': 'Version', 'id': 9},
 {'type': 'Version', 'id': 10}, {'type': 'Version', 'id': 11}]
```

蛇足ですが、projectのフィルタについて。
ここでは、とりあえずfind_oneで得られたものをそのまま値として使用しましたが、
実は、**'name'はなくても**フィルタリングには使えます。

```py
>> sg.find('Version',[['project','is',{'type': 'Project', 'id': 65}]],[],limit=10)
{'type': 'Version', 'id': 2}, {'type': 'Version', 'id': 3}, {'type': 'Version', 'id': 4}, {'type': 'Version', 'id': 5},
{'type': 'Version', 'id': 6}, {'type': 'Version', 'id': 7}, {'type': 'Version', 'id': 8}, {'type': 'Version', 'id': 9},
{'type': 'Version', 'id': 10}, {'type': 'Version', 'id': 11}]
```

##やってみる 3　（Shotにつけられたバージョンのみ得る）
先ほどのフィルタに、以下の様なものを追加します。

`['entity','type_is','Shot']`

これで、Shotgun(web)でいうところの「リンク」カラムがショットに紐付いているバージョン、
＝ショットに対してのバージョンだけを得ることが出来ます。

```py
>>> sg.find('Version',[['project','is',{'type': 'Project', 'id': 65}],['entity','type_is','Shot']],['entity'],limit=10)
[{'type': 'Version', 'id': 2, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id'
: 3, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id': 4, 'entity': {'type': '
Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id': 5, 'entity': {'type': 'Shot', 'id': 860, 'name':
 'bunny_010_0010'}}, {'type': 'Version', 'id': 6, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'ty
pe': 'Version', 'id': 7, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id': 8,
'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id': 9, 'entity': {'type': 'Shot'
, 'id': 860, 'name': 'bunny_010_0010'}}, {'type': 'Version', 'id': 10, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bu
nny_010_0010'}}, {'type': 'Version', 'id': 11, 'entity': {'type': 'Shot', 'id': 860, 'name': 'bunny_010_0010'}}]
```

Assetに対してのバージョンならば、こうです

```py
>>> sg.find('Version',[['project','is',{'type': 'Project', 'id': 65}],['entity','type_is','Asset']],['entity'],limit=10)

[{'type': 'Version', 'id': 6002, 'entity': {'type': 'Asset', 'id': 726, 'name': 'Buck'}}]
```

![linktype.png](https://qiita-image-store.s3.amazonaws.com/0/102581/4494e28f-364b-1c38-9782-681059465125.png)
※バージョンをlink typeでグルーピングしてみたところ。

#まとめ

アセットへのバージョンいっこかよ