FitbitはAPIがしっかり整備されていて、OAuth2 endpoint経由でデータが取り放題。
せっかくなので、話題のExternal Network Access(2023年12月現在 PuPr)を試してみようと思う。
つまり、FitbitAPI→Snowflakeをやってみようと思う。
Fitbit APIを使用するにはOAuth2.0 Authorizationを通す必要がある。
Snowflakeの公式にOAuth2.0 Endpoint経由でGoogle翻訳APIと連携する段取りが書かれていて、
それをそのままFitbit APIのものに差し替えるだけで動いた。
外部ネットワークアクセスについては以下。
特定の外部ネットワークロケーションへのアクセスを有効にするには、外部ロケーションのリストと使用を許可されるシークレットのリストを指定する外部アクセス統合を作成します。UDF の作成時、あるいは CREATEFUNCTION または CREATEPROCEDURE でプロシージャを作成する際に、 EXTERNAL_ACCESS_INTEGRATIONS 句を使用してこの統合を参照することで、ハンドラーコードが外部ロケーションとの認証コードにシークレットを使用できるようになります。
2016年6月に書いた記事。phpで検証をしていた。
この辺りからバッテリーがダメになる度に新しいFitbit Charge(1,2,3)を買って溜めてきた。
この間、FitbitがGoogleに買われてしまったり、スマホアプリが大幅に変わったり、色々あった。
基本的な機能はずっと動いているので、7年分のデータが溜まっているんじゃないかな、と期待。
Fitbit APIからデータを取得するためには OAuth2 をパスする必要がある。FitbitAPI は以下の2つのflowをサポートしている。 Authenticate grant flow implicit grant flow ドキュメントでは、サーバサイドコードからFitbitAPIにアクセスする場合、Authenticate grant flow が推奨されている。 このエントリでは、djchen/oauth2 というoauth2クライアントを利用して、Authenticate grant flow をパスするサ
Fitbit API側の準備
OAuth2連携に必要な情報を dev.fitbit.com から取得する必要がある。
Authorization Code Grant Flow with PKCE
こちらを参考にさせていただいた。
Fitbit Sense2を購入しました。はじめてのスマートウォッチです。Fitbitデバイスでは、心拍数や歩数等、収集したデータをWeb APIで取得することが可能です。さっそく使って遊んでみようと思ったら、Web APIの認証がなかなか通らない、、、ドキュメントはとても充実しているのですが、OAuth2.0の認証パターンがImplicit Grant Flowの場合、Authorization Code Grant Flowの場合、PKCEを使う場合、、、などなど、情報量がとにかく多く混乱してしまいました。何はともあれ、何とか認証を通して、こんな感じで歩数などのアクティビティ情報や、心拍数や血中酸素濃度(SpO2)を取得することが出来ました。
以下を準備すればOK。
- access-token
- refresh-token
- client-id
Snowflakeでリソース作り
Snowsightでポチポチとリソースを作っていく。
USE ROLE SYSADMIN;
-- 外部ロケーションを表すネットワークルールの作成
--
CREATE OR REPLACE NETWORK RULE fitbit_apis_network_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = ('api.fitbit.com');
-- 外部ロケーションとの認証に必要なOAuth認証情報を保持するセキュリティ統合の作成
--
CREATE OR REPLACE SECURITY INTEGRATION fitbit_api_oauth
TYPE = API_AUTHENTICATION
AUTH_TYPE = OAUTH2
OAUTH_CLIENT_ID = ''
OAUTH_CLIENT_SECRET = ''
OAUTH_TOKEN_ENDPOINT = 'https://api.fitbit.com/oauth2/token'
OAUTH_AUTHORIZATION_ENDPOINT = 'https://www.fitbit.com/oauth2/authorize'
ENABLED = TRUE;
-- セキュリティ統合に含まれる認証情報を表すシークレットの作成
--
CREATE OR REPLACE SECRET fitbit_api_oauth_token
TYPE = oauth2
API_AUTHENTICATION = fitbit_api_oauth
OAUTH_REFRESH_TOKEN = '';
最後に外部アクセス統合を作成する。
ストレージ統合や、Notification統合など、統合の作成にはACCOUNTADMINが必要で、
同様に外部アクセス統合の作成にはACCOUNTADMINが必要とのこと。
USE ROLE ACCOUNTADMIN;
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION fitbit_apis_access_integration
ALLOWED_NETWORK_RULES = (fitbit_apis_network_rule)
ALLOWED_AUTHENTICATION_SECRETS = (fitbit_api_oauth_token)
ENABLED = TRUE;
外部ロケーション(ネットワーク)にアクセスするUDFsを書くロールを作成する。
UDFsを書く際に、シークレットを参照する必要がある。
UDFsを書けるロールにシークレットのREAD権限を付与しておく必要がある。
以下、そのままでは SECURITYADMINがDB・スキーマに触れないので環境により修正が必要。
USE ROLE USERADMIN;
CREATE OR REPLACE ROLE ikuty_fitbitapi_developer;
USE ROLE SECURITYADMIN;
USE SCHEMA IKUTY_DB.PUBLIC;
GRANT READ ON SECRET IKUTY_DB.PUBLIC.fitbit_api_oauth_token TO ROLE ikuty_fitbitapi_developer;
GRANT USAGE ON INTEGRATION fitbit_apis_access_integration TO ROLE ikuty_fitbitapi_developer;
GRANT ROLE ikuty_fitbitapi_developer TO role SYSADMIN;
本体の実装
PythonでOAuth2 Endpoint経由でFitbit APIにGETリクエストを投げるFunctionを書く。
最初、トークンのexpire時のrefreshを自力で書いていたが、get_oauth_access_token(‘cred’)により、
自動的にrefreshしてくれていることに気づいた。
use role sysadmin;
use schema IKUTY_DB.PUBLIC;
CREATE OR REPLACE FUNCTION fitbit_python()
RETURNS STRING
LANGUAGE PYTHON
RUNTIME_VERSION = 3.8
HANDLER = 'hello_fitbit'
EXTERNAL_ACCESS_INTEGRATIONS = (fitbit_apis_access_integration)
PACKAGES = ('snowflake-snowpark-python','requests')
SECRETS = ('cred' = fitbit_api_oauth_token )
AS
$$
import _snowflake
import requests
import json
def hello_fitbit():
with requests.Session() as s:
access_token = _snowflake.get_oauth_access_token('cred')
url = "https://api.fitbit.com/1/user/-/activities/steps/date/today/1m.json"
res = s.get(url,headers={"Authorization": "Bearer " + access_token})
res_data = res.json()
return res_data
$$;
実行結果は以下。1日毎の歩数を1ヶ月分取得できた(恥…)。
select parse_json(fitbit_python());
{
"activities-steps": [
{
"dateTime": "2023-11-23",
"value": "15570"
},
{
"dateTime": "2023-11-24",
"value": "5392"
},
{
"dateTime": "2023-11-25",
"value": "8993"
},
{
"dateTime": "2023-11-26",
"value": "10525"
},
{
"dateTime": "2023-11-27",
"value": "6371"
},
{
"dateTime": "2023-11-28",
"value": "2713"
},
{
"dateTime": "2023-11-29",
"value": "9252"
},
{
"dateTime": "2023-11-30",
"value": "0"
},
{
"dateTime": "2023-12-01",
"value": "7947"
},
{
"dateTime": "2023-12-02",
"value": "11265"
},
{
"dateTime": "2023-12-03",
"value": "8557"
},
{
"dateTime": "2023-12-04",
"value": "2366"
},
{
"dateTime": "2023-12-05",
"value": "7985"
},
{
"dateTime": "2023-12-06",
"value": "8109"
},
{
"dateTime": "2023-12-07",
"value": "6852"
},
{
"dateTime": "2023-12-08",
"value": "3707"
},
{
"dateTime": "2023-12-09",
"value": "12640"
},
{
"dateTime": "2023-12-10",
"value": "7122"
},
{
"dateTime": "2023-12-11",
"value": "7190"
},
{
"dateTime": "2023-12-12",
"value": "8034"
},
{
"dateTime": "2023-12-13",
"value": "5228"
},
{
"dateTime": "2023-12-14",
"value": "2861"
},
{
"dateTime": "2023-12-15",
"value": "6785"
},
{
"dateTime": "2023-12-16",
"value": "11720"
},
{
"dateTime": "2023-12-17",
"value": "11021"
},
{
"dateTime": "2023-12-18",
"value": "0"
},
{
"dateTime": "2023-12-19",
"value": "11021"
},
{
"dateTime": "2023-12-20",
"value": "0"
},
{
"dateTime": "2023-12-21",
"value": "2703"
},
{
"dateTime": "2023-12-22",
"value": "3336"
},
{
"dateTime": "2023-12-23",
"value": "7497"
}
]
}
結論
PuPrのExternal Network Accessを使用して、FitbitAPI→Snowflakeが出来ることを確認した。
(途中、自動的にトークンをrefreshしてくれている、と書いたが、何度かExpireさせないと良くわからない。)
相手がOAuth2.0ならとても簡単に繋ぐことができると思う。
次は、せっかくなのでSiS(Streamlit in Snowflake)で可視化してみたりしたい。