55import base64
66import binascii
77import json
8- from collections .abc import Generator , Mapping , Sequence
8+ from collections .abc import Callable , Generator , Mapping , Sequence
99from contextlib import contextmanager , suppress
1010from dataclasses import dataclass
1111from datetime import datetime
@@ -564,6 +564,7 @@ def upload_resumable(
564564 * ,
565565 content_type : str = "application/octet-stream" ,
566566 part_size : int | None = None ,
567+ on_progress : Callable [[int , int ], None ] | None = None ,
567568 ) -> StorageObject :
568569 """Upload bytes or a binary stream through a resumable session."""
569570 path = _storage_path (path )
@@ -577,7 +578,13 @@ def upload_resumable(
577578 )
578579 upload_succeeded = False
579580 try :
580- self ._upload_session_parts (path , source , session )
581+ self ._upload_session_parts (
582+ path ,
583+ source ,
584+ session ,
585+ total_size ,
586+ on_progress ,
587+ )
581588 upload_succeeded = True
582589 finally :
583590 if not upload_succeeded :
@@ -589,14 +596,21 @@ def _upload_session_parts(
589596 path : str ,
590597 source : BinaryIO ,
591598 session : UploadSession ,
599+ total_size : int ,
600+ on_progress : Callable [[int , int ], None ] | None ,
592601 ) -> None :
602+ uploaded = 0
593603 for part_index in range (session .total_parts ):
604+ part = _read_upload_part (source , session .part_size )
594605 self .upload_part (
595606 path ,
596607 session_id = session .session_id ,
597608 part_number = part_index + 1 ,
598- data = _read_upload_part ( source , session . part_size ) ,
609+ data = part ,
599610 )
611+ uploaded += len (part )
612+ if on_progress is not None :
613+ on_progress (uploaded , total_size )
600614
601615 def _abort_failed_upload (self , path : str , session_id : str ) -> None :
602616 with suppress (Exception ):
0 commit comments