22
33from __future__ import annotations
44
5+ import asyncio
6+ from datetime import timedelta
7+ import io
8+ import tarfile
59from typing import Dict , Mapping , Optional
610from pathlib import Path
711from typing_extensions import Unpack
@@ -371,7 +375,7 @@ async def upload_from_file(
371375 path = Path (file_path )
372376
373377 try :
374- content = path .read_bytes ()
378+ content = await asyncio . to_thread ( lambda : path .read_bytes () )
375379 except OSError as error :
376380 raise OSError (f"Failed to read file { path } : { error } " ) from error
377381
@@ -382,6 +386,50 @@ async def upload_from_file(
382386 await obj .complete ()
383387 return obj
384388
389+ async def upload_from_dir (
390+ self ,
391+ dir_path : str | Path ,
392+ name : str | None = None ,
393+ * ,
394+ metadata : Optional [Dict [str , str ]] = None ,
395+ ttl : timedelta | None = None ,
396+ ** options : Unpack [LongRequestOptions ],
397+ ) -> AsyncStorageObject :
398+ """Create and upload an object from a local directory.
399+
400+ The resulting object will be uploaded as a compressed tarball.
401+
402+ Args:
403+ dir_path: Local filesystem directory path to tar.
404+ name: Optional object name; defaults to the directory name + '.tar.gz'.
405+ metadata: Optional key-value metadata.
406+ ttl: Optional Time-To-Live, after which the object is automatically deleted.
407+ **options: Additional request configuration.
408+
409+ Returns:
410+ AsyncStorageObject: Wrapper for the uploaded object.
411+
412+ Raises:
413+ OSError: If any of the files in the directory could not be read.
414+ """
415+ path = Path (dir_path )
416+ name = name or f"{ path .name } .tar.gz"
417+ ttl_ms = int (ttl .total_seconds ()) * 1000 if ttl else None
418+
419+ def synchronous_io () -> io .BytesIO :
420+ tar_buffer = io .BytesIO ()
421+ with tarfile .open (fileobj = tar_buffer , mode = "w:gz" ) as tar :
422+ tar .add (path , arcname = "." , recursive = True )
423+ tar_buffer .seek (0 )
424+ return tar_buffer
425+
426+ tar_buffer = await asyncio .to_thread (synchronous_io )
427+
428+ obj = await self .create (name = name , content_type = "tgz" , metadata = metadata , ttl_ms = ttl_ms , ** options )
429+ await obj .upload_content (tar_buffer )
430+ await obj .complete ()
431+ return obj
432+
385433 async def upload_from_text (
386434 self ,
387435 text : str ,
0 commit comments