基于object.finalized触发器的GCS转Google Drive代码报错求助
问题:GCS文件复制到Google Drive的Cloud Function报错AttributeError
问题背景
通过object.finalized类型Cloud Function触发器,将GCS存储桶文件复制到指定Google Drive文件夹,代码运行后出现AttributeError: 'str' object has no attribute 'write'错误。
原代码
import functions_framework import base64 import tempfile import traceback from google.cloud import storage from googleapiclient.discovery import build import google.auth from googleapiclient.http import MediaFileUpload def upload_to_drive(temp_file, remote_path, credentials): """Uploads a file to the specified folder in Google Drive. Args: temp_file (tempfile.NamedTemporaryFile): Temporary file object containing the data to upload. remote_path (str): Name of the file to be created in Google Drive. credentials (google.auth.credentials.Credentials): Authentication credentials. """ folder_id = str("1nNqtRloaFT0f71").split(",") service = build('drive', 'v3', credentials=credentials) print(f"Uploading file: {temp_file.name} to Google Drive folder: {folder_id}") # Use media body directly for efficient upload from temporary file response = service.files().create( body={ "name": remote_path.split("/")[-1], "parents": folder_id, "mimeType": "text/csv" # Set MIME type for CSV files }, media_body=temp_file ).execute() print(f"File uploaded successfully: {response['id']}") def download_file_from_gcs(bucket, key): """ Downloads a file from Cloud Storage and writes it to a temporary file. Args: bucket (str): Name of the Cloud Storage bucket. key (str): Name of the file in the bucket. Returns: str: Path to the downloaded file within the temporary location. """ print(f"Downloading key: {key} in bucket: {bucket}") client = storage.Client() source_bucket = client.bucket(bucket) blob_object = source_bucket.blob(key) with tempfile.NamedTemporaryFile(delete=False) as temp_file: blob_object.download_to_file(temp_file.name) local_path = temp_file.name # Capture the temporary file path return local_path # Triggered by a change in a storage bucket @functions_framework.cloud_event def drive_upload(cloud_event): data = cloud_event.data print("Full event data:", data) try: # Access bucket name assuming a nested structure bucket_name = data["bucket"] file_name = data["name"] print(f"File name extracted from event data: {file_name}") print(f"Bucket name extracted from event data: {bucket_name}") except KeyError: print("Bucket or file name not found in event data.") return # Gracefully exit the function if file_name in ["Trials", "Control"]: try: # Credentials for Google Drive API access credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/drive"]) # Downloading file from GCS with temporary file with tempfile.NamedTemporaryFile(delete=False) as temp_file: local = download_file_from_gcs(bucket_name, file_name) print(f("Temp File Path: {local}")) # Uploading the file to drive upload_to_drive(temp_file, file_name, credentials) print(f'File {file_name} copied to Google Drive folder') except Exception as e: print(traceback.format_exc())
错误日志
2024-03-17 23:18:25.996 SAST Traceback (most recent call last): File "/workspace/main.py", line 74, in drive_upload local = download_file_from_gcs(bucket_name, file_name) 2024-03-17 23:18:25.996 SAST ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2024-03-17 23:18:25.996 SAST File "/workspace/main.py", line 49, in download_file_from_gcs 2024-03-17 23:18:25.996 SAST blob_object.download_to_file(temp_file.name) 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 1150, in download_to_file 2024-03-17 23:18:25.996 SAST self._prep_and_do_download( 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 4352, in _prep_and_do_download 2024-03-17 23:18:25.996 SAST self._do_download( 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 1007, in _do_download 2024-03-17 23:18:25.996 SAST response = download.consume(transport, timeout=timeout) 2024-03-17 23:18:25.996 SAST ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 237, in consume 2024-03-17 23:18:25.996 SAST return _request_helpers.wait_and_retry( 2024-03-17 23:18:25.996 SAST ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/resumable_media/requests/_request_helpers.py", line 155, in wait_and_retry 2024-03-17 23:18:25.996 SAST response = func() 2024-03-17 23:18:25.996 SAST ^^^^^^ 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 233, in retriable_request 2024-03-17 23:18:25.996 SAST self._write_to_stream(result) 2024-03-17 23:18:25.996 SAST File "/layers/google.python.pip/pip/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 124, in _write_to_stream 2024-03-17 23:18:25.996 SAST self._stream.write(chunk) 2024-03-17 23:18:25.996 SAST ^^^^^^^^^^^^^^^^^^ 2024-03-17 23:18:25.996 SAST AttributeError: 'str' object has no attribute 'write'
问题分析
- 核心错误原因:
download_file_from_gcs函数中,blob_object.download_to_file方法需要接收打开的文件对象(具备write方法),但传入的是temp_file.name(字符串类型的文件路径),导致内部调用write时触发AttributeError。 - 次要问题:
drive_upload中重复创建临时文件,download_file_from_gcs内部已创建临时文件,外层无需再创建print(f("Temp File Path: {local}"))存在语法错误,多了一层括号folder_id = str("1nNqtRloaFT0f71").split(",")写法冗余,直接定义列表即可upload_to_drive接收的临时文件对象与实际下载的文件路径不匹配,逻辑混乱
修复方案
修正后的完整代码
import functions_framework import tempfile import traceback from google.cloud import storage from googleapiclient.discovery import build import google.auth from googleapiclient.http import MediaFileUpload def upload_to_drive(file_path, remote_path, credentials): """Uploads a file to the specified folder in Google Drive. Args: file_path (str): Local path to the file to upload. remote_path (str): Name of the file to be created in Google Drive. credentials (google.auth.credentials.Credentials): Authentication credentials. """ # 直接定义目标文件夹ID列表 folder_id = ["1nNqtRloaFT0f71"] service = build('drive', 'v3', credentials=credentials) print(f"Uploading file: {file_path} to Google Drive folder: {folder_id}") # 使用MediaFileUpload加载本地文件 media_body = MediaFileUpload(file_path, mimetype="text/csv") response = service.files().create( body={ "name": remote_path.split("/")[-1], "parents": folder_id }, media_body=media_body ).execute() print(f"File uploaded successfully: {response['id']}") def download_file_from_gcs(bucket, key): """ Downloads a file from Cloud Storage and writes it to a temporary file. Args: bucket (str): Name of the Cloud Storage bucket. key (str): Name of the file in the bucket. Returns: str: Path to the downloaded file within the temporary location. """ print(f"Downloading key: {key} in bucket: {bucket}") client = storage.Client() source_bucket = client.bucket(bucket) blob_object = source_bucket.blob(key) # 使用download_to_filename直接下载到临时文件路径,无需手动打开文件对象 with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as temp_file: blob_object.download_to_filename(temp_file.name) local_path = temp_file.name return local_path # Triggered by a change in a storage bucket @functions_framework.cloud_event def drive_upload(cloud_event): data = cloud_event.data print("Full event data:", data) try: bucket_name = data["bucket"] file_name = data["name"] print(f"File name extracted from event data: {file_name}") print(f"Bucket name extracted from event data: {bucket_name}") except KeyError: print("Bucket or file name not found in event data.") return if file_name in ["Trials", "Control"]: try: # 获取Drive API授权凭证 credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/drive"]) # 从GCS下载文件到本地临时路径 local_file_path = download_file_from_gcs(bucket_name, file_name) print(f"Temp File Path: {local_file_path}") # 上传到Google Drive upload_to_drive(local_file_path, file_name, credentials) print(f'File {file_name} copied to Google Drive folder') except Exception as e: print(traceback.format_exc())
关键修复点
- 将
download_file_from_gcs中的download_to_file(temp_file.name)改为download_to_filename(temp_file.name),该方法直接接收文件路径,无需传入文件对象。 - 重构
upload_to_drive,改为接收文件路径,使用MediaFileUpload加载文件,避免文件对象关闭导致的问题。 - 移除
drive_upload中多余的临时文件创建逻辑,直接使用download_file_from_gcs返回的路径。 - 修复
print语句的语法错误,去掉多余括号。 - 简化
folder_id的定义方式,直接用列表存储目标文件夹ID。
内容的提问来源于stack exchange,提问作者Abdu Kikhia
相关产品推荐
相关产品推荐

