@@ -221,6 +221,13 @@ class S3FS(FS):
221221 PyFilesystem specification exactly. Set to ``False`` to disable
222222 validation of destination paths which may speed up uploads /
223223 downloads.
224+ :param str cache_control: Sets the 'Cache-Control' header for uploads.
225+ :param str acl: Sets the Access Control List header for uploads.
226+ :param dict upload_args: A dictionary for additional upload arguments.
227+ See https://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Object.put
228+ for details.
229+ :param dict download_args: Dictionary of extra arguments passed to
230+ the S3 client.
224231
225232 """
226233
@@ -354,12 +361,16 @@ def _get_object(self, path, key):
354361 else :
355362 return obj
356363
357- def _upload_args (self , key ):
358- if 'ContentType' not in self .upload_args :
359- mimetype = mimetypes .guess_type (key )[0 ] or 'binary/octet-stream'
360- return dict (ContentType = mimetype , ** self .upload_args )
361- else :
362- return self .upload_args
364+ def _get_upload_args (self , key ):
365+ upload_args = (
366+ self .upload_args .copy () if self .upload_args else {}
367+ )
368+ if 'ContentType' not in upload_args :
369+ mime_type , _encoding = mimetypes .guess_type (key )
370+ if six .PY2 and mime_type is not None :
371+ mime_type = mime_type .decode ('utf-8' , 'replace' )
372+ upload_args ['ContentType' ] = mime_type or 'binary/octet-stream'
373+ return upload_args
363374
364375 @property
365376 def s3 (self ):
@@ -543,7 +554,8 @@ def makedir(self, path, permissions=None, recreate=False):
543554 else :
544555 raise errors .DirectoryExists (path )
545556 with s3errors (path ):
546- self .s3 .Object (self ._bucket_name , _key ).put (** self ._upload_args (_key ))
557+ _obj = self .s3 .Object (self ._bucket_name , _key )
558+ _obj .put (** self ._get_upload_args (_key ))
547559 return SubFS (self , path )
548560
549561 def openbin (self , path , mode = "r" , buffering = - 1 , ** options ):
@@ -561,7 +573,10 @@ def on_close_create(s3file):
561573 s3file .raw .seek (0 )
562574 with s3errors (path ):
563575 self .client .upload_fileobj (
564- s3file .raw , self ._bucket_name , _key , ExtraArgs = self ._upload_args (_key )
576+ s3file .raw ,
577+ self ._bucket_name ,
578+ _key ,
579+ ExtraArgs = self ._get_upload_args (_key )
565580 )
566581 finally :
567582 s3file .raw .close ()
@@ -589,7 +604,10 @@ def on_close_create(s3file):
589604 try :
590605 with s3errors (path ):
591606 self .client .download_fileobj (
592- self ._bucket_name , _key , s3file .raw , ExtraArgs = self .download_args
607+ self ._bucket_name ,
608+ _key ,
609+ s3file .raw ,
610+ ExtraArgs = self .download_args
593611 )
594612 except errors .ResourceNotFound :
595613 pass
@@ -610,15 +628,21 @@ def on_close(s3file):
610628 s3file .raw .seek (0 , os .SEEK_SET )
611629 with s3errors (path ):
612630 self .client .upload_fileobj (
613- s3file .raw , self ._bucket_name , _key , ExtraArgs = self ._upload_args (_key )
631+ s3file .raw ,
632+ self ._bucket_name ,
633+ _key ,
634+ ExtraArgs = self ._get_upload_args (_key )
614635 )
615636 finally :
616637 s3file .raw .close ()
617638
618639 s3file = S3File .factory (path , _mode , on_close = on_close )
619640 with s3errors (path ):
620641 self .client .download_fileobj (
621- self ._bucket_name , _key , s3file .raw , ExtraArgs = self .download_args
642+ self ._bucket_name ,
643+ _key ,
644+ s3file .raw ,
645+ ExtraArgs = self .download_args
622646 )
623647 s3file .seek (0 , os .SEEK_SET )
624648 return s3file
@@ -681,7 +705,10 @@ def getbytes(self, path):
681705 bytes_file = io .BytesIO ()
682706 with s3errors (path ):
683707 self .client .download_fileobj (
684- self ._bucket_name , _key , bytes_file , ExtraArgs = self .download_args
708+ self ._bucket_name ,
709+ _key ,
710+ bytes_file ,
711+ ExtraArgs = self .download_args
685712 )
686713 return bytes_file .getvalue ()
687714
@@ -695,7 +722,10 @@ def getfile(self, path, file, chunk_size=None, **options):
695722 _key = self ._path_to_key (_path )
696723 with s3errors (path ):
697724 self .client .download_fileobj (
698- self ._bucket_name , _key , file , ExtraArgs = self .download_args
725+ self ._bucket_name ,
726+ _key ,
727+ file ,
728+ ExtraArgs = self .download_args
699729 )
700730
701731 def exists (self , path ):
@@ -779,7 +809,10 @@ def setbytes(self, path, contents):
779809 bytes_file = io .BytesIO (contents )
780810 with s3errors (path ):
781811 self .client .upload_fileobj (
782- bytes_file , self ._bucket_name , _key , ExtraArgs = self ._upload_args (_key )
812+ bytes_file ,
813+ self ._bucket_name ,
814+ _key ,
815+ ExtraArgs = self ._get_upload_args (_key )
783816 )
784817
785818 def setbinfile (self , path , file ):
@@ -797,7 +830,12 @@ def setbinfile(self, path, file):
797830 pass
798831
799832 with s3errors (path ):
800- self .client .upload_fileobj (file , self ._bucket_name , _key , self ._upload_args (_key ))
833+ self .client .upload_fileobj (
834+ file ,
835+ self ._bucket_name ,
836+ _key ,
837+ ExtraArgs = self ._get_upload_args (_key )
838+ )
801839
802840 def copy (self , src_path , dst_path , overwrite = False ):
803841 if not overwrite and self .exists (dst_path ):
0 commit comments