HTTP Transport 與 Batch API¶
回到 資料流索引
Google Calendar 和 Google Tasks 共用一套 Batch API Transport 層,將多個請求打包為單一 HTTP POST(multipart/mixed),減少網路往返次數。
層級結構¶
| 層 | 檔案 | 主要類別/函數 |
|---|---|---|
| Request Model | models_request.py (Calendar) | ListEventsRequest, InsertEventRequest, ListCalendarListRequest 等 |
| Request Model | models_request.py (Tasks) | ListTasksRequest, InsertTaskRequest, ListTaskListsRequest 等 |
| API Client | api_client_async.py (Calendar) | list_events_async(), list_calendar_list_async(), _execute_single_async() |
| API Client | api_client_async.py (Tasks) | list_tasks_async(), list_tasklists_async(), _execute_single_async() |
| Dispatcher | google_api_dispatcher.py | batch_execute_async() |
| HTTP Tool | http_batch_tool.py | build_generic_batch_body(), parse_generic_batch_response(), send_batch_request_payload() |
Request Model 設計¶
每個 Request Model 是 Pydantic BaseModel,必須實作兩個方法:
class ListEventsRequest(GoogleCalendarRequest):
def to_http(self) -> dict:
"""將參數轉為 HTTP 請求元件 {method, url, params, json}"""
return {"method": "GET", "url": "/calendar/v3/calendars/.../events", "params": {...}}
def parse_response(self, data: dict) -> list:
"""將 API 回傳的 JSON dict 轉為 Read Layer Model"""
return [GoogleEventRead.from_raw(item) for item in data.get("items", [])]
執行流程¶
Request Model → to_http() → {method, url, params, json}
↓
API Client → _execute_single_async() (len=1 Batch)
↓
Dispatcher → batch_execute_async()
↓
HTTP Batch Tool → build_generic_batch_body() → multipart/mixed 字串
↓
send_batch_request_payload() → POST to Google Batch Endpoint
↓
parse_generic_batch_response() → List[{ok, status, data}]
↓
Dispatcher (raw=True) → 直接回傳 {ok, data: JSON dict}
Dispatcher (raw=False)→ req.parse_response(data) → Read Model
batch_execute_async() 函數詳解¶
- 做了什麼:
- 從 credentials 提取 token
- 呼叫每個 request 的
to_http()取得 HTTP 元件 - 用
build_generic_batch_body()打包為multipart/mixed格式 - 用
send_batch_request_payload()發送至 Google Batch Endpoint - 用
parse_generic_batch_response()解析回應 - 若
raw=True,直接回傳{ok, status, data}dict - 若
raw=False,呼叫req.parse_response(data)轉換為 Read Model
Batch Endpoint¶
| 整合 | Endpoint |
|---|---|
| Google Calendar | https://www.googleapis.com/batch/calendar/v3 |
| Google Tasks | https://www.googleapis.com/batch/tasks/v1 |
API 原始 JSON 範例¶
Google Calendar Event(API 回傳的單一事件)¶
Timed Event (含具體時間)¶
{
"kind": "calendar#event",
"id": "abc123def456",
"summary": "週會",
"start": {
"dateTime": "2026-01-28T10:00:00+08:00",
"timeZone": "Asia/Taipei"
},
"end": {
"dateTime": "2026-01-28T11:00:00+08:00",
"timeZone": "Asia/Taipei"
},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=WE"],
"reminders": {"useDefault": false, "overrides": [{"method": "popup", "minutes": 10}]}
}
All-Day Event (全天事件)¶
{
"kind": "calendar#event",
"id": "def456ghi789",
"summary": "清明節連假",
"start": {
"date": "2026-04-04"
},
"end": {
"date": "2026-04-06"
}
}
{dateTime: ...} 或 {date: ...})出現。 - 若為全天事件,只會有 date 欄位(start 為當天,end 由於非包含性質,會是結束日的隔天)。 - 這些巢狀結構使得轉換層(GoogleEventRead.from_dict)必須解構提取,不可直接視為字串。 Google Tasks Task(API 回傳的單一任務)¶
{
"kind": "tasks#task",
"id": "MTIzNDU2Nzg5MA",
"title": "完成期末報告",
"updated": "2026-01-27T08:30:00.000Z",
"parent": "OTg3NjU0MzIx",
"notes": "需要包含數據分析章節",
"status": "needsAction",
"due": "2026-02-01T00:00:00.000Z",
"completed": null
}
時間欄位觀察: - Google Tasks API 的時間是直接的字串欄位(如 "due": "2026-02-01T00:00:00.000Z")。 - due 欄位永遠是 T00:00:00.000Z(UTC 零點),這代表它實際上只有日期概念,缺乏精確到幾點幾分的時間資訊。 - 這也是為何在 GoogleTaskRead.from_dict 中可以放心地將它轉換為 UTC+8 字串 YYYY-MM-DD HH:MM(此時時間必定為 08:00),且把 all_day 寫死為 True。
Moodle Calendar Event(AJAX 回傳的單一事件,簡化)¶
{
"id": 12345,
"name": "作業一:Python 基礎練習",
"description": "<p>請完成以下練習...</p>",
"component": "mod_assign",
"modulename": "assign",
"activityname": "作業一:Python 基礎練習 已於 2025年11月15日 星期六 23:59 到期",
"timestart": 1731686399,
"timeduration": 0,
"overdue": true,
"course": {
"id": 67890,
"fullname": "114學年度 第 1 學期 資訊工程系 EE1001 程式設計 張教授",
"shortname": "EE1001-114-1"
},
"isactionevent": true,
"action": {
"name": "繳交作業",
"url": "https://moodle.ntust.edu.tw/mod/assign/view.php?id=...",
"itemcount": 1,
"actionable": true
}
}