はじめに#
「OKAZAKI Shogo のひとりアドベントカレンダー2024」の17日目です。 ファイルのアップロード機能に、 Google Drive へのアップロード機能をつけていきます。
サービスアカウント経由で Google ドライブにファイルをアップロードするための準備#
ボーイスカウトでは、 Google Workspace のアカウントが配布されているので、そこに割り当てられている Google Drive を利用する。
普通のユーザーではなく、アプリケーションなどの人以外のシステムが使う IAM ユーザーである「サービスアカウント」を作成して利用する。
Google Cloud Project を作成する#
Google 公式のドキュメントに沿って、今回のアプリで利用するプロジェクトを作成する。
今回は、プロジェクト ID を bshssa-member-system
としている。
Google Drive の API を有効化#
プロジェクトが作成できたら、左メニューの「すべてのプロダクト」を選択
「API とサービス」を選択する
検索欄から「Google Drive」と検索し、「Google Drive API」を選択
「有効にする」を押して、 API を有効化する。
サービスアカウントを作成する#
再度、「API とサービス」の画面に戻り、上の「認証情報を作成」>「サービスアカウント」を選択。
内容を適当に埋めていく。
「ロールを選択」から「基本」>「編集者」を選択
最後に「完了」を押してサービスアカウントの作成を完了する。
秘密鍵生成#
再度、「API とサービス」の画面に戻り、「認証情報」を選択。先ほど作成したサービスアカウントを選択する。
「鍵」のタブを選択し、「キーを追加」を選択。
秘密鍵は JSON のものを選択して作成する。
JSON がダウンロードされたら、client_email:
の後に記述されているメールアドレスをコピーしておく。
Google Drive の画面に行き、サービスアカウントで利用したいフォルダを選択し、「共有」を押す。
先ほど作成したメールアドレスを編集者として追加して共有する。
これで、 JSON に書かれた秘密鍵をリヨすいて、先ほど共有設定をしたフォルダを操作することができるようになる。
Google Drive を Python で操作する#
必要なライブラリを追加する。
1poetry add google-api-python-client google-auth-httplib2 google-auth-oauthlib
Google Drive を操作するためのコード#
今回はアップロードするためのコードを実装する。
app/util/google_drive.py
1import logging
2import os
3from dataclasses import dataclass
4from typing import List, Optional
5
6from google.oauth2 import service_account
7from googleapiclient.discovery import build
8from googleapiclient.http import MediaFileUpload
9from instance.config.dev import CREDENTIAL_SERVICE_ACCOUNT_PATH
10
11error_logger = logging.getLogger("error")
12app_logger = logging.getLogger("app")
13
14
15@dataclass(frozen=True)
16class MimeMapping:
17 """ファイルの拡張子と MINE タイプを対応づけるデータクラス"""
18
19 extension: str
20 mine_type: str
21
22
23# 対応可能なファイルタイプ一覧
24# 参考資料: https://developer.mozilla.org/ja/docs/Web/HTTP/MIME_types/Common_types
25MIME_MAPPINGS = [
26 MimeMapping(".pdf", "application/pdf"),
27 MimeMapping(".doc", "application/msword"),
28 MimeMapping(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
29 MimeMapping(".xls", "application/vnd.ms-excel"),
30 MimeMapping(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
31]
32
33# Google Drive スコープ定数
34GOOGLE_DRIVE_SCOPE_EDIT = ["https://www.googleapis.com/auth/drive"]
35GOOGLE_DRIVE_SCOPE_READONLY = ["https://www.googleapis.com/auth/drive.readonly"]
36
37# Google Drive のフォルダ ID
38GOOGLE_DRIVE_FOLDER_ID = "1tmbiC2eiHxenEVF6X5dtfSDIQ8lxIGJ6"
39
40
41def __get_mime_type(extension: str) -> Optional[str]:
42 """特定の拡張子の MIME Type を取得する関数"""
43 for mapping in MIME_MAPPINGS:
44 if mapping.extension == extension:
45 return mapping.mine_type
46 # 未知の拡張子の場合のデフォルト
47 return None
48
49
50def __get_document_path(file_name: str) -> Optional[str]:
51 """documentsフォルダ内の指定されたファイルの絶対パスを返す。
52
53 :param file_name: 参照したいファイル名
54 :return: ファイルの絶対パス
55 """
56 # このスクリプトの位置からプロジェクトのルートディレクトリを計算
57 project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
58 documents_path = os.path.join(project_root, "documents", file_name)
59 app_logger.debug(documents_path)
60
61 # ファイルの存在を確認
62 if not os.path.exists(documents_path):
63 error_logger.error(f"'{file_name}' does not exist in 'documents'.")
64 return None
65
66 return documents_path
67
68
69def __get_credentials(scopes: List[str]):
70 """スコープを受け取り、Google のクレデンシャル情報を返却する"""
71 return service_account.Credentials.from_service_account_file(CREDENTIAL_SERVICE_ACCOUNT_PATH, scopes=scopes)
72
73
74def upload_file_to_gdrive(file_name: str) -> bool:
75 """指定されたファイル名のファイルを Google Drive にアップロードする"""
76 try:
77 app_logger.debug(f"file_name:{file_name}")
78 file_path = __get_document_path(file_name)
79 app_logger.debug(f"file_path:{file_path}")
80
81 _, extension = os.path.splitext(file_path)
82 app_logger.debug(f"ext: {extension}")
83
84 mine_type = __get_mime_type(extension)
85 app_logger.debug(f"mine_type:{mine_type}")
86
87 if file_path and mine_type:
88 credentials = __get_credentials(GOOGLE_DRIVE_SCOPE_EDIT)
89 drive_service = build("drive", "v3", credentials=credentials)
90 media = MediaFileUpload(file_path, mimetype=mine_type, resumable=True)
91 file_metadata = {"name": file_name, "mimeType": mine_type, "parents": [GOOGLE_DRIVE_FOLDER_ID]}
92 # Google Driveへのアップロード処理
93 file = drive_service.files().create(body=file_metadata, media_body=media).execute()
94 app_logger.info(file)
95 return True
96 return False
97 except Exception as e:
98 error_logger.error(f"Error uploading file: {e}")
99 return False
upload_file_to_gdrive()
を Flask のコード内で用いれば、指定したファイルを Google Drive にアップすることができる。