Googleドライブに用意したドキュメントのテンプレートをPyDrive2で複製する


はじめに

この記事では、PythonでGoogleドライブを操作するパッケージの中からPyDrive2(GitHub)を取り上げます。
PyDrive2からDrive APIを介してGoogleドライブを操作し、指定したドキュメントを複製する方法を備忘録としてまとめます。

PyDrive2について

PyDrive2はPyDriveのメンテナンスされたフォークです。
PyDriveの更新は1年以上前から止まっており、2020年1月にPyDrive2がリリースされています。

もう少し詳しい説明や、PyDrive2を使う際の認証まわりの設定については以下をご確認ください。
PythonでGoogleドライブを操作するパッケージPyDrive2の認証まわり

なお、PyDriveやPyDrive2は、Drive API v2にアクセスします。
Drive APIにはv3もあるため、ご注意ください。

動作環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G3020
$ python -V
Python 3.8.1
$ pip list | grep PyDrive2
PyDrive2                 1.4.10

PythonでGoogleドライブを操作するパッケージPyDrive2の認証まわりに沿って、ファイルを配置しています。

.
├── my_client_secrets.json
├── saved_credentials.json
└── settings.yaml

やりたいこと

Googleドキュメントの議事録テンプレートの複製をPythonスクリプトにやらせます。

背景

MTGの際にGoogleドキュメントに共同で議事録をとっています。

MTGのたびに議事録テンプレートを複製するのが退屈に感じ、自動化を考えるようになりました。
そこで、自動化の一歩目として、GoogleドキュメントのIDを指定したら複製するスクリプトを作ってみました。

GoogleドキュメントのIDとは

GoogleドキュメントのURLは以下のようになっています。

https://docs.google.com/document/d/1******************************************o/edit#

このうちの 1******************************************o の部分がIDです(マスクする意味でアスタリスクを使っています)。

指定したドキュメントを複製するPythonスクリプト

copy_template.py
import argparse

from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive


def main():
    # コマンドラインから渡された引数の解析 (1)
    parser = argparse.ArgumentParser()
    parser.add_argument("source_id")
    args = parser.parse_args()
    source_id = args.source_id

    # Googleドライブを操作するためのオブジェクトを取得 (2)
    gauth = GoogleAuth()
    gauth.LocalWebserverAuth()
    drive = GoogleDrive(gauth)

    # コピー元のファイルの名前に copied_ をつけたファイル名とする (3)
    source = drive.CreateFile({"id": source_id})
    source.FetchMetadata("title")
    dest_title = f"copied_{source['title']}"

    # コピーを作成 (4)
    copied_file = {"title": dest_title}
    f = (
        drive.auth.service.files()
        .copy(fileId=source_id, body=copied_file)
        .execute()
    )

    # 作成したファイルにアクセスするためのURLを表示 (5)
    dest = drive.CreateFile({"id": f["id"]})
    dest.FetchMetadata("alternateLink")
    print(dest["alternateLink"])


if __name__ == "__main__":
    main()

実行結果

ファイル配置
.
├── copy_template.py
├── my_client_secrets.json
├── saved_credentials.json
└── settings.yaml
使い方
$ python copy_template.py --help
usage: copy_template.py [-h] source_id

positional arguments:
  source_id

optional arguments:
  -h, --help  show this help message and exit

実行すると、複製した結果のドキュメントのURLが表示されます。

$ python copy_template.py 1******************************************o
https://docs.google.com/document/d/1******************************************c/edit?usp=drivesdk

コード解説

PyDriveのドキュメント1には指定したドキュメントを複製(コピー)する方法は見つけられませんでした。
そこでPyDriveのIssueを検索し、参考にできそうなIssueを見つけました。
Create a copy of the file #85 (Comment)
こちらに沿って実装しています。

複製の仕組み(コード (4))

認証結果を元に、Googleドライブを操作するためのオブジェクト(gdrive)を作成します。

複製には、複製元のドキュメントのIDと、複製先のドキュメントのタイトルが必要です。

  • 複製元のドキュメントのID:コマンドラインから渡される
  • 複製先のタイトル:決め打ちでもOK。今回は複製元のドキュメントのタイトルにcopied_というprefixをつける

Googleドライブのファイルを取得する(コード (3))

gdriveオブジェクトからGoogleドライブ上のファイルを操作するには、CreateFileメソッドを使います。

source = drive.CreateFile({"id": source_id})

CreateFileメソッドの返り値はGoogleDriveFileインスタンスです。
IDを指定してCreateFileし、該当のファイルをGoogleDriveFileインスタンスを介して操作できるようにします。

Googleドライブのファイルのメタデータを取得する(コード (3),(5))

Googleドライブのファイルには様々な情報(メタデータ)がありますが、上記の方法ではIDしか取得できていません。
取得したい情報がある場合は、

  1. GoogleDriveFileFetchMetadataメソッドで取得したい属性を指定
    • 例:source.FetchMetadata("title")
  2. 取得したい属性名をキーにしてアクセス
    • 例:source['title']

という手順になります。

1で取得したい属性を指定する場合は、以下のリファレンスに沿って指定します。
https://developers.google.com/drive/api/v2/reference/files

  • title: The title of this file.
  • alternateLink: A link for opening the file in a relevant Google editor or viewer.

Googleドライブのファイルをコピーする(コード (4))

GoogleDriveGoogleDriveFileにはファイルのコピーを作成するメソッドがないので、PyDrive2がラップしているgoogle-api-python-clientから、Files: copyエンドポイントを直接呼び出しているようです。

共有ドライブだと動かない?

共有ドライブにあるドキュメントのIDを指定したい場合は、コピーするコード (4) の変更が必要です。
supportsAllDrives引数にTrueを渡します。

copy_template.py
    # コピーを作成 (4)
    copied_file = {"title": dest_title}
    f = (
        drive.auth.service.files()
        .copy(fileId=source_id, body=copied_file, supportsAllDrives=True)
        .execute()
    )

https://developers.google.com/drive/api/v2/enable-shareddrives によると、

supportsAllDrives — Whether the requesting application supports both My Drives and shared drives. If false, then shared drive items are not included in the response.

なので、Trueと指定することで共有ドライブのファイルも操作できます。
この引数を指定しない場合は、File not foundとDrive APIから返ってきます。

supportsAllDrives=Trueが指定されていても、マイドライブのファイルはコピーできます。
そのため、常に指定してもいいと思いますが、2020年6月以降は指定不要になるようです。
Files: copyエンドポイントのドキュメントによると、2020年6月1日以降はsupportsAllDrives引数が廃止され、指定しなくても共有ドライブのファイルを操作できる2そうです。

共有ドライブの操作は、PyDriveにいくつかIssueが見られました3が、PyDrive2では対応済み4のようです。

スコープが絞れるか

PythonでGoogleドライブを操作するパッケージPyDrive2の認証まわり にて述べたように、広めのスコープ(https://www.googleapis.com/auth/drive)を使っています。
ファイルの複製をする上でこれを絞れるか指定を試したのですが、絞れませんでした5

Files: copyエンドポイントのスコープを参照したところ、https://www.googleapis.com/auth/drive からは絞れなさそうとわかりました。

終わりに

PyDrive2でGoogleドキュメントのIDを指定して複製する方法についてまとめました。

ドキュメントもスプレッドシートもGoogleドライブから見ればどちらもファイルです。
ですので、この方法でドキュメント以外も複製できると考えています(後日検証予定)。


  1. PyDrive2のドキュメントとしてPyDriveのドキュメントが案内されています(2020年5月時点) 

  2. 「Deprecated - Whether the requesting application supports both My Drives and shared drives. This parameter will only be effective until June 1, 2020. Afterwards all applications are assumed to support shared drives. (Default: false)」 

  3. Downloading files from Team Drive #160 

  4. 2020年1月のコミットで取り込まれていました 

  5. スコープとしてdrive.filedrive.metadataやその組合せを試しましたが、The user has not granted the app {appId} {verb} access to the file {fileId} というエラーが解消できていません