88import socket
99from contextlib import contextmanager
1010from errno import ESPIPE
11- from typing import Any , BinaryIO , Dict , Iterator , List , Optional , Set , Tuple , Type , Union , cast
11+ from typing import Any , BinaryIO , Dict , Iterator , List , Optional , Set , Tuple , Union , cast
1212
1313import boto3
1414from botocore .exceptions import ReadTimeoutError
@@ -188,7 +188,7 @@ def close(self) -> List[Dict[str, Union[str, int]]]:
188188 return self ._sort_by_part_number (parts = self ._results )
189189
190190
191- class _S3ObjectBase : # pylint: disable=too-many-instance-attributes
191+ class _S3ObjectBase ( io . RawIOBase ) : # pylint: disable=too-many-instance-attributes
192192 """Class to abstract S3 objects as ordinary files."""
193193
194194 def __init__ (
@@ -201,12 +201,8 @@ def __init__(
201201 boto3_session : Optional [boto3 .Session ],
202202 newline : Optional [str ],
203203 encoding : Optional [str ],
204- raw_buffer : bool ,
205204 ) -> None :
206- if raw_buffer is True and "w" not in mode :
207- raise exceptions .InvalidArgumentValue ("raw_buffer=True is only acceptable on write mode." )
208- self ._raw_buffer : bool = raw_buffer
209- self .closed : bool = False
205+ super ().__init__ ()
210206 self ._use_threads = use_threads
211207 self ._newline : str = "\n " if newline is None else newline
212208 self ._encoding : str = "utf-8" if encoding is None else encoding
@@ -408,9 +404,9 @@ def seek(self, loc: int, whence: int = 0) -> int:
408404
409405 def flush (self , force : bool = False ) -> None :
410406 """Write buffered data to S3."""
411- if self .closed :
407+ if self .closed : # pylint: disable=using-constant-test
412408 raise RuntimeError ("I/O operation on closed file." )
413- if self .writable ():
409+ if self .writable () and self . _buffer . closed is False :
414410 total_size : int = self ._buffer .tell ()
415411 if total_size < _MIN_WRITE_BLOCK and force is False :
416412 return None
@@ -465,7 +461,7 @@ def writable(self) -> bool:
465461
466462 def close (self ) -> None :
467463 """Clean up the cache."""
468- if self .closed :
464+ if self .closed : # pylint: disable=using-constant-test
469465 return None
470466 if self .writable ():
471467 _logger .debug ("Closing: %s parts" , self ._parts_count )
@@ -488,7 +484,7 @@ def close(self) -> None:
488484 ),
489485 )
490486 _logger .debug ("complete_multipart_upload done!" )
491- elif self ._buffer .tell () > 0 or self . _raw_buffer is True :
487+ elif self ._buffer .tell () > 0 :
492488 _logger .debug ("put_object" )
493489 _utils .try_it (
494490 f = self ._client .put_object ,
@@ -503,23 +499,17 @@ def close(self) -> None:
503499 ),
504500 )
505501 self ._parts_count = 0
502+ self ._upload_proxy .close ()
506503 self ._buffer .seek (0 )
507504 self ._buffer .truncate (0 )
508- self ._upload_proxy .close ()
509505 self ._buffer .close ()
510506 elif self .readable ():
511507 self ._cache = b""
512508 else :
513509 raise RuntimeError (f"Invalid mode: { self ._mode } " )
514- self . closed = True
510+ super (). close ()
515511 return None
516512
517- def get_raw_buffer (self ) -> io .BytesIO :
518- """Return the Raw Buffer if it is possible."""
519- if self ._raw_buffer is False :
520- raise exceptions .InvalidArgumentValue ("Trying to get raw buffer with raw_buffer=False." )
521- return self ._buffer
522-
523513 def read (self , length : int = - 1 ) -> bytes :
524514 """Return cached data and fetch on demand chunks."""
525515 if self .readable () is False :
@@ -534,8 +524,9 @@ def read(self, length: int = -1) -> bytes:
534524 self ._loc += len (out )
535525 return out
536526
537- def readline (self , length : int = - 1 ) -> bytes :
527+ def readline (self , length : Optional [ int ] = - 1 ) -> bytes :
538528 """Read until the next line terminator."""
529+ length = - 1 if length is None else length
539530 end : int = self ._loc + self ._s3_block_size
540531 end = self ._size if end > self ._size else end
541532 self ._fetch (self ._loc , end )
@@ -553,17 +544,11 @@ def readline(self, length: int = -1) -> bytes:
553544 end = self ._size if end > self ._size else end
554545 self ._fetch (self ._loc , end )
555546
556- def readlines (self ) -> List [bytes ]:
557- """Return all lines as list."""
558- return list (self )
559-
560-
561- class _S3ObjectWriter (_S3ObjectBase ):
562- def write (self , data : bytes ) -> int :
547+ def write (self , data : Union [bytes , bytearray , memoryview ]) -> int : # type: ignore
563548 """Write data to buffer and only upload on close() or if buffer is greater than or equal to _MIN_WRITE_BLOCK."""
564549 if self .writable () is False :
565550 raise RuntimeError ("File not in write mode." )
566- if self .closed :
551+ if self .closed : # pylint: disable=using-constant-test
567552 raise RuntimeError ("I/O operation on closed file." )
568553 n : int = self ._buffer .write (data )
569554 self ._loc += n
@@ -583,14 +568,12 @@ def open_s3_object(
583568 boto3_session : Optional [boto3 .Session ] = None ,
584569 newline : Optional [str ] = "\n " ,
585570 encoding : Optional [str ] = "utf-8" ,
586- raw_buffer : bool = False ,
587- ) -> Iterator [Union [_S3ObjectBase , _S3ObjectWriter , io .TextIOWrapper , io .BytesIO ]]:
571+ ) -> Iterator [Union [_S3ObjectBase , io .TextIOWrapper ]]:
588572 """Return a _S3Object or TextIOWrapper based in the received mode."""
589- s3obj : Optional [Union [ _S3ObjectBase , _S3ObjectWriter ] ] = None
573+ s3obj : Optional [_S3ObjectBase ] = None
590574 text_s3obj : Optional [io .TextIOWrapper ] = None
591- s3_class : Union [Type [_S3ObjectBase ], Type [_S3ObjectWriter ]] = _S3ObjectWriter if "w" in mode else _S3ObjectBase
592575 try :
593- s3obj = s3_class (
576+ s3obj = _S3ObjectBase (
594577 path = path ,
595578 s3_block_size = s3_block_size ,
596579 mode = mode ,
@@ -599,11 +582,8 @@ def open_s3_object(
599582 boto3_session = boto3_session ,
600583 encoding = encoding ,
601584 newline = newline ,
602- raw_buffer = raw_buffer ,
603585 )
604- if raw_buffer is True : # Only useful for plain io.BytesIO write
605- yield s3obj .get_raw_buffer ()
606- elif "b" in mode : # binary
586+ if "b" in mode : # binary
607587 yield s3obj
608588 else : # text
609589 text_s3obj = io .TextIOWrapper (
0 commit comments