$ cat node-template.py
D
Drive Folder Reader
// Reads all documents from a Drive folder recursively. Returns document references that can be passed to a Drive Document Reader.
Input
Storage
#drive#storage#folder
template.py
1"""drive-folder-reader — list document DriveItem ids in a Drive folder.23Migrated from the legacy monolithic template to the new multi-file format.4Privileged Drive IO is delegated to the gais.drive SDK (Gais.drive.list);5this node imports no HTTP client. Returns DriveItem ids (NOT Document refIds)6so downstream nodes can validate permissions via the DriveItem path.7"""89from __future__ import annotations1011import json12import re13import sys14import traceback15from typing import Any1617from gais import Gais1819_UUID_RE = re.compile(20 r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",21 re.IGNORECASE,22)232425def _normalize_drive_item_id(value: Any) -> str:26 """Accept a bare UUID or a mention://drive_{file,folder}/<uuid> URI."""27 if not isinstance(value, str):28 raise ValueError(f"Drive item id must be a string, got {type(value).__name__}")29 s = value.strip()30 for prefix in ("mention://drive_file/", "mention://drive_folder/"):31 if s.startswith(prefix):32 s = s[len(prefix):]33 break34 if not _UUID_RE.match(s):35 raise ValueError(f"Drive item id is not a valid UUID: {value!r}")36 return s373839def process(inputs: dict[str, Any]) -> dict[str, Any]:40 """List every descendant file/document of the folder via gais.drive.list."""41 folder_id = inputs.get("driveItemId")42 if not folder_id:43 raise ValueError("driveItemId input is required")4445 folder_id = _normalize_drive_item_id(folder_id)4647 # Recursive query: every descendant that is a file/document (refId != null),48 # NOT subfolders. The Drive API applies the user's permissions automatically49 # (gais.drive carries the user token).50 result = Gais.drive.list(51 query=f"path descendantOf '{folder_id}' and refId!=null",52 page_size=1000,53 )5455 # Return DriveItem ids (NOT Document refIds) so downstream nodes can56 # validate permissions via the DriveItem path.57 drive_item_ids = [item["id"] for item in result.data.get("items", []) if item.get("id")]58 return {"driveItemIds": drive_item_ids}596061def main() -> None:62 try:63 envelope = json.loads(sys.stdin.read() or "{}")64 if not isinstance(envelope, dict):65 envelope = {}66 inputs = envelope.get("inputs", {}) or {}67 json.dump(process(inputs), sys.stdout)68 except Exception as e:69 print(70 json.dumps({71 "error": str(e),72 "errorType": type(e).__name__,73 "traceback": traceback.format_exc(),74 }),75 file=sys.stderr,76 )77 sys.exit(1)787980if __name__ == "__main__":81 main()$ git log --oneline
v1.3.0
HEAD
2026-08-18v1.1.22026-05-22
v1.1.12026-05-07
v1.1.02026-04-09
Updated description to clarify the node returns document IDs recursively.