Microsopf GraphはMSサービスのAPI
利用できるAPIを色々お試しできます。
利用するパターンをクリックするとgit上のサンプルコードが開かれるようになっています。
パターンによってOauth2.0でのトークン付与方法が異なります。
今回の場合はmacbookからpythonで個人アカウントのAPIを叩きます。
何のアプリも通さずに、pythonから直接APIを叩くのでBrowserless Appになります。
サンプルコードでinitiate_device_flow()を利用していたことからトークン付与の分類としてはdevicecode flowになるようです。
AzureのGUI操作
GUI操作は先人の知恵に頼るということで(丸投げ)
まずはこちらの動画の42秒から2分30秒あたりまでを参考にのGUI操作を行います。
それ以降はライブコーディングの動画なのでスキップしてもらってOKです。
MicrosoftのアカウントでAzureポータルにログインし、APIのアプリを作成し必要な設定をしていきます。
動画中の注意
- 動画中ではMethod #1: CredentialCrilentApplicationという方法とMethod #2: PublicClientApplicationの二つのtoken付与方法が紹介されていますが、今回は#2の方法でtoken付与を行います。
- GUI操作から情報を得られるもののうち、#2で必要なのはAPPLICATION_ID(CLIENT_IDと同義)とSCOPESの二つのみです。
メモパッドなどに情報をコピペしてきましょう。 CLIENT_SECRETは利用しません。 - APIのアクセス許可(API premissions)の中で必要な権限を許可します。
今回の場合だと、OneNoteの読み取りを行うNotes.Readなどです。
動画でカバーされていない操作
python実行前の準備
-
pip install msal pyperclip
で必要なパッケージのインストール - GUIからAPP_IDをコピペ
- GUIで選択したSCOPESをList[str]型で書く
APP_ID = 'Your APP_ID'
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']
pythonの実行
上記のYouTubeの別動画とMediumの記事の合わせ技になっています。
import os
from pprint import pprint as pp
import requests
import webbrowser
import msal
import pyperclip
APP_ID = 'Your APP_ID'
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']
def generate_access_token(app_id=APP_ID, scopes=SCOPES):
# read access_token_cache.json in current dir if exists
access_token_cache = msal.SerializableTokenCache()
if os.path.exists('access_token_cache.json'):
access_token_cache.deserialize(
open('access_token_cache.json', 'r').read())
# init auth client
client = msal.PublicClientApplication(
client_id=app_id, token_cache=access_token_cache)
# use access_token_cache.json
accouts = client.get_accounts()
if accouts:
access_token = client.acquire_token_silent(scopes, accouts[0])
pp('access_token_cache.json is used.')
# generate access_token
else:
flow = client.initiate_device_flow(scopes)
pyperclip.copy(flow['user_code'])
print('User code is copied on your clipboard.\n')
print(flow['user_code'])
print('\n')
# open vertification_uri
# need to authenticate on GUI
webbrowser.open(flow['verification_uri'])
access_token = client.acquire_token_by_device_flow(flow)
# write access_token_cache.json
with open('access_token_cache.json', 'w') as _f:
_f.write(access_token_cache.serialize())
return access_token
def get_pages(token, pagination=True):
url = 'https://graph.microsoft.com/v1.0/me//onenote/pages'
headers = {'Authorization': 'Bearer ' + token['access_token']}
graph_results = []
# get data while nextLink exists
while url:
try:
response = requests.get(url=url, headers=headers).json()
graph_results.extend(response['value'])
if pagination:
url = response['@odata.nextLink']
else:
url = None
except BaseException as e:
pp(e)
pp('Fail to get')
break
else:
pass
return graph_results
if __name__ == '__main__':
access_token = generate_access_token()
res = get_pages(access_token)
print(len(res))
token発行
- token取得のためのclientで、要求するスコープ(どのアプリで何をするか)を渡してflow認証を開始
client = msal.PublicClientApplication(client_id=app_id, token_cache=access_token_cache)
...
flow = client.initiate_device_flow(scopes)
- 認証・認可の画面で最初に求められるuser_codeをクリップボードにコピーする
- ブラウザを自動で起動し、認証・認可の画面を開く
- tokenのcacheをaccess_token_cache.jsonとして保存
- 次回以降は、tokenのcacheが有効であれば再利用する
OneNoteのページ情報取得
- requests.getでAPIを叩く
- 追加ページがなくなるまで繰り返しgetする
大量のデータがあるものについてはクエリに対して返すオブジェクトの数が限られています。
onenote/pagesでは20個が上限です。
データが続く場合はresponseに'@odata.nextlinkが含まれるのでそのリンクを使って続きからデータを取得します。
その挙動について参考になったのはこちら。
やりたかったこと
私はOneNoteに勉強したことや知識のまとめ、ちょっとした日記を溜め込むようにしています。
細かく整理はせずにとにかくページを作っては書く、という使い方をしているため、
改めて眺めていると異なるセクションで同じ関連するページが存在することがわかりました。
例えば、、
NW, Cisco, Junos, Linux, pythonなどのセクションがあるのですが、
- NWのセクションの中にはBGPの基礎知識のページ
- Ciscoのセクションの中にはBGPのconfig例
- Linuxのセクションの中にはbgpdumpのページ
- pythonでBGPのshowコマンドを実行するページ
のように、各セクションにBGPに関するページが散乱しています。
各セクションに紐づいている状態は正しいのですが、
BGPという一つのテーマを横断的に見ていくのにはあまり良くない構造になっています。
このままだと、BGPの知識を引っ張り出したい時に
複数のセクションをGUIでチェックしていくことになり手間がかかります。。
そこで、BGP関連のページのリンク集を作る、あるいはマインドマップ的なものを作る、
ということができたらいいなと思ったのがGraphを触ることになったきっかけです。
はまったところ
そもそもAPIの認可ってなんだ
トークンが必要そうなのはわかるけど、なんでこんなに手間かかるねん、と思いながら試行錯誤していたので苦労しましたが、こちらのOAuthのわかりやすい解説記事でOAuthのイメージを掴んでおけばよかったです。
認証方式が多く、どれを使えばいいのかわからない
これは前述のpythonSDKのページを早く見つけられれば解決した気はしますが、、
公式ドキュメントを上から読んでいくのは気が遠くなる作業だったのでとりあえずyoutubeを参考に進めていった結果、何が正解か分からない中でたくさんのエラーに遭遇してなかなかストレスでした。。
Azureに不慣れでGUI操作がわかりづらい
APIのtoken発行にAzure上でAPIアプリの作成が必要になりますが、Azureを普段触ることもないので少し苦労しました。
pythonのスクリプト例が少ない
Graph Explorerのcode snippetsにpythonがありません。。
また、github上で使えそうなモジュールはいくつか出てくるものの、認証・認可について詳細な説明がなく、最終的には利用しませんでした。
遭遇したエラー
scopeのエラー
tokenを発行するために、認可して欲しい内容をSCOPESとして渡します。
通常はSCOPES = ['https://graph.microsoft.com/.default']
とすれば、
GUI上で設定したスコープを読み込んでくれる?ようですが、私の場合だと以下のようなエラーが出てきてしまいました。
エラーコードのリファレンスはこちら。ちなみにMSサービス全体として、ここでエラーコードの検索をかけられるようです。
AADSTS70011: The provided value for the input parameter 'scope' is not valid. One or more scopes in 'https://graph.microsoft.com/.default openid offline_access' are not compatible with each other.
追加した覚えのないopenidとoffline_accessがscopesの引数として渡されているようです。
そもそもの/.defaultについての説明はこちらの公式ドキュメントにありますが、解読は諦めました。。
initiate_device_flow()を利用した場合のサンプルコードでSCOPESは具体的に書いていたため、
.defaultは利用せず以下のように直で書くので良しとしました。。
SCOPES = ['User.Read', 'Notes.Read', 'Notes.ReadWrite', 'Notes.Create']
ConfidentialClientApplicationだとAPIを叩けない
序盤で参考にしたMediumの記事ではmsal.ConfidentialClientApplicationを利用していました。tokenの付与はできたものの、エラーメッセージで「この認証方法ではAPI叩けない」と言われて悲しかったです。。
結局、msal.PublicClientApplicationを利用しています。