Python クライアントライブラリで Google Cloud Storage の参照・作成・更新・削除操作をするにはどのメソッドを使えばよいのか確認してみた

2020.05.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは、みかみです。

バッチ処理で BigQuery を使う場合に、データのロードやエクスポートなどで GCS を使うケースはよくあります。

やりたいこと

  • GCS Python クライアントライブラリから GCS を操作したい
  • GCS Python クライアントライブラリのどのメソッドでどんな操作ができるのか確認したい

目次

前提

gcloud CLI や GCS クライアントライブラリを実行できる環境は準備済みです。

サービスアカウントを作成

クライアントライブラリからはサービスアカウントで GCS にアクセスするため、まずは GCS アクセス権限のあるサービスアカウントを gcloud CLI で作成します。

[ec2-user@ip-10-0-43-239 test_gcs]$ gcloud iam service-accounts create test-gcs
Created service account [test-gcs].

なお、サービスアカウント名は、英数小文字とハイフンが許容されており、6~30 文字で入力する必要があるとのことです。

アカウント名にアンダースコアを入れようとしたところ、以下のエラーになりました。。

[ec2-user@ip-10-0-43-239 test_gcs]$ gcloud iam service-accounts create test_gcs
ERROR: (gcloud.iam.service-accounts.create) argument NAME: Bad value [test_gcs]: Service account name must be between 6 and 30 characters (inclusive), must begin with a lowercase letter, and consist of lowercase alphanumeric characters that can be separated by hyphens.
Usage: gcloud iam service-accounts create NAME [optional flags]
  optional flags may be  --description | --display-name | --help

For detailed information on this command and its flags, run:
  gcloud iam service-accounts create --help

続いて、作成したサービスアカウントに GCS を操作するためのロールを付与し、アカウントキーファイルを取得します。

今回はバケット作成などの一連の操作を確認したいため、Storage 管理者の事前定義ロールをサービスアカウントに付与しました。

[ec2-user@ip-10-0-43-239 test_gcs]$ gcloud projects add-iam-policy-binding cm-da-mikami-yuki-258308 --member "serviceAccount:test-gcs@cm-da-mikami-yuki-258308.iam.gserviceaccount.com" --role "roles/storage.admin"
Updated IAM policy for project [cm-da-mikami-yuki-258308].
bindings:
(省略)
- members:
  - serviceAccount:test-gcs@cm-da-mikami-yuki-258308.iam.gserviceaccount.com
  role: roles/storage.admin
(省略)
version: 1
[ec2-user@ip-10-0-43-239 test_gcs]$ gcloud iam service-accounts keys create test-gcs.json --iam-account test-gcs@cm-da-mikami-yuki-258308.iam.gserviceaccount.com
created key [034643e95f70ab698a041187e282329b081ee7e6] of type  as [test-gcs.json] for [test-gcs@cm-da-mikami-yuki-258308.iam.gserviceaccount.com]
[ec2-user@ip-10-0-43-239 test_gcs]$ ls | grep test-gcs.json
test-gcs.json

カレントディレクトリにアカウントキーファイルが作成されたことが確認できました。

なお、クライアントライブラリを使用する場合、アカウントキーファイルを環境変数( GOOGLE_APPLICATION_CREDENTIALS )に設定しておくとデフォルトのサービスアカウントとして使用できるので便利ですが、今回は Python コードで使用するサービスアカウントを指定するため、環境変数の設定は省略します。

バケット関連操作

クライアントライブラリから、バケットの参照や作成、更新、削除を実行してみます。

バケット情報参照

google.cloud.storage.Client クラスの list_buckets および get_bucket メソッドで、バケット情報が取得できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json
from pprint import pprint

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

# get list
buckets = client.list_buckets()
for obj in buckets:
    print('-------->')
    pprint(vars(obj))
    # get
    bucket = client.get_bucket(obj.id)
    print('\t-------->')
    pprint(vars(bucket))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket.py
-------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7faa3d7ca090>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7faa3d7ba0d0>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7faa3d7cfdd0>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAE=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-cm-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA',
                 'locationType': 'multi-region',
                 'metageneration': '1',
                 'name': 'test-cm-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-cm-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-13T04:53:58.641Z',
                 'updated': '2020-03-13T04:53:58.641Z'},
 '_user_project': None,
 'name': 'test-cm-mikami'}
        -------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7faa3d7cfcd0>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7faa3d7ba0d0>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7faa3d7cfb90>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAE=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-cm-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA',
                 'locationType': 'multi-region',
                 'metageneration': '1',
                 'name': 'test-cm-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-cm-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-13T04:53:58.641Z',
                 'updated': '2020-03-13T04:53:58.641Z'},
 '_user_project': None,
 'name': 'test-cm-mikami'}
-------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7faa3d7cffd0>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7faa3d7ba0d0>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7faa3d7cfc90>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAQ=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA-NORTHEAST1',
                 'locationType': 'region',
                 'metageneration': '4',
                 'name': 'test-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-24T08:47:02.958Z',
                 'updated': '2020-05-11T09:49:01.521Z'},
 '_user_project': None,
 'name': 'test-mikami'}
        -------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7faa3d7e6ed0>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7faa3d7ba0d0>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7faa3d7e6e10>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAQ=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA-NORTHEAST1',
                 'locationType': 'region',
                 'metageneration': '4',
                 'name': 'test-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-24T08:47:02.958Z',
                 'updated': '2020-05-11T09:49:01.521Z'},
 '_user_project': None,
 'name': 'test-mikami'}

google.cloud.storage.Client クラスには lookup_bucket というメソッドもあり、get_bucket と同じ情報が取得できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json
from pprint import pprint

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

# get list
buckets = client.list_buckets()
for obj in buckets:
    print('-------->')
    print(obj.id)
    # lookup
    bucket = client.lookup_bucket(obj.id)
    print('\t-------->')
    pprint(vars(bucket))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_lookup.py
-------->
test-cm-mikami
        -------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7f2748a10c50>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7f27489fc490>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7f2748a10dd0>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAE=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-cm-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA',
                 'locationType': 'multi-region',
                 'metageneration': '1',
                 'name': 'test-cm-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-cm-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-13T04:53:58.641Z',
                 'updated': '2020-03-13T04:53:58.641Z'},
 '_user_project': None,
 'name': 'test-cm-mikami'}
-------->
test-mikami
        -------->
{'_acl': <google.cloud.storage.acl.BucketACL object at 0x7f2748a26990>,
 '_changes': set(),
 '_client': <google.cloud.storage.client.Client object at 0x7f27489fc490>,
 '_default_object_acl': <google.cloud.storage.acl.DefaultObjectACL object at 0x7f2748a26b50>,
 '_label_removals': set(),
 '_properties': {'defaultEventBasedHold': False,
                 'etag': 'CAQ=',
                 'iamConfiguration': {'bucketPolicyOnly': {'enabled': False},
                                      'uniformBucketLevelAccess': {'enabled': False}},
                 'id': 'test-mikami',
                 'kind': 'storage#bucket',
                 'location': 'ASIA-NORTHEAST1',
                 'locationType': 'region',
                 'metageneration': '4',
                 'name': 'test-mikami',
                 'projectNumber': '797147019523',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/test-mikami',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-03-24T08:47:02.958Z',
                 'updated': '2020-05-11T09:49:01.521Z'},
 '_user_project': None,
 'name': 'test-mikami'}

クライアントライブラリのコードを確認したところ、内部的には get_bucket を実行していました。

    def lookup_bucket(self, bucket_name, timeout=_DEFAULT_TIMEOUT):
(省略)
        try:
            return self.get_bucket(bucket_name, timeout=timeout)
        except NotFound:
            return None

バケット作成

google.cloud.storage.Client クラスの create_bucket で、新規バケットを作成できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
bucket = client.create_bucket(bucket_name)
print("Bucket {} created.".format(bucket.name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_create.py
Bucket bucket_mikami created.

google.cloud.storage.Bucket クラスの create メソッドでも、同じくバケット作成可能です。

(省略)
bucket_name = 'bucket_mikami_2'
bucket = client.bucket(bucket_name)
bucket.create()
print("Bucket {} created.".format(bucket.name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_create_2.py
Bucket bucket_mikami_2 created.

バケット更新

google.cloud.storage.Bucket クラスの update メソッドでは、バケット情報を更新できます。

試しに、ラベルの付与とバージョニング設定を更新してみました。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
bucket = client.get_bucket(bucket_name)
print('labels: {}'.format(bucket.labels))
print('versioning_enabled: {}'.format(bucket.versioning_enabled))

# update
bucket.labels = dict(description='test-update')
bucket.versioning_enabled = True
bucket.update()
print("Update bucket {}.".format(bucket.name))

bucket_updated = client.get_bucket(bucket_name)
print('labels: {}'.format(bucket_updated.labels))
print('versioning_enabled: {}'.format(bucket_updated.versioning_enabled))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_update.py
labels: {}
versioning_enabled: False
Update bucket bucket_mikami.
labels: {'description': 'test-update'}
versioning_enabled: True

なお、ラベルには半角スペースは使用できません。

はじめ、以下のように半角スペース込みの文字列でラベル付与しようとしたところ、Invalid argument エラーになりました。

(省略)
# update
bucket.labels = dict(description='for test update')
bucket.versioning_enabled = True
bucket.update()
print("Update bucket {}.".format(bucket.name))
(省略)
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_update.py
labels: {}
versioning_enabled: False
Traceback (most recent call last):
  File "bucket_update.py", line 23, in <module>
    bucket.update()
  File "/home/ec2-user/test_gcs/lib64/python3.7/site-packages/google/cloud/storage/_helpers.py", line 246, in update
    timeout=timeout,
  File "/home/ec2-user/test_gcs/lib64/python3.7/site-packages/google/cloud/_http.py", line 423, in api_request
    raise exceptions.from_http_response(response)
google.api_core.exceptions.BadRequest: 400 PUT https://storage.googleapis.com/storage/v1/b/bucket_mikami?projection=full: Invalid argument

バケット削除

google.cloud.storage.Bucket クラスの delete メソッドで、バケットを削除します。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
bucket = client.get_bucket(bucket_name)

# delete
bucket.delete()
print("Bucket {} deleted.".format(bucket.name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python bucket_delete.py
Bucket bucket_mikami deleted.

オブジェクト関連操作

バッチなどの処理では、バケット関連の操作よりも、データの参照やオブジェクト作成など、オブジェクト関連の操作がメインになるかと思います。

クライアントライブラリから、オブジェクトの参照や作成、更新、削除を実行してみます。

オブジェクト情報参照

google.cloud.storage.Client クラスの list_blobs メソッドで、バケットにアップロード済みのオブジェクトの一覧を取得できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json
from pprint import pprint

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
blobs = client.list_blobs(bucket_name)
for blob in blobs:
    print('-------->')
    pprint(vars(blob))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object.py
-------->
{'_acl': <google.cloud.storage.acl.ObjectACL object at 0x7f6bdd0e8210>,
 '_bucket': <Bucket: bucket_mikami>,
 '_changes': set(),
 '_chunk_size': None,
 '_encryption_key': None,
 '_properties': {'bucket': 'bucket_mikami',
                 'contentType': 'application/vnd.ms-excel',
                 'crc32c': 'fl4Y3g==',
                 'etag': 'CKz62smBxOkCEAE=',
                 'generation': '1590031675735340',
                 'id': 'bucket_mikami/sample.csv/1590031675735340',
                 'kind': 'storage#object',
                 'md5Hash': 'tYiYF8Bmy8fF5+5bRkpt0Q==',
                 'mediaLink': 'https://storage.googleapis.com/download/storage/v1/b/bucket_mikami/o/sample.csv?generation=1590031675735340&alt=media',
                 'metageneration': '1',
                 'name': 'sample.csv',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/bucket_mikami/o/sample.csv',
                 'size': '1735',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-05-21T03:27:55.735Z',
                 'timeStorageClassUpdated': '2020-05-21T03:27:55.735Z',
                 'updated': '2020-05-21T03:27:55.735Z'},
 'name': 'sample.csv'}

同じ操作は google.cloud.storage.Bucket クラス list_blobs でも可能です。

また、Bucket クラスには get_blob メソッドもあります。

(省略)
bucket_name = 'bucket_mikami'
# get list
bucket = client.get_bucket(bucket_name)
blobs = bucket.list_blobs()
for obj in blobs:
    print('-------->')
    pprint(vars(obj))
    # get
    blob = bucket.get_blob(obj.name)
    print('\t-------->')
    pprint(vars(blob))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_2.py
-------->
{'_acl': <google.cloud.storage.acl.ObjectACL object at 0x7f7c22c31e90>,
 '_bucket': <Bucket: bucket_mikami>,
 '_changes': set(),
 '_chunk_size': None,
 '_encryption_key': None,
 '_properties': {'bucket': 'bucket_mikami',
                 'contentType': 'application/vnd.ms-excel',
                 'crc32c': 'fl4Y3g==',
                 'etag': 'CKz62smBxOkCEAE=',
                 'generation': '1590031675735340',
                 'id': 'bucket_mikami/sample.csv/1590031675735340',
                 'kind': 'storage#object',
                 'md5Hash': 'tYiYF8Bmy8fF5+5bRkpt0Q==',
                 'mediaLink': 'https://storage.googleapis.com/download/storage/v1/b/bucket_mikami/o/sample.csv?generation=1590031675735340&alt=media',
                 'metageneration': '1',
                 'name': 'sample.csv',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/bucket_mikami/o/sample.csv',
                 'size': '1735',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-05-21T03:27:55.735Z',
                 'timeStorageClassUpdated': '2020-05-21T03:27:55.735Z',
                 'updated': '2020-05-21T03:27:55.735Z'},
 'name': 'sample.csv'}
        -------->
{'_acl': <google.cloud.storage.acl.ObjectACL object at 0x7f7c22c4a950>,
 '_bucket': <Bucket: bucket_mikami>,
 '_changes': set(),
 '_chunk_size': None,
 '_encryption_key': None,
 '_properties': {'bucket': 'bucket_mikami',
                 'contentType': 'application/vnd.ms-excel',
                 'crc32c': 'fl4Y3g==',
                 'etag': 'CKz62smBxOkCEAE=',
                 'generation': '1590031675735340',
                 'id': 'bucket_mikami/sample.csv/1590031675735340',
                 'kind': 'storage#object',
                 'md5Hash': 'tYiYF8Bmy8fF5+5bRkpt0Q==',
                 'mediaLink': 'https://storage.googleapis.com/download/storage/v1/b/bucket_mikami/o/sample.csv?generation=1590031675735340&alt=media',
                 'metageneration': '1',
                 'name': 'sample.csv',
                 'selfLink': 'https://www.googleapis.com/storage/v1/b/bucket_mikami/o/sample.csv',
                 'size': '1735',
                 'storageClass': 'STANDARD',
                 'timeCreated': '2020-05-21T03:27:55.735Z',
                 'timeStorageClassUpdated': '2020-05-21T03:27:55.735Z',
                 'updated': '2020-05-21T03:27:55.735Z'},
 'name': 'sample.csv'}

オブジェクト作成

google.cloud.storage.Bucket クラスの upload_from_string メソッドで、テキスト文字列を GCS オブジェクトとして出力してみます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
blob_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(blob_name)

stream = 'GCS オブジェクト作成のテスト'
blob.upload_from_string(stream)
print("Blob {} created.".format(blob_name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_create.py
Blob test.csv created.

管理コンソールからも、オブジェクトが作成されたことが確認できました。

ダウンロードしてファイルの中身も確認してみます。

GCS オブジェクト作成のテスト

ちゃんと Python コード内で指定した文字列がファイル出力されていることが確認できました。

また、upload_from_filename メソッドで、ローカルファイルをバケットにアップロードすることもできます。

Python コードと同じディレクトリに配置した、以下の都道府県コードのデータファイルをアップロードします。

code,name
01,北海道
02,青森県
03,岩手県
(省略)
47,沖縄県
(省略)
bucket_name = 'bucket_mikami'
file_name = 'pref_code_utf8.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(file_name)

blob.upload_from_filename(file_name)
print("Blob {} created.".format(file_name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_create_from_file.py
Blob pref_code_utf8.csv created.

確認してみます。

(test_gcs) [ec2-user@ip-10-0-43-239 temp]$ gsutil cp gs://bucket_mikami/pref_code_utf8.csv ./
Copying gs://bucket_mikami/pref_code_utf8.csv...
/ [1 files][  676.0 B/  676.0 B]
Operation completed over 1 objects/676.0 B.
(test_gcs) [ec2-user@ip-10-0-43-239 temp]$ view pref_code_utf8.csv
code,name
01,北海道
02,青森県
03,岩手県
04,宮城県
05,秋田県
(省略)
45,宮崎県
46,鹿児島県
47,沖縄県

ちゃんとアップロードされたことが確認できました。

upload_from_file メソッドを使えば、データを格納したファイルオブジェクトからGCS オブジェクトを作成することもできます。

クライアントライブラリのコードを確認してみると、動作確認した upload_from_stringupload_from_filename も、内部的には upload_from_file を使用していました。

(省略)
class Blob(_PropertyMixin):
(省略)
    def upload_from_filename(
        self,
        filename,
        content_type=None,
        client=None,
        predefined_acl=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
    ):
(省略)
        content_type = self._get_content_type(content_type, filename=filename)

        with open(filename, "rb") as file_obj:
            total_bytes = os.fstat(file_obj.fileno()).st_size
            self.upload_from_file(
                file_obj,
                content_type=content_type,
                client=client,
                size=total_bytes,
                predefined_acl=predefined_acl,
                if_generation_match=if_generation_match,
                if_generation_not_match=if_generation_not_match,
                if_metageneration_match=if_metageneration_match,
                if_metageneration_not_match=if_metageneration_not_match,
            )

    def upload_from_string(
        self,
        data,
        content_type="text/plain",
        client=None,
        predefined_acl=None,
        if_generation_match=None,
        if_generation_not_match=None,
        if_metageneration_match=None,
        if_metageneration_not_match=None,
    ):
(省略)
        data = _to_bytes(data, encoding="utf-8")
        string_buffer = BytesIO(data)
        self.upload_from_file(
            file_obj=string_buffer,
            size=len(data),
            content_type=content_type,
            client=client,
            predefined_acl=predefined_acl,
            if_generation_match=if_generation_match,
            if_generation_not_match=if_generation_not_match,
            if_metageneration_match=if_metageneration_match,
            if_metageneration_not_match=if_metageneration_not_match,
        )

google.cloud.storage.Bucket クラスには、既存のオブジェクトを別のバケットにコピーする copy_blob メソッドもあります。

(省略)
bucket_name = 'bucket_mikami'
bucket = client.get_bucket(bucket_name)
bucket_name_dst = 'test-mikami'
dst_bucket = client.get_bucket(bucket_name_dst)

blob_name = 'test.csv'
blob = bucket.blob(blob_name)
new_blob = bucket.copy_blob(blob, dst_bucket)
new_blob.acl.save(blob.acl)
print("Blob {} copied.".format(blob_name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_copy.py
Blob test.csv copied.

コピー元バケットのロケーションは US マルチリージョン、コピー先は東京リージョンでしたが、問題なくコピーできました。

(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ gsutil ls -L -b gs://bucket_mikami/ | grep Location
        Location type:                  multi-region
        Location constraint:            US
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ gsutil ls -L -b gs://test-mikami/ | grep Location
        Location type:                  region
        Location constraint:            ASIA-NORTHEAST1

オブジェクトデータ取得

google.cloud.storage.Blob クラスの download_as_string メソッドで、GCS オブジェクトデータを取得できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
blob_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(blob_name)

stream = blob.download_as_string()
print("read: [{}]".format(stream.decode('utf-8')))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_read.py
read: [GCS オブジェクト作成のテスト]

なお、オブジェクトデータは bytes 型で返却されます。取得結果をそのまま print したところ以下の出力だったため、デコードを追加して print しました。。

(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_read.py
read: [b'GCS \xe3\x82\xaa\xe3\x83\x96\xe3\x82\xb8\xe3\x82\xa7\xe3\x82\xaf\xe3\x83\x88\xe4\xbd\x9c\xe6\x88\x90\xe3\x81\xae\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88']

オブジェクトをファイルとしてダウンロードする場合は、download_to_filename メソッドが便利です。

(省略)
bucket_name = 'bucket_mikami'
file_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(file_name)

blob.download_to_filename(file_name)
print("File {} downloaded.".format(file_name))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_read_file.py
File test.csv downloaded.
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ ls -l | grep test.csv
-rw-rw-r-- 1 ec2-user ec2-user   40 May 21 10:01 test.csv

オブジェクト作成同様、download_to_file で、ファイルオブジェクトとして取得することもできます。

また、google.cloud.storage.Client クラスにも、GCS オブジェクトをファイルオブジェクトとして取得する download_blob_to_file メソッドがあります。

(省略)
bucket_name = 'bucket_mikami'
file_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(file_name)

buffer = BytesIO()
with BytesIO() as stream:
    client.download_blob_to_file(blob, stream)
    print("read: [{}]".format(stream.getvalue().decode('utf-8')))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_read_client.py
read: [GCS オブジェクト作成のテスト]

download_blob_to_file のパラメータには、オブジェクトの uri を指定することもできます。

(省略)
path = 'gs://bucket_mikami/test.csv'
buffer = BytesIO()
with BytesIO() as stream:
    client.download_blob_to_file(path, stream)
    print("read: [{}]".format(stream.getvalue().decode('utf-8')))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_read_client_2.py
read: [GCS オブジェクト作成のテスト]

オブジェクトのパスが分かっている場合には、こちらの方がシンプルに取得できました。

オブジェクト削除

google.cloud.storage.Blob クラスの delete メソッドで、オブジェクトを削除できます。

from google.cloud import storage
from google.oauth2 import service_account
import os
import json

key_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test-gcs.json')
service_account_info = json.load(open(key_path))
credentials = service_account.Credentials.from_service_account_info(service_account_info)
client = storage.Client(
    credentials=credentials,
    project=credentials.project_id,
)

bucket_name = 'bucket_mikami'
blob_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(blob_name)

print('blobs.exists -> {}'.format(blob.exists()))
blob.delete()
print("Blob {} deleted.".format(blob_name))
print('blobs.exists -> {}'.format(blob.exists()))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_delete.py
blobs.exists -> True
Blob test.csv deleted.
blobs.exists -> False

オブジェクトの削除は google.cloud.storage.Bucket クラスの delete_blob メソッドでも可能です。

(省略)
bucket_name = 'bucket_mikami'
blob_name = 'test.csv'
bucket = client.get_bucket(bucket_name)
blob = bucket.blob(blob_name)

print('blobs.exists -> {}'.format(blob.exists()))
bucket.delete_blob(blob_name)
print("Blob {} deleted.".format(blob_name))
print('blobs.exists -> {}'.format(blob.exists()))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_delete_2.py
blobs.exists -> True
Blob test.csv deleted.
blobs.exists -> False

また、delete_blobs メソッドを使えば、複数のオブジェクトを一気に削除することもできます。

(省略)
bucket_name = 'bucket_mikami'
bucket = client.get_bucket(bucket_name)

blobs = bucket.list_blobs()
print('num: {}'.format(len(list(blobs))))

blobs = bucket.list_blobs()
bucket.delete_blobs(blobs)
print("Blob list deleted.")

blobs = bucket.list_blobs()
print('num: {}'.format(len(list(blobs))))
(test_gcs) [ec2-user@ip-10-0-43-239 test_gcs]$ python object_delete_list.py
num: 2
Blob list deleted.
num: 0

GCS 操作関連メソッド一覧

どのクラスのどのメソッドでどんな操作ができるのか、下表にまとめました。

function class method
バケット情報参照 google.cloud.storage.Client list_buckets
get_bucket
lookup_bucket
バケット作成 google.cloud.storage.Client create_bucket
google.cloud.storage.Bucket create
バケット更新 google.cloud.storage.Bucket update
バケット削除 google.cloud.storage.Bucket delete
オブジェクト情報参照 google.cloud.storage.Client list_blobs
google.cloud.storage.Bucket list_blobs
get_blob
オブジェクト作成 google.cloud.storage.Bucket copy_blob
google.cloud.storage.Blob upload_from_file
upload_from_filename
upload_from_string
オブジェクトデータ取得 google.cloud.storage.Client download_blob_to_file
google.cloud.storage.Blob download_to_file
download_to_filename
download_as_string
オブジェクト削除 google.cloud.storage.Bucket delete_blob
delete_blobs
google.cloud.storage.Blob delete

まとめ(所感)

GCS Python クライアントライブラリには、同じ操作ができるメソッドが複数あるので、処理に合わせて使い分けることができそうです。

どのクラスにどんなメソッドがあるか、パラメータに何を指定すればよいかなど、よく分からない場合はソースコードを直接参照すると良さそうです。

参考