tamar-file-hub-client 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,11 @@ from ...schemas import (
12
12
  File,
13
13
  FileListResponse,
14
14
  GetFileResponse,
15
+ CompressionStatusResponse,
16
+ GetVariantsResponse,
17
+ RecompressionResponse,
18
+ VariantDownloadUrlResponse,
19
+ CompressedVariant,
15
20
  )
16
21
  from ...errors import FileNotFoundError
17
22
 
@@ -275,3 +280,215 @@ class AsyncFileService(BaseFileService):
275
280
  files = [self._convert_file_info(f) for f in response.files]
276
281
 
277
282
  return FileListResponse(files=files)
283
+
284
+ async def get_compression_status(
285
+ self,
286
+ file_id: str,
287
+ *,
288
+ request_id: Optional[str] = None,
289
+ **metadata
290
+ ) -> CompressionStatusResponse:
291
+ """
292
+ 获取文件压缩状态
293
+
294
+ Args:
295
+ file_id: 文件ID
296
+ request_id: 请求ID,用于追踪
297
+ **metadata: 额外的gRPC元数据
298
+
299
+ Returns:
300
+ CompressionStatusResponse: 压缩状态响应
301
+ """
302
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
303
+
304
+ stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
305
+
306
+ request = file_service_pb2.CompressionStatusRequest(file_id=file_id)
307
+
308
+ # 构建元数据
309
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
310
+
311
+ response = await stub.GetCompressionStatus(request, metadata=grpc_metadata)
312
+
313
+ # 转换压缩变体
314
+ variants = []
315
+ for variant in response.variants:
316
+ variants.append(CompressedVariant(
317
+ variant_name=variant.variant_name,
318
+ variant_type=variant.variant_type,
319
+ media_type=variant.media_type,
320
+ width=variant.width,
321
+ height=variant.height,
322
+ file_size=variant.file_size,
323
+ format=variant.format,
324
+ quality=variant.quality if variant.quality else None,
325
+ duration=variant.duration if variant.duration else None,
326
+ bitrate=variant.bitrate if variant.bitrate else None,
327
+ fps=variant.fps if variant.fps else None,
328
+ compression_ratio=variant.compression_ratio,
329
+ stored_path=variant.stored_path
330
+ ))
331
+
332
+ return CompressionStatusResponse(
333
+ status=response.status,
334
+ error_message=response.error_message if response.error_message else None,
335
+ variants=variants
336
+ )
337
+
338
+ async def get_compressed_variants(
339
+ self,
340
+ file_id: str,
341
+ *,
342
+ variant_type: Optional[str] = None,
343
+ request_id: Optional[str] = None,
344
+ **metadata
345
+ ) -> GetVariantsResponse:
346
+ """
347
+ 获取文件的压缩变体
348
+
349
+ Args:
350
+ file_id: 文件ID
351
+ variant_type: 变体类型(image, video, thumbnail)
352
+ request_id: 请求ID,用于追踪
353
+ **metadata: 额外的gRPC元数据
354
+
355
+ Returns:
356
+ GetVariantsResponse: 压缩变体响应
357
+ """
358
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
359
+
360
+ stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
361
+
362
+ request = file_service_pb2.GetVariantsRequest(file_id=file_id)
363
+ if variant_type:
364
+ request.variant_type = variant_type
365
+
366
+ # 构建元数据
367
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
368
+
369
+ response = await stub.GetCompressedVariants(request, metadata=grpc_metadata)
370
+
371
+ # 转换压缩变体
372
+ variants = []
373
+ for variant in response.variants:
374
+ variants.append(CompressedVariant(
375
+ variant_name=variant.variant_name,
376
+ variant_type=variant.variant_type,
377
+ media_type=variant.media_type,
378
+ width=variant.width,
379
+ height=variant.height,
380
+ file_size=variant.file_size,
381
+ format=variant.format,
382
+ quality=variant.quality if variant.quality else None,
383
+ duration=variant.duration if variant.duration else None,
384
+ bitrate=variant.bitrate if variant.bitrate else None,
385
+ fps=variant.fps if variant.fps else None,
386
+ compression_ratio=variant.compression_ratio,
387
+ stored_path=variant.stored_path
388
+ ))
389
+
390
+ return GetVariantsResponse(variants=variants)
391
+
392
+ async def trigger_recompression(
393
+ self,
394
+ file_id: str,
395
+ *,
396
+ force_reprocess: bool = False,
397
+ request_id: Optional[str] = None,
398
+ **metadata
399
+ ) -> RecompressionResponse:
400
+ """
401
+ 触发文件重新压缩
402
+
403
+ Args:
404
+ file_id: 文件ID
405
+ force_reprocess: 是否强制重新处理
406
+ request_id: 请求ID,用于追踪
407
+ **metadata: 额外的gRPC元数据
408
+
409
+ Returns:
410
+ RecompressionResponse: 重新压缩响应
411
+ """
412
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
413
+
414
+ stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
415
+
416
+ request = file_service_pb2.RecompressionRequest(
417
+ file_id=file_id,
418
+ force_reprocess=force_reprocess
419
+ )
420
+
421
+ # 构建元数据
422
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
423
+
424
+ response = await stub.TriggerRecompression(request, metadata=grpc_metadata)
425
+
426
+ return RecompressionResponse(
427
+ task_id=response.task_id,
428
+ status=response.status
429
+ )
430
+
431
+ async def generate_variant_download_url(
432
+ self,
433
+ file_id: str,
434
+ variant_name: str,
435
+ *,
436
+ expire_seconds: int = 3600,
437
+ is_cdn: bool = False,
438
+ request_id: Optional[str] = None,
439
+ **metadata
440
+ ) -> VariantDownloadUrlResponse:
441
+ """
442
+ 生成变体下载URL
443
+
444
+ Args:
445
+ file_id: 文件ID
446
+ variant_name: 变体名称(large/medium/small/thumbnail)
447
+ expire_seconds: 过期时间(秒)
448
+ is_cdn: 是否使用CDN
449
+ request_id: 请求ID,用于追踪
450
+ **metadata: 额外的gRPC元数据
451
+
452
+ Returns:
453
+ VariantDownloadUrlResponse: 变体下载URL响应
454
+ """
455
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
456
+
457
+ stub = await self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
458
+
459
+ request = file_service_pb2.VariantDownloadUrlRequest(
460
+ file_id=file_id,
461
+ variant_name=variant_name,
462
+ expire_seconds=expire_seconds,
463
+ is_cdn=is_cdn
464
+ )
465
+
466
+ # 构建元数据
467
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
468
+
469
+ response = await stub.GenerateVariantDownloadUrl(request, metadata=grpc_metadata)
470
+
471
+ # 转换变体信息
472
+ variant_info = None
473
+ if response.variant_info:
474
+ variant_info = CompressedVariant(
475
+ variant_name=response.variant_info.variant_name,
476
+ variant_type=response.variant_info.variant_type,
477
+ media_type=response.variant_info.media_type,
478
+ width=response.variant_info.width,
479
+ height=response.variant_info.height,
480
+ file_size=response.variant_info.file_size,
481
+ format=response.variant_info.format,
482
+ quality=response.variant_info.quality if response.variant_info.quality else None,
483
+ duration=response.variant_info.duration if response.variant_info.duration else None,
484
+ bitrate=response.variant_info.bitrate if response.variant_info.bitrate else None,
485
+ fps=response.variant_info.fps if response.variant_info.fps else None,
486
+ compression_ratio=response.variant_info.compression_ratio,
487
+ stored_path=response.variant_info.stored_path
488
+ )
489
+
490
+ return VariantDownloadUrlResponse(
491
+ url=response.url,
492
+ error=response.error if response.error else None,
493
+ variant_info=variant_info
494
+ )
@@ -9,7 +9,7 @@ from .base_file_service import BaseFileService
9
9
  from ...enums import UploadMode
10
10
  from ...errors import ValidationError
11
11
  from ...rpc import SyncGrpcClient
12
- from ...schemas import FileUploadResponse, UploadUrlResponse, BatchDownloadUrlResponse, DownloadUrlInfo, GcsUrlInfo, GetGcsUrlResponse, BatchGcsUrlResponse
12
+ from ...schemas import FileUploadResponse, UploadUrlResponse, BatchDownloadUrlResponse, DownloadUrlInfo, GcsUrlInfo, GetGcsUrlResponse, BatchGcsUrlResponse, CompressionStatusResponse, GetVariantsResponse, RecompressionResponse, VariantDownloadUrlResponse, CompressedVariant
13
13
  from ...utils import HttpUploader, HttpDownloader, retry_with_backoff, get_file_mime_type
14
14
 
15
15
 
@@ -119,7 +119,8 @@ class SyncBlobService(BaseFileService):
119
119
  request_id: Optional[str] = None,
120
120
  **metadata
121
121
  ) -> FileUploadResponse:
122
- """客户端直传实现"""
122
+ """客户端直传实现(GCS 直传)"""
123
+
123
124
  # 获取上传URL,以及对应的文件和上传文件信息
124
125
  upload_url_resp = self.generate_upload_url(
125
126
  file_name=file_name,
@@ -142,11 +143,17 @@ class SyncBlobService(BaseFileService):
142
143
  upload_file=upload_url_resp.upload_file
143
144
  )
144
145
 
146
+ # 构建HTTP头,包含Content-Type和固定的Cache-Control
147
+ headers = {
148
+ "Content-Type": mime_type,
149
+ "Cache-Control": "public, max-age=86400" # 24小时公共缓存
150
+ }
151
+
145
152
  # 上传文件到对象存储
146
153
  self.http_uploader.upload(
147
154
  url=upload_url_resp.upload_url,
148
155
  content=content,
149
- headers={"Content-Type": mime_type},
156
+ headers=headers,
150
157
  total_size=file_size,
151
158
  )
152
159
 
@@ -178,7 +185,8 @@ class SyncBlobService(BaseFileService):
178
185
  request_id: Optional[str] = None,
179
186
  **metadata
180
187
  ) -> FileUploadResponse:
181
- """断点续传实现"""
188
+ """断点续传实现(GCS 直传)"""
189
+
182
190
  # 获取断点续传URL,以及对应的文件和上传文件信息
183
191
  upload_url_resp = self.generate_resumable_upload_url(
184
192
  file_name=file_name,
@@ -201,6 +209,12 @@ class SyncBlobService(BaseFileService):
201
209
  upload_file=upload_url_resp.upload_file
202
210
  )
203
211
 
212
+ # 构建HTTP头,包含Content-Type和固定的Cache-Control
213
+ headers = {
214
+ "Content-Type": mime_type,
215
+ "Cache-Control": "public, max-age=86400" # 24小时公共缓存
216
+ }
217
+
204
218
  # 开启断点续传
205
219
  upload_url = self.http_uploader.start_resumable_session(
206
220
  url=upload_url_resp.upload_url,
@@ -212,7 +226,7 @@ class SyncBlobService(BaseFileService):
212
226
  self.http_uploader.upload(
213
227
  url=upload_url,
214
228
  content=content,
215
- headers={"Content-Type": mime_type},
229
+ headers=headers,
216
230
  total_size=file_size,
217
231
  is_resume=True
218
232
  )
@@ -411,6 +425,8 @@ class SyncBlobService(BaseFileService):
411
425
 
412
426
  Note:
413
427
  必须提供 file 或 url 参数之一
428
+
429
+ Cache-Control 头在 GCS 直传模式(STREAM/RESUMABLE)下自动设置为 "public, max-age=86400"
414
430
  """
415
431
  # 参数验证:必须提供 file 或 url 之一
416
432
  if file is None and not url:
@@ -466,7 +482,7 @@ class SyncBlobService(BaseFileService):
466
482
 
467
483
  # 根据上传模式执行不同的上传逻辑
468
484
  if mode == UploadMode.NORMAL:
469
- # 普通上传(通过gRPC
485
+ # 普通上传(通过gRPC)- 不需要 Cache-Control
470
486
  return self._upload_file(
471
487
  file_name=extracted_file_name,
472
488
  content=content,
@@ -481,7 +497,7 @@ class SyncBlobService(BaseFileService):
481
497
  )
482
498
 
483
499
  elif mode == UploadMode.STREAM:
484
- # 流式上传
500
+ # 流式上传 - 需要 Cache-Control
485
501
  return self._upload_stream(
486
502
  file_name=extracted_file_name,
487
503
  content=content,
@@ -498,7 +514,7 @@ class SyncBlobService(BaseFileService):
498
514
  )
499
515
 
500
516
  elif mode == UploadMode.RESUMABLE:
501
- # 断点续传
517
+ # 断点续传 - 需要 Cache-Control
502
518
  return self._upload_resumable(
503
519
  file_name=extracted_file_name,
504
520
  content=content,
@@ -585,6 +601,30 @@ class SyncBlobService(BaseFileService):
585
601
  chunk_size=chunk_size,
586
602
  )
587
603
 
604
+ def download_to_bytes(
605
+ self,
606
+ file_id: str,
607
+ *,
608
+ request_id: Optional[str] = None,
609
+ **metadata
610
+ ) -> bytes:
611
+ """
612
+ 下载文件并返回字节数据
613
+
614
+ Args:
615
+ file_id: 文件ID
616
+ request_id: 请求ID(可选,如果不提供则自动生成)
617
+ **metadata: 额外的元数据
618
+
619
+ Returns:
620
+ 文件的字节数据
621
+ """
622
+ # 获取下载URL
623
+ download_url = self.generate_download_url(file_id, request_id=request_id, **metadata)
624
+
625
+ # 下载到内存并返回字节数据
626
+ return self.http_downloader.download(url=download_url, save_path=None)
627
+
588
628
  def batch_generate_download_url(
589
629
  self,
590
630
  file_ids: List[str],
@@ -628,6 +668,7 @@ class SyncBlobService(BaseFileService):
628
668
  download_urls.append(DownloadUrlInfo(
629
669
  file_id=url_info.file_id,
630
670
  url=url_info.url,
671
+ mime_type=url_info.mime_type,
631
672
  error=url_info.error if url_info.HasField('error') else None
632
673
  ))
633
674
 
@@ -707,3 +748,215 @@ class SyncBlobService(BaseFileService):
707
748
  ))
708
749
 
709
750
  return BatchGcsUrlResponse(gcs_urls=gcs_urls)
751
+
752
+ def get_compression_status(
753
+ self,
754
+ file_id: str,
755
+ *,
756
+ request_id: Optional[str] = None,
757
+ **metadata
758
+ ) -> CompressionStatusResponse:
759
+ """
760
+ 获取文件压缩状态
761
+
762
+ Args:
763
+ file_id: 文件ID
764
+ request_id: 请求ID,用于追踪
765
+ **metadata: 额外的gRPC元数据
766
+
767
+ Returns:
768
+ CompressionStatusResponse: 压缩状态响应
769
+ """
770
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
771
+
772
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
773
+
774
+ request = file_service_pb2.CompressionStatusRequest(file_id=file_id)
775
+
776
+ # 构建元数据
777
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
778
+
779
+ response = stub.GetCompressionStatus(request, metadata=grpc_metadata)
780
+
781
+ # 转换压缩变体
782
+ variants = []
783
+ for variant in response.variants:
784
+ variants.append(CompressedVariant(
785
+ variant_name=variant.variant_name,
786
+ variant_type=variant.variant_type,
787
+ media_type=variant.media_type,
788
+ width=variant.width,
789
+ height=variant.height,
790
+ file_size=variant.file_size,
791
+ format=variant.format,
792
+ quality=variant.quality if variant.quality else None,
793
+ duration=variant.duration if variant.duration else None,
794
+ bitrate=variant.bitrate if variant.bitrate else None,
795
+ fps=variant.fps if variant.fps else None,
796
+ compression_ratio=variant.compression_ratio,
797
+ stored_path=variant.stored_path
798
+ ))
799
+
800
+ return CompressionStatusResponse(
801
+ status=response.status,
802
+ error_message=response.error_message if response.error_message else None,
803
+ variants=variants
804
+ )
805
+
806
+ def get_compressed_variants(
807
+ self,
808
+ file_id: str,
809
+ *,
810
+ variant_type: Optional[str] = None,
811
+ request_id: Optional[str] = None,
812
+ **metadata
813
+ ) -> GetVariantsResponse:
814
+ """
815
+ 获取文件的压缩变体
816
+
817
+ Args:
818
+ file_id: 文件ID
819
+ variant_type: 变体类型(image, video, thumbnail)
820
+ request_id: 请求ID,用于追踪
821
+ **metadata: 额外的gRPC元数据
822
+
823
+ Returns:
824
+ GetVariantsResponse: 压缩变体响应
825
+ """
826
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
827
+
828
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
829
+
830
+ request = file_service_pb2.GetVariantsRequest(file_id=file_id)
831
+ if variant_type:
832
+ request.variant_type = variant_type
833
+
834
+ # 构建元数据
835
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
836
+
837
+ response = stub.GetCompressedVariants(request, metadata=grpc_metadata)
838
+
839
+ # 转换压缩变体
840
+ variants = []
841
+ for variant in response.variants:
842
+ variants.append(CompressedVariant(
843
+ variant_name=variant.variant_name,
844
+ variant_type=variant.variant_type,
845
+ media_type=variant.media_type,
846
+ width=variant.width,
847
+ height=variant.height,
848
+ file_size=variant.file_size,
849
+ format=variant.format,
850
+ quality=variant.quality if variant.quality else None,
851
+ duration=variant.duration if variant.duration else None,
852
+ bitrate=variant.bitrate if variant.bitrate else None,
853
+ fps=variant.fps if variant.fps else None,
854
+ compression_ratio=variant.compression_ratio,
855
+ stored_path=variant.stored_path
856
+ ))
857
+
858
+ return GetVariantsResponse(variants=variants)
859
+
860
+ def trigger_recompression(
861
+ self,
862
+ file_id: str,
863
+ *,
864
+ force_reprocess: bool = False,
865
+ request_id: Optional[str] = None,
866
+ **metadata
867
+ ) -> RecompressionResponse:
868
+ """
869
+ 触发文件重新压缩
870
+
871
+ Args:
872
+ file_id: 文件ID
873
+ force_reprocess: 是否强制重新处理
874
+ request_id: 请求ID,用于追踪
875
+ **metadata: 额外的gRPC元数据
876
+
877
+ Returns:
878
+ RecompressionResponse: 重新压缩响应
879
+ """
880
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
881
+
882
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
883
+
884
+ request = file_service_pb2.RecompressionRequest(
885
+ file_id=file_id,
886
+ force_reprocess=force_reprocess
887
+ )
888
+
889
+ # 构建元数据
890
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
891
+
892
+ response = stub.TriggerRecompression(request, metadata=grpc_metadata)
893
+
894
+ return RecompressionResponse(
895
+ task_id=response.task_id,
896
+ status=response.status
897
+ )
898
+
899
+ def generate_variant_download_url(
900
+ self,
901
+ file_id: str,
902
+ variant_name: str,
903
+ *,
904
+ expire_seconds: int = 3600,
905
+ is_cdn: bool = False,
906
+ request_id: Optional[str] = None,
907
+ **metadata
908
+ ) -> VariantDownloadUrlResponse:
909
+ """
910
+ 生成变体下载URL
911
+
912
+ Args:
913
+ file_id: 文件ID
914
+ variant_name: 变体名称(large/medium/small/thumbnail)
915
+ expire_seconds: 过期时间(秒)
916
+ is_cdn: 是否使用CDN
917
+ request_id: 请求ID,用于追踪
918
+ **metadata: 额外的gRPC元数据
919
+
920
+ Returns:
921
+ VariantDownloadUrlResponse: 变体下载URL响应
922
+ """
923
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
924
+
925
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
926
+
927
+ request = file_service_pb2.VariantDownloadUrlRequest(
928
+ file_id=file_id,
929
+ variant_name=variant_name,
930
+ expire_seconds=expire_seconds,
931
+ is_cdn=is_cdn
932
+ )
933
+
934
+ # 构建元数据
935
+ grpc_metadata = self.client.build_metadata(request_id=request_id, **metadata)
936
+
937
+ response = stub.GenerateVariantDownloadUrl(request, metadata=grpc_metadata)
938
+
939
+ # 转换变体信息
940
+ variant_info = None
941
+ if response.variant_info:
942
+ variant_info = CompressedVariant(
943
+ variant_name=response.variant_info.variant_name,
944
+ variant_type=response.variant_info.variant_type,
945
+ media_type=response.variant_info.media_type,
946
+ width=response.variant_info.width,
947
+ height=response.variant_info.height,
948
+ file_size=response.variant_info.file_size,
949
+ format=response.variant_info.format,
950
+ quality=response.variant_info.quality if response.variant_info.quality else None,
951
+ duration=response.variant_info.duration if response.variant_info.duration else None,
952
+ bitrate=response.variant_info.bitrate if response.variant_info.bitrate else None,
953
+ fps=response.variant_info.fps if response.variant_info.fps else None,
954
+ compression_ratio=response.variant_info.compression_ratio,
955
+ stored_path=response.variant_info.stored_path
956
+ )
957
+
958
+ return VariantDownloadUrlResponse(
959
+ url=response.url,
960
+ error=response.error if response.error else None,
961
+ variant_info=variant_info
962
+ )