Microsoft Graph 実務入門 #5 OneDrive と SharePoint のファイル操作 — アップロードセッションと共有

読了 6分

今回はファイルです。成果物を SharePoint に自動アップロードする、毎日生まれるログをバックアップする、生成したレポートの共有リンクを作る、といったシナリオです。ファイル API のよいところは、OneDrive と SharePoint が同じモデルを使うことです。一度覚えれば、個人のドライブでもチームのサイトでも同じコードが動きます。

権限は、delegated なら Files.ReadWrite(自分のファイル)から始め、application なら Sites.ReadWrite.All が基本です。ただしこの権限は組織のすべてのサイトを開くので、後で扱う Sites.Selected で絞るのが近年の定石です。

モデル — drive と driveItem の二つで終わりです #

  • drive — ファイルの保管庫一つです。ユーザーの OneDrive も drive、SharePoint サイトのドキュメントライブラリも drive、Teams のファイルタブの裏にあるものも結局 SharePoint の drive です。
  • driveItem — drive の中の項目です。ファイルもフォルダも driveItem で、folder プロパティがあればフォルダ、file プロパティがあればファイル、という形で区別します。

アクセスのパスだけ、保管庫の種類によって違います。

drive に到達するパス
# ユーザーの OneDrive
GET /users/{user-id}/drive

# SharePoint サイトの既定のドキュメントライブラリ
GET /sites/{site-id}/drive

# サイト ID は URL で探すのが実務的
GET /sites/contoso.sharepoint.com:/sites/DevTeam
  → レスポンスの id が site-id

# drive の中の項目へのアクセス — パスベースが便利です
GET /drives/{drive-id}/root:/reports/2026/weekly.xlsx

最後の行のパスベースのアドレス(コロン記法)が実務の主力です。root:/フォルダ/ファイル名 の形で、ID を知らなくても人が知っているパスのままアクセスできます。自動化のコードでは「サイト URL → site-id → drive-id」を一度照会して設定に固定しておき、以後はパスベースだけで移動するパターンがすっきりしています。

ダウンロードと単純アップロード #

読み取りと小さいファイルの書き込みは、それぞれ一呼び出しです。

ダウンロードと単純アップロード
async def download(client, drive_id: str, path: str) -> bytes:
    return await (
        client.drives.by_drive_id(drive_id)
        .root.item_with_path(path)
        .content.get()
    )

async def upload_small(client, drive_id: str, path: str, data: bytes):
    # PUT .../root:/{path}:/content — 250MB まで一度で
    return await (
        client.drives.by_drive_id(drive_id)
        .root.item_with_path(path)
        .content.put(data)
    )

単純アップロード(PUT /content)は 250MB まで対応します。ただし公式の推奨は、10MiB を超えるファイルからは次の節のアップロードセッションを使うことです。大きいファイルの単一リクエストは、ネットワークが一度切れると最初からやり直しなので、失敗時に再開できるセッション方式が運用では結局得だからです。

もう一つ、同じパスにファイルがすでにあると上書きされます。意図した動作でないなら、アップロード時に @microsoft.graph.conflictBehavior(rename / fail / replace)を指定して衝突の動作を明示するのが安全です。

大容量アップロード — アップロードセッション #

大きいファイルの定石は 3 段階です。① アップロードセッションを作り、② 断片(チャンク)を順に PUT し、③ 最後の断片が上がると自動で完了します。

アップロードセッション — チャンクアップロード
import httpx
from msgraph.generated.drives.item.items.item.create_upload_session.create_upload_session_post_request_body import (
    CreateUploadSessionPostRequestBody,
)
from msgraph.generated.models.drive_item_uploadable_properties import (
    DriveItemUploadableProperties,
)

CHUNK = 5 * 1024 * 1024  # 5MiB — 320KiB(327,680)の倍数である必要があります

async def upload_large(client, drive_id: str, path: str, filepath: str):
    body = CreateUploadSessionPostRequestBody(
        item=DriveItemUploadableProperties(
            additional_data={"@microsoft.graph.conflictBehavior": "replace"},
        ),
    )
    session = await (
        client.drives.by_drive_id(drive_id)
        .root.item_with_path(path)
        .create_upload_session.post(body)
    )

    # セッション URL には認証トークンが不要です(事前認証済みの URL)
    import os
    total = os.path.getsize(filepath)
    async with httpx.AsyncClient() as http:
        with open(filepath, "rb") as f:
            offset = 0
            while chunk := f.read(CHUNK):
                end = offset + len(chunk) - 1
                resp = await http.put(
                    session.upload_url,
                    content=chunk,
                    headers={
                        "Content-Range": f"bytes {offset}-{end}/{total}",
                        "Content-Length": str(len(chunk)),
                    },
                )
                resp.raise_for_status()
                offset = end + 1

ルールがいくつかあり、破ると最後の断片で失敗するという嫌な形で現れるので、先に押さえておきます。

  • チャンクの大きさは 320KiB(327,680 バイト)の倍数である必要があります。安定した回線では 5〜10MiB が推奨の範囲で、一リクエストの上限は 60MiB です。
  • 断片は順番どおりに上げます(OneDrive/SharePoint 基準)。
  • 5xx やネットワーク切断に遭遇したら、セッション URL に GET を送って nextExpectedRanges(サーバーがまだ受け取っていない範囲)を確認し、そこから再開します。404 が来たらセッションが期限切れなので、最初からやり直します。
  • セッションには期限がありますが、断片が上がるたびに延長されるので、アップロードが進んでいる限り生き続けます。

なお各言語の SDK には、この手順を包んだ大容量アップロードのタスクヘルパーがあります(例: LargeFileUploadTask)。ヘルパーがあればそれを使うほうが簡潔です。上のコードは、どの環境でも通じるように手順そのものを見せたものです。

共有リンクの生成 — createLink #

アップロードしたレポートを人に届ける最後のピースです。createLink でリンクを作り、その URL を第 4 回のメールや第 6 回の Teams メッセージに載せれば、自動化の一サイクルが閉じます。

共有リンクの生成
from msgraph.generated.drives.item.items.item.create_link.create_link_post_request_body import (
    CreateLinkPostRequestBody,
)

async def share_link(client, drive_id: str, item_id: str) -> str:
    body = CreateLinkPostRequestBody(
        type="view",           # view / edit
        scope="organization",  # organization / anonymous / users
    )
    perm = await (
        client.drives.by_drive_id(drive_id)
        .items.by_drive_item_id(item_id)
        .create_link.post(body)
    )
    return perm.link.web_url

scope の選択がそのままセキュリティの決定です。社内の自動化なら organization(組織内のみ閲覧可)がデフォルトであるべきで、anonymous(リンクさえあれば誰でも)は組織のポリシーで無効化されている場合も多く、無効化されているのがむしろ普通です。自動化が作るリンクは数が多くなるので、広い scope の習慣はそのまま情報漏えいの入り口になります。

application 権限を絞る — Sites.Selected #

この回の最後は権限の話です。application の Sites.ReadWrite.All は組織のすべての SharePoint サイトを開くので、「自動化一つのために全体は開けられない」という反対に遭いやすいところです。その答えが Sites.Selected です。

動きが特殊なので押さえておきます。Sites.Selected という権限自体はどのサイトも開きません。 管理者がサイト単位で「このアプリにこのサイトの read/write を与える」という付与(site permission)を別途追加して、初めて実際のアクセスが生まれます(この付与の作業自体も Graph のサイト権限 API や PnP PowerShell で行います)。つまり「アプリの権限は一覧に一つ、実際の範囲はサイト別の許可リスト」という構造です。ファイル自動化の本番構成ではこちらを基本にして、.All は本当に全サイトを扱う場合に限ることをおすすめします。

まとめ #

  • モデルは drive(保管庫)と driveItem(ファイル・フォルダ)の二つだけで、OneDrive と SharePoint が同じモデルを使います。項目へのアクセスはパスベース(コロン記法)が実務の主力です。
  • 単純アップロードは 250MB までできますが、10MiB を超えたらアップロードセッションが定石です。チャンクは 320KiB の倍数(推奨 5〜10MiB)、失敗時は nextExpectedRanges で再開します。
  • アップロードの衝突の動作(conflictBehavior)は明示します。デフォルトに任せると、いつか上書きの事故が起きます。
  • 共有リンクの scope は organization がデフォルトです。自動化が作る anonymous リンクは漏えいの入り口になります。
  • application のファイル自動化の権限の定石は Sites.Selected + サイト別の付与です。.All は例外として扱います。

次回は Teams です。メッセージを読むことと送ることの権限の地形が大きく違う領域なので、「通知を送る三つの経路」を中心に整理します。

X