クラウドストレージ用プロバイダ
This commit is contained in:
parent
0055c66731
commit
84c2b9f2e7
100
src/providers/google_cloud_storage_provider.py
Normal file
100
src/providers/google_cloud_storage_provider.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import os
|
||||||
|
import io
|
||||||
|
from typing import Optional, List, Dict, Any, Union, BinaryIO,
|
||||||
|
from datetime import timedelta
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from google.cloud import storage
|
||||||
|
|
||||||
|
from google.oauth2 import service_account
|
||||||
|
|
||||||
|
from lib.custom_logger import get_logger
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
class GoogleCloudStorageProvider:
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, cred_path: Optional[str] = None, project: Optional[str] = None):
|
||||||
|
try:
|
||||||
|
if cred_path:
|
||||||
|
creds = service_account.Credentials.from_service_account_file(cred_path)
|
||||||
|
# プロジェクト未指定なら credentials から取得
|
||||||
|
effective_project = project or creds.project_id
|
||||||
|
self._client = storage.Client(
|
||||||
|
project=effective_project, credentials=creds
|
||||||
|
)
|
||||||
|
logger.info(f"GCS client initialized with service account file. project={effective_project}")
|
||||||
|
elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON"):
|
||||||
|
cred_json = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
|
||||||
|
creds = service_account.Credentials.from_service_account_info(cred_json)
|
||||||
|
effective_project = project or creds.project_id
|
||||||
|
self._client = storage.Client(
|
||||||
|
project=effective_project, credentials=creds
|
||||||
|
)
|
||||||
|
logger.info("GCS client initialized with credentials from environment variable.")
|
||||||
|
else:
|
||||||
|
self._client = storage.Client(project=project)
|
||||||
|
logger.info("GCS client initialized with default credentials (ADC).")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"GCS initialization failed: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Private methods to get bucket and blob references
|
||||||
|
def _bucket(self, bucket: str) -> storage.Bucket:
|
||||||
|
return self._client.bucket(bucket)
|
||||||
|
|
||||||
|
def _blob(self, bucket: str, object_name: str) -> storage.Blob:
|
||||||
|
return self._bucket(bucket).blob(object_name)
|
||||||
|
|
||||||
|
|
||||||
|
# バケット操作
|
||||||
|
def get_buckets(self) -> List[str]:
|
||||||
|
buckets: List[storage.Bucket] = self._client.list_buckets()
|
||||||
|
return [b.name for b in buckets]
|
||||||
|
|
||||||
|
def create_bucket(self, bucket_name: str, location: str = "ASIA-NORTHEAST1", storage_class: str = "STANDARD"):
|
||||||
|
b = storage.Bucket(self._client, name=bucket_name)
|
||||||
|
b.storage_class = storage_class
|
||||||
|
return self._client.create_bucket(b, location=location)
|
||||||
|
|
||||||
|
def is_exists_bucket(self, bucket_name: str) -> bool:
|
||||||
|
try:
|
||||||
|
self._client.get_bucket(bucket_name)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# オブジェクト操作
|
||||||
|
def get_items(self, bucket: str, prefix: str | None = None) -> List[Dict[str, Any]]:
|
||||||
|
items: List[storage.Blob] = self._client.list_blobs(bucket, prefix=prefix)
|
||||||
|
return [{"name": bl.name, "size": bl.size, "updated": bl.updated, "content_type": bl.content_type}
|
||||||
|
for bl in items]
|
||||||
|
|
||||||
|
def is_exists_item(self, bucket: str, object_name: str) -> bool:
|
||||||
|
return self._blob(bucket, object_name).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def write_item(self, bucket: str, object_name: str, data: Union[bytes, BinaryIO, str],
|
||||||
|
content_type: str | None = None) -> Dict[str, Any]:
|
||||||
|
blob = self._blob(bucket, object_name)
|
||||||
|
if content_type is None:
|
||||||
|
content_type = mimetypes.guess_type(object_name)[0] or "application/octet-stream"
|
||||||
|
blob.content_type = content_type
|
||||||
|
|
||||||
|
if isinstance(data, (bytes, bytearray)):
|
||||||
|
blob.upload_from_file(io.BytesIO(data), content_type=content_type, rewind=True)
|
||||||
|
elif hasattr(data, "read"):
|
||||||
|
blob.upload_from_file(data, content_type=content_type, rewind=True)
|
||||||
|
elif isinstance(data, str) and os.path.exists(data):
|
||||||
|
blob.upload_from_filename(data, content_type=content_type)
|
||||||
|
else:
|
||||||
|
raise ValueError("data must be bytes, file-like, or existing filepath")
|
||||||
|
return {"name": blob.name, "size": blob.size, "content_type": blob.content_type}
|
||||||
|
|
||||||
|
def read_item(self, bucket: str, object_name: str, as_text: bool = False, encoding: str = "utf-8"):
|
||||||
|
data = self._blob(bucket, object_name).download_as_bytes()
|
||||||
|
return data.decode(encoding) if as_text else data
|
||||||
|
|
||||||
|
def generate_signed_url(self, bucket: str, object_name: str, method: str = "GET",
|
||||||
|
expires: timedelta = timedelta(hours=1)) -> str:
|
||||||
|
return self._blob(bucket, object_name).generate_signed_url(expiration=expires, method=method)
|
Loading…
x
Reference in New Issue
Block a user