10
10
from collections import defaultdict
11
11
from dataclasses import dataclass
12
12
from enum import IntEnum
13
- from typing import Any , Callable , Coroutine , Iterable , TypeVar
13
+ from typing import Any , Callable , Coroutine , Iterable , TypeVar , cast
14
14
15
15
from fsspec .asyn import AsyncFileSystem , _run_coros_in_chunks , sync , sync_wrapper
16
16
from fsspec .exceptions import FSTimeoutError
@@ -373,9 +373,9 @@ async def _rm_file(self, path: str, **kwargs: Any) -> None:
373
373
374
374
async def _touch (self , path : str , truncate : bool = False , ** kwargs : Any ) -> None :
375
375
if truncate or not await self ._exists (path ):
376
- status , _ = await _async_wrap ( self . _myclient . truncate )(
377
- path , size = 0 , timeout = self . timeout
378
- )
376
+ f = client . File ()
377
+ status , _ = await _async_wrap ( f . open )( path , OpenFlags . DELETE )
378
+ await _async_wrap ( f . close )( )
379
379
if not status .ok :
380
380
raise OSError (f"File not touched properly: { status .message } " )
381
381
else :
@@ -756,9 +756,9 @@ def __init__(
756
756
from fsspec .core import caches
757
757
758
758
self .timeout = fs .timeout
759
- # by this point, mode will have a "b" in it
760
- # update "+" mode removed for now since seek() is read only
761
- if "x" in mode :
759
+ if mode == "r+b" :
760
+ self . mode = OpenFlags . UPDATE
761
+ elif "x" in mode :
762
762
self .mode = OpenFlags .NEW
763
763
elif "a" in mode :
764
764
self .mode = OpenFlags .UPDATE
@@ -834,7 +834,7 @@ def __init__(
834
834
835
835
self .kwargs = kwargs
836
836
837
- if mode not in {"ab" , "rb" , "wb" }:
837
+ if mode not in {"ab" , "rb" , "wb" , "r+b" }:
838
838
raise NotImplementedError ("File mode not supported" )
839
839
if mode == "rb" :
840
840
if size is not None :
@@ -849,6 +849,13 @@ def __init__(
849
849
self .forced = False
850
850
self .location = None
851
851
self .offset = 0
852
+ self .size = self ._myFile .stat ()[1 ].size
853
+ if mode == "r+b" :
854
+ self .cache = caches [cache_type ](
855
+ self .blocksize , self ._fetch_range , self .size , ** cache_options
856
+ )
857
+ if "a" in mode :
858
+ self .loc = self .size
852
859
853
860
def _locate_sources (self , logical_filename : str ) -> list [str ]:
854
861
"""Find hosts that have the desired file.
@@ -943,3 +950,81 @@ def close(self) -> None:
943
950
if not status .ok :
944
951
raise OSError (f"File did not close properly: { status .message } " )
945
952
self .closed = True
953
+
954
+ def seek (self , loc : int , whence : int = 0 ) -> int :
955
+ """Set current file location
956
+
957
+ Parameters
958
+ ----------
959
+ loc: int
960
+ byte location
961
+ whence: {0, 1, 2}
962
+ from start of file, current location or end of file, resp.
963
+ """
964
+ loc = int (loc )
965
+ if whence == 0 :
966
+ nloc = loc
967
+ elif whence == 1 :
968
+ nloc = self .loc + loc
969
+ elif whence == 2 :
970
+ nloc = self .size + loc
971
+ else :
972
+ raise ValueError (f"invalid whence ({ whence } , should be 0, 1 or 2)" )
973
+ if nloc < 0 :
974
+ raise ValueError ("Seek before start of file" )
975
+ self .loc = nloc
976
+ return self .loc
977
+
978
+ def writable (self ) -> bool :
979
+ """Whether opened for writing"""
980
+ return self .mode in {"wb" , "ab" , "xb" , "r+b" } and not self .closed
981
+
982
+ def write (self , data : bytes ) -> int :
983
+ """
984
+ Write data to buffer.
985
+
986
+ Buffer only sent on flush() or if buffer is greater than
987
+ or equal to blocksize.
988
+
989
+ Parameters
990
+ ----------
991
+ data: bytes
992
+ Set of bytes to be written.
993
+ """
994
+ if not self .writable ():
995
+ raise ValueError ("File not in write mode" )
996
+ if self .closed :
997
+ raise ValueError ("I/O operation on closed file." )
998
+ if self .forced :
999
+ raise ValueError ("This file has been force-flushed, can only close" )
1000
+ status , _n = self ._myFile .write (data , self .loc , len (data ), timeout = self .timeout )
1001
+ self .loc += len (data )
1002
+ self .size = max (self .size , self .loc )
1003
+ if not status .ok :
1004
+ raise OSError (f"File did not write properly: { status .message } " )
1005
+ return len (data )
1006
+
1007
+ def read (self , length : int = - 1 ) -> bytes :
1008
+ """
1009
+ Return data from cache, or fetch pieces as necessary
1010
+
1011
+ Parameters
1012
+ ----------
1013
+ length: int (-1)
1014
+ Number of bytes to read; if <0, all remaining bytes.
1015
+ """
1016
+ length = int (length )
1017
+ if self .mode not in {"rb" , "r+b" }:
1018
+ raise ValueError ("File not in read mode" )
1019
+ if length < 0 :
1020
+ length = self .size - self .loc
1021
+ if self .closed :
1022
+ raise ValueError ("I/O operation on closed file." )
1023
+ if length == 0 :
1024
+ # don't even bother calling fetch
1025
+ return b""
1026
+ # for mypy
1027
+ out = cast (bytes , self .cache ._fetch (self .loc , self .loc + length ))
1028
+
1029
+ self .loc += len (out )
1030
+ return out
0 commit comments