はじめに
2025年6月上旬から、Graph APIでOutlookカレンダーの全ての予定表をGETしようとすると500 Internal Server Errorを吐く事態となった。
Graph Explorerで試してみると、以下のような反応が返ってくる。
GET https://graph.microsoft.com/v1.0/me/calendars
// レスポンス
{
"error": {
"code": "ErrorInternalServerError",
"message": "An internal server error occurred. The operation failed."
}
}
Internal Server Error - 500
解決策
X(Twitter)で検索をかけてみると、上記の問題を指摘し、Microsoftに修正を要求しているMark Messinger氏の投稿があった。
Hey, @Microsoft365, your Graph API is busted. Your Graph Explorer confirms with certain accounts (e.g., Family 365), GET All my calendars throws Internal Server Error - 500. Business 365 account I've tested is not impacted. Your API, your test tool, your GET query. Please fix.
— Mark Messinger (@MarkMessinger) June 2, 2025
Messinger氏のスレッドの下の方を見てみると、どうやらconference
フィールドを除いてGraph APIを呼び出すとOutlookカレンダーのすべての予定表が取得できるとのこと。
Developers of applications using the Microsoft Office 365 Graph API have found that making a call that does not include the "conference" fields will allow users to successfully sync their Outlook calendars.
— Mark Messinger (@MarkMessinger) June 3, 2025
Fix your API, Microsoft.
そこで、以下のようなクエリを足したうえでGraph APIを叩いてみるとMessinger氏の言う通り、Outlook予定表のデータを取得することができた。(Graph Explorerで確認済)
GET https://graph.microsoft.com/v1.0/me/calendars?$select=id,name,color,isDefaultCalendar
OK - 200
では、Pythonでどう動かすか?
私は普段、PythonとO365ライブラリを使って、Graph APIから情報を取得したり、操作したりしている。確かに、クエリを足せばGraph APIからカレンダーを取得できることは分かったが、O365のget_calendar()
メソッドには残念ながら$select
パラメータを指定できるような引数は存在しない。どうにかしてO365でも$select
パラメータを指定してGETできるようにしたい。
O365ライブラリのcalendar.py
を直接編集する
Windowsならだいたい"C:\Users\hoge\AppData\Local\Programs\Python\Python3xx\Lib\site-packages\O365"
とかにO365ライブラリのソースコードがある。
O365ライブラリのソースコードを探ってみると、APIを叩くURLを生成しているのは、connection.py
のget()
メソッドであることが分かった。
def get(self, url: str, params: Optional[dict] = None, **kwargs) -> Response:
"""Shorthand for self.oauth_request(url, 'get')
:param str url: url to send get oauth request to
:param dict params: request parameter to get the service data
:param kwargs: extra params to send to request api
:return: Response of the request
:rtype: requests.Response
"""
return self.oauth_request(url, "get", params=params, **kwargs)
このparams
に$select
パラメータの内容を渡せばいいことが分かる。
def get_calendar(self, calendar_id=None, calendar_name=None, query=None):
"""
省略
"""
if calendar_id:
# get calendar by it's id
url = self.build_url(
self._endpoints.get('get_calendar').format(id=calendar_id))
params = {"$select": "id,name,color,isDefaultCalendar"} # 書き換え箇所
# params = None
else:
# get calendar by name
url = self.build_url(self._endpoints.get('root_calendars'))
params = {
'$filter': "{} eq '{}'".format(self._cc('name'), calendar_name),
'$top': 1,
"$select": "id,name,color,isDefaultCalendar"} # 書き換え箇所
"""
params = {
'$filter': "{} eq '{}'".format(self._cc('name'), calendar_name),
'$top': 1}
"""
response = self.con.get(url, params=params)
"""
省略
"""
# Everything received from cloud must be passed as self._cloud_data_key
return self.calendar_constructor(parent=self,
**{self._cloud_data_key: data})
あとは、calendar.py
のget_calendar
メソッドを書き換えて、con.get()
に$select
の内容を含んだparams
を読ませるようにする。
これで無事、Python+O365でもOutlookカレンダーが取得できるようになった。
というか、Microsoftは早くこのバグを直してくれヽ(`Д´)ノ