ext_storage.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import base64
  2. import os
  3. import shutil
  4. from collections.abc import Generator
  5. from contextlib import closing
  6. from datetime import datetime, timedelta, timezone
  7. from typing import Union
  8. import boto3
  9. import oss2 as aliyun_s3
  10. from azure.storage.blob import AccountSasPermissions, BlobServiceClient, ResourceTypes, generate_account_sas
  11. from botocore.client import Config
  12. from botocore.exceptions import ClientError
  13. from flask import Flask
  14. from google.cloud import storage as GoogleStorage
  15. class Storage:
  16. def __init__(self):
  17. self.storage_type = None
  18. self.bucket_name = None
  19. self.client = None
  20. self.folder = None
  21. def init_app(self, app: Flask):
  22. self.storage_type = app.config.get('STORAGE_TYPE')
  23. if self.storage_type == 's3':
  24. self.bucket_name = app.config.get('S3_BUCKET_NAME')
  25. self.client = boto3.client(
  26. 's3',
  27. aws_secret_access_key=app.config.get('S3_SECRET_KEY'),
  28. aws_access_key_id=app.config.get('S3_ACCESS_KEY'),
  29. endpoint_url=app.config.get('S3_ENDPOINT'),
  30. region_name=app.config.get('S3_REGION'),
  31. config=Config(s3={'addressing_style': app.config.get('S3_ADDRESS_STYLE')})
  32. )
  33. elif self.storage_type == 'azure-blob':
  34. self.bucket_name = app.config.get('AZURE_BLOB_CONTAINER_NAME')
  35. sas_token = generate_account_sas(
  36. account_name=app.config.get('AZURE_BLOB_ACCOUNT_NAME'),
  37. account_key=app.config.get('AZURE_BLOB_ACCOUNT_KEY'),
  38. resource_types=ResourceTypes(service=True, container=True, object=True),
  39. permission=AccountSasPermissions(read=True, write=True, delete=True, list=True, add=True, create=True),
  40. expiry=datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(hours=1)
  41. )
  42. self.client = BlobServiceClient(account_url=app.config.get('AZURE_BLOB_ACCOUNT_URL'),
  43. credential=sas_token)
  44. elif self.storage_type == 'aliyun-oss':
  45. self.bucket_name = app.config.get('ALIYUN_OSS_BUCKET_NAME')
  46. self.client = aliyun_s3.Bucket(
  47. aliyun_s3.Auth(app.config.get('ALIYUN_OSS_ACCESS_KEY'), app.config.get('ALIYUN_OSS_SECRET_KEY')),
  48. app.config.get('ALIYUN_OSS_ENDPOINT'),
  49. self.bucket_name,
  50. connect_timeout=30
  51. )
  52. elif self.storage_type == 'google-storage':
  53. self.bucket_name = app.config.get('GOOGLE_STORAGE_BUCKET_NAME')
  54. service_account_json = base64.b64decode(app.config.get('GOOGLE_STORAGE_SERVICE_ACCOUNT_JSON_BASE64')).decode('utf-8')
  55. self.client = GoogleStorage.Client().from_service_account_json(service_account_json)
  56. else:
  57. self.folder = app.config.get('STORAGE_LOCAL_PATH')
  58. if not os.path.isabs(self.folder):
  59. self.folder = os.path.join(app.root_path, self.folder)
  60. def save(self, filename, data):
  61. if self.storage_type == 's3':
  62. self.client.put_object(Bucket=self.bucket_name, Key=filename, Body=data)
  63. elif self.storage_type == 'azure-blob':
  64. blob_container = self.client.get_container_client(container=self.bucket_name)
  65. blob_container.upload_blob(filename, data)
  66. elif self.storage_type == 'aliyun-oss':
  67. self.client.put_object(filename, data)
  68. elif self.storage_type == 'google-storage':
  69. bucket = self.client.get_bucket(self.bucket_name)
  70. blob = bucket.blob(filename)
  71. blob.upload_from_file(data)
  72. else:
  73. if not self.folder or self.folder.endswith('/'):
  74. filename = self.folder + filename
  75. else:
  76. filename = self.folder + '/' + filename
  77. folder = os.path.dirname(filename)
  78. os.makedirs(folder, exist_ok=True)
  79. with open(os.path.join(os.getcwd(), filename), "wb") as f:
  80. f.write(data)
  81. def load(self, filename: str, stream: bool = False) -> Union[bytes, Generator]:
  82. if stream:
  83. return self.load_stream(filename)
  84. else:
  85. return self.load_once(filename)
  86. def load_once(self, filename: str) -> bytes:
  87. if self.storage_type == 's3':
  88. try:
  89. with closing(self.client) as client:
  90. data = client.get_object(Bucket=self.bucket_name, Key=filename)['Body'].read()
  91. except ClientError as ex:
  92. if ex.response['Error']['Code'] == 'NoSuchKey':
  93. raise FileNotFoundError("File not found")
  94. else:
  95. raise
  96. elif self.storage_type == 'azure-blob':
  97. blob = self.client.get_container_client(container=self.bucket_name)
  98. blob = blob.get_blob_client(blob=filename)
  99. data = blob.download_blob().readall()
  100. elif self.storage_type == 'aliyun-oss':
  101. with closing(self.client.get_object(filename)) as obj:
  102. data = obj.read()
  103. elif self.storage_type == 'google-storage':
  104. bucket = self.client.get_bucket(self.bucket_name)
  105. blob = bucket.get_blob(filename)
  106. data = blob.download_as_bytes()
  107. else:
  108. if not self.folder or self.folder.endswith('/'):
  109. filename = self.folder + filename
  110. else:
  111. filename = self.folder + '/' + filename
  112. if not os.path.exists(filename):
  113. raise FileNotFoundError("File not found")
  114. with open(filename, "rb") as f:
  115. data = f.read()
  116. return data
  117. def load_stream(self, filename: str) -> Generator:
  118. def generate(filename: str = filename) -> Generator:
  119. if self.storage_type == 's3':
  120. try:
  121. with closing(self.client) as client:
  122. response = client.get_object(Bucket=self.bucket_name, Key=filename)
  123. for chunk in response['Body'].iter_chunks():
  124. yield chunk
  125. except ClientError as ex:
  126. if ex.response['Error']['Code'] == 'NoSuchKey':
  127. raise FileNotFoundError("File not found")
  128. else:
  129. raise
  130. elif self.storage_type == 'azure-blob':
  131. blob = self.client.get_blob_client(container=self.bucket_name, blob=filename)
  132. with closing(blob.download_blob()) as blob_stream:
  133. while chunk := blob_stream.readall(4096):
  134. yield chunk
  135. elif self.storage_type == 'aliyun-oss':
  136. with closing(self.client.get_object(filename)) as obj:
  137. while chunk := obj.read(4096):
  138. yield chunk
  139. elif self.storage_type == 'google-storage':
  140. bucket = self.client.get_bucket(self.bucket_name)
  141. blob = bucket.get_blob(filename)
  142. with closing(blob.open(mode='rb')) as blob_stream:
  143. while chunk := blob_stream.read(4096):
  144. yield chunk
  145. else:
  146. if not self.folder or self.folder.endswith('/'):
  147. filename = self.folder + filename
  148. else:
  149. filename = self.folder + '/' + filename
  150. if not os.path.exists(filename):
  151. raise FileNotFoundError("File not found")
  152. with open(filename, "rb") as f:
  153. while chunk := f.read(4096): # Read in chunks of 4KB
  154. yield chunk
  155. return generate()
  156. def download(self, filename, target_filepath):
  157. if self.storage_type == 's3':
  158. with closing(self.client) as client:
  159. client.download_file(self.bucket_name, filename, target_filepath)
  160. elif self.storage_type == 'azure-blob':
  161. blob = self.client.get_blob_client(container=self.bucket_name, blob=filename)
  162. with open(target_filepath, "wb") as my_blob:
  163. blob_data = blob.download_blob()
  164. blob_data.readinto(my_blob)
  165. elif self.storage_type == 'aliyun-oss':
  166. self.client.get_object_to_file(filename, target_filepath)
  167. elif self.storage_type == 'google-storage':
  168. bucket = self.client.get_bucket(self.bucket_name)
  169. blob = bucket.get_blob(filename)
  170. with open(target_filepath, "wb") as my_blob:
  171. blob_data = blob.download_blob()
  172. blob_data.readinto(my_blob)
  173. else:
  174. if not self.folder or self.folder.endswith('/'):
  175. filename = self.folder + filename
  176. else:
  177. filename = self.folder + '/' + filename
  178. if not os.path.exists(filename):
  179. raise FileNotFoundError("File not found")
  180. shutil.copyfile(filename, target_filepath)
  181. def exists(self, filename):
  182. if self.storage_type == 's3':
  183. with closing(self.client) as client:
  184. try:
  185. client.head_object(Bucket=self.bucket_name, Key=filename)
  186. return True
  187. except:
  188. return False
  189. elif self.storage_type == 'azure-blob':
  190. blob = self.client.get_blob_client(container=self.bucket_name, blob=filename)
  191. return blob.exists()
  192. elif self.storage_type == 'aliyun-oss':
  193. return self.client.object_exists(filename)
  194. elif self.storage_type == 'google-storage':
  195. bucket = self.client.get_bucket(self.bucket_name)
  196. blob = bucket.blob(filename)
  197. return blob.exists()
  198. else:
  199. if not self.folder or self.folder.endswith('/'):
  200. filename = self.folder + filename
  201. else:
  202. filename = self.folder + '/' + filename
  203. return os.path.exists(filename)
  204. def delete(self, filename):
  205. if self.storage_type == 's3':
  206. self.client.delete_object(Bucket=self.bucket_name, Key=filename)
  207. elif self.storage_type == 'azure-blob':
  208. blob_container = self.client.get_container_client(container=self.bucket_name)
  209. blob_container.delete_blob(filename)
  210. elif self.storage_type == 'aliyun-oss':
  211. self.client.delete_object(filename)
  212. elif self.storage_type == 'google-storage':
  213. bucket = self.client.get_bucket(self.bucket_name)
  214. bucket.delete_blob(filename)
  215. else:
  216. if not self.folder or self.folder.endswith('/'):
  217. filename = self.folder + filename
  218. else:
  219. filename = self.folder + '/' + filename
  220. if os.path.exists(filename):
  221. os.remove(filename)
  222. storage = Storage()
  223. def init_app(app: Flask):
  224. storage.init_app(app)