動作環境
- Spotfire Server 14.0 LTS
本資料記載の内容はSpotfire Server 14.0 LTS以降に適用できます。
概要
Library REST API v2には以下の機能が提供されています。
- SBDF/DXP/MODファイルをライブラリにアップロード
- ファイルのダウンロード
- ライブラリ管理
- ライブラリ項目のバージョン履歴管理
利用手順
REST APIの利用手順については「Spotfire Server REST API 利用手順」を参照ください。
API一覧
Library REST API v2には以下のAPIが提供されています。
APIの呼び出し例
一部APIを呼び出す場合の例を以下にて説明します。
事前に「Spotfire Server REST API 利用手順」を参考にアクセストークンを取得してください。
ライブラリの一般情報を取得
def get_library_info(server_url, token):
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/info")
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.get(url, headers=headers)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
return resp.json()
info = get_library_info(server_url, token)
print(json.dumps(info, indent=2))
結果の例:
{
"rootItem": "6b67ec30-712e-11dd-7434-00100a64217d",
"itemTypes": [
"spotfire.mod",
"spotfire.dxp",
...省略
],
"uploadInfo": {
"allowedItemTypes": [
"spotfire.sbdf",
"spotfire.dxp",
"spotfire.mod"
],
"maxConcurrentJobsPerClient": 10,
"maxUploadSizeBytes": 2147483648
},
"downloadInfo": {
"allowedItemTypes": [
"spotfire.mod",
"spotfire.dxp",
...省略
]
}
}
ライブラリ項目を検索
対象パス(path)とタイプ(type)に完全一致する項目を検索します。
def search_library(server_url, token, search):
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/items")
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.get(url, headers=headers, params=search)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
return resp.json()
# 対象パス(path)とタイプ(type)に完全一致する項目を検索
search = {"path": "/samples", "type": "spotfire.folder", "attributes": "path"}
res = search_library(server_url, token, search)
print(json.dumps(res, indent=2))
結果の例:
{
"items": [
{
"id": "21e7f7f9-6811-45bb-a78c-4a491907b267",
"title": "samples",
"path": "/samples",
"description": null,
"type": "spotfire.folder",
"createdBy": {
"id": "38110cad-3c3c-40c7-9c83-c39b48e20908",
"name": "admin",
"domainName": "SPOTFIRE",
"displayName": "admin"
},
"created": 1699344928000,
"modifiedBy": null,
"modified": 1712974186438,
"accessed": 1699345051000,
"parentId": "6b67ec30-712e-11dd-7434-00100a64217d",
"size": 0,
"versionId": null,
"isFavorite": false
}
]
}
検索条件(searchExpression)に満たす項目の一覧を取得します。
# 検索条件(searchExpression)に満たす項目の一覧を取得
search = {"searchExpression": "type:dxp", "maxResults": "100", "attributes": "path"}
res = search_library(server_url, token, search)
print(json.dumps(res, indent=2))
結果の例:
{
"items": [
{
"id": "9ce527a1-b030-4b61-bf82-3efe5737c216",
"title": "Advanced Analytics",
"path": "/samples/Advanced Analytics",
"description": "Shows forecasting, clustering and other \r\nmore advanced analytic operations",
...省略
"size": 3328551,
"versionId": "3890487b-d1f6-4542-962e-a6800f7f7e3e",
"isFavorite": false
},
{
"id": "fc7e2f75-bd68-4b20-a5c7-81999291289c",
"title": "Analyzing Stock Performance",
"path": "/samples/Analyzing Stock Performance",
"description": "Interactive application for analyzing \r\nstock performance",
...省略
"size": 4831000,
"versionId": "a0d43b0a-af5a-4265-ab6a-d2e077a5474f",
"isFavorite": false
},
...省略
]
}
フォルダの中身の一覧を取得
フォルダの直下にある項目の一覧を取得します。事前に対象フォルダをIDを取得する必要があります。
import pandas as pd
def list_folder_contents(server_url, token, folder_id):
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/items/{itemId}/children".format(itemId=folder_id))
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.get(url, headers=headers)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
res = resp.json()
# 結果を整形(参考)
for i in res["items"]:
if i["createdBy"]:
i["createdBy"] = i["createdBy"]["name"]
if i["modifiedBy"]:
i["modifiedBy"] = i["modifiedBy"]["name"]
items = pd.json_normalize(res["items"])
items["created"] = pd.to_datetime(items["created"], unit="ms", utc=True).dt.tz_convert("Asia/Tokyo")
items["modified"] = pd.to_datetime(items["modified"], unit="ms", utc=True).dt.tz_convert("Asia/Tokyo")
items["accessed"] = pd.to_datetime(items["accessed"], unit="ms", utc=True).dt.tz_convert("Asia/Tokyo")
return items
folder_id = "21e7f7f9-6811-45bb-a78c-4a491907b267"
contents = list_folder_contents(server_url, token, folder_id)
print(contents)
結果は略。
ファイルをダウンロード
ライブラリ項目をローカルにダウンロードします。事前に対象項目のIDを取得する必要があります。
def download_file(server_url, token, item_id, save_path):
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/items/{itemId}/contents".format(itemId=item_id))
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.get(url, headers=headers)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
with open(save_path, mode="wb") as f:
f.write(resp.content)
save_path = "C:/tmp/Advanced Analytics.dxp"
download_file(server_url, token, "9ce527a1-b030-4b61-bf82-3efe5737c216", save_path)
ファイルをアップロード
ローカルファイルをライブラリへアップロードします。事前に格納先ライブラリフォルダのIDを取得する必要があります。
ファイルをアップロードする際の処理フローは以下です。
1.アップロード作業を開始します。
2.ファイルのデータを送信します。
※パラメータ「"finish": True」を指定して送信後に直ちに完了させる場合は下記の3.は不要です。
3.アップロード作業を完了させます。
注意事項:
・DXP、SBDF、MODファイルのみアップロードできます。
・アップロードが失敗した場合はジョブをキャンセルしてください。
import base64
import hashlib
def upload_file(server_url, token, local_file, library_folder_id, title, type):
with open(local_file, mode="rb") as f:
content = f.read()
size = len(content)
digest = "SHA-256=" + base64.b64encode(hashlib.sha256(content).digest()).decode("ascii")
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/upload")
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
data = {
"item": {"title": title, "type": type, "parentId": library_folder_id},
"numberOfBytes": str(size),
"numberOfChunks": 1,
"overwriteIfExists": "true",
}
resp = requests.post(url, headers=headers, json=data)
assert resp.status_code == 201, "{} {}".format(resp.status_code, resp.text)
jobid = resp.json()["jobId"]
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/upload/{jobId}".format(jobId=jobid))
headers = {
"Authorization": "Bearer " + token,
"Content-type": "application/raw",
"Content-Length": str(size),
"Digest": digest,
}
params = {"chunk": 1, "finish": True}
resp = requests.post(url, params=params, headers=headers, data=content)
# assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
if resp.status_code != 200:
print("{} {}".format(resp.status_code, resp.text))
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/upload/{jobId}".format(jobId=jobid))
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.delete(url, headers=headers)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
file = "C:/tmp/myfile.dxp"
upload_file(server_url, token, file, library_folder_id, "myfile", "spotfire.dxp")
ファイルアップロードの同時実行数やファイルサイズなどが以下の設定項目(参考資料)によって制限されています。設定値は「Spotfire Server設定変更手順」を参考に変更できます。
設定項目 | デフォルト値 | 説明 |
---|---|---|
public-api.library.upload.limit.max-concurrent-jobs | 1000 個 | アップロード作業の同時実行数の上限 |
public-api.library.upload.limit.max-concurrent-jobs-per-client | 10 個 | クライアントごとにアップロード作業の同時実行数の上限 |
public-api.library.upload.limit.max-upload-size |
2147483648 バイト(2GB) |
アップロードできるファイルのサイズの上限 |
public-api.library.upload.job.idle-exp-minutes |
30 分 |
アップロード作業がアイドル状態にいられる時間の上限 アイドル時間がこの上限を超えた場合は作業が中止されます。 |
public-api.library.upload.job.absolute-exp-minutes |
1440 分(24時間) |
アップロード作業の実行時間の上限 作業開始からこの時間が過ぎると作業が中止されます。 |
public-api.library.upload.job.cleanup-interval-seconds |
300 秒(5 分) |
アップロード作業をクリーンアップする処理の実行間隔 |
Spotfire Server 14.0.3までの既知問題:
1.Analyst 14.0 LTS以前のバージョンで作成・保存したDXPはアップロードできません。アップロード時には以下のエラー(エラーコード:400)が返されます。
{"error":{"code":"invalid_request","description":"Analysis metadata JSON missing as this DXP is saved by an old version of Spotfire"}}
2.Analyst 14.0 LTS以前のバージョンで作成してAnalyst 14.0 LTSで保存し直したDXPをアップロードする際には以下のエラー(エラーコード:415)が発生することがあります。
{"error":{"code":"unsupported_mediatype","description":"The media type of the request payload is unsupported"}}
Spotfire Server 14.0.4(リリースノート)では上記の問題が改善され、以下の仕様になります。
・Analyst 12.4以降で作成・保存したDXPはLibrary REST APIを利用してライブラリへアップロードできます。
・Analyst 12.4より前のバージョンで作成したDXPは一旦Analyst 12.4以降で保存しなおしてからアップロードする必要があります。
ライブラリ項目の情報を更新
ライブラリにあるDXPファイルの説明文を更新する例です。
from datetime import datetime
def update_item_metadata(server_url, token, search, update):
res = search_library(server_url, token, search)
id = res["items"][0]["id"]
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/items/{itemId}".format(itemId=id))
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.patch(url, headers=headers, json=update)
assert resp.status_code == 200, "{} {}".format(resp.status_code, resp.text)
return resp.json()
search = {"path": "/samples/Sales and Marketing", "type": "spotfire.dxp"}
update = {"description": "updated by Library REST API at {}".format(datetime.now())}
update_item_metadata(server_url, token, search, update)
ライブラリ項目を作成
ライブラリ内にフォルダを新規作成します。すでに存在する場合は「409」エラーで失敗します。
格納先フォルダのIDを事前に取得する必要があります。
def create_item(server_url, token, create):
url = urllib.parse.urljoin(server_url, "/spotfire/api/rest/library/v2/items/")
headers = {"Authorization": "Bearer " + token, "Content-type": "application/json"}
resp = requests.post(url, headers=headers, json=create)
assert resp.status_code == 201, "{} {}".format(resp.status_code, resp.text)
return resp.json()
create = {"title": "folder1", "type": "spotfire.folder", "parentId": "21e7f7f9-6811-45bb-a78c-4a491907b267"}
created = create_item(server_url, token, create)
print(json.dumps(created, indent=2))
参考資料